Files
upload-gltf/lib/diff-files.ts
T
Tom Boullay 78f4aa83e0 refactor: full codebase audit — extract modules, fix type safety, clean dead code
- Extract API helpers from UploadZone into lib/upload-api.ts (FormData builder, checkFolderDiffs, uploadDrive, uploadGit)
- Extract upload orchestration into hooks/useUploadOrchestrator.ts (UploadZone: 489 → 162 lines)
- Extract file diff classification into lib/diff-files.ts (from git route)
- Extract shared SVG icons into components/ui/icons.tsx (7 icons, 0 duplication)
- Extract shared modal wrapper into components/ui/Modal.tsx + ModalActions
- Extract DriveStatusLine sub-component from FolderCard
- Fix checkFolderDiffs silently swallowing auth/network errors (now throws)
- Fix type safety: remove as never casts, add isHttpError type guard, use discriminated union for validateFolder
- Fix nextcloud: cache getConfig, add max bound to findNextVersion, optimize mkdirRecursive (skip PROPFIND)
- Fix drive route: remove req.clone(), extend parseMultiUpload to return extra fields
- Fix commit message: model shown as unchanged with ↔️ on updates (not falsely marked as modified)
- Clean dead code: unused folderExists import, FileStatus/DriveStatus exports, ParsedFile.textureName, getConfig basePath
- Add security headers in next.config.ts (HSTS, X-Content-Type-Options, X-Frame-Options, etc.)
- Update README with new project structure
2026-04-14 17:19:10 +02:00

84 lines
3.0 KiB
TypeScript

// ---------------------------------------------------------------------------
// File diff classification — compares local files against a remote file map
// ---------------------------------------------------------------------------
import { MODEL_EXTENSIONS } from './constants'
import type { FileChange } from './types'
interface PushFile {
path: string
contentBase64: string
}
export interface DiffResult {
/** Map of lowercase filename → change status (for commit message) */
fileChanges: Map<string, FileChange>
/** Files that actually need to be pushed (new or changed) */
changedFilesToPush: PushFile[]
/** Filenames that were on remote but not in the new upload */
deletedFileNames: string[]
/** Full paths for deletion on remote */
deletePaths: string[]
}
/**
* Classify each file as new, changed, or unchanged by comparing against
* the remote file map.
*
* Rules:
* - Models: always re-pushed (compression makes size comparison unreliable),
* but marked as 'unchanged' in the commit message when the folder already
* exists (we can't know if the model really changed after Blender).
* - Textures: compared by size (not compressed, reliable).
* - Orphan remote files: classified as deletions.
*/
export function classifyFileChanges(
filesToPush: PushFile[],
remoteFileMap: Map<string, number>,
folderPath: string,
): DiffResult {
const fileChanges = new Map<string, FileChange>()
const changedFilesToPush: PushFile[] = []
for (const f of filesToPush) {
const filename = f.path.split('/').pop() ?? ''
const ext = filename.slice(filename.lastIndexOf('.')).toLowerCase()
const isModel = MODEL_EXTENSIONS.has(ext)
if (isModel) {
// Model: always re-push since compression makes size comparison unreliable.
// Mark as 'unchanged' for the commit message when the folder already exists.
const remoteSize = remoteFileMap.get(filename.toLowerCase())
fileChanges.set(filename.toLowerCase(), remoteSize === undefined ? 'new' : 'unchanged')
changedFilesToPush.push(f)
} else {
// Texture: compare by size
const localSize = Buffer.from(f.contentBase64, 'base64').length
const remoteSize = remoteFileMap.get(filename.toLowerCase())
if (remoteSize === undefined) {
fileChanges.set(filename.toLowerCase(), 'new')
changedFilesToPush.push(f)
} else if (remoteSize !== localSize) {
fileChanges.set(filename.toLowerCase(), 'changed')
changedFilesToPush.push(f)
} else {
fileChanges.set(filename.toLowerCase(), 'unchanged')
}
}
}
// Files on remote not in the new upload → deleted (orphans)
const newFileNames = new Set(filesToPush.map((f) => (f.path.split('/').pop() ?? '').toLowerCase()))
const deletedFileNames: string[] = []
const deletePaths: string[] = []
for (const [name] of remoteFileMap) {
if (!newFileNames.has(name)) {
deletedFileNames.push(name)
deletePaths.push(`${folderPath}/${name}`)
}
}
return { fileChanges, changedFilesToPush, deletedFileNames, deletePaths }
}