File size: 5,484 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
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const events_1 = require("events");
const sha1_1 = __importDefault(require("sha1"));
const diff = __importStar(require("diff"));
const asyncCall_1 = __importDefault(require("./asyncCall"));
class FileProxy extends events_1.EventEmitter {
    constructor(filePath) {
        super();
        this.alive = true;
        this.filePath = filePath;
        //console.log("File proxy created:", filePath);
        if (!fs_1.default.existsSync(filePath))
            throw new Error(`file not exist: ${filePath}`);
        asyncCall_1.default(fs_1.default.stat, filePath)
            .then(stats => {
            this.diskTimestamp = stats.mtime.getTime();
            this.timestamp = this.diskTimestamp;
            return asyncCall_1.default(fs_1.default.readFile, filePath);
        })
            .then(buffer => {
            this.content = buffer.toString();
            this.fullSync();
        });
        this.fileListener = (current) => __awaiter(this, void 0, void 0, function* () {
            this.diskTimestamp = current.mtime.getTime();
            this.timestamp = this.diskTimestamp;
            const buffer = yield asyncCall_1.default(fs_1.default.readFile, filePath);
            if (!buffer) {
                this.emit("error", { description: "file reading failed" });
                return;
            }
            const newContent = buffer.toString();
            const newHash = sha1_1.default(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_1.default.watchFile(filePath, this.fileListener);
        this.keepWriteFile();
    }
    dispose() {
        this.alive = false;
        if (this.fileListener)
            fs_1.default.unwatchFile(this.filePath, this.fileListener);
    }
    makeWritePromise() {
        return new Promise(resolve => this.writeFile = resolve);
    }
    keepWriteFile() {
        return __awaiter(this, void 0, void 0, function* () {
            let writeSignal = this.makeWritePromise();
            while (this.alive) {
                yield writeSignal;
                writeSignal = this.makeWritePromise();
                //console.debug("keepWriteFile:", this.timestamp, this.diskTimestamp);
                if (this.timestamp > this.diskTimestamp) {
                    yield asyncCall_1.default(fs_1.default.writeFile, this.filePath, this.content);
                }
            }
        });
    }
    get hash() {
        return sha1_1.default(this.content);
    }
    fullSync() {
        this.emit("fullSync", {
            timestamp: this.timestamp,
            content: this.content,
            hash: this.hash,
        });
    }
    increase({ timestamp, fromHash, toHash, patch }) {
        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();
        }
    }
}
exports.default = FileProxy;
;