refresh signed files
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -72,7 +71,7 @@ func ListFiles(c *gin.Context) {
|
|||||||
|
|
||||||
func ListFile(c *gin.Context) {
|
func ListFile(c *gin.Context) {
|
||||||
|
|
||||||
id := c.Query("id")
|
id := c.Param("id")
|
||||||
|
|
||||||
dirs, err := os.ReadDir("./uploads/")
|
dirs, err := os.ReadDir("./uploads/")
|
||||||
|
|
||||||
@@ -175,12 +174,10 @@ func UploadFile(c *gin.Context) {
|
|||||||
|
|
||||||
c.SaveUploadedFile(file, dst)
|
c.SaveUploadedFile(file, dst)
|
||||||
|
|
||||||
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|
||||||
c.JSON(http.StatusNotImplemented, gin.H{
|
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "NOT_IMPLEMENTED",
|
"code": "SUCCESS",
|
||||||
"message": "Upload not yet implemented",
|
"message": "Uploaded",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { API_BASE_URL } from '../constants/api';
|
import { API_BASE_URL, ENDPOINTS } from '../constants/api';
|
||||||
import { ApiError } from '../types';
|
import { ApiError } from '../types';
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
@@ -43,6 +43,10 @@ class ApiClient {
|
|||||||
async delete<T>(endpoint: string): Promise<T> {
|
async delete<T>(endpoint: string): Promise<T> {
|
||||||
return this.request<T>(endpoint, { method: 'DELETE' });
|
return this.request<T>(endpoint, { method: 'DELETE' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFileUrl(fileName: string): Promise<{ data: { url: string; name: string } }> {
|
||||||
|
return this.request(`${ENDPOINTS.FILE}/${fileName}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiClient = new ApiClient(API_BASE_URL);
|
export const apiClient = new ApiClient(API_BASE_URL);
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ interface FileCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function FileCard({ file, onPress }: FileCardProps) {
|
export function FileCard({ file, onPress }: FileCardProps) {
|
||||||
const { localUri, loading } = useFileImage(file.url);
|
const { localUri, loading } = useFileImage(file.url, file.name);
|
||||||
console.log(localUri)
|
|
||||||
const formatSize = (bytes: number) => {
|
const formatSize = (bytes: number) => {
|
||||||
if (bytes < 1024) return `${bytes} B`;
|
if (bytes < 1024) return `${bytes} B`;
|
||||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL || 'http://192.
|
|||||||
|
|
||||||
export const ENDPOINTS = {
|
export const ENDPOINTS = {
|
||||||
FILES: '/files',
|
FILES: '/files',
|
||||||
|
FILE: '/file',
|
||||||
UPLOAD: '/files/upload',
|
UPLOAD: '/files/upload',
|
||||||
SEARCH: '/files/search',
|
SEARCH: '/files/search',
|
||||||
OCR_JOBS: '/ocr/jobs',
|
OCR_JOBS: '/ocr/jobs',
|
||||||
|
|||||||
@@ -1,36 +1,70 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { downloadAsync, documentDirectory, makeDirectoryAsync, getInfoAsync } from 'expo-file-system/legacy';
|
import { downloadAsync, documentDirectory, makeDirectoryAsync, getInfoAsync, deleteAsync } from 'expo-file-system/legacy';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiClient } from '../api/client';
|
||||||
|
import { FileItem, PaginatedResponse } from '../types';
|
||||||
|
|
||||||
const CACHE_DIR = `${documentDirectory}file-images/`;
|
const CACHE_DIR = `${documentDirectory}file-images/`;
|
||||||
|
|
||||||
function getCacheKey(url: string): string {
|
function getCacheKey(name: string): string {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let i = 0; i < url.length; i++) {
|
for (let i = 0; i < name.length; i++) {
|
||||||
hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
|
hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
|
||||||
}
|
}
|
||||||
return Math.abs(hash).toString(36);
|
return Math.abs(hash).toString(36);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtension(url: string): string {
|
function getExtension(name: string): string {
|
||||||
const pathname = new URL(url).pathname;
|
const dot = name.lastIndexOf('.');
|
||||||
const dot = pathname.lastIndexOf('.');
|
return dot >= 0 ? name.slice(dot) : '.jpg';
|
||||||
return dot >= 0 ? pathname.slice(dot) : '.jpg';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFileImage(url: string | undefined) {
|
export function useFileImage(url: string | undefined, fileName: string | undefined) {
|
||||||
const [localUri, setLocalUri] = useState<string | null>(null);
|
const [localUri, setLocalUri] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const refreshUrl = useCallback(async (): Promise<string | null> => {
|
||||||
|
if (!fileName) return null;
|
||||||
|
try {
|
||||||
|
const res = await apiClient.getFileUrl(fileName);
|
||||||
|
const freshUrl = res.data.url;
|
||||||
|
|
||||||
|
queryClient.setQueriesData<PaginatedResponse<FileItem>>(
|
||||||
|
{ queryKey: ['files'] },
|
||||||
|
(old) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
data: old.data.map((f) =>
|
||||||
|
f.name === fileName ? { ...f, url: freshUrl } : f
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return freshUrl;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [fileName, queryClient]);
|
||||||
|
|
||||||
|
const download = useCallback(async (downloadUrl: string, fileUri: string) => {
|
||||||
|
await makeDirectoryAsync(CACHE_DIR, { intermediates: true });
|
||||||
|
const result = await downloadAsync(downloadUrl, fileUri);
|
||||||
|
return result.uri;
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!url) return;
|
if (!url || !fileName) return;
|
||||||
|
|
||||||
const resolvedUrl = url;
|
const resolvedUrl = url;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
const cacheKey = getCacheKey(fileName);
|
||||||
|
const ext = getExtension(fileName);
|
||||||
|
const fileUri = `${CACHE_DIR}${cacheKey}${ext}`;
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
const fileName = `${getCacheKey(resolvedUrl)}${getExtension(resolvedUrl)}`;
|
|
||||||
const fileUri = `${CACHE_DIR}${fileName}`;
|
|
||||||
|
|
||||||
const info = await getInfoAsync(fileUri);
|
const info = await getInfoAsync(fileUri);
|
||||||
if (info.exists) {
|
if (info.exists) {
|
||||||
if (!cancelled) setLocalUri(info.uri);
|
if (!cancelled) setLocalUri(info.uri);
|
||||||
@@ -39,13 +73,19 @@ export function useFileImage(url: string | undefined) {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await makeDirectoryAsync(CACHE_DIR, { intermediates: true });
|
const uri = await download(resolvedUrl, fileUri);
|
||||||
const result = await downloadAsync(resolvedUrl, fileUri);
|
if (!cancelled) setLocalUri(uri);
|
||||||
if (!cancelled) {
|
|
||||||
setLocalUri(result.uri);
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) {
|
const freshUrl = await refreshUrl();
|
||||||
|
if (freshUrl && !cancelled) {
|
||||||
|
try {
|
||||||
|
await deleteAsync(fileUri, { idempotent: true });
|
||||||
|
const uri = await download(freshUrl, fileUri);
|
||||||
|
if (!cancelled) setLocalUri(uri);
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) setLocalUri(null);
|
||||||
|
}
|
||||||
|
} else if (!cancelled) {
|
||||||
setLocalUri(null);
|
setLocalUri(null);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -58,7 +98,7 @@ export function useFileImage(url: string | undefined) {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [url]);
|
}, [url, fileName, download, refreshUrl]);
|
||||||
|
|
||||||
return { localUri, loading };
|
return { localUri, loading };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user