You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
import { IDiff, IFileList, Record } from "./types";
|
|
import fs from "fs";
|
|
import crypto from "crypto";
|
|
|
|
export async function fileHash(filename: string, algorithm: "md5" | "sha1" | "sha256" | "sha512"): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
// Algorithm depends on availability of OpenSSL on platform
|
|
// Another algorithms: "sha1", "md5", "sha256", "sha512" ...
|
|
let shasum = crypto.createHash(algorithm);
|
|
try {
|
|
let s = fs.createReadStream(filename);
|
|
s.on("data", function (data: any) {
|
|
shasum.update(data)
|
|
});
|
|
|
|
s.on("error", function (error) {
|
|
reject(error);
|
|
});
|
|
|
|
// making digest
|
|
s.on("end", function () {
|
|
const hash = shasum.digest("hex")
|
|
return resolve(hash);
|
|
});
|
|
}
|
|
catch (error) {
|
|
return reject("calc fail");
|
|
}
|
|
});
|
|
}
|
|
|
|
export class HashDiff implements IDiff {
|
|
getDiffs(localFiles: IFileList, serverFiles: IFileList) {
|
|
const uploadList: Record[] = [];
|
|
const deleteList: Record[] = [];
|
|
const replaceList: Record[] = [];
|
|
|
|
const sameList: Record[] = [];
|
|
|
|
let sizeUpload = 0;
|
|
let sizeDelete = 0;
|
|
let sizeReplace = 0;
|
|
|
|
// alphabetize each list based off path
|
|
const localFilesSorted = localFiles.data.sort((first, second) => first.name.localeCompare(second.name));
|
|
const serverFilesSorted = serverFiles.data.sort((first, second) => first.name.localeCompare(second.name));
|
|
|
|
let localPosition = 0;
|
|
let serverPosition = 0;
|
|
while (localPosition + serverPosition < localFilesSorted.length + serverFilesSorted.length) {
|
|
let localFile: Record | undefined = localFilesSorted[localPosition];
|
|
let serverFile: Record | undefined = serverFilesSorted[serverPosition];
|
|
|
|
let fileNameCompare = 0;
|
|
if (localFile === undefined) {
|
|
fileNameCompare = 1;
|
|
}
|
|
if (serverFile === undefined) {
|
|
fileNameCompare = -1;
|
|
}
|
|
if (localFile !== undefined && serverFile !== undefined) {
|
|
fileNameCompare = localFile.name.localeCompare(serverFile.name);
|
|
}
|
|
|
|
if (fileNameCompare < 0) {
|
|
uploadList.push(localFile);
|
|
sizeUpload += localFile.size ?? 0;
|
|
localPosition += 1;
|
|
}
|
|
else if (fileNameCompare > 0) {
|
|
deleteList.push(serverFile);
|
|
sizeDelete += serverFile.size ?? 0;
|
|
serverPosition += 1;
|
|
}
|
|
else if (fileNameCompare === 0) {
|
|
// paths are a match
|
|
if (localFile.type === "file" && serverFile.type === "file") {
|
|
if (localFile.hash === serverFile.hash) {
|
|
sameList.push(localFile);
|
|
}
|
|
else {
|
|
sizeReplace += localFile.size ?? 0;
|
|
replaceList.push(localFile);
|
|
}
|
|
}
|
|
|
|
localPosition += 1;
|
|
serverPosition += 1;
|
|
}
|
|
}
|
|
|
|
// optimize modifications
|
|
let foldersToDelete = deleteList.filter((item) => item.type === "folder");
|
|
|
|
// remove files/folders that have a nested parent folder we plan on deleting
|
|
const optimizedDeleteList = deleteList.filter((itemToDelete) => {
|
|
const parentFolderIsBeingDeleted = foldersToDelete.find((folder) => {
|
|
const isSameFile = itemToDelete.name === folder.name;
|
|
const parentFolderExists = itemToDelete.name.startsWith(folder.name);
|
|
|
|
return parentFolderExists && !isSameFile;
|
|
}) !== undefined;
|
|
|
|
if (parentFolderIsBeingDeleted) {
|
|
// a parent folder is being deleted, no need to delete this one
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return {
|
|
upload: uploadList,
|
|
delete: optimizedDeleteList,
|
|
replace: replaceList,
|
|
same: sameList,
|
|
sizeDelete,
|
|
sizeReplace,
|
|
sizeUpload
|
|
};
|
|
}
|
|
}
|