braidfs 0.0.119 → 0.0.121
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 +212 -48
- package/index.sh +4 -1
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -4,10 +4,26 @@ var { diff_main } = require(`${__dirname}/diff.js`),
|
|
|
4
4
|
braid_text = require("braid-text"),
|
|
5
5
|
braid_fetch = require('braid-http').fetch
|
|
6
6
|
|
|
7
|
+
// Helper function to check if a file is binary based on its extension
|
|
8
|
+
function is_binary(filename) {
|
|
9
|
+
const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.mp4', '.mp3', '.zip', '.tar', '.rar', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.exe', '.dll', '.so', '.dylib', '.bin', '.iso', '.img', '.bmp', '.tiff', '.svg', '.webp', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.wav', '.flac', '.aac', '.ogg', '.wma', '.7z', '.gz', '.bz2', '.xz'];
|
|
10
|
+
return binaryExtensions.includes(require('path').extname(filename).toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
braid_fetch.set_fetch(fetch_http2)
|
|
8
14
|
|
|
9
|
-
var sync_base = `${require('os').homedir()}/http
|
|
10
|
-
|
|
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`,
|
|
11
27
|
braidfs_config_file = `${braidfs_config_dir}/config`,
|
|
12
28
|
sync_base_meta = `${braidfs_config_dir}/proxy_base_meta`
|
|
13
29
|
braid_text.db_folder = `${braidfs_config_dir}/braid-text-db`
|
|
@@ -62,8 +78,7 @@ to_run_in_background = ''
|
|
|
62
78
|
|
|
63
79
|
console.log(`braidfs version: ${require(`${__dirname}/package.json`).version}`)
|
|
64
80
|
|
|
65
|
-
// process command line args
|
|
66
|
-
var argv = process.argv.slice(2)
|
|
81
|
+
// process command line args (argv was already processed above for --sync-base)
|
|
67
82
|
if (argv.length === 1 && argv[0].match(/^(run|serve)$/)) {
|
|
68
83
|
return main()
|
|
69
84
|
} else if (argv.length && argv.length % 2 == 0 && argv.every((x, i) => i % 2 != 0 || x.match(/^(sync|unsync)$/))) {
|
|
@@ -166,13 +181,11 @@ async function main() {
|
|
|
166
181
|
await braid_text.put(sync.url, { version, parents, patches, merge_type: 'dt' })
|
|
167
182
|
|
|
168
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
|
|
169
|
-
|
|
184
|
+
await new Promise(done => {
|
|
170
185
|
sync.file_written_cbs.push(done)
|
|
171
186
|
sync.signal_file_needs_writing()
|
|
172
187
|
})
|
|
173
|
-
|
|
174
|
-
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
175
|
-
return res.end(stat.mtimeMs.toString())
|
|
188
|
+
return res.end('')
|
|
176
189
|
} else return res.end('null')
|
|
177
190
|
}
|
|
178
191
|
|
|
@@ -239,7 +252,7 @@ async function main() {
|
|
|
239
252
|
watch_files()
|
|
240
253
|
setTimeout(scan_files, 1200)
|
|
241
254
|
}).on('error', e => {
|
|
242
|
-
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`)
|
|
243
256
|
throw e
|
|
244
257
|
})
|
|
245
258
|
}
|
|
@@ -365,7 +378,7 @@ async function scan_files() {
|
|
|
365
378
|
if (!sync) return await trash_file(fullpath, path)
|
|
366
379
|
|
|
367
380
|
stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
368
|
-
if (
|
|
381
|
+
if ('' + stat.mtimeNs !== sync.file_mtimeNs_str) {
|
|
369
382
|
console.log(`scan thinks ${path} has changed`)
|
|
370
383
|
sync.signal_file_needs_reading()
|
|
371
384
|
return true
|
|
@@ -440,14 +453,175 @@ async function sync_url(url) {
|
|
|
440
453
|
}
|
|
441
454
|
sync_url.cache[path] = sync_url.chain = sync_url.chain.then(init)
|
|
442
455
|
}
|
|
443
|
-
|
|
456
|
+
|
|
457
|
+
async function init_binary_sync() {
|
|
444
458
|
if (freed) return
|
|
445
459
|
|
|
446
|
-
|
|
460
|
+
console.log(`init_binary_sync: ${url}`)
|
|
447
461
|
|
|
448
|
-
|
|
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
|
+
}))
|
|
468
|
+
}
|
|
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) {}
|
|
475
|
+
if (freed) return
|
|
476
|
+
|
|
477
|
+
if (!self.peer) self.peer = Math.random().toString(36).slice(2)
|
|
478
|
+
|
|
479
|
+
await save_meta()
|
|
480
|
+
})
|
|
481
|
+
if (freed) return
|
|
482
|
+
|
|
483
|
+
var waitTime = 1
|
|
484
|
+
var last_connect_timer = null
|
|
485
|
+
|
|
486
|
+
self.signal_file_needs_reading = () => {}
|
|
487
|
+
|
|
488
|
+
connect()
|
|
489
|
+
async function connect() {
|
|
490
|
+
if (freed) return
|
|
491
|
+
if (last_connect_timer) return
|
|
492
|
+
|
|
493
|
+
var closed = false
|
|
494
|
+
var prev_disconnect = self.disconnect
|
|
495
|
+
self.disconnect = async () => {
|
|
496
|
+
if (closed) return
|
|
497
|
+
closed = true
|
|
498
|
+
reconnect_rate_limiter.on_diss(url)
|
|
499
|
+
for (var a of aborts) a.abort()
|
|
500
|
+
aborts.clear()
|
|
501
|
+
}
|
|
502
|
+
self.reconnect = connect
|
|
503
|
+
|
|
504
|
+
await prev_disconnect?.()
|
|
505
|
+
if (freed || closed) return
|
|
506
|
+
|
|
507
|
+
await reconnect_rate_limiter.get_turn(url)
|
|
508
|
+
if (freed || closed) return
|
|
509
|
+
|
|
510
|
+
function retry(e) {
|
|
511
|
+
if (freed || closed) return
|
|
512
|
+
var p = self.disconnect()
|
|
513
|
+
|
|
514
|
+
console.log(`reconnecting in ${waitTime}s: ${url} after error: ${e}`)
|
|
515
|
+
last_connect_timer = setTimeout(async () => {
|
|
516
|
+
await p
|
|
517
|
+
last_connect_timer = null
|
|
518
|
+
connect()
|
|
519
|
+
}, waitTime * 1000)
|
|
520
|
+
waitTime = Math.min(waitTime + 1, 3)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
var a = new AbortController()
|
|
525
|
+
aborts.add(a)
|
|
526
|
+
var res = await braid_fetch(url, {
|
|
527
|
+
signal: a.signal,
|
|
528
|
+
headers: {
|
|
529
|
+
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
|
|
530
|
+
},
|
|
531
|
+
subscribe: true,
|
|
532
|
+
heartbeats: 120,
|
|
533
|
+
peer: self.peer,
|
|
534
|
+
...(self.version != null ? {parents: ['' + self.version]} : {})
|
|
535
|
+
})
|
|
536
|
+
if (freed || closed) return
|
|
537
|
+
|
|
538
|
+
if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
|
|
539
|
+
|
|
540
|
+
if (res.status !== 209)
|
|
541
|
+
return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
|
|
542
|
+
|
|
543
|
+
console.log(`connected to ${url}${res.headers.get('editable') !== 'true' ? ' (readonly)' : ''}`)
|
|
544
|
+
|
|
545
|
+
reconnect_rate_limiter.on_conn(url)
|
|
546
|
+
|
|
547
|
+
res.subscribe(async update => {
|
|
548
|
+
if (freed || closed) return
|
|
549
|
+
if (update.version.length === 0) return
|
|
550
|
+
if (update.version.length !== 1) throw 'unexpected'
|
|
551
|
+
var version = 1*update.version[0]
|
|
552
|
+
if (!update.body) return
|
|
553
|
+
|
|
554
|
+
if (self.version != null &&
|
|
555
|
+
version <= self.version) return
|
|
556
|
+
self.version = version
|
|
557
|
+
|
|
558
|
+
await within_fiber(url, async () => {
|
|
559
|
+
var fullpath = await get_fullpath()
|
|
560
|
+
if (freed || closed) return
|
|
561
|
+
|
|
562
|
+
await wait_on(require('fs').promises.writeFile(fullpath, update.body))
|
|
563
|
+
if (freed || closed) return
|
|
564
|
+
|
|
565
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
566
|
+
if (freed || closed) return
|
|
567
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
568
|
+
self.last_touch = Date.now()
|
|
569
|
+
|
|
570
|
+
await save_meta()
|
|
571
|
+
})
|
|
572
|
+
}, retry)
|
|
573
|
+
|
|
574
|
+
self.signal_file_needs_reading = () => {
|
|
575
|
+
within_fiber(url, async () => {
|
|
576
|
+
if (freed || closed) return
|
|
577
|
+
|
|
578
|
+
var fullpath = await get_fullpath()
|
|
579
|
+
if (freed || closed) return
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
var stat = await require('fs').promises.stat(fullpath, { bigint: true })
|
|
583
|
+
} catch (e) { return }
|
|
584
|
+
if (freed || closed) return
|
|
585
|
+
|
|
586
|
+
if ('' + stat.mtimeNs !== self.file_mtimeNs_str) {
|
|
587
|
+
self.version = Math.max((self.version || 0) + 1,
|
|
588
|
+
Math.round(Number(stat.mtimeNs) / 1000000))
|
|
589
|
+
|
|
590
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
591
|
+
self.last_touch = Date.now()
|
|
592
|
+
|
|
593
|
+
await save_meta()
|
|
594
|
+
|
|
595
|
+
var body = await require('fs').promises.readFile(fullpath)
|
|
596
|
+
if (freed || closed) return
|
|
597
|
+
|
|
598
|
+
var a = new AbortController()
|
|
599
|
+
aborts.add(a)
|
|
600
|
+
|
|
601
|
+
await braid_fetch(url, {
|
|
602
|
+
method: 'PUT',
|
|
603
|
+
signal: a.signal,
|
|
604
|
+
version: ['' + self.version],
|
|
605
|
+
body,
|
|
606
|
+
headers: {
|
|
607
|
+
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
|
|
608
|
+
},
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
self.signal_file_needs_reading()
|
|
614
|
+
} catch (e) { return retry(e) }
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return self
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function init() {
|
|
449
621
|
if (freed) return
|
|
450
622
|
|
|
623
|
+
// console.log(`sync_url: ${url}`)
|
|
624
|
+
|
|
451
625
|
// if we're accessing /blah/index, it will be normalized to /blah,
|
|
452
626
|
// but we still want to create a directory out of blah in this case
|
|
453
627
|
if (wasnt_normal && !(await is_dir(fullpath))) {
|
|
@@ -459,13 +633,25 @@ async function sync_url(url) {
|
|
|
459
633
|
await ensure_path(require("path").dirname(fullpath))
|
|
460
634
|
if (freed) return
|
|
461
635
|
|
|
636
|
+
// Check if this is a binary file
|
|
637
|
+
// for now, this will be stuff in the "blobs" directory..
|
|
638
|
+
if (is_external_link && path.split('/')[1] === 'blobs') {
|
|
639
|
+
// Opts into the code for FS watcher (file_needs_reading, file_needs_writing) & unsyncing (disconnect)
|
|
640
|
+
// It notably does NOT handle `.braidfs/set_version/` and `.braidfs/get_version/` correctly!
|
|
641
|
+
// Search ` sync_url.cache[` to see how it's all handled.
|
|
642
|
+
return await init_binary_sync()
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
var resource = await braid_text.get_resource(url)
|
|
646
|
+
if (freed) return
|
|
647
|
+
|
|
462
648
|
self.peer = Math.random().toString(36).slice(2)
|
|
463
649
|
self.local_edit_counter = 0
|
|
464
650
|
self.fork_point = null
|
|
465
651
|
var file_last_version = null,
|
|
466
652
|
file_last_digest = null
|
|
467
653
|
self.file_last_text = null
|
|
468
|
-
self.
|
|
654
|
+
self.file_mtimeNs_str = null
|
|
469
655
|
self.file_read_only = null
|
|
470
656
|
var file_needs_reading = true,
|
|
471
657
|
file_needs_writing = null,
|
|
@@ -647,14 +833,14 @@ async function sync_url(url) {
|
|
|
647
833
|
add_to_version_cache(text, file_last_version)
|
|
648
834
|
|
|
649
835
|
// console.log(`no changes found in: ${fullpath}`)
|
|
650
|
-
if (
|
|
651
|
-
if (Date.now() > (self.
|
|
836
|
+
if ('' + stat.mtimeNs === self.file_mtimeNs_str) {
|
|
837
|
+
if (Date.now() > (self.last_touch ?? 0) + 1000)
|
|
652
838
|
on_watcher_miss(`expected change to: ${fullpath}`)
|
|
653
839
|
// else console.log(`no changes expected`)
|
|
654
840
|
} // else console.log('found change in file stat')
|
|
655
841
|
}
|
|
656
|
-
self.
|
|
657
|
-
self.
|
|
842
|
+
self.file_mtimeNs_str = '' + stat.mtimeNs
|
|
843
|
+
self.last_touch = Date.now()
|
|
658
844
|
}
|
|
659
845
|
if (file_needs_writing === 'just_meta_file') {
|
|
660
846
|
file_needs_writing = false
|
|
@@ -685,7 +871,7 @@ async function sync_url(url) {
|
|
|
685
871
|
|
|
686
872
|
file_last_version = version
|
|
687
873
|
self.file_last_text = body
|
|
688
|
-
self.
|
|
874
|
+
self.last_touch = Date.now()
|
|
689
875
|
await wait_on(require('fs').promises.writeFile(fullpath, self.file_last_text))
|
|
690
876
|
if (freed) return
|
|
691
877
|
}
|
|
@@ -695,15 +881,15 @@ async function sync_url(url) {
|
|
|
695
881
|
|
|
696
882
|
if (await wait_on(is_read_only(fullpath)) !== self.file_read_only) {
|
|
697
883
|
if (freed) return
|
|
698
|
-
self.
|
|
884
|
+
self.last_touch = Date.now()
|
|
699
885
|
await wait_on(set_read_only(fullpath, self.file_read_only))
|
|
700
886
|
}
|
|
701
887
|
if (freed) return
|
|
702
888
|
|
|
703
|
-
self.
|
|
889
|
+
self.file_mtimeNs_str = '' + (await wait_on(require('fs').promises.stat(fullpath, { bigint: true }))).mtimeNs
|
|
704
890
|
if (freed) return
|
|
705
891
|
|
|
706
|
-
for (var cb of self.file_written_cbs) cb(
|
|
892
|
+
for (var cb of self.file_written_cbs) cb()
|
|
707
893
|
self.file_written_cbs = []
|
|
708
894
|
}
|
|
709
895
|
}
|
|
@@ -935,24 +1121,10 @@ async function sync_url(url) {
|
|
|
935
1121
|
if (!update.status) {
|
|
936
1122
|
// console.log(`got initial update about ${url}`)
|
|
937
1123
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
var start_i = 1 + resource.doc.getLocalVersion().reduce((a, b) => Math.max(a, b), -1)
|
|
943
|
-
resource.doc.mergeBytes(bytes)
|
|
944
|
-
|
|
945
|
-
// update resource.actor_seqs
|
|
946
|
-
var end_i = resource.doc.getLocalVersion().reduce((a, b) => Math.max(a, b), -1)
|
|
947
|
-
for (var i = start_i; i <= end_i; i++) {
|
|
948
|
-
var v = resource.doc.localToRemoteVersion([i])[0]
|
|
949
|
-
if (!resource.actor_seqs[v[0]]) resource.actor_seqs[v[0]] = new braid_text.RangeSet()
|
|
950
|
-
resource.actor_seqs[v[0]].add_range(v[1], v[1])
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
resource.val = resource.doc.get()
|
|
954
|
-
resource.need_defrag = true
|
|
955
|
-
await resource.db_delta(bytes)
|
|
1124
|
+
await braid_text.put(resource, {
|
|
1125
|
+
body: update.body,
|
|
1126
|
+
transfer_encoding: 'dt'
|
|
1127
|
+
})
|
|
956
1128
|
if (freed || closed) return
|
|
957
1129
|
|
|
958
1130
|
self.update_fork_point(JSON.parse(`[${res.headers.get('current-version')}]`), self.fork_point)
|
|
@@ -1340,14 +1512,6 @@ function v_eq(v1, v2) {
|
|
|
1340
1512
|
return v1.length === v2?.length && v1.every((x, i) => x == v2[i])
|
|
1341
1513
|
}
|
|
1342
1514
|
|
|
1343
|
-
function stat_eq(a, b) {
|
|
1344
|
-
return (!a && !b) || (a && b &&
|
|
1345
|
-
a.mode === b.mode &&
|
|
1346
|
-
a.size === b.size &&
|
|
1347
|
-
a.mtimeNs === b.mtimeNs &&
|
|
1348
|
-
a.ctimeNs === b.ctimeNs)
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
1515
|
async function is_read_only(fullpath) {
|
|
1352
1516
|
const stat = await require('fs').promises.stat(fullpath)
|
|
1353
1517
|
return require('os').platform() === "win32" ?
|
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,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braidfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.121",
|
|
4
4
|
"description": "braid technology synchronizing files and webpages",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braidfs",
|
|
7
7
|
"homepage": "https://braid.org",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"braid-http": "~1.3.79",
|
|
10
|
-
"braid-text": "~0.2.
|
|
10
|
+
"braid-text": "~0.2.64",
|
|
11
|
+
"braid-blob": "~0.0.8",
|
|
11
12
|
"chokidar": "^4.0.3"
|
|
12
13
|
},
|
|
13
14
|
"bin": {
|