braidfs 0.0.120 → 0.0.122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +233 -210
- package/index.sh +4 -1
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -12,8 +12,18 @@ function is_binary(filename) {
|
|
|
12
12
|
|
|
13
13
|
braid_fetch.set_fetch(fetch_http2)
|
|
14
14
|
|
|
15
|
-
var sync_base = `${require('os').homedir()}/http
|
|
16
|
-
|
|
15
|
+
var sync_base = `${require('os').homedir()}/http`
|
|
16
|
+
// Check for --sync-base argument (hidden for testing)
|
|
17
|
+
var argv = process.argv.slice(2)
|
|
18
|
+
var sync_base_index = argv.indexOf('--sync-base')
|
|
19
|
+
if (sync_base_index !== -1 && sync_base_index < argv.length - 1) {
|
|
20
|
+
sync_base = argv[sync_base_index + 1]
|
|
21
|
+
// Remove the --sync-base and its value from argv
|
|
22
|
+
argv.splice(sync_base_index, 2)
|
|
23
|
+
console.log(`[Testing mode] Using sync_base: ${sync_base}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var braidfs_config_dir = `${sync_base}/.braidfs`,
|
|
17
27
|
braidfs_config_file = `${braidfs_config_dir}/config`,
|
|
18
28
|
sync_base_meta = `${braidfs_config_dir}/proxy_base_meta`
|
|
19
29
|
braid_text.db_folder = `${braidfs_config_dir}/braid-text-db`
|
|
@@ -68,8 +78,7 @@ to_run_in_background = ''
|
|
|
68
78
|
|
|
69
79
|
console.log(`braidfs version: ${require(`${__dirname}/package.json`).version}`)
|
|
70
80
|
|
|
71
|
-
// process command line args
|
|
72
|
-
var argv = process.argv.slice(2)
|
|
81
|
+
// process command line args (argv was already processed above for --sync-base)
|
|
73
82
|
if (argv.length === 1 && argv[0].match(/^(run|serve)$/)) {
|
|
74
83
|
return main()
|
|
75
84
|
} else if (argv.length && argv.length % 2 == 0 && argv.every((x, i) => i % 2 != 0 || x.match(/^(sync|unsync)$/))) {
|
|
@@ -172,13 +181,11 @@ async function main() {
|
|
|
172
181
|
await braid_text.put(sync.url, { version, parents, patches, merge_type: 'dt' })
|
|
173
182
|
|
|
174
183
|
// may be able to do this more efficiently.. we want to make sure we're capturing a file write that is after our version was written.. there may be a way we can avoid calling file_needs_writing here
|
|
175
|
-
|
|
184
|
+
await new Promise(done => {
|
|
176
185
|
sync.file_written_cbs.push(done)
|
|
177
186
|
sync.signal_file_needs_writing()
|
|
178
187
|
})
|
|
179
|
-
|
|
180
|
-
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
181
|
-
return res.end(stat.mtimeMs.toString())
|
|
188
|
+
return res.end('')
|
|
182
189
|
} else return res.end('null')
|
|
183
190
|
}
|
|
184
191
|
|
|
@@ -245,7 +252,7 @@ async function main() {
|
|
|
245
252
|
watch_files()
|
|
246
253
|
setTimeout(scan_files, 1200)
|
|
247
254
|
}).on('error', e => {
|
|
248
|
-
if (e.code === 'EADDRINUSE') return console.log(`port ${config.port} is in use`)
|
|
255
|
+
if (e.code === 'EADDRINUSE') return console.log(`ERROR: port ${config.port} is in use`)
|
|
249
256
|
throw e
|
|
250
257
|
})
|
|
251
258
|
}
|
|
@@ -371,7 +378,7 @@ async function scan_files() {
|
|
|
371
378
|
if (!sync) return await trash_file(fullpath, path)
|
|
372
379
|
|
|
373
380
|
stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
374
|
-
if (
|
|
381
|
+
if ('' + stat.mtimeNs !== sync.file_mtimeNs_str) {
|
|
375
382
|
console.log(`scan thinks ${path} has changed`)
|
|
376
383
|
sync.signal_file_needs_reading()
|
|
377
384
|
return true
|
|
@@ -452,202 +459,220 @@ async function sync_url(url) {
|
|
|
452
459
|
|
|
453
460
|
console.log(`init_binary_sync: ${url}`)
|
|
454
461
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// self.file_written_cbs = [] // not needed
|
|
462
|
-
|
|
463
|
-
self.signal_file_needs_reading = () => {
|
|
464
|
-
if (freed) return
|
|
465
|
-
file_needs_reading = true
|
|
466
|
-
file_loop_pump()
|
|
462
|
+
async function save_meta() {
|
|
463
|
+
await require('fs').promises.writeFile(meta_path, JSON.stringify({
|
|
464
|
+
peer: self.peer,
|
|
465
|
+
version: self.version,
|
|
466
|
+
file_mtimeNs_str: self.file_mtimeNs_str
|
|
467
|
+
}))
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
|
|
470
|
+
await within_fiber(url, async () => {
|
|
471
|
+
try {
|
|
472
|
+
Object.assign(self, JSON.parse(
|
|
473
|
+
await require('fs').promises.readFile(meta_path, 'utf8')))
|
|
474
|
+
} catch (e) {}
|
|
470
475
|
if (freed) return
|
|
471
|
-
file_needs_writing = true
|
|
472
|
-
file_loop_pump()
|
|
473
|
-
}
|
|
474
476
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
+
if (!self.peer) self.peer = Math.random().toString(36).slice(2)
|
|
478
|
+
|
|
479
|
+
// create file if it doesn't exist
|
|
477
480
|
var fullpath = await get_fullpath()
|
|
478
481
|
if (freed) return
|
|
482
|
+
if (!(await file_exists(fullpath))) {
|
|
483
|
+
if (freed) return
|
|
484
|
+
await wait_on(require('fs').promises.writeFile(fullpath, ''))
|
|
485
|
+
if (freed) return
|
|
486
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
487
|
+
if (freed) return
|
|
488
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
489
|
+
self.last_touch = Date.now()
|
|
490
|
+
}
|
|
491
|
+
if (freed) return
|
|
479
492
|
|
|
480
|
-
|
|
481
|
-
|
|
493
|
+
await save_meta()
|
|
494
|
+
})
|
|
495
|
+
if (freed) return
|
|
496
|
+
|
|
497
|
+
var waitTime = 1
|
|
498
|
+
var last_connect_timer = null
|
|
499
|
+
|
|
500
|
+
self.signal_file_needs_reading = () => {}
|
|
501
|
+
|
|
502
|
+
connect()
|
|
503
|
+
async function connect() {
|
|
504
|
+
if (freed) return
|
|
505
|
+
if (last_connect_timer) return
|
|
506
|
+
|
|
507
|
+
var closed = false
|
|
508
|
+
var prev_disconnect = self.disconnect
|
|
509
|
+
self.disconnect = async () => {
|
|
510
|
+
if (closed) return
|
|
511
|
+
closed = true
|
|
512
|
+
reconnect_rate_limiter.on_diss(url)
|
|
513
|
+
for (var a of aborts) a.abort()
|
|
514
|
+
aborts.clear()
|
|
515
|
+
}
|
|
516
|
+
self.reconnect = connect
|
|
517
|
+
|
|
518
|
+
await prev_disconnect?.()
|
|
519
|
+
if (freed || closed) return
|
|
520
|
+
|
|
521
|
+
await reconnect_rate_limiter.get_turn(url)
|
|
522
|
+
if (freed || closed) return
|
|
523
|
+
|
|
524
|
+
function retry(e) {
|
|
525
|
+
if (freed || closed) return
|
|
526
|
+
var p = self.disconnect()
|
|
527
|
+
|
|
528
|
+
console.log(`reconnecting in ${waitTime}s: ${url} after error: ${e}`)
|
|
529
|
+
last_connect_timer = setTimeout(async () => {
|
|
530
|
+
await p
|
|
531
|
+
last_connect_timer = null
|
|
532
|
+
connect()
|
|
533
|
+
}, waitTime * 1000)
|
|
534
|
+
waitTime = Math.min(waitTime + 1, 3)
|
|
535
|
+
}
|
|
482
536
|
|
|
483
|
-
// If the file exists locally, subscribe first; this will also create
|
|
484
|
-
// an early-timestamp file on server if missing. Then compare mtimes
|
|
485
|
-
// to decide whether to upload or download.
|
|
486
537
|
try {
|
|
487
|
-
var
|
|
488
|
-
|
|
489
|
-
|
|
538
|
+
var a = new AbortController()
|
|
539
|
+
aborts.add(a)
|
|
540
|
+
var res = await braid_fetch(url, {
|
|
541
|
+
signal: a.signal,
|
|
490
542
|
headers: {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
|
|
544
|
+
},
|
|
545
|
+
subscribe: true,
|
|
546
|
+
heartbeats: 120,
|
|
547
|
+
peer: self.peer,
|
|
548
|
+
parents: self.version != null ? ['' + self.version] : []
|
|
494
549
|
})
|
|
550
|
+
if (freed || closed) return
|
|
495
551
|
|
|
496
|
-
if (
|
|
497
|
-
self.server_subscription = subscribeRes
|
|
498
|
-
|
|
499
|
-
// Read local mtime (or 0 if missing)
|
|
500
|
-
var local_mtime_ms = 0
|
|
501
|
-
if (local_exists) {
|
|
502
|
-
try {
|
|
503
|
-
const statResult = await wait_on(require('fs').promises.stat(fullpath));
|
|
504
|
-
local_mtime_ms = Math.round(Number(statResult.mtimeMs));
|
|
505
|
-
} catch (e) {}
|
|
506
|
-
}
|
|
552
|
+
if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
|
|
507
553
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
var server_mtime_ms = 0
|
|
511
|
-
if (server_mtime_header) {
|
|
512
|
-
server_mtime_ms = Math.round(Number(server_mtime_header))
|
|
513
|
-
}
|
|
554
|
+
if (res.status !== 209)
|
|
555
|
+
return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
|
|
514
556
|
|
|
515
|
-
|
|
516
|
-
if (local_exists && local_mtime_ms > server_mtime_ms) {
|
|
517
|
-
// console.log(local_mtime_ms)
|
|
518
|
-
// console.log(server_mtime_ms)
|
|
519
|
-
try {
|
|
520
|
-
var fileData = await wait_on(require('fs').promises.readFile(fullpath))
|
|
521
|
-
var putRes = await braid_fetch(url, {
|
|
522
|
-
method: 'PUT',
|
|
523
|
-
body: fileData,
|
|
524
|
-
headers: { 'Content-Type': 'application/octet-stream', 'peer': self.peer, 'X-Timestamp': local_mtime_ms }
|
|
525
|
-
})
|
|
526
|
-
if (putRes.ok) console.log(`Uploaded newer local file to server: ${url}`)
|
|
527
|
-
} catch (e) { console.log(`Failed to upload newer local file: ${e}`) }
|
|
528
|
-
}
|
|
557
|
+
self.file_read_only = res.headers.get('editable') === 'false'
|
|
529
558
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
// console.log(update)
|
|
534
|
-
// ignore first responce if we already have the up-to-date file, although not needed.
|
|
535
|
-
var update_timestamp = Math.round(Number(update.version[0]));
|
|
536
|
-
// if (isNaN(update_timestamp)) update_timestamp = Date.now() // should throw error if can't read server timestamp
|
|
537
|
-
if (local_exists && local_mtime_ms < update_timestamp) {
|
|
538
|
-
try {
|
|
539
|
-
// console.log(Math.round(local_mtime_ms))
|
|
540
|
-
// console.log(Math.round(update_timestamp))
|
|
541
|
-
var writePath = await get_fullpath()
|
|
542
|
-
await wait_on(ensure_path(require("path").dirname(writePath)))
|
|
543
|
-
await wait_on(require('fs').promises.writeFile(writePath, update.body))
|
|
544
|
-
|
|
545
|
-
// Set the file timestamp to the update timestamp
|
|
546
|
-
var mtime = update_timestamp / 1000
|
|
547
|
-
await wait_on(require('fs').promises.utimes(writePath, mtime, mtime))
|
|
548
|
-
// const statResult = await wait_on(require('fs').promises.stat(writePath));
|
|
549
|
-
// console.log(Math.round(Number(statResult.mtimeMs))) // checking for writing vs written issues
|
|
550
|
-
|
|
551
|
-
var st = await wait_on(require('fs').promises.stat(writePath, { bigint: true }))
|
|
552
|
-
self.file_last_stat = st
|
|
553
|
-
console.log(`Updated local binary file from server: ${writePath}`)
|
|
554
|
-
} catch (e) { console.log(`Failed to update local file from server: ${e}`) }
|
|
555
|
-
}
|
|
556
|
-
})
|
|
557
|
-
}
|
|
559
|
+
await wait_on(within_fiber(url, async () => {
|
|
560
|
+
var fullpath = await get_fullpath()
|
|
561
|
+
if (freed || closed) return
|
|
558
562
|
|
|
563
|
+
await set_read_only(fullpath, self.file_read_only)
|
|
564
|
+
}))
|
|
559
565
|
|
|
560
|
-
|
|
561
|
-
console.log(`Failed to subscribe to server for binary file: ${e}`)
|
|
562
|
-
}
|
|
566
|
+
console.log(`connected to ${url}${self.file_read_only ? ' (readonly)' : ''}`)
|
|
563
567
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
})
|
|
568
|
+
reconnect_rate_limiter.on_conn(url)
|
|
569
|
+
|
|
570
|
+
res.subscribe(async update => {
|
|
571
|
+
if (freed || closed) return
|
|
572
|
+
if (update.version.length === 0) return
|
|
573
|
+
if (update.version.length !== 1) throw 'unexpected'
|
|
574
|
+
var version = 1*update.version[0]
|
|
575
|
+
if (!update.body) return
|
|
573
576
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (freed) return
|
|
578
|
-
if (file_loop_pump_lock) return
|
|
579
|
-
file_loop_pump_lock++
|
|
577
|
+
if (self.version != null &&
|
|
578
|
+
version <= self.version) return
|
|
579
|
+
self.version = version
|
|
580
580
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
581
|
+
await within_fiber(url, async () => {
|
|
582
|
+
var fullpath = await get_fullpath()
|
|
583
|
+
if (freed || closed) return
|
|
584
584
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
585
|
+
await wait_on(set_read_only(fullpath, false))
|
|
586
|
+
if (freed || closed) return
|
|
587
|
+
await wait_on(require('fs').promises.writeFile(fullpath, update.body))
|
|
588
|
+
if (freed || closed) return
|
|
589
|
+
await wait_on(set_read_only(fullpath, self.file_read_only))
|
|
590
|
+
if (freed || closed) return
|
|
588
591
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
if (freed) return
|
|
592
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
593
|
+
if (freed || closed) return
|
|
594
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
595
|
+
self.last_touch = Date.now()
|
|
595
596
|
|
|
596
|
-
|
|
597
|
-
|
|
597
|
+
await save_meta()
|
|
598
|
+
})
|
|
599
|
+
}, retry)
|
|
598
600
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
+
async function send_file(fullpath) {
|
|
602
|
+
var body = await require('fs').promises.readFile(fullpath)
|
|
603
|
+
if (freed || closed) return
|
|
601
604
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
605
|
+
try {
|
|
606
|
+
var a = new AbortController()
|
|
607
|
+
aborts.add(a)
|
|
608
|
+
var r = await braid_fetch(url, {
|
|
609
|
+
method: 'PUT',
|
|
610
|
+
signal: a.signal,
|
|
611
|
+
version: ['' + self.version],
|
|
612
|
+
body,
|
|
613
|
+
headers: {
|
|
614
|
+
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
|
|
615
|
+
},
|
|
616
|
+
})
|
|
617
|
+
if (freed || closed) return
|
|
608
618
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
body: fileData,
|
|
618
|
-
headers: { 'Content-Type': 'application/octet-stream', 'peer': self.peer, 'X-Timestamp': lower_precision_mtimems }
|
|
619
|
-
})
|
|
620
|
-
if (response.ok) {
|
|
621
|
-
console.log(`Uploaded changed binary file to server: ${url}`)
|
|
622
|
-
}
|
|
623
|
-
} catch (e) {
|
|
624
|
-
console.log(`Failed to upload changed file: ${e}`)
|
|
625
|
-
}
|
|
619
|
+
// if we're not authorized,
|
|
620
|
+
if (r.status == 401 || r.status == 403) {
|
|
621
|
+
// then revert it
|
|
622
|
+
console.log(`access denied: reverting local edits`)
|
|
623
|
+
unsync_url(url)
|
|
624
|
+
sync_url(url)
|
|
625
|
+
} else if (!r.ok) {
|
|
626
|
+
retry(new Error(`unexpected PUT status: ${r.status}`))
|
|
626
627
|
}
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (file_needs_writing) {
|
|
631
|
-
file_needs_writing = false
|
|
632
|
-
// Binary files are handled by the file watcher and server subscription
|
|
633
|
-
// No additional processing needed here
|
|
634
|
-
}
|
|
628
|
+
} catch (e) { retry(e) }
|
|
635
629
|
}
|
|
636
|
-
})
|
|
637
630
|
|
|
638
|
-
|
|
639
|
-
|
|
631
|
+
// if what we have now is newer than what the server has,
|
|
632
|
+
// go ahead and send it
|
|
633
|
+
await within_fiber(url, async () => {
|
|
634
|
+
if (freed || closed) return
|
|
635
|
+
var fullpath = await get_fullpath()
|
|
636
|
+
if (freed || closed) return
|
|
637
|
+
try {
|
|
638
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
639
|
+
} catch (e) { return }
|
|
640
|
+
if (freed || closed) return
|
|
640
641
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
642
|
+
var server_v = JSON.parse(`[${res.headers.get('current-version')}]`)
|
|
643
|
+
if (self.version != null &&
|
|
644
|
+
'' + stat.mtimeNs === self.file_mtimeNs_str && (
|
|
645
|
+
!server_v.length ||
|
|
646
|
+
1*server_v[0] < self.version
|
|
647
|
+
)) await send_file(fullpath)
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
self.signal_file_needs_reading = () => {
|
|
651
|
+
within_fiber(url, async () => {
|
|
652
|
+
if (freed || closed) return
|
|
653
|
+
|
|
654
|
+
var fullpath = await get_fullpath()
|
|
655
|
+
if (freed || closed) return
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
659
|
+
} catch (e) { return }
|
|
660
|
+
if (freed || closed) return
|
|
661
|
+
|
|
662
|
+
if ('' + stat.mtimeNs !== self.file_mtimeNs_str) {
|
|
663
|
+
self.version = Math.max((self.version || 0) + 1,
|
|
664
|
+
Math.round(Number(stat.mtimeNs) / 1000000))
|
|
665
|
+
|
|
666
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
667
|
+
self.last_touch = Date.now()
|
|
668
|
+
|
|
669
|
+
await save_meta()
|
|
670
|
+
await send_file(fullpath)
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
self.signal_file_needs_reading()
|
|
675
|
+
} catch (e) { return retry(e) }
|
|
651
676
|
}
|
|
652
677
|
|
|
653
678
|
return self
|
|
@@ -658,19 +683,6 @@ async function sync_url(url) {
|
|
|
658
683
|
|
|
659
684
|
// console.log(`sync_url: ${url}`)
|
|
660
685
|
|
|
661
|
-
// Check if this is a binary file
|
|
662
|
-
var is_binary_file = is_binary(path)
|
|
663
|
-
|
|
664
|
-
if (is_binary_file) {
|
|
665
|
-
// Opts into the code for FS watcher (file_needs_reading, file_needs_writing) & unsyncing (disconnect)
|
|
666
|
-
// It notably does NOT handle `.braidfs/set_version/` and `.braidfs/get_version/` correctly!
|
|
667
|
-
// Search ` sync_url.cache[` to see how it's all handled.
|
|
668
|
-
return await init_binary_sync()
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
var resource = await braid_text.get_resource(url)
|
|
672
|
-
if (freed) return
|
|
673
|
-
|
|
674
686
|
// if we're accessing /blah/index, it will be normalized to /blah,
|
|
675
687
|
// but we still want to create a directory out of blah in this case
|
|
676
688
|
if (wasnt_normal && !(await is_dir(fullpath))) {
|
|
@@ -682,13 +694,25 @@ async function sync_url(url) {
|
|
|
682
694
|
await ensure_path(require("path").dirname(fullpath))
|
|
683
695
|
if (freed) return
|
|
684
696
|
|
|
697
|
+
// Check if this is a binary file
|
|
698
|
+
// for now, this will be stuff in the "blobs" directory..
|
|
699
|
+
if (is_external_link && path.split('/')[1] === 'blobs') {
|
|
700
|
+
// Opts into the code for FS watcher (file_needs_reading, file_needs_writing) & unsyncing (disconnect)
|
|
701
|
+
// It notably does NOT handle `.braidfs/set_version/` and `.braidfs/get_version/` correctly!
|
|
702
|
+
// Search ` sync_url.cache[` to see how it's all handled.
|
|
703
|
+
return await init_binary_sync()
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
var resource = await braid_text.get_resource(url)
|
|
707
|
+
if (freed) return
|
|
708
|
+
|
|
685
709
|
self.peer = Math.random().toString(36).slice(2)
|
|
686
710
|
self.local_edit_counter = 0
|
|
687
711
|
self.fork_point = null
|
|
688
712
|
var file_last_version = null,
|
|
689
713
|
file_last_digest = null
|
|
690
714
|
self.file_last_text = null
|
|
691
|
-
self.
|
|
715
|
+
self.file_mtimeNs_str = null
|
|
692
716
|
self.file_read_only = null
|
|
693
717
|
var file_needs_reading = true,
|
|
694
718
|
file_needs_writing = null,
|
|
@@ -870,14 +894,14 @@ async function sync_url(url) {
|
|
|
870
894
|
add_to_version_cache(text, file_last_version)
|
|
871
895
|
|
|
872
896
|
// console.log(`no changes found in: ${fullpath}`)
|
|
873
|
-
if (
|
|
874
|
-
if (Date.now() > (self.
|
|
897
|
+
if ('' + stat.mtimeNs === self.file_mtimeNs_str) {
|
|
898
|
+
if (Date.now() > (self.last_touch ?? 0) + 1000)
|
|
875
899
|
on_watcher_miss(`expected change to: ${fullpath}`)
|
|
876
900
|
// else console.log(`no changes expected`)
|
|
877
901
|
} // else console.log('found change in file stat')
|
|
878
902
|
}
|
|
879
|
-
self.
|
|
880
|
-
self.
|
|
903
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
904
|
+
self.last_touch = Date.now()
|
|
881
905
|
}
|
|
882
906
|
if (file_needs_writing === 'just_meta_file') {
|
|
883
907
|
file_needs_writing = false
|
|
@@ -908,7 +932,7 @@ async function sync_url(url) {
|
|
|
908
932
|
|
|
909
933
|
file_last_version = version
|
|
910
934
|
self.file_last_text = body
|
|
911
|
-
self.
|
|
935
|
+
self.last_touch = Date.now()
|
|
912
936
|
await wait_on(require('fs').promises.writeFile(fullpath, self.file_last_text))
|
|
913
937
|
if (freed) return
|
|
914
938
|
}
|
|
@@ -918,15 +942,15 @@ async function sync_url(url) {
|
|
|
918
942
|
|
|
919
943
|
if (await wait_on(is_read_only(fullpath)) !== self.file_read_only) {
|
|
920
944
|
if (freed) return
|
|
921
|
-
self.
|
|
945
|
+
self.last_touch = Date.now()
|
|
922
946
|
await wait_on(set_read_only(fullpath, self.file_read_only))
|
|
923
947
|
}
|
|
924
948
|
if (freed) return
|
|
925
949
|
|
|
926
|
-
self.
|
|
950
|
+
self.file_mtimeNs_str = '' + (await wait_on(require('fs').promises.stat(fullpath, { bigint: true }))).mtimeNs
|
|
927
951
|
if (freed) return
|
|
928
952
|
|
|
929
|
-
for (var cb of self.file_written_cbs) cb(
|
|
953
|
+
for (var cb of self.file_written_cbs) cb()
|
|
930
954
|
self.file_written_cbs = []
|
|
931
955
|
}
|
|
932
956
|
}
|
|
@@ -1144,12 +1168,12 @@ async function sync_url(url) {
|
|
|
1144
1168
|
if (res.status !== 209)
|
|
1145
1169
|
return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
|
|
1146
1170
|
|
|
1147
|
-
console.log(`connected to ${url}${res.headers.get('editable') !== 'true' ? ' (readonly)' : ''}`)
|
|
1148
|
-
|
|
1149
1171
|
reconnect_rate_limiter.on_conn(url)
|
|
1150
1172
|
|
|
1151
1173
|
self.file_read_only = res.headers.get('editable') === 'false'
|
|
1152
1174
|
self.signal_file_needs_writing()
|
|
1175
|
+
|
|
1176
|
+
console.log(`connected to ${url}${self.file_read_only ? ' (readonly)' : ''}`)
|
|
1153
1177
|
|
|
1154
1178
|
initial_connect_done()
|
|
1155
1179
|
res.subscribe(async update => {
|
|
@@ -1549,14 +1573,6 @@ function v_eq(v1, v2) {
|
|
|
1549
1573
|
return v1.length === v2?.length && v1.every((x, i) => x == v2[i])
|
|
1550
1574
|
}
|
|
1551
1575
|
|
|
1552
|
-
function stat_eq(a, b) {
|
|
1553
|
-
return (!a && !b) || (a && b &&
|
|
1554
|
-
a.mode === b.mode &&
|
|
1555
|
-
a.size === b.size &&
|
|
1556
|
-
a.mtimeNs === b.mtimeNs &&
|
|
1557
|
-
a.ctimeNs === b.ctimeNs)
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
1576
|
async function is_read_only(fullpath) {
|
|
1561
1577
|
const stat = await require('fs').promises.stat(fullpath)
|
|
1562
1578
|
return require('os').platform() === "win32" ?
|
|
@@ -1573,8 +1589,15 @@ async function set_read_only(fullpath, read_only) {
|
|
|
1573
1589
|
})
|
|
1574
1590
|
} else {
|
|
1575
1591
|
let mode = (await require('fs').promises.stat(fullpath)).mode
|
|
1576
|
-
|
|
1577
|
-
|
|
1592
|
+
|
|
1593
|
+
// Check if chmod is actually needed
|
|
1594
|
+
if (read_only && (mode & 0o222) === 0) return
|
|
1595
|
+
if (!read_only && (mode & 0o200) !== 0) return
|
|
1596
|
+
|
|
1597
|
+
// Perform chmod only if necessary
|
|
1598
|
+
if (read_only) mode &= ~0o222 // Remove all write permissions
|
|
1599
|
+
else mode |= 0o200 // Add owner write permission
|
|
1600
|
+
|
|
1578
1601
|
await require('fs').promises.chmod(fullpath, mode)
|
|
1579
1602
|
}
|
|
1580
1603
|
}
|
package/index.sh
CHANGED
|
@@ -35,8 +35,11 @@ urlencode() {
|
|
|
35
35
|
echo "$encoded"
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
# Get the base directory (can be overridden by setting BRAIDFS_BASE_DIR)
|
|
39
|
+
BASE_DIR="${BRAIDFS_BASE_DIR:-${HOME}/http}"
|
|
40
|
+
|
|
38
41
|
# Get the configuration file to read the port
|
|
39
|
-
CONFIG_FILE="${
|
|
42
|
+
CONFIG_FILE="${BASE_DIR}/.braidfs/config"
|
|
40
43
|
if [ -f "$CONFIG_FILE" ]; then
|
|
41
44
|
PORT=$(grep -o '"port":[^,}]*' "$CONFIG_FILE" | sed 's/"port"://; s/ //g')
|
|
42
45
|
if [ -z "$PORT" ]; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braidfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.122",
|
|
4
4
|
"description": "braid technology synchronizing files and webpages",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braidfs",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"braid-http": "~1.3.79",
|
|
10
10
|
"braid-text": "~0.2.64",
|
|
11
|
+
"braid-blob": "~0.0.8",
|
|
11
12
|
"chokidar": "^4.0.3"
|
|
12
13
|
},
|
|
13
14
|
"bin": {
|