docs: mark AUR package as pending registration reopen
This commit is contained in:
@@ -6,355 +6,379 @@
|
||||
* download queue (max 2 concurrent).
|
||||
*/
|
||||
|
||||
import { createSignal } from "solid-js"
|
||||
import { DownloadStatus } from "../types/episode"
|
||||
import type { DownloadedEpisode } from "../types/episode"
|
||||
import type { Episode } from "../types/episode"
|
||||
import { downloadEpisode } from "../utils/episode-downloader"
|
||||
import { ensureConfigDir, getConfigFilePath } from "../utils/config-dir"
|
||||
import { backupConfigFile } from "../utils/config-backup"
|
||||
import { createSignal } from "solid-js";
|
||||
import { DownloadStatus } from "../types/episode";
|
||||
import type { DownloadedEpisode } from "../types/episode";
|
||||
import type { Episode } from "../types/episode";
|
||||
import { downloadEpisode } from "../utils/episode-downloader";
|
||||
import { ensureConfigDir, getConfigFilePath } from "../utils/config-dir";
|
||||
import { backupConfigFile } from "../utils/config-backup";
|
||||
|
||||
const DOWNLOADS_FILE = "downloads.json"
|
||||
const MAX_CONCURRENT = 2
|
||||
const DOWNLOADS_FILE = "downloads.json";
|
||||
const MAX_CONCURRENT = 2;
|
||||
|
||||
/** Serializable download record for persistence */
|
||||
interface DownloadRecord {
|
||||
episodeId: string
|
||||
feedId: string
|
||||
status: DownloadStatus
|
||||
filePath: string | null
|
||||
downloadedAt: string | null
|
||||
fileSize: number
|
||||
error: string | null
|
||||
audioUrl: string
|
||||
episodeTitle: string
|
||||
episodeId: string;
|
||||
feedId: string;
|
||||
status: DownloadStatus;
|
||||
filePath: string | null;
|
||||
downloadedAt: string | null;
|
||||
fileSize: number;
|
||||
error: string | null;
|
||||
audioUrl: string;
|
||||
episodeTitle: string;
|
||||
}
|
||||
|
||||
/** Queue item for pending downloads */
|
||||
interface QueueItem {
|
||||
episodeId: string
|
||||
feedId: string
|
||||
audioUrl: string
|
||||
episodeTitle: string
|
||||
episodeId: string;
|
||||
feedId: string;
|
||||
audioUrl: string;
|
||||
episodeTitle: string;
|
||||
}
|
||||
|
||||
/** Create download store */
|
||||
export function createDownloadStore() {
|
||||
const [downloads, setDownloads] = createSignal<Map<string, DownloadedEpisode>>(new Map())
|
||||
const [queue, setQueue] = createSignal<QueueItem[]>([])
|
||||
const [activeCount, setActiveCount] = createSignal(0)
|
||||
const [downloads, setDownloads] = createSignal<
|
||||
Map<string, DownloadedEpisode>
|
||||
>(new Map());
|
||||
const [queue, setQueue] = createSignal<QueueItem[]>([]);
|
||||
const [activeCount, setActiveCount] = createSignal(0);
|
||||
|
||||
/** Active AbortControllers keyed by episodeId */
|
||||
const abortControllers = new Map<string, AbortController>()
|
||||
/** Active AbortControllers keyed by episodeId */
|
||||
const abortControllers = new Map<string, AbortController>();
|
||||
|
||||
// Load persisted downloads on init
|
||||
;(async () => {
|
||||
const loaded = await loadDownloads()
|
||||
if (loaded.size > 0) setDownloads(loaded)
|
||||
// Resume any queued downloads from previous session
|
||||
resumeIncomplete()
|
||||
})()
|
||||
// Load persisted downloads on init
|
||||
(async () => {
|
||||
const loaded = await loadDownloads();
|
||||
if (loaded.size > 0) setDownloads(loaded);
|
||||
// Resume any queued downloads from previous session
|
||||
resumeIncomplete();
|
||||
})();
|
||||
|
||||
/** Load downloads from JSON file */
|
||||
async function loadDownloads(): Promise<Map<string, DownloadedEpisode>> {
|
||||
try {
|
||||
const filePath = getConfigFilePath(DOWNLOADS_FILE)
|
||||
const file = Bun.file(filePath)
|
||||
if (!(await file.exists())) return new Map()
|
||||
/** Load downloads from JSON file */
|
||||
async function loadDownloads(): Promise<Map<string, DownloadedEpisode>> {
|
||||
try {
|
||||
const filePath = getConfigFilePath(DOWNLOADS_FILE);
|
||||
const file = Bun.file(filePath);
|
||||
if (!(await file.exists())) return new Map();
|
||||
|
||||
const raw: DownloadRecord[] = await file.json()
|
||||
if (!Array.isArray(raw)) return new Map()
|
||||
const raw: DownloadRecord[] = await file.json();
|
||||
if (!Array.isArray(raw)) return new Map();
|
||||
|
||||
const map = new Map<string, DownloadedEpisode>()
|
||||
for (const rec of raw) {
|
||||
map.set(rec.episodeId, {
|
||||
episodeId: rec.episodeId,
|
||||
feedId: rec.feedId,
|
||||
status: rec.status === DownloadStatus.DOWNLOADING ? DownloadStatus.QUEUED : rec.status,
|
||||
progress: rec.status === DownloadStatus.COMPLETED ? 100 : 0,
|
||||
filePath: rec.filePath,
|
||||
downloadedAt: rec.downloadedAt ? new Date(rec.downloadedAt) : null,
|
||||
speed: 0,
|
||||
fileSize: rec.fileSize,
|
||||
error: rec.error,
|
||||
})
|
||||
}
|
||||
return map
|
||||
} catch {
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
const map = new Map<string, DownloadedEpisode>();
|
||||
for (const rec of raw) {
|
||||
map.set(rec.episodeId, {
|
||||
episodeId: rec.episodeId,
|
||||
feedId: rec.feedId,
|
||||
status:
|
||||
rec.status === DownloadStatus.DOWNLOADING
|
||||
? DownloadStatus.QUEUED
|
||||
: rec.status,
|
||||
progress: rec.status === DownloadStatus.COMPLETED ? 100 : 0,
|
||||
filePath: rec.filePath,
|
||||
downloadedAt: rec.downloadedAt ? new Date(rec.downloadedAt) : null,
|
||||
speed: 0,
|
||||
fileSize: rec.fileSize,
|
||||
error: rec.error,
|
||||
});
|
||||
}
|
||||
return map;
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
/** Persist downloads to JSON file */
|
||||
async function saveDownloads(): Promise<void> {
|
||||
try {
|
||||
await ensureConfigDir()
|
||||
await backupConfigFile(DOWNLOADS_FILE)
|
||||
const map = downloads()
|
||||
const records: DownloadRecord[] = []
|
||||
for (const [, dl] of map) {
|
||||
// Find the audioUrl from queue or use empty string
|
||||
const qItem = queue().find((q) => q.episodeId === dl.episodeId)
|
||||
records.push({
|
||||
episodeId: dl.episodeId,
|
||||
feedId: dl.feedId,
|
||||
status: dl.status,
|
||||
filePath: dl.filePath,
|
||||
downloadedAt: dl.downloadedAt?.toISOString() ?? null,
|
||||
fileSize: dl.fileSize,
|
||||
error: dl.error,
|
||||
audioUrl: qItem?.audioUrl ?? "",
|
||||
episodeTitle: qItem?.episodeTitle ?? "",
|
||||
})
|
||||
}
|
||||
const filePath = getConfigFilePath(DOWNLOADS_FILE)
|
||||
await Bun.write(filePath, JSON.stringify(records, null, 2))
|
||||
} catch {
|
||||
// Silently ignore write errors
|
||||
}
|
||||
}
|
||||
/** Persist downloads to JSON file */
|
||||
async function saveDownloads(): Promise<void> {
|
||||
try {
|
||||
await ensureConfigDir();
|
||||
await backupConfigFile(DOWNLOADS_FILE);
|
||||
const map = downloads();
|
||||
const records: DownloadRecord[] = [];
|
||||
for (const [, dl] of map) {
|
||||
// Find the audioUrl from queue or use empty string
|
||||
const qItem = queue().find((q) => q.episodeId === dl.episodeId);
|
||||
records.push({
|
||||
episodeId: dl.episodeId,
|
||||
feedId: dl.feedId,
|
||||
status: dl.status,
|
||||
filePath: dl.filePath,
|
||||
downloadedAt: dl.downloadedAt?.toISOString() ?? null,
|
||||
fileSize: dl.fileSize,
|
||||
error: dl.error,
|
||||
audioUrl: qItem?.audioUrl ?? "",
|
||||
episodeTitle: qItem?.episodeTitle ?? "",
|
||||
});
|
||||
}
|
||||
const filePath = getConfigFilePath(DOWNLOADS_FILE);
|
||||
await Bun.write(filePath, JSON.stringify(records, null, 2));
|
||||
} catch {
|
||||
// Silently ignore write errors
|
||||
}
|
||||
}
|
||||
|
||||
/** Resume incomplete downloads from a previous session */
|
||||
function resumeIncomplete(): void {
|
||||
const map = downloads()
|
||||
for (const [, dl] of map) {
|
||||
if (dl.status === DownloadStatus.QUEUED) {
|
||||
// Re-queue — but we lack audioUrl from persistence alone.
|
||||
// These will sit as QUEUED until the user re-triggers them.
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Resume incomplete downloads from a previous session */
|
||||
function resumeIncomplete(): void {
|
||||
const map = downloads();
|
||||
for (const [, dl] of map) {
|
||||
if (dl.status === DownloadStatus.QUEUED) {
|
||||
// Re-queue — but we lack audioUrl from persistence alone.
|
||||
// These will sit as QUEUED until the user re-triggers them.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Update a single download entry and trigger reactivity */
|
||||
function updateDownload(episodeId: string, updates: Partial<DownloadedEpisode>): void {
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev)
|
||||
const existing = next.get(episodeId)
|
||||
if (existing) {
|
||||
next.set(episodeId, { ...existing, ...updates })
|
||||
}
|
||||
return next
|
||||
})
|
||||
}
|
||||
/** Update a single download entry and trigger reactivity */
|
||||
function updateDownload(
|
||||
episodeId: string,
|
||||
updates: Partial<DownloadedEpisode>,
|
||||
): void {
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev);
|
||||
const existing = next.get(episodeId);
|
||||
if (existing) {
|
||||
next.set(episodeId, { ...existing, ...updates });
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
/** Process the download queue — starts downloads up to MAX_CONCURRENT */
|
||||
function processQueue(): void {
|
||||
const current = activeCount()
|
||||
const q = queue()
|
||||
/** Process the download queue — starts downloads up to MAX_CONCURRENT */
|
||||
function processQueue(): void {
|
||||
const current = activeCount();
|
||||
const q = queue();
|
||||
|
||||
if (current >= MAX_CONCURRENT || q.length === 0) return
|
||||
if (current >= MAX_CONCURRENT || q.length === 0) return;
|
||||
|
||||
const slotsAvailable = MAX_CONCURRENT - current
|
||||
const toStart = q.slice(0, slotsAvailable)
|
||||
const slotsAvailable = MAX_CONCURRENT - current;
|
||||
const toStart = q.slice(0, slotsAvailable);
|
||||
|
||||
// Remove started items from queue
|
||||
if (toStart.length > 0) {
|
||||
setQueue((prev) => prev.slice(toStart.length))
|
||||
}
|
||||
// Remove started items from queue
|
||||
if (toStart.length > 0) {
|
||||
setQueue((prev) => prev.slice(toStart.length));
|
||||
}
|
||||
|
||||
for (const item of toStart) {
|
||||
executeDownload(item)
|
||||
}
|
||||
}
|
||||
for (const item of toStart) {
|
||||
executeDownload(item);
|
||||
}
|
||||
}
|
||||
|
||||
/** Execute a single download */
|
||||
async function executeDownload(item: QueueItem): Promise<void> {
|
||||
const controller = new AbortController()
|
||||
abortControllers.set(item.episodeId, controller)
|
||||
setActiveCount((c) => c + 1)
|
||||
/** Execute a single download */
|
||||
async function executeDownload(item: QueueItem): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
abortControllers.set(item.episodeId, controller);
|
||||
setActiveCount((c) => c + 1);
|
||||
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.DOWNLOADING,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
error: null,
|
||||
})
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.DOWNLOADING,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const result = await downloadEpisode(
|
||||
item.audioUrl,
|
||||
item.episodeTitle,
|
||||
item.feedId,
|
||||
(progress) => {
|
||||
updateDownload(item.episodeId, {
|
||||
progress: progress.percent >= 0 ? progress.percent : 0,
|
||||
speed: progress.speed,
|
||||
fileSize: progress.totalBytes,
|
||||
})
|
||||
},
|
||||
controller.signal,
|
||||
)
|
||||
const result = await downloadEpisode(
|
||||
item.audioUrl,
|
||||
item.episodeTitle,
|
||||
item.feedId,
|
||||
(progress) => {
|
||||
updateDownload(item.episodeId, {
|
||||
progress: progress.percent >= 0 ? progress.percent : 0,
|
||||
speed: progress.speed,
|
||||
fileSize: progress.totalBytes,
|
||||
});
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
|
||||
abortControllers.delete(item.episodeId)
|
||||
setActiveCount((c) => Math.max(0, c - 1))
|
||||
abortControllers.delete(item.episodeId);
|
||||
setActiveCount((c) => Math.max(0, c - 1));
|
||||
|
||||
if (result.success) {
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.COMPLETED,
|
||||
progress: 100,
|
||||
filePath: result.filePath,
|
||||
fileSize: result.fileSize,
|
||||
downloadedAt: new Date(),
|
||||
speed: 0,
|
||||
error: null,
|
||||
})
|
||||
} else {
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.FAILED,
|
||||
speed: 0,
|
||||
error: result.error ?? "Unknown error",
|
||||
})
|
||||
}
|
||||
if (result.success) {
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.COMPLETED,
|
||||
progress: 100,
|
||||
filePath: result.filePath,
|
||||
fileSize: result.fileSize,
|
||||
downloadedAt: new Date(),
|
||||
speed: 0,
|
||||
error: null,
|
||||
});
|
||||
} else {
|
||||
updateDownload(item.episodeId, {
|
||||
status: DownloadStatus.FAILED,
|
||||
speed: 0,
|
||||
error: result.error ?? "Unknown error",
|
||||
});
|
||||
}
|
||||
|
||||
saveDownloads().catch(() => {})
|
||||
// Process next items in queue
|
||||
processQueue()
|
||||
}
|
||||
saveDownloads().catch(() => {});
|
||||
// Process next items in queue
|
||||
processQueue();
|
||||
}
|
||||
|
||||
/** Get download status for an episode */
|
||||
const getDownloadStatus = (episodeId: string): DownloadStatus => {
|
||||
return downloads().get(episodeId)?.status ?? DownloadStatus.NONE
|
||||
}
|
||||
/** Get download status for an episode */
|
||||
const getDownloadStatus = (episodeId: string): DownloadStatus => {
|
||||
return downloads().get(episodeId)?.status ?? DownloadStatus.NONE;
|
||||
};
|
||||
|
||||
/** Get download progress for an episode (0-100) */
|
||||
const getDownloadProgress = (episodeId: string): number => {
|
||||
return downloads().get(episodeId)?.progress ?? 0
|
||||
}
|
||||
/** Get download progress for an episode (0-100) */
|
||||
const getDownloadProgress = (episodeId: string): number => {
|
||||
return downloads().get(episodeId)?.progress ?? 0;
|
||||
};
|
||||
|
||||
/** Get full download info for an episode */
|
||||
const getDownload = (episodeId: string): DownloadedEpisode | undefined => {
|
||||
return downloads().get(episodeId)
|
||||
}
|
||||
/** Get full download info for an episode */
|
||||
const getDownload = (episodeId: string): DownloadedEpisode | undefined => {
|
||||
return downloads().get(episodeId);
|
||||
};
|
||||
|
||||
/** Get the local file path for a completed download */
|
||||
const getDownloadedFilePath = (episodeId: string): string | null => {
|
||||
const dl = downloads().get(episodeId)
|
||||
if (dl?.status === DownloadStatus.COMPLETED && dl.filePath) {
|
||||
return dl.filePath
|
||||
}
|
||||
return null
|
||||
}
|
||||
/** Get the local file path for a completed download */
|
||||
const getDownloadedFilePath = (episodeId: string): string | null => {
|
||||
const dl = downloads().get(episodeId);
|
||||
if (dl?.status === DownloadStatus.COMPLETED && dl.filePath) {
|
||||
return dl.filePath;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Start downloading an episode */
|
||||
const startDownload = (episode: Episode, feedId: string): void => {
|
||||
const existing = downloads().get(episode.id)
|
||||
if (existing?.status === DownloadStatus.DOWNLOADING || existing?.status === DownloadStatus.QUEUED) {
|
||||
return // Already downloading or queued
|
||||
}
|
||||
/** Start downloading an episode */
|
||||
const startDownload = (episode: Episode, feedId: string): void => {
|
||||
const existing = downloads().get(episode.id);
|
||||
if (
|
||||
existing?.status === DownloadStatus.DOWNLOADING ||
|
||||
existing?.status === DownloadStatus.QUEUED
|
||||
) {
|
||||
return; // Already downloading or queued
|
||||
}
|
||||
|
||||
// Create download entry
|
||||
const entry: DownloadedEpisode = {
|
||||
episodeId: episode.id,
|
||||
feedId,
|
||||
status: DownloadStatus.QUEUED,
|
||||
progress: 0,
|
||||
filePath: null,
|
||||
downloadedAt: null,
|
||||
speed: 0,
|
||||
fileSize: episode.fileSize ?? 0,
|
||||
error: null,
|
||||
}
|
||||
// Create download entry
|
||||
const entry: DownloadedEpisode = {
|
||||
episodeId: episode.id,
|
||||
feedId,
|
||||
status: DownloadStatus.QUEUED,
|
||||
progress: 0,
|
||||
filePath: null,
|
||||
downloadedAt: null,
|
||||
speed: 0,
|
||||
fileSize: episode.fileSize ?? 0,
|
||||
error: null,
|
||||
};
|
||||
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev)
|
||||
next.set(episode.id, entry)
|
||||
return next
|
||||
})
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(episode.id, entry);
|
||||
return next;
|
||||
});
|
||||
|
||||
// Add to queue
|
||||
const queueItem: QueueItem = {
|
||||
episodeId: episode.id,
|
||||
feedId,
|
||||
audioUrl: episode.audioUrl,
|
||||
episodeTitle: episode.title,
|
||||
}
|
||||
setQueue((prev) => [...prev, queueItem])
|
||||
// Add to queue
|
||||
const queueItem: QueueItem = {
|
||||
episodeId: episode.id,
|
||||
feedId,
|
||||
audioUrl: episode.audioUrl,
|
||||
episodeTitle: episode.title,
|
||||
};
|
||||
setQueue((prev) => [...prev, queueItem]);
|
||||
|
||||
saveDownloads().catch(() => {})
|
||||
processQueue()
|
||||
}
|
||||
saveDownloads().catch(() => {});
|
||||
processQueue();
|
||||
};
|
||||
|
||||
/** Cancel a download */
|
||||
const cancelDownload = (episodeId: string): void => {
|
||||
// Abort active download
|
||||
const controller = abortControllers.get(episodeId)
|
||||
if (controller) {
|
||||
controller.abort()
|
||||
abortControllers.delete(episodeId)
|
||||
}
|
||||
/** Cancel a download */
|
||||
const cancelDownload = (episodeId: string): void => {
|
||||
// Abort active download
|
||||
const controller = abortControllers.get(episodeId);
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
abortControllers.delete(episodeId);
|
||||
}
|
||||
|
||||
// Remove from queue
|
||||
setQueue((prev) => prev.filter((q) => q.episodeId !== episodeId))
|
||||
// Remove from queue
|
||||
setQueue((prev) => prev.filter((q) => q.episodeId !== episodeId));
|
||||
|
||||
// Update status
|
||||
updateDownload(episodeId, {
|
||||
status: DownloadStatus.NONE,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
error: null,
|
||||
})
|
||||
// Update status
|
||||
updateDownload(episodeId, {
|
||||
status: DownloadStatus.NONE,
|
||||
progress: 0,
|
||||
speed: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
saveDownloads().catch(() => {})
|
||||
}
|
||||
saveDownloads().catch(() => {});
|
||||
};
|
||||
|
||||
/** Remove a completed download (delete file and metadata) */
|
||||
const removeDownload = async (episodeId: string): Promise<void> => {
|
||||
const dl = downloads().get(episodeId)
|
||||
if (dl?.filePath) {
|
||||
try {
|
||||
const { unlink } = await import("fs/promises")
|
||||
await unlink(dl.filePath)
|
||||
} catch {
|
||||
// File may already be gone
|
||||
}
|
||||
}
|
||||
/** Remove a completed download (delete file and metadata) */
|
||||
const removeDownload = async (episodeId: string): Promise<void> => {
|
||||
const dl = downloads().get(episodeId);
|
||||
if (dl?.filePath) {
|
||||
try {
|
||||
const { unlink } = await import("fs/promises");
|
||||
await unlink(dl.filePath);
|
||||
} catch {
|
||||
// File may already be gone
|
||||
}
|
||||
}
|
||||
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev)
|
||||
next.delete(episodeId)
|
||||
return next
|
||||
})
|
||||
setDownloads((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(episodeId);
|
||||
return next;
|
||||
});
|
||||
|
||||
saveDownloads().catch(() => {})
|
||||
}
|
||||
saveDownloads().catch(() => {});
|
||||
};
|
||||
|
||||
/** Get all downloads as an array */
|
||||
const getAllDownloads = (): DownloadedEpisode[] => {
|
||||
return Array.from(downloads().values())
|
||||
}
|
||||
/** Remove every download (active/queued/completed) belonging to a feed —
|
||||
* abort in-flight transfers, drop queued items, delete files + metadata. */
|
||||
const removeDownloadsForFeed = async (feedId: string): Promise<void> => {
|
||||
const eps = Array.from(downloads().values()).filter(
|
||||
(d) => d.feedId === feedId,
|
||||
);
|
||||
for (const d of eps) {
|
||||
cancelDownload(d.episodeId);
|
||||
await removeDownload(d.episodeId);
|
||||
}
|
||||
};
|
||||
|
||||
/** Get the current queue */
|
||||
const getQueue = (): QueueItem[] => {
|
||||
return queue()
|
||||
}
|
||||
/** Get all downloads as an array */
|
||||
const getAllDownloads = (): DownloadedEpisode[] => {
|
||||
return Array.from(downloads().values());
|
||||
};
|
||||
|
||||
/** Get count of active downloads */
|
||||
const getActiveCount = (): number => {
|
||||
return activeCount()
|
||||
}
|
||||
/** Get the current queue */
|
||||
const getQueue = (): QueueItem[] => {
|
||||
return queue();
|
||||
};
|
||||
|
||||
return {
|
||||
// Getters
|
||||
getDownloadStatus,
|
||||
getDownloadProgress,
|
||||
getDownload,
|
||||
getDownloadedFilePath,
|
||||
getAllDownloads,
|
||||
getQueue,
|
||||
getActiveCount,
|
||||
/** Get count of active downloads */
|
||||
const getActiveCount = (): number => {
|
||||
return activeCount();
|
||||
};
|
||||
|
||||
// Actions
|
||||
startDownload,
|
||||
cancelDownload,
|
||||
removeDownload,
|
||||
}
|
||||
return {
|
||||
// Getters
|
||||
getDownloadStatus,
|
||||
getDownloadProgress,
|
||||
getDownload,
|
||||
getDownloadedFilePath,
|
||||
getAllDownloads,
|
||||
getQueue,
|
||||
getActiveCount,
|
||||
|
||||
// Actions
|
||||
startDownload,
|
||||
cancelDownload,
|
||||
removeDownload,
|
||||
removeDownloadsForFeed,
|
||||
};
|
||||
}
|
||||
|
||||
/** Singleton download store */
|
||||
let downloadStoreInstance: ReturnType<typeof createDownloadStore> | null = null
|
||||
let downloadStoreInstance: ReturnType<typeof createDownloadStore> | null = null;
|
||||
|
||||
export function useDownloadStore() {
|
||||
if (!downloadStoreInstance) {
|
||||
downloadStoreInstance = createDownloadStore()
|
||||
}
|
||||
return downloadStoreInstance
|
||||
if (!downloadStoreInstance) {
|
||||
downloadStoreInstance = createDownloadStore();
|
||||
}
|
||||
return downloadStoreInstance;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user