master
parent
8d246e2c1b
commit
14777853d3
@ -1,122 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
## local file hash
|
## local file hash
|
||||||
|
|
||||||
用于辅助Github actions: FTP Deploy的差异比较
|
用于辅助Github actions: FTP Deploy的差异比较\
|
||||||
FTP Deploy: 文件夹深度较深或文件较多时, 上传耗时很久, 因为每个文件计算差异之后打开ftp 连接上传
|
FTP Deploy: 文件夹深度较深或文件较多时, 上传耗时很久, 因为每个文件计算差异之后打开ftp 连接上传
|
||||||
先打包上传后, 生成文件的hash, 用于后续action的差异比较
|
先打包上传后, 生成文件的hash, 用于后续action的差异比较
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import readdir from "@jsdevtools/readdir-enhanced";
|
|
||||||
import { Record, IFileList, syncFileDescription, currentSyncFileVersion, IFtpDeployArgumentsWithDefaults } from "./types";
|
|
||||||
import { fileHash } from "./HashDiff";
|
|
||||||
import { applyExcludeFilter } from "./utilities";
|
|
||||||
|
|
||||||
export async function getLocalFiles(args: IFtpDeployArgumentsWithDefaults): Promise<IFileList> {
|
|
||||||
const files = await readdir.async(args["local-dir"], { deep: true, stats: true, sep: "/", filter: (stat) => applyExcludeFilter(stat, args.exclude) });
|
|
||||||
const records: Record[] = [];
|
|
||||||
|
|
||||||
for (let stat of files) {
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
records.push({
|
|
||||||
type: "folder",
|
|
||||||
name: stat.path,
|
|
||||||
size: undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stat.isFile()) {
|
|
||||||
records.push({
|
|
||||||
type: "file",
|
|
||||||
name: stat.path,
|
|
||||||
size: stat.size,
|
|
||||||
hash: await fileHash(args["local-dir"] + stat.path, "sha256")
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stat.isSymbolicLink()) {
|
|
||||||
console.warn("This script is currently unable to handle symbolic links - please add a feature request if you need this");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
description: syncFileDescription,
|
|
||||||
version: currentSyncFileVersion,
|
|
||||||
generatedTime: new Date().getTime(),
|
|
||||||
data: records
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue