Skip to content

Commit

Permalink
Add Sync Events
Browse files Browse the repository at this point in the history
  • Loading branch information
tfedor committed Sep 16, 2024
1 parent 023b7df commit a8bdb7c
Show file tree
Hide file tree
Showing 40 changed files with 218 additions and 112 deletions.
1 change: 1 addition & 0 deletions src/js/Background/EAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum EAction {
Export = "itad.export",
Sync = "itad.sync",
LastImport = "itad.lastimport",
SyncEvents = "itad.syncevents",
InWaitlist = "itad.inwaitlist",
AddToWaitlist = "itad.addtowaitlist",
RemoveFromWaitlist = "itad.removefromwaitlist",
Expand Down
164 changes: 87 additions & 77 deletions src/js/Background/Modules/IsThereAnyDeal/ITADApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
TLastImportResponse,
TNotesList,
TPushNotesStatus,
TShopInfo
TShopInfo, TSyncEvent
} from "./_types";
import type MessageHandlerInterface from "@Background/MessageHandlerInterface";
import Authorization from "./Authorization";
Expand Down Expand Up @@ -181,19 +181,30 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
private async disconnect(): Promise<void> {
await AccessToken.clear();
await LocalStorage.remove("lastItadImport");
await LocalStorage.remove("syncEvents");
return IndexedDB.clear("collection", "waitlist", "itadImport");
}

private async getLastImport(): Promise<TLastImportResponse> {
return (await LocalStorage.get("lastItadImport")) ?? {from: null, to: null};
}

private async recordLastImport() {
private async recordLastImport(): Promise<void> {
let lastImport = await this.getLastImport();
lastImport.from = TimeUtils.now();
await LocalStorage.set("lastItadImport", lastImport);
}

private async getSyncEvents(): Promise<TSyncEvent[]> {
return (await LocalStorage.get("syncEvents")) ?? [];
}

private async recordSyncEvent(section: string, type: "push"|"pull", count: number): Promise<void> {
const events = await this.getSyncEvents();
events.unshift({section, type, timestamp: TimeUtils.now(), count});
await LocalStorage.set("syncEvents", events.slice(0, 20));
}

private async removeFromWaitlist(appids: number[]) {
const accessToken = await AccessToken.load();
if (!accessToken) {
Expand Down Expand Up @@ -288,6 +299,10 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
private async exportToItad(force: boolean): Promise<void> {
await SettingsStore.load();

if (!Settings.itad_sync_library && !Settings.itad_sync_wishlist) {
return;
}

if (force) {
await IndexedDB.clear("dynamicStore");
} else {
Expand All @@ -298,60 +313,49 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
}
}

const db = IndexedDB.db;
const tx = db.transaction(["dynamicStore", "itadImport"]);
const dynamicStore = tx.objectStore("dynamicStore");
const itadImport = tx.objectStore("itadImport");

let ownedApps: number[] = [];
let ownedPackages: number[] = [];
let wishlisted: number[] = [];

let newOwnedApps: number[] = [];
let newOwnedPackages: number[] = [];
let newWishlisted: number[] = [];

if (Settings.itad_sync_library) {
const lastOwnedApps = new Set(await itadImport.get("lastOwnedApps") ?? []);
const lastOwnedPackages = new Set(await itadImport.get("lastOwnedPackages") ?? []);
const tx = IndexedDB.db.transaction(["dynamicStore", "itadImport"]);
const dynamicStore = tx.objectStore("dynamicStore");
const itadImport = tx.objectStore("itadImport");

ownedApps = await dynamicStore.get("ownedApps") ?? [];
ownedPackages = await dynamicStore.get("ownedPackages") ?? [];
const lastOwnedApps: Set<number> = new Set(await itadImport.get("lastOwnedApps") ?? []);
const lastOwnedPackages: Set<number> = new Set(await itadImport.get("lastOwnedPackages") ?? []);

newOwnedApps = ownedApps.filter(id => !lastOwnedApps.has(id));
newOwnedPackages = ownedPackages.filter(id => !lastOwnedPackages.has(id));
}

if (Settings.itad_sync_wishlist) {
const lastWishlisted = new Set(await itadImport.get("lastWishlisted") ?? []);

wishlisted = await dynamicStore.get("wishlisted") ?? [];
newWishlisted = wishlisted.filter(id => !lastWishlisted.has(id));
}
const ownedApps: number[] = await dynamicStore.get("ownedApps") ?? [];
const ownedPackages: number[] = await dynamicStore.get("ownedPackages") ?? [];
await tx.done;

await tx.done;
const newOwnedApps: number[] = ownedApps.filter(id => !lastOwnedApps.has(id));
const newOwnedPackages: number[] = ownedPackages.filter(id => !lastOwnedPackages.has(id));

const promises = [];

if (newOwnedApps.length > 0 || newOwnedPackages.length > 0) {
promises.push((async () => {
if (newOwnedApps.length > 0 || newOwnedPackages.length > 0) {
await this.addToCollection(newOwnedApps, newOwnedPackages);
await IndexedDB.putMany("itadImport", [
["lastOwnedApps", ownedApps],
["lastOwnedPackages", ownedPackages],
]);
})());
["lastOwnedPackages", ownedPackages]
])
}
await this.recordSyncEvent("collection", "push", newOwnedApps.length + newOwnedPackages.length);
}

if (newWishlisted.length > 0) {
promises.push((async () => {
if (Settings.itad_sync_wishlist) {
const tx = IndexedDB.db.transaction(["dynamicStore", "itadImport"]);
const dynamicStore = tx.objectStore("dynamicStore");
const itadImport = tx.objectStore("itadImport");

const lastWishlisted: Set<number> = new Set(await itadImport.get("lastWishlisted") ?? []);
const wishlisted: number[] = await dynamicStore.get("wishlisted") ?? [];
await tx.done;

const newWishlisted: number[] = wishlisted.filter(id => !lastWishlisted.has(id));

if (newWishlisted.length > 0) {
await this.addToWaitlist(newWishlisted);
await IndexedDB.put("itadImport", wishlisted, "lastWishlisted");
})());
}
await this.recordSyncEvent("waitlist", "push", newWishlisted.length);
}

await Promise.all(promises);

let lastImport = await this.getLastImport();
lastImport.to = TimeUtils.now();

Expand All @@ -369,6 +373,7 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
await IndexedDB.replaceAll("waitlist", data ? [...data.entries()] : []);
await IndexedDB.setStoreExpiry("waitlist", 15*60);
await this.recordLastImport();
await this.recordSyncEvent("waitlist", "pull", data?.size ?? 0);
}
}

Expand All @@ -383,6 +388,7 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
await IndexedDB.replaceAll("collection", data ? [...data.entries()] : []);
await IndexedDB.setStoreExpiry("collection", 15*60);
await this.recordLastImport();
await this.recordSyncEvent("collection", "pull", data?.size ?? 0);
}
}

Expand Down Expand Up @@ -436,50 +442,50 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
}
});

if (response.length === 0) {
await IndexedDB.setStoreExpiry("notes", TTL);
return 0;
}

const notes: Map<string, string> = new Map<string, string>(
response.map(o => [o.gid, o.note])
);

const steamIds = await this.fetchSteamIds([...notes.keys()]);

const result: Map<number, string> = new Map();
for (let [steamId, gid] of steamIds) {
if (!steamId.startsWith("app/")) {
continue;
}

const appid = Number(steamId.substring(4));
const note = notes.get(gid)!;
if (response.length !== 0) {
const notes: Map<string, string> = new Map<string, string>(
response.map(o => [o.gid, o.note])
);

result.set(appid, note);
}
const steamIds = await this.fetchSteamIds([...notes.keys()]);

switch(Settings.user_notes_adapter) {
case "synced_storage":
/**
* A little bit of hack, but SyncedStorageAdapter may be used in Background,
* and it does better handling that I would want to do here.
* Synced Storage should be removed in future versions.
*/
const adapter = new SyncedStorageAdapter();
for (const [appid, note] of result) {
await adapter.set(appid, note);
for (let [steamId, gid] of steamIds) {
if (!steamId.startsWith("app/")) {
continue;
}
break;

case "idb":
await IndexedDB.putMany("notes",
[...result.entries()].map(([appid, note]) => [Number(appid), note])
);
break;
const appid = Number(steamId.substring(4));
const note = notes.get(gid)!;

result.set(appid, note);
}

switch(Settings.user_notes_adapter) {
case "synced_storage":
/**
* A little bit of hack, but SyncedStorageAdapter may be used in Background,
* and it does better handling that I would want to do here.
* Synced Storage should be removed in future versions.
*/
const adapter = new SyncedStorageAdapter();
for (const [appid, note] of result) {
await adapter.set(appid, note);
}
break;

case "idb":
await IndexedDB.putMany("notes",
[...result.entries()].map(([appid, note]) => [Number(appid), note])
);
break;
}
}

await IndexedDB.setStoreExpiry("notes", TTL);
await this.recordLastImport();
await this.recordSyncEvent("notes", "pull", result.size);
return result.size;
}

Expand Down Expand Up @@ -550,6 +556,7 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
}

status.pushed = toPush.length;
await this.recordSyncEvent("notes", "push", status.pushed);
return status;
}

Expand Down Expand Up @@ -603,6 +610,9 @@ export default class ITADApi extends Api implements MessageHandlerInterface {
case EAction.LastImport:
return this.getLastImport();

case EAction.SyncEvents:
return this.getSyncEvents();

case EAction.InWaitlist:
return this.inWaitlist(message.params.storeIds);

Expand Down
7 changes: 7 additions & 0 deletions src/js/Background/Modules/IsThereAnyDeal/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ export type TLastImportResponse = {
to: number|null
};

export type TSyncEvent = {
section: string,
type: "push"|"pull",
timestamp: number,
count: number
};

export type TInWaitlistResponse = Record<string, boolean>;

export type TInCollectionResponse = Record<string, boolean>;
Expand Down
6 changes: 5 additions & 1 deletion src/js/Content/Modules/Facades/ITADApiFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
TCollectionCopy,
TGetStoreListResponse, TInCollectionResponse,
TInWaitlistResponse,
TLastImportResponse, TNotesList, TPushNotesStatus
TLastImportResponse, TNotesList, TPushNotesStatus, TSyncEvent
} from "@Background/Modules/IsThereAnyDeal/_types";
import Background from "@Core/Background";
import {EAction} from "@Background/EAction";
Expand Down Expand Up @@ -33,6 +33,10 @@ export default class ITADApiFacade {
return Background.send(EAction.LastImport);
}

static getSyncEvents(): Promise<TSyncEvent[]> {
return Background.send(EAction.SyncEvents);
}

static async inWaitlist(storeIds: string[]): Promise<TInWaitlistResponse> {
return Background.send(EAction.InWaitlist, {storeIds});
}
Expand Down
4 changes: 4 additions & 0 deletions src/js/Content/Modules/Widgets/ITADSync/ITADSyncStatus.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import ESyncStatus from "@Core/Sync/ESyncStatus";
import SyncIndicator from "@Core/Sync/SyncIndicator.svelte";
import {fade} from "svelte/transition";
import {createEventDispatcher} from "svelte";
const dispatch = createEventDispatcher<{syncEvent: void}>();
export let isConnected: boolean;
export let enableSync: boolean;
Expand All @@ -26,6 +29,7 @@
await ITADApiFacade.sync(true);
status = ESyncStatus.OK;
await updateLastImport();
dispatch("syncEvent");
} catch (e) {
status = ESyncStatus.Error;
Expand Down
6 changes: 6 additions & 0 deletions src/js/Core/Storage/LocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export interface LocalStorageSchema extends StorageSchema {
from: null|number,
to: null|number
},
syncEvents: Array<{
section: string,
type: "push"|"pull",
timestamp: number,
count: number
}>,
db_cleanup: number,
show_review_section: boolean,
hide_login_warn_store: boolean,
Expand Down
27 changes: 23 additions & 4 deletions src/js/Options/Modules/Options/ITADOptions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import {L} from "@Core/Localization/Localization";
import {
__itad_enableSyncLibrary,
__itad_enableSyncWishlist,
__itad_import,
__itad_enableSyncWishlist, __itad_syncEvents,
__options_addToWaitlist,
__options_collectionBannerNotOwned,
__options_userNotes_userNotes,
Expand All @@ -22,16 +21,30 @@
import ITADConnection from "./Settings/ITADConnection.svelte";
import type {SettingsSchema} from "../../Data/_types";
import NotesSyncControls from "@Options/Modules/Options/Settings/NotesSyncControls.svelte";
import ITADApiFacade from "@Content/Modules/Facades/ITADApiFacade";
import SyncEvents from "@Options/Modules/Options/Settings/SyncEvents.svelte";
import {onMount} from "svelte";
import type {TSyncEvent} from "@Background/Modules/IsThereAnyDeal/_types";
let settings: Writable<SettingsSchema> = writable(Settings);
let isConnected: boolean;
let events: TSyncEvent[];
async function loadSyncEvents(): Promise<void> {
events = await ITADApiFacade.getSyncEvents();
}
onMount(() => {
loadSyncEvents();
});
</script>


<div>
<Section title="IsThereAnyDeal">
<OptionGroup>
<ITADConnection {settings} bind:isConnected />
<ITADConnection {settings} bind:isConnected on:syncEvent={loadSyncEvents} />
</OptionGroup>
</Section>

Expand Down Expand Up @@ -63,11 +76,17 @@
</div>

<OptionGroup>
<NotesSyncControls {isConnected} />
<NotesSyncControls {isConnected} on:syncEvent={loadSyncEvents} />
</OptionGroup>

<OptionGroup>
<Toggle bind:value={$settings.itad_sync_notes}>{L(__userNote_syncOption)}</Toggle>
</OptionGroup>
</Section>

{#if events}
<Section title={L(__itad_syncEvents)}>
<SyncEvents {events} />
</Section>
{/if}
</div>
Loading

0 comments on commit a8bdb7c

Please sign in to comment.