mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Add a retry mechanizum for downloads
This commit is contained in:
		
							parent
							
								
									85ba643bcc
								
							
						
					
					
						commit
						55a246d806
					
				@ -53,6 +53,7 @@ export type State = {
 | 
				
			|||||||
	resume: (() => void) | null;
 | 
						resume: (() => void) | null;
 | 
				
			||||||
	remove: () => void;
 | 
						remove: () => void;
 | 
				
			||||||
	play: (router: Router) => void;
 | 
						play: (router: Router) => void;
 | 
				
			||||||
 | 
						retry: (() => void) | null;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const downloadAtom = atom<
 | 
					export const downloadAtom = atom<
 | 
				
			||||||
@ -81,19 +82,21 @@ const setupDownloadTask = (
 | 
				
			|||||||
	state: { data: Episode | Movie; info: WatchInfo; path: string },
 | 
						state: { data: Episode | Movie; info: WatchInfo; path: string },
 | 
				
			||||||
	task: DownloadTask,
 | 
						task: DownloadTask,
 | 
				
			||||||
	store: ReturnType<typeof useStore>,
 | 
						store: ReturnType<typeof useStore>,
 | 
				
			||||||
 | 
						stateAtom?: PrimitiveAtom<State>,
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
	const stateAtom = atom({
 | 
						if (!stateAtom) stateAtom = atom({} as State);
 | 
				
			||||||
 | 
						store.set(stateAtom, {
 | 
				
			||||||
		status: task.state,
 | 
							status: task.state,
 | 
				
			||||||
		progress: task.percent * 100,
 | 
							progress: task.percent * 100,
 | 
				
			||||||
		size: task.totalBytes,
 | 
							size: task.totalBytes,
 | 
				
			||||||
		availableSize: task.bytesWritten,
 | 
							availableSize: task.bytesWritten,
 | 
				
			||||||
		pause: () => {
 | 
							pause: () => {
 | 
				
			||||||
			task.pause();
 | 
								task.pause();
 | 
				
			||||||
			store.set(stateAtom, (x) => ({ ...x, state: "PAUSED" }));
 | 
								store.set(stateAtom!, (x) => ({ ...x, state: "PAUSED" }));
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		resume: () => {
 | 
							resume: () => {
 | 
				
			||||||
			task.resume();
 | 
								task.resume();
 | 
				
			||||||
			store.set(stateAtom, (x) => ({ ...x, state: "DOWNLOADING" }));
 | 
								store.set(stateAtom!, (x) => ({ ...x, state: "DOWNLOADING" }));
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		remove: () => {
 | 
							remove: () => {
 | 
				
			||||||
			task.stop();
 | 
								task.stop();
 | 
				
			||||||
@ -102,7 +105,19 @@ const setupDownloadTask = (
 | 
				
			|||||||
		play: () => {
 | 
							play: () => {
 | 
				
			||||||
			ToastAndroid.show("The file has not finished downloading", ToastAndroid.LONG);
 | 
								ToastAndroid.show("The file has not finished downloading", ToastAndroid.LONG);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	} as State);
 | 
							retry: () => {
 | 
				
			||||||
 | 
								const [newTask, path] = download(
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										type: state.data.kind,
 | 
				
			||||||
 | 
										id: state.data.id,
 | 
				
			||||||
 | 
										slug: state.data.slug,
 | 
				
			||||||
 | 
										extension: state.info.extension,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									getCurrentAccount()!,
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								setupDownloadTask({ ...state, path }, newTask, store, stateAtom);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// we use the store instead of the onMount because we want to update the state to cache it even if it was not
 | 
						// we use the store instead of the onMount because we want to update the state to cache it even if it was not
 | 
				
			||||||
	// used during this launch of the app.
 | 
						// used during this launch of the app.
 | 
				
			||||||
@ -110,11 +125,17 @@ const setupDownloadTask = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	task
 | 
						task
 | 
				
			||||||
		.begin(({ expectedBytes }) => update((x) => ({ ...x, size: expectedBytes })))
 | 
							.begin(({ expectedBytes }) => update((x) => ({ ...x, size: expectedBytes })))
 | 
				
			||||||
		.progress((percent, availableSize, size) =>
 | 
							.progress((percent, availableSize, size) => {
 | 
				
			||||||
			update((x) => ({ ...x, percent, size, availableSize, status: "DOWNLOADING" })),
 | 
								update((x) => ({
 | 
				
			||||||
		)
 | 
									...x,
 | 
				
			||||||
 | 
									progress: Math.round(percent * 100),
 | 
				
			||||||
 | 
									size,
 | 
				
			||||||
 | 
									availableSize,
 | 
				
			||||||
 | 
									status: "DOWNLOADING",
 | 
				
			||||||
 | 
								}));
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		.done(() => {
 | 
							.done(() => {
 | 
				
			||||||
			update((x) => ({ ...x, percent: 100, status: "DONE" }));
 | 
								update((x) => ({ ...x, progress: 100, status: "DONE" }));
 | 
				
			||||||
			// apparently this is needed for ios /shrug i'm totaly gona forget this
 | 
								// apparently this is needed for ios /shrug i'm totaly gona forget this
 | 
				
			||||||
			// if i ever implement ios so keeping this here
 | 
								// if i ever implement ios so keeping this here
 | 
				
			||||||
			if (Platform.OS === "ios") RNBackgroundDownloader.completeHandler(task.id);
 | 
								if (Platform.OS === "ios") RNBackgroundDownloader.completeHandler(task.id);
 | 
				
			||||||
@ -151,6 +172,32 @@ const updater = (
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const download = (
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type,
 | 
				
			||||||
 | 
							id,
 | 
				
			||||||
 | 
							slug,
 | 
				
			||||||
 | 
							extension,
 | 
				
			||||||
 | 
						}: { slug: string; id: string; type: "episode" | "movie"; extension: string },
 | 
				
			||||||
 | 
						account: Account,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
						// TODO: support custom paths
 | 
				
			||||||
 | 
						const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${id}.${extension}`;
 | 
				
			||||||
 | 
						const task = RNBackgroundDownloader.download({
 | 
				
			||||||
 | 
							id: id,
 | 
				
			||||||
 | 
							// TODO: support variant qualities
 | 
				
			||||||
 | 
							url: `${account.apiUrl}/video/${type}/${slug}/direct`,
 | 
				
			||||||
 | 
							destination: path,
 | 
				
			||||||
 | 
							headers: {
 | 
				
			||||||
 | 
								Authorization: account.token.access_token,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// TODO: Implement only wifi
 | 
				
			||||||
 | 
							// network: Network.ALL,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						console.log("Starting download", path);
 | 
				
			||||||
 | 
						return [task, path] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useDownloader = () => {
 | 
					export const useDownloader = () => {
 | 
				
			||||||
	const setDownloads = useSetAtom(downloadAtom);
 | 
						const setDownloads = useSetAtom(downloadAtom);
 | 
				
			||||||
	const store = useStore();
 | 
						const store = useStore();
 | 
				
			||||||
@ -168,21 +215,10 @@ export const useDownloader = () => {
 | 
				
			|||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// TODO: support custom paths
 | 
								const [task, path] = download(
 | 
				
			||||||
			const path = `${RNBackgroundDownloader.directories.documents}/${slug}-${data.id}.${info.extension}`;
 | 
									{ type, slug, id: data.id, extension: info.extension },
 | 
				
			||||||
			const task = RNBackgroundDownloader.download({
 | 
									account,
 | 
				
			||||||
				id: data.id,
 | 
								);
 | 
				
			||||||
				// TODO: support variant qualities
 | 
					 | 
				
			||||||
				url: `${account.apiUrl}/video/${type}/${slug}/direct`,
 | 
					 | 
				
			||||||
				destination: path,
 | 
					 | 
				
			||||||
				headers: {
 | 
					 | 
				
			||||||
					Authorization: account.token.access_token,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				// TODO: Implement only wifi
 | 
					 | 
				
			||||||
				// network: Network.ALL,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
			console.log("Starting download", path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			setDownloads((x) => [...x, setupDownloadTask({ data, info, path }, task, store)]);
 | 
								setDownloads((x) => [...x, setupDownloadTask({ data, info, path }, task, store)]);
 | 
				
			||||||
		} catch (e) {
 | 
							} catch (e) {
 | 
				
			||||||
			console.error("download error", e);
 | 
								console.error("download error", e);
 | 
				
			||||||
@ -205,11 +241,8 @@ export const DownloadProvider = ({ children }: { children: ReactNode }) => {
 | 
				
			|||||||
			const downloads = dls.map((dl) => {
 | 
								const downloads = dls.map((dl) => {
 | 
				
			||||||
				const t = tasks.find((x) => x.id == dl.data.id);
 | 
									const t = tasks.find((x) => x.id == dl.data.id);
 | 
				
			||||||
				if (t) return setupDownloadTask(dl, t, store);
 | 
									if (t) return setupDownloadTask(dl, t, store);
 | 
				
			||||||
				return {
 | 
					
 | 
				
			||||||
					data: z.union([EpisodeP, MovieP]).parse(dl.data),
 | 
									const stateAtom = atom({
 | 
				
			||||||
					info: WatchInfoP.parse(dl.info),
 | 
					 | 
				
			||||||
					path: dl.path,
 | 
					 | 
				
			||||||
					state: atom({
 | 
					 | 
				
			||||||
					status: dl.state.status === "DONE" ? "DONE" : "FAILED",
 | 
										status: dl.state.status === "DONE" ? "DONE" : "FAILED",
 | 
				
			||||||
					progress: dl.state.progress,
 | 
										progress: dl.state.progress,
 | 
				
			||||||
					size: dl.state.size,
 | 
										size: dl.state.size,
 | 
				
			||||||
@ -218,10 +251,8 @@ export const DownloadProvider = ({ children }: { children: ReactNode }) => {
 | 
				
			|||||||
					resume: null,
 | 
										resume: null,
 | 
				
			||||||
					play: (router: Router) => {
 | 
										play: (router: Router) => {
 | 
				
			||||||
						dl.data.links.direct = dl.path;
 | 
											dl.data.links.direct = dl.path;
 | 
				
			||||||
							queryClient.setQueryData(
 | 
											dl.data.links.hls = null;
 | 
				
			||||||
								toQueryKey(Player.query(dl.data.kind, dl.data.slug)),
 | 
											queryClient.setQueryData(toQueryKey(Player.query(dl.data.kind, dl.data.slug)), dl.data);
 | 
				
			||||||
								dl.data,
 | 
					 | 
				
			||||||
							);
 | 
					 | 
				
			||||||
						queryClient.setQueryData(
 | 
											queryClient.setQueryData(
 | 
				
			||||||
							toQueryKey(Player.infoQuery(dl.data.kind, dl.data.slug)),
 | 
												toQueryKey(Player.infoQuery(dl.data.kind, dl.data.slug)),
 | 
				
			||||||
							dl.info,
 | 
												dl.info,
 | 
				
			||||||
@ -236,7 +267,24 @@ export const DownloadProvider = ({ children }: { children: ReactNode }) => {
 | 
				
			|||||||
						deleteAsync(dl.path);
 | 
											deleteAsync(dl.path);
 | 
				
			||||||
						store.set(downloadAtom, (x) => x.filter((y) => y.data.id !== dl.data.id));
 | 
											store.set(downloadAtom, (x) => x.filter((y) => y.data.id !== dl.data.id));
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
					} as State),
 | 
										retry: () => {
 | 
				
			||||||
 | 
											const [newTask, path] = download(
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													type: dl.data.kind,
 | 
				
			||||||
 | 
													id: dl.data.id,
 | 
				
			||||||
 | 
													slug: dl.data.slug,
 | 
				
			||||||
 | 
													extension: dl.info.extension,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												getCurrentAccount()!,
 | 
				
			||||||
 | 
											);
 | 
				
			||||||
 | 
											setupDownloadTask({ ...dl, path }, newTask, store, stateAtom);
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									} as State);
 | 
				
			||||||
 | 
									return {
 | 
				
			||||||
 | 
										data: z.union([EpisodeP, MovieP]).parse(dl.data),
 | 
				
			||||||
 | 
										info: WatchInfoP.parse(dl.info),
 | 
				
			||||||
 | 
										path: dl.path,
 | 
				
			||||||
 | 
										state: stateAtom,
 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			store.set(downloadAtom, downloads);
 | 
								store.set(downloadAtom, downloads);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user