refactor pkgs store (#581)

Co-authored-by: neil molina <neil@neils-MacBook-Pro.local>
This commit is contained in:
Neil 2023-05-10 13:15:29 +08:00 committed by GitHub
parent fb854d85a1
commit dcc9a34e2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 298 additions and 285 deletions

View file

@ -0,0 +1,26 @@
import type { GUIPackage, InstalledPackage, Packages } from "./types";
import Fuse from "fuse.js";
import log from "$libs/logger";
let packagesIndex: Fuse<GUIPackage>;
export function indexPackages(packages: GUIPackage[]) {
try {
packagesIndex = new Fuse(packages, {
keys: ["name", "full_name", "desc", "categories"],
minMatchCharLength: 3,
threshold: 0.3
});
log.info("refreshed packages fuse index");
} catch (error) {
log.error(error);
}
}
export function searchPackages(term: string, limit = 5): GUIPackage[] {
if (!term || !packagesIndex) return [];
// TODO: if online, use algolia else use Fuse
const res = packagesIndex.search(term, { limit });
const matchingPackages: GUIPackage[] = res.map((v) => v.item);
return matchingPackages;
}

View file

@ -7,14 +7,14 @@ import type { GUIPackage } from "$libs/types";
import { getFeaturedPackages, getPackageReviews } from "@native"; import { getFeaturedPackages, getPackageReviews } from "@native";
import initAuthStore from "./stores/auth"; import initAuthStore from "./stores/auth";
import initNavStore from "./stores/nav"; import initNavStore from "./stores/nav";
import initPackagesStore from "./stores/pkgs"; import pkgStore from "./stores/pkgs";
import initNotificationStore from "./stores/notifications"; import initNotificationStore from "./stores/notifications";
import initAppUpdateStore from "./stores/update"; import initAppUpdateStore from "./stores/update";
import { trackSearch } from "./analytics"; import { trackSearch } from "./analytics";
export const featuredPackages = writable<Package[]>([]); export const featuredPackages = writable<Package[]>([]);
export const packagesStore = initPackagesStore(); export const packagesStore = pkgStore;
export const initializeFeaturedPackages = async () => { export const initializeFeaturedPackages = async () => {
console.log("intialize featured packages"); console.log("intialize featured packages");

View file

@ -1,7 +1,6 @@
import { derived, writable } from "svelte/store"; import { derived, writable } from "svelte/store";
import type { GUIPackage, InstalledPackage, Packages } from "../types"; import type { GUIPackage, InstalledPackage, Packages } from "../types";
import { PackageStates } from "../types"; import { PackageStates } from "../types";
import Fuse from "fuse.js";
import { import {
getPackage, getPackage,
getDistPackages, getDistPackages,
@ -29,26 +28,25 @@ import log from "$libs/logger";
import { isPackageUpToDate } from "../packages/pkg-utils"; import { isPackageUpToDate } from "../packages/pkg-utils";
import withDelay from "$libs/utils/delay"; import withDelay from "$libs/utils/delay";
import { indexPackages, searchPackages } from "$libs/search-index";
const packageRefreshInterval = 1000 * 60 * 60; // 1 hour const packageRefreshInterval = 1000 * 60 * 60; // 1 hour
export default function initPackagesStore() { let initialized = false;
let initialized = false; let isDestroyed = false;
let isDestroyed = false; let refreshTimeoutId: ReturnType<typeof setTimeout> | null = null;
let refreshTimeoutId: ReturnType<typeof setTimeout> | null = null;
const packageMap = writable<Packages>({ version: "0", packages: {} }); const packageMap = writable<Packages>({ version: "0", packages: {} });
const packageList = derived(packageMap, ($packages) => const packageList = derived(packageMap, ($packages) =>
Object.values($packages.packages).sort((a, b) => { Object.values($packages.packages).sort((a, b) => {
// implement default sort by last_modified > descending // implement default sort by last_modified > descending
const aDate = new Date(a.last_modified); const aDate = new Date(a.last_modified);
const bDate = new Date(b.last_modified); const bDate = new Date(b.last_modified);
return +bDate - +aDate; return +bDate - +aDate;
}) })
); );
let packagesIndex: Fuse<GUIPackage>; const updateAllPackages = (guiPkgs: GUIPackage[]) => {
const updateAllPackages = (guiPkgs: GUIPackage[]) => {
packageMap.update((pkgs) => { packageMap.update((pkgs) => {
guiPkgs.forEach((pkg) => { guiPkgs.forEach((pkg) => {
const oldPkg = pkgs.packages[pkg.full_name]; const oldPkg = pkgs.packages[pkg.full_name];
@ -57,9 +55,9 @@ export default function initPackagesStore() {
setBadgeCountFromPkgs(pkgs); setBadgeCountFromPkgs(pkgs);
return pkgs; return pkgs;
}); });
}; };
const updatePackage = (full_name: string, props: Partial<GUIPackage>, newVersion?: string) => { const updatePackage = (full_name: string, props: Partial<GUIPackage>, newVersion?: string) => {
packageMap.update((pkgs) => { packageMap.update((pkgs) => {
const pkg = pkgs.packages[full_name]; const pkg = pkgs.packages[full_name];
if (pkg) { if (pkg) {
@ -79,10 +77,10 @@ export default function initPackagesStore() {
} }
return pkgs; return pkgs;
}); });
}; };
// getPackage state centralizes the logic for determining the state of the package based on the other properties // getPackage state centralizes the logic for determining the state of the package based on the other properties
const getPackageState = (pkg: GUIPackage): PackageStates => { const getPackageState = (pkg: GUIPackage): PackageStates => {
if (pkg.isUninstalling) { if (pkg.isUninstalling) {
//TODO: maybe there should be an uninstalling state too? Although that needs UI/UX changes //TODO: maybe there should be an uninstalling state too? Although that needs UI/UX changes
return PackageStates.AVAILABLE; return PackageStates.AVAILABLE;
@ -103,9 +101,9 @@ export default function initPackagesStore() {
} }
return isUpToDate ? PackageStates.INSTALLED : PackageStates.NEEDS_UPDATE; return isUpToDate ? PackageStates.INSTALLED : PackageStates.NEEDS_UPDATE;
}; };
const syncPackageData = async (guiPkg: Partial<GUIPackage> | undefined) => { const syncPackageData = async (guiPkg: Partial<GUIPackage> | undefined) => {
if (!guiPkg) return; if (!guiPkg) return;
const pkg = await getPackage(guiPkg.full_name!); // ATM: pkg only bottles and github:string const pkg = await getPackage(guiPkg.full_name!); // ATM: pkg only bottles and github:string
@ -141,9 +139,9 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
} }
updatePackage(guiPkg.full_name!, updatedPackage); updatePackage(guiPkg.full_name!, updatedPackage);
}; };
const init = async function () { const init = async function () {
log.info("packages store: try initialize"); log.info("packages store: try initialize");
if (!initialized) { if (!initialized) {
@ -155,9 +153,9 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
initialized = true; initialized = true;
} }
log.info("packages store: initialized!"); log.info("packages store: initialized!");
}; };
const refreshPackages = async () => { const refreshPackages = async () => {
if (isDestroyed) return; if (isDestroyed) return;
log.info("packages store: refreshing..."); log.info("packages store: refreshing...");
@ -174,12 +172,8 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
log.info("initialized packages store with ", guiPkgs.length); log.info("initialized packages store with ", guiPkgs.length);
} }
packagesIndex = new Fuse(guiPkgs, { // initialize Fuse index for fuzzy search
keys: ["name", "full_name", "desc", "categories"], indexPackages(guiPkgs);
minMatchCharLength: 3,
threshold: 0.3
});
log.info("refreshed packages fuse index");
try { try {
const installedPkgs: InstalledPackage[] = await getInstalledPackages(); const installedPkgs: InstalledPackage[] = await getInstalledPackages();
@ -205,18 +199,18 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
} }
refreshTimeoutId = setTimeout(() => refreshPackages(), packageRefreshInterval); // refresh every hour refreshTimeoutId = setTimeout(() => refreshPackages(), packageRefreshInterval); // refresh every hour
}; };
// Destructor for the package store // Destructor for the package store
const destroy = () => { const destroy = () => {
isDestroyed = true; isDestroyed = true;
if (refreshTimeoutId) { if (refreshTimeoutId) {
clearTimeout(refreshTimeoutId); clearTimeout(refreshTimeoutId);
} }
log.info("packages store: destroyed"); log.info("packages store: destroyed");
}; };
const installPkg = async (pkg: GUIPackage, version?: string) => { const installPkg = async (pkg: GUIPackage, version?: string) => {
const versionToInstall = version || pkg.version; const versionToInstall = version || pkg.version;
try { try {
@ -239,9 +233,9 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
} finally { } finally {
updatePackage(pkg.full_name, { install_progress_percentage: 100 }); updatePackage(pkg.full_name, { install_progress_percentage: 100 });
} }
}; };
const uninstallPkg = async (pkg: GUIPackage) => { const uninstallPkg = async (pkg: GUIPackage) => {
let fakeTimer: NodeJS.Timer | null = null; let fakeTimer: NodeJS.Timer | null = null;
try { try {
fakeTimer = withFakeLoader(pkg, (progress) => { fakeTimer = withFakeLoader(pkg, (progress) => {
@ -270,22 +264,22 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
fakeTimer && clearTimeout(fakeTimer); fakeTimer && clearTimeout(fakeTimer);
updatePackage(pkg.full_name, { install_progress_percentage: 0, isUninstalling: false }); updatePackage(pkg.full_name, { install_progress_percentage: 0, isUninstalling: false });
} }
}; };
const deletePkg = async (pkg: GUIPackage, version: string) => { const deletePkg = async (pkg: GUIPackage, version: string) => {
log.info("deleting package: ", pkg.full_name, " version: ", version); log.info("deleting package: ", pkg.full_name, " version: ", version);
await deletePackage({ fullName: pkg.full_name, version }); await deletePackage({ fullName: pkg.full_name, version });
updatePackage(pkg.full_name, { updatePackage(pkg.full_name, {
installed_versions: pkg.installed_versions?.filter((v) => v !== version) installed_versions: pkg.installed_versions?.filter((v) => v !== version)
}); });
}; };
const writePackageCacheWithDebounce = withDebounce(writePackageCache); const writePackageCacheWithDebounce = withDebounce(writePackageCache);
packageMap.subscribe(async (pkgs) => { packageMap.subscribe(async (pkgs) => {
writePackageCacheWithDebounce(pkgs); writePackageCacheWithDebounce(pkgs);
}); });
const cachePkgImage = async (pkg: GUIPackage): Promise<string> => { const cachePkgImage = async (pkg: GUIPackage): Promise<string> => {
let cacheFileURL = ""; let cacheFileURL = "";
updatePackage(pkg.full_name, { cached_image_url: "" }); updatePackage(pkg.full_name, { cached_image_url: "" });
if (pkg.thumb_image_url && !pkg.thumb_image_url.includes("package-thumb-nolabel4.jpg")) { if (pkg.thumb_image_url && !pkg.thumb_image_url.includes("package-thumb-nolabel4.jpg")) {
@ -296,40 +290,21 @@ To read more about this package go to [${guiPkg.homepage}](${guiPkg.homepage}).
} }
} }
return cacheFileURL; return cacheFileURL;
}; };
listenToChannel("install-progress", ({ full_name, progress }: any) => { listenToChannel("install-progress", ({ full_name, progress }: any) => {
if (!full_name) { if (!full_name) {
return; return;
} }
updatePackage(full_name, { install_progress_percentage: progress }); updatePackage(full_name, { install_progress_percentage: progress });
}); });
listenToChannel("pkg-installed", ({ full_name, version }: any) => { listenToChannel("pkg-installed", ({ full_name, version }: any) => {
if (!full_name) { if (!full_name) {
return; return;
} }
updatePackage(full_name, {}, version); updatePackage(full_name, {}, version);
}); });
return {
packageList,
search: async (term: string, limit = 5): Promise<GUIPackage[]> => {
if (!term || !packagesIndex) return [];
// TODO: if online, use algolia else use Fuse
const res = packagesIndex.search(term, { limit });
const matchingPackages: GUIPackage[] = res.map((v) => v.item);
return matchingPackages;
},
init,
installPkg,
uninstallPkg,
syncPackageData,
deletePkg,
destroy,
cachePkgImage
};
}
// This is only used for uninstall now // This is only used for uninstall now
export const withFakeLoader = ( export const withFakeLoader = (
@ -364,3 +339,15 @@ const setBadgeCountFromPkgs = (pkgs: Packages) => {
log.error(error); log.error(error);
} }
}; };
export default {
packageList,
search: searchPackages,
init,
installPkg,
uninstallPkg,
syncPackageData,
deletePkg,
destroy,
cachePkgImage
};