mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
Merge pull request #243 from herrrta/feat/show-overall-download-size
Show app usage
This commit is contained in:
@@ -4,7 +4,7 @@ import { ListItem } from "@/components/ListItem";
|
||||
import { SettingToggles } from "@/components/settings/SettingToggles";
|
||||
import {bytesToReadable, useDownload} from "@/providers/DownloadProvider";
|
||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { clearLogs, readFromLog } from "@/utils/log";
|
||||
import {clearLogs, useLog} from "@/utils/log";
|
||||
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import * as Haptics from "expo-haptics";
|
||||
@@ -17,23 +17,18 @@ import * as FileSystem from "expo-file-system";
|
||||
|
||||
export default function settings() {
|
||||
const { logout } = useJellyfin();
|
||||
const { deleteAllFiles, getAppSizeUsage } = useDownload();
|
||||
const { deleteAllFiles, appSizeUsage } = useDownload();
|
||||
const { logs } = useLog();
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
|
||||
const { data: logs } = useQuery({
|
||||
queryKey: ["logs"],
|
||||
queryFn: async () => readFromLog(),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const {data: size , isLoading: appSizeLoading } = useQuery({
|
||||
queryKey: ["appSize"],
|
||||
queryKey: ["appSize", appSizeUsage],
|
||||
queryFn: async () => {
|
||||
const app = await getAppSizeUsage()
|
||||
const app = await appSizeUsage
|
||||
|
||||
const remaining = await FileSystem.getFreeDiskStorageAsync()
|
||||
const total = await FileSystem.getTotalDiskCapacityAsync()
|
||||
@@ -132,6 +127,8 @@ export default function settings() {
|
||||
|
||||
<View className="flex flex-col space-y-2">
|
||||
<Text className="font-bold text-lg mb-2">Storage</Text>
|
||||
<View className="mb-4 space-y-2">
|
||||
{size && <Text>App usage: {bytesToReadable(size.app)}</Text>}
|
||||
<Progress.Bar
|
||||
className="bg-gray-100/10"
|
||||
indeterminate={appSizeLoading}
|
||||
@@ -145,6 +142,7 @@ export default function settings() {
|
||||
{size && (
|
||||
<Text>Available: {bytesToReadable(size.remaining)}, Total: {bytesToReadable(size.total)}</Text>
|
||||
)}
|
||||
</View>
|
||||
<Button
|
||||
color="red"
|
||||
onPress={onDeleteClicked}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
|
||||
import { orientationAtom } from "@/utils/atoms/orientation";
|
||||
import { Settings, useSettings } from "@/utils/atoms/settings";
|
||||
import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import {LogProvider, writeToLog} from "@/utils/log";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
|
||||
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||
@@ -304,15 +304,16 @@ function Layout() {
|
||||
}
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ActionSheetProvider>
|
||||
<JobQueueProvider>
|
||||
<JellyfinProvider>
|
||||
<PlaySettingsProvider>
|
||||
<LogProvider>
|
||||
<DownloadProvider>
|
||||
<BottomSheetModalProvider>
|
||||
<SystemBars style="light" hidden={false} />
|
||||
<SystemBars style="light" hidden={false}/>
|
||||
<ThemeProvider value={DarkTheme}>
|
||||
<Stack initialRouteName="/home">
|
||||
<Stack.Screen
|
||||
@@ -333,9 +334,9 @@ function Layout() {
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="login"
|
||||
options={{ headerShown: false, title: "Login" }}
|
||||
options={{headerShown: false, title: "Login"}}
|
||||
/>
|
||||
<Stack.Screen name="+not-found" />
|
||||
<Stack.Screen name="+not-found"/>
|
||||
</Stack>
|
||||
<Toaster
|
||||
duration={4000}
|
||||
@@ -354,6 +355,7 @@ function Layout() {
|
||||
</ThemeProvider>
|
||||
</BottomSheetModalProvider>
|
||||
</DownloadProvider>
|
||||
</LogProvider>
|
||||
</PlaySettingsProvider>
|
||||
</JellyfinProvider>
|
||||
</JobQueueProvider>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { getOrSetDeviceId } from "@/utils/device";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import {useLog, writeToLog} from "@/utils/log";
|
||||
import {
|
||||
cancelAllJobs,
|
||||
cancelJobById,
|
||||
@@ -69,6 +69,7 @@ function useDownloadProvider() {
|
||||
const [settings] = useSettings();
|
||||
const router = useRouter();
|
||||
const [api] = useAtom(apiAtom);
|
||||
const { logs } = useLog();
|
||||
|
||||
const {saveSeriesPrimaryImage} = useDownloadHelper();
|
||||
const { saveImage } = useImageStorage();
|
||||
@@ -403,7 +404,7 @@ function useDownloadProvider() {
|
||||
});
|
||||
};
|
||||
|
||||
const forEveryDirectory = async (callback: (dir: FileInfo) => void) => {
|
||||
const forEveryDirectoryFile = async (includeMMKV: boolean = true, callback: (file: FileInfo) => void) => {
|
||||
const baseDirectory = FileSystem.documentDirectory;
|
||||
if (!baseDirectory) {
|
||||
throw new Error("Base directory not found");
|
||||
@@ -413,7 +414,7 @@ function useDownloadProvider() {
|
||||
for (const item of dirContents) {
|
||||
// Exclude mmkv directory.
|
||||
// Deleting this deletes all user information as well. Logout should handle this.
|
||||
if (item == "mmkv")
|
||||
if (item == "mmkv" && !includeMMKV)
|
||||
continue
|
||||
const itemInfo = await FileSystem.getInfoAsync(`${baseDirectory}${item}`);
|
||||
if (itemInfo.exists) {
|
||||
@@ -423,9 +424,9 @@ function useDownloadProvider() {
|
||||
}
|
||||
|
||||
const deleteLocalFiles = async (): Promise<void> => {
|
||||
await forEveryDirectory((dir) => {
|
||||
console.warn("Deleting file", dir.uri)
|
||||
FileSystem.deleteAsync(dir.uri, {idempotent: true})
|
||||
await forEveryDirectoryFile(false, (file) => {
|
||||
console.warn("Deleting file", file.uri)
|
||||
FileSystem.deleteAsync(file.uri, {idempotent: true})
|
||||
}
|
||||
)
|
||||
};
|
||||
@@ -534,15 +535,17 @@ function useDownloadProvider() {
|
||||
);
|
||||
}
|
||||
|
||||
const getAppSizeUsage = async () => {
|
||||
const appSizeUsage = useMemo(async () => {
|
||||
const sizes: number[] = [];
|
||||
await forEveryDirectory(dir => {
|
||||
if (dir.exists)
|
||||
sizes.push(dir.size)
|
||||
})
|
||||
await forEveryDirectoryFile(
|
||||
true,
|
||||
file => {
|
||||
if (file.exists) sizes.push(file.size)
|
||||
}
|
||||
)
|
||||
|
||||
return sizes.reduce((sum, size) => sum + size, 0);
|
||||
}
|
||||
}, [logs, downloadedFiles])
|
||||
|
||||
function getDownloadedItem(itemId: string): DownloadedItem | null {
|
||||
try {
|
||||
@@ -623,7 +626,7 @@ function useDownloadProvider() {
|
||||
startDownload,
|
||||
getDownloadedItem,
|
||||
deleteFileByType,
|
||||
getAppSizeUsage
|
||||
appSizeUsage
|
||||
};
|
||||
}
|
||||
|
||||
@@ -646,9 +649,17 @@ export function useDownload() {
|
||||
}
|
||||
|
||||
export function bytesToReadable(bytes: number): string {
|
||||
const gb = bytes / 1e+9;
|
||||
const gb = bytes / 1e9;
|
||||
|
||||
if (gb >= 1)
|
||||
return `${gb.toFixed(2)} GB`
|
||||
return `${(bytes / 1024 / 1024).toFixed(2)} MB`
|
||||
|
||||
const mb = bytes / 1024 / 1024
|
||||
if (mb >= 1)
|
||||
return `${mb.toFixed(2)} MB`
|
||||
|
||||
const kb = bytes / 1024
|
||||
if (kb >= 1)
|
||||
return `${kb.toFixed(2)} KB`
|
||||
return `${bytes.toFixed(2)} B`
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { atomWithStorage, createJSONStorage } from "jotai/utils";
|
||||
import { storage } from "./mmkv";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import React, {createContext, useContext} from "react";
|
||||
|
||||
type LogLevel = "INFO" | "WARN" | "ERROR";
|
||||
|
||||
@@ -17,6 +19,24 @@ const mmkvStorage = createJSONStorage(() => ({
|
||||
}));
|
||||
const logsAtom = atomWithStorage("logs", [], mmkvStorage);
|
||||
|
||||
const LogContext = createContext<ReturnType<typeof useLogProvider> | null>(null);
|
||||
const DownloadContext = createContext<ReturnType<
|
||||
typeof useLogProvider
|
||||
> | null>(null);
|
||||
|
||||
function useLogProvider() {
|
||||
const { data: logs } = useQuery({
|
||||
queryKey: ["logs"],
|
||||
queryFn: async () => readFromLog(),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
return {
|
||||
logs
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const writeToLog = (level: LogLevel, message: string, data?: any) => {
|
||||
const newEntry: LogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -44,4 +64,22 @@ export const clearLogs = () => {
|
||||
storage.delete("logs");
|
||||
};
|
||||
|
||||
export function useLog() {
|
||||
const context = useContext(LogContext);
|
||||
if (context === null) {
|
||||
throw new Error("useLog must be used within a LogProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function LogProvider({children}: { children: React.ReactNode }) {
|
||||
const provider = useLogProvider();
|
||||
|
||||
return (
|
||||
<LogContext.Provider value={provider}>
|
||||
{children}
|
||||
</LogContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default logsAtom;
|
||||
Reference in New Issue
Block a user