-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Reworked settings storage #101
- Loading branch information
Showing
13 changed files
with
552 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default interface IGraphics | ||
{ | ||
Icon?: string; | ||
Thumbnail?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { compress, decompress } from "lzutf8"; | ||
import { Storage } from "webextension-polyfill"; | ||
import { CollectionModel } from "../../Models/Data"; | ||
import { ext } from "../../Utils"; | ||
import CollectionOptimizer from "../../Utils/CollectionOptimizer"; | ||
|
||
/** | ||
* Data repository that provides access to saved collections. | ||
*/ | ||
export default class CollectionsRepository | ||
{ | ||
/** | ||
* Fired when collections are changed. | ||
*/ | ||
public ItemsChanged: (collections: CollectionModel[]) => void; | ||
|
||
private source: Storage.StorageArea = null; | ||
|
||
/** | ||
* Generates a new instance of the class. | ||
* @param source Storage area to be used | ||
*/ | ||
public constructor(source: "sync" | "local") | ||
{ | ||
this.source = source === "sync" ? ext?.storage.sync : ext?.storage.local; | ||
ext?.storage.onChanged.addListener(this.OnStorageChanged); | ||
} | ||
|
||
/** | ||
* Gets saved collections from repository. | ||
* @returns Saved collections | ||
*/ | ||
public async GetCollectionsAsync(): Promise<CollectionModel[]> | ||
{ | ||
if (!this.source) | ||
return []; | ||
|
||
let chunks: { [key: string]: string; } = { }; | ||
|
||
// Setting which data to retrieve and its default value | ||
// Saved collections are now stored in chunks. This is the most efficient way to store these. | ||
for (let i = 0; i < 12; i++) | ||
chunks[`chunk${i}`] = null; | ||
|
||
chunks = await this.source.get(chunks); | ||
|
||
let data: string = ""; | ||
|
||
for (let chunk of Object.values(chunks)) | ||
if (chunk) | ||
data += chunk; | ||
|
||
data = decompress(data, { inputEncoding: "StorageBinaryString" }); | ||
|
||
return CollectionOptimizer.DeserializeCollections(data); | ||
} | ||
|
||
/** | ||
* Adds new collection to repository. | ||
* @param collection Collection to be saved | ||
*/ | ||
public async AddCollectionAsync(collection: CollectionModel): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
let items: CollectionModel[] = await this.GetCollectionsAsync(); | ||
items.push(collection); | ||
|
||
await this.SaveChangesAsync(items); | ||
} | ||
|
||
/** | ||
* Updates existing collection or adds a new one in repository. | ||
* @param collection Collection to be updated | ||
*/ | ||
public async UpdateCollectionAsync(collection: CollectionModel): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
let items: CollectionModel[] = await this.GetCollectionsAsync(); | ||
let index = items.findIndex(i => i.Timestamp === collection.Timestamp); | ||
|
||
if (index === -1) | ||
items.push(collection); | ||
else | ||
items[index] = collection; | ||
|
||
await this.SaveChangesAsync(items); | ||
} | ||
|
||
/** | ||
* Removes collection from repository. | ||
* @param collection Collection to be removed | ||
*/ | ||
public async RemoveCollectionAsync(collection: CollectionModel): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
let items: CollectionModel[] = await this.GetCollectionsAsync(); | ||
items = items.filter(i => i.Timestamp !== collection.Timestamp); | ||
|
||
await this.SaveChangesAsync(items); | ||
} | ||
|
||
/** | ||
* Removes all collections from repository. | ||
*/ | ||
public async Clear(): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
let keys: string[] = []; | ||
|
||
for (let i = 0; i < 12; i++) | ||
keys.push(`chunk${i}`); | ||
|
||
await this.source.remove(keys); | ||
} | ||
|
||
private async SaveChangesAsync(collections: CollectionModel[]): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
let data: string = CollectionOptimizer.SerializeCollections(collections); | ||
data = compress(data, { outputEncoding: "StorageBinaryString" }); | ||
|
||
let chunks: string[] = CollectionOptimizer.SplitIntoChunks(data); | ||
|
||
let items: { [key: string]: string; } = {}; | ||
|
||
for (let i = 0; i < chunks.length; i++) | ||
items[`chunk${i}`] = chunks[i]; | ||
|
||
let chunksToDelete: string[] = []; | ||
|
||
for (let i = chunks.length; i < 12; i++) | ||
chunksToDelete.push(`chunk${i}`); | ||
|
||
await this.source.set(items); | ||
await this.source.remove(chunksToDelete); | ||
} | ||
|
||
private async OnStorageChanged(changes: { [key: string]: Storage.StorageChange }, areaName: string): Promise<void> | ||
{ | ||
if (!this.source) | ||
return; | ||
|
||
if (!Object.keys(changes).some(k => k.startsWith("chunk"))) | ||
return; | ||
|
||
let collections: CollectionModel[] = await this.GetCollectionsAsync(); | ||
this.ItemsChanged?.(collections); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { IGraphics } from "../../Models/Data"; | ||
import { ext } from "../../Utils"; | ||
|
||
/** | ||
* Provides access to saved graphics (icons and thumbnails). | ||
*/ | ||
export default class GraphicsRepository | ||
{ | ||
/** | ||
* Gets saved graphics from storage. | ||
* @returns Dictionary of IGraphics objects, where key is the URL of the graphics. | ||
*/ | ||
public async GetGraphicsAsync(): Promise<Record<string, IGraphics>> | ||
{ | ||
if (!ext) | ||
return { }; | ||
|
||
let data: Record<string, any> = await ext.storage.local.get(null); | ||
let graphics: Record<string, IGraphics> = { }; | ||
|
||
for (let key in data) | ||
try | ||
{ | ||
new URL(key); | ||
graphics[key] = data[key] as IGraphics; | ||
} | ||
catch { continue; } | ||
|
||
return graphics; | ||
} | ||
|
||
/** | ||
* Saves graphics to storage. | ||
* @param graphics Dictionary of IGraphics objects, where key is the URL of the graphics. | ||
*/ | ||
public async AddOrUpdateGraphicsAsync(graphics: Record<string, IGraphics>): Promise<void> | ||
{ | ||
if (!ext) | ||
return; | ||
|
||
let data: Record<string, any> = await ext.storage.local.get(Object.keys(graphics)); | ||
|
||
for (let key in graphics) | ||
if (data[key] === undefined) | ||
data[key] = graphics[key]; | ||
else | ||
data[key] = { ...data[key], ...graphics[key] }; | ||
|
||
await ext.storage.local.set(graphics); | ||
} | ||
|
||
/** | ||
* Removes graphics from storage. | ||
* @param graphics Dictionary of IGraphics objects, where key is the URL of the graphics. | ||
*/ | ||
public async RemoveGraphicsAsync(graphics: Record<string, IGraphics>): Promise<void> | ||
{ | ||
if (!ext) | ||
return; | ||
|
||
await ext.storage.local.remove(Object.keys(graphics)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Storage } from "webextension-polyfill"; | ||
import { SettingsModel } from "../../Models/Data"; | ||
import { ext } from "../../Utils"; | ||
|
||
/** | ||
* Data repository that provides access to saved settings. | ||
*/ | ||
export default class SettingsRepository | ||
{ | ||
/** | ||
* Fired when settings are changed. | ||
*/ | ||
public ItemsChanged: (changes: Partial<SettingsModel>) => void; | ||
|
||
public constructor() | ||
{ | ||
ext?.storage.sync.onChanged.addListener(this.OnStorageChanged); | ||
} | ||
|
||
/** | ||
* Gets saved settings. | ||
* @returns Saved settings | ||
*/ | ||
public async GetSettingsAsync(): Promise<SettingsModel> | ||
{ | ||
let fallbackOptions = new SettingsModel(); | ||
|
||
if (!ext) | ||
return fallbackOptions; | ||
|
||
let options: Record<string, any> = await ext.storage.sync.get(fallbackOptions); | ||
|
||
return new SettingsModel(options); | ||
} | ||
|
||
/** | ||
* Saves settings. | ||
* @param changes Changes to be saved | ||
*/ | ||
public async UpdateSettingsAsync(changes: Partial<SettingsModel>): Promise<void> | ||
{ | ||
if (ext) | ||
await ext.storage.sync.set(changes); | ||
else if (this.ItemsChanged) | ||
this.ItemsChanged(changes); | ||
} | ||
|
||
private OnStorageChanged(changes: { [key: string]: Storage.StorageChange }): void | ||
{ | ||
let propsList: string[] = Object.keys(new SettingsRepository()); | ||
let settings: { [key: string]: any; } = {}; | ||
|
||
Object.entries(changes) | ||
.filter(i => propsList.includes(i[0])) | ||
.map(i => settings[i[0]] = i[1].newValue); | ||
|
||
this.ItemsChanged?.(settings as Partial<SettingsModel>); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.