Merge pull request #243 from herrrta/feat/show-overall-download-size

Show app usage
This commit is contained in:
Fredrik Burmester
2024-12-03 16:23:24 +01:00
committed by GitHub
4 changed files with 131 additions and 82 deletions

View File

@@ -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}

View File

@@ -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>

View File

@@ -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`
}

View File

@@ -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;