194 lines
6.6 KiB
JavaScript
194 lines
6.6 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
// SHA-256 hashing and cache management
|
|
|
|
const HASH_CACHE_FILENAME = '.hashes.json';
|
|
|
|
// Calculate SHA-256 hash for a file (delegates to shared/hash.js)
|
|
async function calculateFileHash(file) {
|
|
return zddc.crypto.sha256File(file);
|
|
}
|
|
|
|
// Load hash cache for a directory
|
|
async function loadHashCache(dirHandle) {
|
|
try {
|
|
const cacheHandle = await dirHandle.getFileHandle(HASH_CACHE_FILENAME);
|
|
const cacheFile = await cacheHandle.getFile();
|
|
const cacheText = await cacheFile.text();
|
|
return JSON.parse(cacheText);
|
|
} catch (err) {
|
|
// Cache doesn't exist or can't be read
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Save hash cache for a directory
|
|
async function saveHashCache(dirHandle, cache) {
|
|
try {
|
|
const cacheHandle = await dirHandle.getFileHandle(HASH_CACHE_FILENAME, { create: true });
|
|
const writable = await cacheHandle.createWritable();
|
|
await writable.write(JSON.stringify(cache, null, 2));
|
|
await writable.close();
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('Unable to save hash cache:', err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Hash files in a directory with caching
|
|
async function hashDirectoryFiles(dirHandle, files) {
|
|
const cache = await loadHashCache(dirHandle);
|
|
const updatedCache = {};
|
|
const results = {};
|
|
|
|
for (const fileInfo of files) {
|
|
try {
|
|
const file = await fileInfo.handle.getFile();
|
|
const cacheKey = file.name;
|
|
const lastModified = file.lastModified;
|
|
|
|
// Check if we have a cached hash
|
|
if (cache[cacheKey] && cache[cacheKey].lastModified === lastModified) {
|
|
// Use cached hash
|
|
results[fileInfo.id] = cache[cacheKey].hash;
|
|
updatedCache[cacheKey] = cache[cacheKey];
|
|
} else {
|
|
// Calculate new hash
|
|
const hash = await calculateFileHash(file);
|
|
results[fileInfo.id] = hash;
|
|
updatedCache[cacheKey] = {
|
|
hash: hash,
|
|
lastModified: lastModified,
|
|
size: file.size
|
|
};
|
|
}
|
|
} catch (err) {
|
|
console.error(`Error hashing file ${fileInfo.name}:`, err);
|
|
}
|
|
}
|
|
|
|
// Try to save updated cache
|
|
await saveHashCache(dirHandle, updatedCache);
|
|
|
|
return results;
|
|
}
|
|
|
|
// Add hash information to files
|
|
async function addHashesToFiles() {
|
|
if (!window.app.files.length) return;
|
|
|
|
// Hash operations require local file handles — not available in HTTP mode
|
|
if (window.app.sourceMode === 'http') {
|
|
console.log('Hash operations not available in HTTP mode.');
|
|
return;
|
|
}
|
|
|
|
window.app.modules.export.showProgress('Calculating file hashes...', 0, window.app.files.length);
|
|
|
|
try {
|
|
// Group files by directory
|
|
const filesByDir = {};
|
|
window.app.files.forEach(file => {
|
|
const dirPath = file.folderPath;
|
|
if (!filesByDir[dirPath]) {
|
|
filesByDir[dirPath] = {
|
|
handle: null,
|
|
files: []
|
|
};
|
|
}
|
|
filesByDir[dirPath].files.push(file);
|
|
});
|
|
|
|
// Find directory handles
|
|
for (const dirPath in filesByDir) {
|
|
const folder = window.app.transmittalFolders.find(f => f.path === dirPath);
|
|
if (folder) {
|
|
filesByDir[dirPath].handle = folder.handle;
|
|
}
|
|
}
|
|
|
|
// Hash files in each directory
|
|
let processed = 0;
|
|
for (const dirPath in filesByDir) {
|
|
const dirInfo = filesByDir[dirPath];
|
|
if (dirInfo.handle) {
|
|
const hashes = await hashDirectoryFiles(dirInfo.handle, dirInfo.files);
|
|
|
|
// Update file objects with hashes
|
|
dirInfo.files.forEach(file => {
|
|
if (hashes[file.id]) {
|
|
file.hash = hashes[file.id];
|
|
}
|
|
});
|
|
}
|
|
|
|
processed += dirInfo.files.length;
|
|
window.app.modules.export.showProgress('Calculating file hashes...', processed, window.app.files.length);
|
|
}
|
|
|
|
window.app.modules.export.hideProgress();
|
|
|
|
} catch (err) {
|
|
window.app.modules.export.hideProgress();
|
|
console.error('Error calculating hashes:', err);
|
|
}
|
|
}
|
|
|
|
// Verify file integrity
|
|
async function verifyFileIntegrity(fileId) {
|
|
const file = window.app.files.find(f => f.id === fileId);
|
|
if (!file || !file.hash) {
|
|
alert('No hash available for this file.');
|
|
return;
|
|
}
|
|
|
|
if (!file.handle) {
|
|
alert('File integrity verification is not available in HTTP mode.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const fileData = await file.handle.getFile();
|
|
const currentHash = await calculateFileHash(fileData);
|
|
|
|
if (currentHash === file.hash) {
|
|
alert('File integrity verified. Hash matches.');
|
|
} else {
|
|
alert('WARNING: File has been modified! Hash does not match.');
|
|
}
|
|
} catch (err) {
|
|
alert('Error verifying file: ' + err.message);
|
|
}
|
|
}
|
|
|
|
// Export hash report
|
|
function exportHashReport() {
|
|
const headers = ['File Path', 'SHA-256 Hash', 'Size', 'Last Modified'];
|
|
const rows = [headers];
|
|
|
|
window.app.filteredFiles.forEach(file => {
|
|
if (file.hash) {
|
|
rows.push([
|
|
file.path,
|
|
file.hash,
|
|
window.app.modules.export.formatFileSize(file.size),
|
|
new Date(file.modified).toISOString()
|
|
]);
|
|
}
|
|
});
|
|
|
|
window.app.modules.export.downloadFile(window.app.modules.export.rowsToCSV(rows), 'file-hashes.csv', 'text/csv');
|
|
}
|
|
|
|
window.app.modules.hash = {
|
|
calculateFileHash,
|
|
loadHashCache,
|
|
saveHashCache,
|
|
hashDirectoryFiles,
|
|
addHashesToFiles,
|
|
verifyFileIntegrity,
|
|
exportHashReport
|
|
};
|
|
|
|
})();
|