Aurora SpotiRip desktop and web app
- Spotify playlist, library, album and artist browsing with in-app playback - Song, playlist and album downloads via spotdl with YouTube and Bandcamp fallbacks - Electron desktop app (castLabs Widevine) with a permanent sidebar layout - Linux and Windows packaging scripts
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
export type LibraryTrackRecord = {
|
||||
id: string;
|
||||
isrc?: string;
|
||||
title: string;
|
||||
artist: string;
|
||||
album?: string;
|
||||
albumArtist?: string;
|
||||
savedPath?: string;
|
||||
artworkUrl?: string;
|
||||
source?: string;
|
||||
addedAt: number;
|
||||
};
|
||||
|
||||
const DB_NAME = "aurora-spotirip-library";
|
||||
const TRACK_STORE = "tracks";
|
||||
const HISTORY_STORE = "history";
|
||||
|
||||
function normalize(value: string) {
|
||||
return value
|
||||
.normalize("NFKD")
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function trackIdentity(track: {
|
||||
isrc?: string;
|
||||
artist: string;
|
||||
title: string;
|
||||
}) {
|
||||
return track.isrc?.trim().toUpperCase() ||
|
||||
`${normalize(track.artist)}::${normalize(track.title)}`;
|
||||
}
|
||||
|
||||
function openDb() {
|
||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, 1);
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(TRACK_STORE)) {
|
||||
db.createObjectStore(TRACK_STORE, { keyPath: "id" });
|
||||
}
|
||||
if (!db.objectStoreNames.contains(HISTORY_STORE)) {
|
||||
db.createObjectStore(HISTORY_STORE, { keyPath: "id" });
|
||||
}
|
||||
};
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error || new Error("Unable to open library index."));
|
||||
});
|
||||
}
|
||||
|
||||
export async function findLibraryDuplicate(track: {
|
||||
isrc?: string;
|
||||
artist: string;
|
||||
title: string;
|
||||
}) {
|
||||
if (typeof indexedDB === "undefined") return undefined;
|
||||
const db = await openDb();
|
||||
try {
|
||||
const id = trackIdentity(track);
|
||||
return await new Promise<LibraryTrackRecord | undefined>((resolve, reject) => {
|
||||
const tx = db.transaction(TRACK_STORE, "readonly");
|
||||
const request = tx.objectStore(TRACK_STORE).get(id);
|
||||
request.onsuccess = () => resolve(request.result as LibraryTrackRecord | undefined);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function rememberLibraryTrack(
|
||||
track: Omit<LibraryTrackRecord, "id" | "addedAt">,
|
||||
) {
|
||||
const db = await openDb();
|
||||
try {
|
||||
const record: LibraryTrackRecord = {
|
||||
...track,
|
||||
id: trackIdentity(track),
|
||||
addedAt: Date.now(),
|
||||
};
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction([TRACK_STORE, HISTORY_STORE], "readwrite");
|
||||
tx.objectStore(TRACK_STORE).put(record);
|
||||
tx.objectStore(HISTORY_STORE).put(record);
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error || new Error("Unable to update library history."));
|
||||
});
|
||||
return record;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function listLibraryHistory() {
|
||||
if (typeof indexedDB === "undefined") return [] as LibraryTrackRecord[];
|
||||
const db = await openDb();
|
||||
try {
|
||||
const records = await new Promise<LibraryTrackRecord[]>((resolve, reject) => {
|
||||
const tx = db.transaction(HISTORY_STORE, "readonly");
|
||||
const request = tx.objectStore(HISTORY_STORE).getAll();
|
||||
request.onsuccess = () => resolve((request.result || []) as LibraryTrackRecord[]);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
return records.sort((a, b) => b.addedAt - a.addedAt);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearLibraryHistory() {
|
||||
const db = await openDb();
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const tx = db.transaction(HISTORY_STORE, "readwrite");
|
||||
tx.objectStore(HISTORY_STORE).clear();
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user