chore: Apply linting rules and add git hok (#611)

Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
lostb1t
2025-03-16 18:01:12 +01:00
committed by GitHub
parent 2688e1b981
commit 92513e234f
268 changed files with 9197 additions and 8394 deletions

View File

@@ -7,14 +7,14 @@ import { getItemImage } from "@/utils/getItemImage";
import { useLog, writeToLog } from "@/utils/log";
import { storage } from "@/utils/mmkv";
import {
type JobStatus,
cancelAllJobs,
cancelJobById,
deleteDownloadItemInfoFromDiskTmp,
getAllJobsByDeviceId,
getDownloadItemInfoFromDiskTmp,
JobStatus,
} from "@/utils/optimize-server";
import {
import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
@@ -23,11 +23,12 @@ import { focusManager, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import * as Application from "expo-application";
import * as FileSystem from "expo-file-system";
import { FileInfo } from "expo-file-system";
import type { FileInfo } from "expo-file-system";
import Notifications from "expo-notifications";
import { useRouter } from "expo-router";
import { atom, useAtom } from "jotai";
import React, {
import type React from "react";
import {
createContext,
useCallback,
useContext,
@@ -35,7 +36,7 @@ import React, {
useMemo,
} from "react";
import { useTranslation } from "react-i18next";
import { AppState, AppStateStatus, Platform } from "react-native";
import { AppState, type AppStateStatus, Platform } from "react-native";
import { toast } from "sonner-native";
import { apiAtom } from "./JellyfinProvider";
@@ -113,12 +114,12 @@ function useDownloadProvider() {
.filter((p) => jobs.some((j) => j.id === p.id));
const updatedProcesses = jobs.filter(
(j) => !downloadingProcesses.some((p) => p.id === j.id)
(j) => !downloadingProcesses.some((p) => p.id === j.id),
);
setProcesses([...updatedProcesses, ...downloadingProcesses]);
for (let job of jobs) {
for (const job of jobs) {
const process = processes.find((p) => p.id === job.id);
if (
process &&
@@ -140,7 +141,7 @@ function useDownloadProvider() {
toast.dismiss();
},
},
}
},
);
Notifications.scheduleNotificationAsync({
content: {
@@ -188,7 +189,7 @@ function useDownloadProvider() {
console.error(error);
}
},
[settings?.optimizedVersionsServerUrl, authHeader]
[settings?.optimizedVersionsServerUrl, authHeader],
);
const APP_CACHE_DOWNLOAD_DIRECTORY = `${FileSystem.cacheDirectory}${Application.applicationId}/Downloads/`;
@@ -206,8 +207,8 @@ function useDownloadProvider() {
status: "downloading",
progress: 0,
}
: p
)
: p,
),
);
BackGroundDownloader?.setConfig({
@@ -230,7 +231,7 @@ function useDownloadProvider() {
toast.dismiss();
},
},
}
},
);
const baseDirectory = FileSystem.documentDirectory;
@@ -250,8 +251,8 @@ function useDownloadProvider() {
status: "downloading",
progress: 0,
}
: p
)
: p,
),
);
})
.progress((data) => {
@@ -265,14 +266,14 @@ function useDownloadProvider() {
status: "downloading",
progress: percent,
}
: p
)
: p,
),
);
})
.done(async (doneHandler) => {
await saveDownloadedItemInfo(
process.item,
doneHandler.bytesDownloaded
doneHandler.bytesDownloaded,
);
toast.success(
t("home.downloads.toasts.download_completed_for_item", {
@@ -287,7 +288,7 @@ function useDownloadProvider() {
toast.dismiss();
},
},
}
},
);
setTimeout(() => {
BackGroundDownloader.completeHandler(process.id);
@@ -308,7 +309,7 @@ function useDownloadProvider() {
t("home.downloads.toasts.download_failed_for_item", {
item: process.item.Name,
error: errorMsg,
})
}),
);
writeToLog("ERROR", `Download failed for ${process.item.Name}`, {
error,
@@ -323,7 +324,7 @@ function useDownloadProvider() {
});
});
},
[queryClient, settings?.optimizedVersionsServerUrl, authHeader]
[queryClient, settings?.optimizedVersionsServerUrl, authHeader],
);
const startBackgroundDownload = useCallback(
@@ -359,7 +360,7 @@ function useDownloadProvider() {
"Content-Type": "application/json",
Authorization: authHeader,
},
}
},
);
if (response.status !== 201) {
@@ -378,7 +379,7 @@ function useDownloadProvider() {
toast.dismiss();
},
},
}
},
);
} catch (error) {
writeToLog("ERROR", "Error in startBackgroundDownload", error);
@@ -394,13 +395,13 @@ function useDownloadProvider() {
t("home.downloads.toasts.failed_to_start_download_for_item", {
item: item.Name,
message: error.message,
})
}),
);
if (error.response) {
toast.error(
t("home.downloads.toasts.server_responded_with_status", {
statusCode: error.response.status,
})
}),
);
} else if (error.request) {
t("home.downloads.toasts.no_response_received_from_server");
@@ -412,13 +413,13 @@ function useDownloadProvider() {
toast.error(
t(
"home.downloads.toasts.failed_to_start_download_for_item_unexpected_error",
{ item: item.Name }
)
{ item: item.Name },
),
);
}
}
},
[settings?.optimizedVersionsServerUrl, authHeader]
[settings?.optimizedVersionsServerUrl, authHeader],
);
const deleteAllFiles = async (): Promise<void> => {
@@ -431,24 +432,24 @@ function useDownloadProvider() {
.then(() =>
toast.success(
t(
"home.downloads.toasts.all_files_folders_and_jobs_deleted_successfully"
)
)
"home.downloads.toasts.all_files_folders_and_jobs_deleted_successfully",
),
),
)
.catch((reason) => {
console.error("Failed to delete all files, folders, and jobs:", reason);
toast.error(
t(
"home.downloads.toasts.an_error_occured_while_deleting_files_and_jobs"
)
"home.downloads.toasts.an_error_occured_while_deleting_files_and_jobs",
),
);
});
};
const forEveryDocumentDirFile = async (
includeMMKV: boolean = true,
includeMMKV = true,
ignoreList: string[] = [],
callback: (file: FileInfo) => void
callback: (file: FileInfo) => void,
) => {
const baseDirectory = FileSystem.documentDirectory;
if (!baseDirectory) {
@@ -553,7 +554,7 @@ function useDownloadProvider() {
} catch (error) {
console.error(
`Failed to delete file and storage entry for ID ${id}:`,
error
error,
);
}
};
@@ -563,17 +564,17 @@ function useDownloadProvider() {
items.map((i) => {
if (i.Id) return deleteFile(i.Id);
return;
})
}),
).then(() => successHapticFeedback());
};
const cleanCacheDirectory = async () => {
const cacheDir = await FileSystem.getInfoAsync(
APP_CACHE_DOWNLOAD_DIRECTORY
APP_CACHE_DOWNLOAD_DIRECTORY,
);
if (cacheDir.exists) {
const cachedFiles = await FileSystem.readDirectoryAsync(
APP_CACHE_DOWNLOAD_DIRECTORY
APP_CACHE_DOWNLOAD_DIRECTORY,
);
let position = 0;
const batchSize = 3;
@@ -584,14 +585,14 @@ function useDownloadProvider() {
await Promise.all(
itemsForBatch.map(async (file) => {
const info = await FileSystem.getInfoAsync(
`${APP_CACHE_DOWNLOAD_DIRECTORY}${file}`
`${APP_CACHE_DOWNLOAD_DIRECTORY}${file}`,
);
if (info.exists) {
await FileSystem.deleteAsync(info.uri, { idempotent: true });
return Promise.resolve(file);
}
return Promise.reject();
})
}),
);
position += batchSize;
@@ -609,24 +610,24 @@ function useDownloadProvider() {
promises.push(deleteFile(file.item.SeriesId));
promises.push(deleteFile(file.item.Id!));
return promises;
}) || []
}) || [],
);
};
const appSizeUsage = useMemo(async () => {
const sizes: number[] =
downloadedFiles?.map((d) => {
return getDownloadedItemSize(d.item.Id!!);
return getDownloadedItemSize(d.item.Id!);
}) || [];
await forEveryDocumentDirFile(
true,
getAllDownloadedItems().map((d) => d.item.Id!!),
getAllDownloadedItems().map((d) => d.item.Id!),
(file) => {
if (file.exists) {
sizes.push(file.size);
}
}
},
).catch((e) => {
console.error(e);
});
@@ -663,10 +664,10 @@ function useDownloadProvider() {
}
}
function saveDownloadedItemInfo(item: BaseItemDto, size: number = 0) {
function saveDownloadedItemInfo(item: BaseItemDto, size = 0) {
try {
const downloadedItems = storage.getString("downloadedItems");
let items: DownloadedItem[] = downloadedItems
const items: DownloadedItem[] = downloadedItems
? JSON.parse(downloadedItems)
: [];
@@ -676,7 +677,7 @@ function useDownloadProvider() {
if (!data?.mediaSource)
throw new Error(
"Media source not found in tmp storage. Did you forget to save it before starting download?"
"Media source not found in tmp storage. Did you forget to save it before starting download?",
);
const newItem = { item, mediaSource: data.mediaSource };
@@ -697,14 +698,14 @@ function useDownloadProvider() {
} catch (error) {
console.error(
"Failed to save downloaded item information with media source:",
error
error,
);
}
}
function getDownloadedItemSize(itemId: string): number {
const size = storage.getString("downloadedItemSize-" + itemId);
return size ? parseInt(size) : 0;
return size ? Number.parseInt(size) : 0;
}
return {

View File

@@ -1,13 +1,14 @@
import { storage } from "@/utils/mmkv";
import { JobStatus } from "@/utils/optimize-server";
import {
import type { JobStatus } from "@/utils/optimize-server";
import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
import * as Application from "expo-application";
import * as FileSystem from "expo-file-system";
import { atom, useAtom } from "jotai";
import React, { createContext, useCallback, useContext, useMemo } from "react";
import type React from "react";
import { createContext, useCallback, useContext, useMemo } from "react";
export type DownloadedItem = {
item: Partial<BaseItemDto>;
@@ -38,7 +39,7 @@ function useDownloadProvider() {
async (url: string, item: BaseItemDto, mediaSource: MediaSourceInfo) => {
return null;
},
[]
[],
);
const deleteAllFiles = async (): Promise<void> => {};
@@ -59,11 +60,11 @@ function useDownloadProvider() {
return null;
}
function saveDownloadedItemInfo(item: BaseItemDto, size: number = 0) {}
function saveDownloadedItemInfo(item: BaseItemDto, size = 0) {}
function getDownloadedItemSize(itemId: string): number {
const size = storage.getString("downloadedItemSize-" + itemId);
return size ? parseInt(size) : 0;
return size ? Number.parseInt(size) : 0;
}
const APP_CACHE_DOWNLOAD_DIRECTORY = `${FileSystem.cacheDirectory}${Application.applicationId}/Downloads/`;

View File

@@ -2,19 +2,21 @@ import "@/augmentations";
import { useInterval } from "@/hooks/useInterval";
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
import { useSettings } from "@/utils/atoms/settings";
import { writeErrorLog, writeInfoLog } from "@/utils/log";
import { storage } from "@/utils/mmkv";
import { store } from "@/utils/store";
import { Api, Jellyfin } from "@jellyfin/sdk";
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import { type Api, Jellyfin } from "@jellyfin/sdk";
import type { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
import { useMutation, useQuery } from "@tanstack/react-query";
import axios, { AxiosError } from "axios";
import { router, useSegments } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { atom, useAtom } from "jotai";
import React, {
import type React from "react";
import {
type ReactNode,
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
@@ -25,7 +27,6 @@ import { useTranslation } from "react-i18next";
import { Platform } from "react-native";
import { getDeviceName } from "react-native-device-info";
import uuid from "react-native-uuid";
import {writeErrorLog, writeInfoLog} from "@/utils/log";
interface Server {
address: string;
@@ -45,7 +46,7 @@ interface JellyfinContextValue {
}
const JellyfinContext = createContext<JellyfinContextValue | undefined>(
undefined
undefined,
);
export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
@@ -68,7 +69,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
name: deviceName,
id,
},
})
}),
);
setDeviceId(id);
})();
@@ -104,7 +105,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
null,
{
headers,
}
},
);
if (response?.status === 200) {
setSecret(response?.data?.Secret);
@@ -124,7 +125,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
try {
const response = await api.axiosInstance.get(
`${api.basePath}/QuickConnect/Connect?Secret=${secret}`
`${api.basePath}/QuickConnect/Connect?Secret=${secret}`,
);
if (response.status === 200) {
@@ -138,7 +139,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
},
{
headers,
}
},
);
const { AccessToken, User } = authResponse.data;
@@ -167,7 +168,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
await refreshStreamyfinPluginSettings();
})();
}, []);
useEffect(() => {
store.set(apiAtom, api);
}, [api]);
@@ -176,9 +177,8 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min
const discoverServers = async (url: string): Promise<Server[]> => {
const servers = await jellyfin?.discovery.getRecommendedServerCandidates(
url
);
const servers =
await jellyfin?.discovery.getRecommendedServerCandidates(url);
return servers?.map((server) => ({ address: server.address })) || [];
};
@@ -193,7 +193,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
},
onSuccess: (_, server) => {
const previousServers = JSON.parse(
storage.getString("previousServers") || "[]"
storage.getString("previousServers") || "[]",
);
const updatedServers = [
server,
@@ -201,7 +201,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
];
storage.set(
"previousServers",
JSON.stringify(updatedServers.slice(0, 5))
JSON.stringify(updatedServers.slice(0, 5)),
);
},
onError: (error) => {
@@ -241,7 +241,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const recentPluginSettings = await refreshStreamyfinPluginSettings();
if (recentPluginSettings?.jellyseerrServerUrl?.value) {
const jellyseerrApi = new JellyseerrApi(
recentPluginSettings.jellyseerrServerUrl.value
recentPluginSettings.jellyseerrServerUrl.value,
);
await jellyseerrApi.test().then((result) => {
if (result.isValid && result.requiresPass) {
@@ -257,23 +257,23 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
throw new Error(t("login.invalid_username_or_password"));
case 403:
throw new Error(
t("login.user_does_not_have_permission_to_log_in")
t("login.user_does_not_have_permission_to_log_in"),
);
case 408:
throw new Error(
t("login.server_is_taking_too_long_to_respond_try_again_later")
t("login.server_is_taking_too_long_to_respond_try_again_later"),
);
case 429:
throw new Error(
t("login.server_received_too_many_requests_try_again_later")
t("login.server_received_too_many_requests_try_again_later"),
);
case 500:
throw new Error(t("login.there_is_a_server_error"));
default:
throw new Error(
t(
"login.an_unexpected_error_occured_did_you_enter_the_correct_url"
)
"login.an_unexpected_error_occured_did_you_enter_the_correct_url",
),
);
}
}
@@ -287,9 +287,12 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const logoutMutation = useMutation({
mutationFn: async () => {
api?.delete(`/Streamyfin/device/${deviceId}`)
.then(r => writeInfoLog("Deleted expo push token for device"))
.catch(e => writeErrorLog(`Failed to delete expo push token for device`))
api
?.delete(`/Streamyfin/device/${deviceId}`)
.then((r) => writeInfoLog("Deleted expo push token for device"))
.catch((e) =>
writeErrorLog(`Failed to delete expo push token for device`),
);
storage.delete("token");
setUser(null);

View File

@@ -1,5 +1,6 @@
import React, { createContext } from "react";
import { useJobProcessor } from "@/utils/atoms/queue";
import type React from "react";
import { createContext } from "react";
const JobQueueContext = createContext(null);

View File

@@ -1,14 +1,15 @@
import { Bitrate } from "@/components/BitrateSelector";
import type { Bitrate } from "@/components/BitrateSelector";
import { settingsAtom } from "@/utils/atoms/settings";
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
import native from "@/utils/profiles/native";
import {
import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
import { useAtomValue } from "jotai";
import React, {
import type React from "react";
import {
createContext,
useCallback,
useContext,
@@ -32,7 +33,7 @@ type PlaySettingsContextType = {
dataOrUpdater:
| PlaybackType
| null
| ((prev: PlaybackType | null) => PlaybackType | null)
| ((prev: PlaybackType | null) => PlaybackType | null),
) => Promise<{ url: string | null; sessionId: string | null } | null>;
playUrl?: string | null;
setPlayUrl: React.Dispatch<React.SetStateAction<string | null>>;
@@ -41,7 +42,7 @@ type PlaySettingsContextType = {
};
const PlaySettingsContext = createContext<PlaySettingsContextType | undefined>(
undefined
undefined,
);
export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
@@ -65,7 +66,7 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
dataOrUpdater:
| PlaybackType
| null
| ((prev: PlaybackType | null) => PlaybackType | null)
| ((prev: PlaybackType | null) => PlaybackType | null),
): Promise<{ url: string | null; sessionId: string | null } | null> => {
if (!api || !user || !settings) {
_setPlaySettings(null);
@@ -109,7 +110,7 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
return null;
}
},
[api, user, settings, playSettings]
[api, user, settings, playSettings],
);
// useEffect(() => {
@@ -153,7 +154,7 @@ export const usePlaySettings = () => {
const context = useContext(PlaySettingsContext);
if (context === undefined) {
throw new Error(
"usePlaySettings must be used within a PlaySettingsProvider"
"usePlaySettings must be used within a PlaySettingsProvider",
);
}
return context;

View File

@@ -1,19 +1,16 @@
import { apiAtom, getOrSetDeviceId } from "@/providers/JellyfinProvider";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
import { useAtomValue } from "jotai";
import React, {
createContext,
useContext,
useEffect,
useState,
ReactNode,
type ReactNode,
useMemo,
useCallback,
} from "react";
import { AppState, AppStateStatus } from "react-native";
import { useAtomValue } from "jotai";
import {
apiAtom,
getOrSetDeviceId,
} from "@/providers/JellyfinProvider";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api";
import { AppState, type AppStateStatus } from "react-native";
interface WebSocketProviderProps {
children: ReactNode;
@@ -121,7 +118,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
const subscription = AppState.addEventListener(
"change",
handleAppStateChange
handleAppStateChange,
);
return () => {
@@ -141,7 +138,7 @@ export const useWebSocketContext = (): WebSocketContextType => {
const context = useContext(WebSocketContext);
if (!context) {
throw new Error(
"useWebSocketContext must be used within a WebSocketProvider"
"useWebSocketContext must be used within a WebSocketProvider",
);
}
return context;