Spaces:
Sleeping
Sleeping
File size: 2,993 Bytes
4cadbaf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
import fs from "fs";
import {EventEmitter} from "events";
import sha1 from "sha1";
import * as diff from "diff";
import asyncCall from "./asyncCall";
export default class FileProxy extends EventEmitter {
content: string;
timestamp: number;
diskTimestamp: number;
filePath: string;
alive: boolean = true;
writeFile: () => void;
fileListener: (curr: fs.Stats) => Promise<void>;
constructor (filePath: string) {
super();
this.filePath = filePath;
//console.log("File proxy created:", filePath);
if (!fs.existsSync(filePath))
throw new Error(`file not exist: ${filePath}`);
asyncCall(fs.stat, filePath)
.then(stats => {
this.diskTimestamp = stats.mtime.getTime();
this.timestamp = this.diskTimestamp;
return asyncCall(fs.readFile, filePath);
})
.then(buffer => {
this.content = buffer.toString();
this.fullSync();
});
this.fileListener = async current => {
this.diskTimestamp = current.mtime.getTime();
this.timestamp = this.diskTimestamp;
const buffer = await asyncCall(fs.readFile, filePath);
if (!buffer) {
this.emit("error", {description: "file reading failed"});
return;
}
const newContent = buffer.toString();
const newHash = sha1(newContent);
if (newHash !== this.hash) {
const patch = diff.createPatch(filePath, this.content, newContent);
this.emit("increase", {
timestamp: this.timestamp,
fromHash: this.hash,
toHash: newHash,
patch,
});
this.content = newContent;
}
};
fs.watchFile(filePath, this.fileListener);
this.keepWriteFile();
}
dispose () {
this.alive = false;
if (this.fileListener)
fs.unwatchFile(this.filePath, this.fileListener);
}
makeWritePromise (): Promise<void> {
return new Promise(resolve => this.writeFile = resolve);
}
async keepWriteFile () {
let writeSignal = this.makeWritePromise();
while (this.alive) {
await writeSignal;
writeSignal = this.makeWritePromise();
//console.debug("keepWriteFile:", this.timestamp, this.diskTimestamp);
if (this.timestamp > this.diskTimestamp) {
await asyncCall(fs.writeFile, this.filePath, this.content);
}
}
}
get hash (): string {
return sha1(this.content);
}
fullSync () {
this.emit("fullSync", {
timestamp: this.timestamp,
content: this.content,
hash: this.hash,
});
}
increase ({timestamp, fromHash, toHash, patch}: {
timestamp: number,
fromHash: string,
toHash: string,
patch: string,
}) {
if (this.hash !== fromHash) {
if (this.timestamp > timestamp)
// web content is out of date
this.fullSync();
else
console.warn("[FileProxy] disk file content is behind increase base:", this.timestamp, timestamp);
}
else {
this.content = diff.applyPatch(this.content, patch);
this.timestamp = timestamp;
console.assert(this.hash === toHash, "[FileProxy] verify failed:", this.hash, toHash, this.content);
// trigger file writing
this.writeFile();
}
}
};
|