braidfs 0.0.81 → 0.0.84
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 +186 -149
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -48,14 +48,17 @@ require('fs').mkdirSync(proxy_base_meta, { recursive: true })
|
|
|
48
48
|
require('fs').mkdirSync(trash, { recursive: true })
|
|
49
49
|
require('fs').mkdirSync(temp_folder, { recursive: true })
|
|
50
50
|
|
|
51
|
-
//
|
|
52
|
-
|
|
51
|
+
// Add instructions for how to run in the background on this OS
|
|
52
|
+
var to_run_in_background = process.platform === 'darwin' ? `
|
|
53
53
|
To run daemon in background:
|
|
54
54
|
launchctl submit -l org.braid.braidfs -- braidfs run` : ''
|
|
55
|
-
|
|
55
|
+
// ...except this doesn't work yet. So disable.
|
|
56
|
+
to_run_in_background = ''
|
|
56
57
|
|
|
57
58
|
console.log(`braidfs version: ${require(`${__dirname}/package.json`).version}`)
|
|
58
59
|
|
|
60
|
+
// process command line args
|
|
61
|
+
var argv = process.argv.slice(2)
|
|
59
62
|
if (argv.length === 1 && argv[0].match(/^(run|serve)$/)) {
|
|
60
63
|
return main()
|
|
61
64
|
} else if (argv.length && argv.length % 2 == 0 && argv.every((x, i) => i % 2 != 0 || x.match(/^(sync|unsync)$/))) {
|
|
@@ -255,7 +258,6 @@ function skip_file(path) {
|
|
|
255
258
|
async function trash_file(fullpath, path) {
|
|
256
259
|
// throw this unrecognized file into the trash,
|
|
257
260
|
let dest = `${trash}/${braid_text.encode_filename(path)}_${Math.random().toString(36).slice(2)}`
|
|
258
|
-
console.log(`moving untracked file ${fullpath} to ${dest}`)
|
|
259
261
|
await require('fs').promises.rename(fullpath, dest)
|
|
260
262
|
|
|
261
263
|
// and log an error
|
|
@@ -263,6 +265,8 @@ async function trash_file(fullpath, path) {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
async function log_error(text) {
|
|
268
|
+
console.log(`LOGGING ERROR: ${text}`)
|
|
269
|
+
|
|
266
270
|
var x = await braid_text.get('.braidfs/errors', {}),
|
|
267
271
|
len = [...x.body].length
|
|
268
272
|
await braid_text.put('.braidfs/errors', {
|
|
@@ -426,6 +430,7 @@ async function proxy_url(url) {
|
|
|
426
430
|
|
|
427
431
|
self.peer = Math.random().toString(36).slice(2)
|
|
428
432
|
self.local_edit_counter = 0
|
|
433
|
+
self.fork_point = null
|
|
429
434
|
var file_last_version = null,
|
|
430
435
|
file_last_digest = null
|
|
431
436
|
self.file_last_text = null
|
|
@@ -468,37 +473,60 @@ async function proxy_url(url) {
|
|
|
468
473
|
file_loop_pump()
|
|
469
474
|
}
|
|
470
475
|
|
|
471
|
-
self.signal_file_needs_writing = () => {
|
|
476
|
+
self.signal_file_needs_writing = (just_meta_file) => {
|
|
472
477
|
if (freed) return
|
|
473
|
-
|
|
478
|
+
|
|
479
|
+
if (!just_meta_file) file_needs_writing = true
|
|
480
|
+
else if (just_meta_file && !file_needs_writing)
|
|
481
|
+
file_needs_writing = 'just_meta_file'
|
|
482
|
+
|
|
474
483
|
file_loop_pump()
|
|
475
484
|
}
|
|
476
485
|
|
|
477
|
-
async function
|
|
486
|
+
async function my_fetch(params) {
|
|
478
487
|
if (!start_something()) return
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
488
|
+
try {
|
|
489
|
+
var a = new AbortController()
|
|
490
|
+
aborts.add(a)
|
|
491
|
+
return await braid_fetch(url, {
|
|
492
|
+
signal: a.signal,
|
|
493
|
+
headers: {
|
|
494
|
+
"Merge-Type": "dt",
|
|
495
|
+
"Content-Type": 'text/plain',
|
|
496
|
+
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
|
|
497
|
+
},
|
|
498
|
+
retry: true,
|
|
499
|
+
...params
|
|
500
|
+
})
|
|
501
|
+
} catch (e) {
|
|
502
|
+
if (e?.name !== "AbortError") console.log(e)
|
|
503
|
+
} finally {
|
|
504
|
+
aborts.delete(a)
|
|
505
|
+
finish_something()
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function send_out(stuff) {
|
|
510
|
+
if (!is_external_link) return
|
|
511
|
+
|
|
512
|
+
console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
|
|
513
|
+
|
|
514
|
+
var r = await my_fetch({ method: "PUT", ...stuff })
|
|
515
|
+
|
|
516
|
+
// the server has acknowledged this version,
|
|
517
|
+
// so add it to the fork point
|
|
518
|
+
if (r.ok) await self.update_fork_point(stuff.version[0], stuff.parents)
|
|
519
|
+
|
|
520
|
+
// if we're not authorized,
|
|
521
|
+
if (r.status == 401 || r.status == 403) {
|
|
522
|
+
// and it's one of our versions (a local edit),
|
|
523
|
+
if (self.peer === braid_text.decode_version(stuff.version[0])[0]) {
|
|
524
|
+
// then revert it
|
|
525
|
+
console.log(`access denied: reverting local edits`)
|
|
526
|
+
unproxy_url(url)
|
|
527
|
+
proxy_url(url)
|
|
499
528
|
}
|
|
500
529
|
}
|
|
501
|
-
finish_something()
|
|
502
530
|
}
|
|
503
531
|
|
|
504
532
|
if (!start_something()) return
|
|
@@ -513,7 +541,8 @@ async function proxy_url(url) {
|
|
|
513
541
|
version: file_last_version,
|
|
514
542
|
digest: file_last_digest,
|
|
515
543
|
peer: self.peer,
|
|
516
|
-
local_edit_counter: self.local_edit_counter
|
|
544
|
+
local_edit_counter: self.local_edit_counter,
|
|
545
|
+
fork_point: self.fork_point
|
|
517
546
|
} = Array.isArray(meta) ? { version: meta } : meta)
|
|
518
547
|
|
|
519
548
|
if (!self.peer) self.peer = Math.random().toString(36).slice(2)
|
|
@@ -559,6 +588,16 @@ async function proxy_url(url) {
|
|
|
559
588
|
var fullpath = await get_fullpath()
|
|
560
589
|
|
|
561
590
|
while (file_needs_reading || file_needs_writing) {
|
|
591
|
+
async function write_meta_file() {
|
|
592
|
+
await require('fs').promises.writeFile(meta_path, JSON.stringify({
|
|
593
|
+
version: file_last_version,
|
|
594
|
+
digest: sha256(self.file_last_text),
|
|
595
|
+
peer: self.peer,
|
|
596
|
+
local_edit_counter: self.local_edit_counter,
|
|
597
|
+
fork_point: self.fork_point
|
|
598
|
+
}))
|
|
599
|
+
}
|
|
600
|
+
|
|
562
601
|
if (file_needs_reading) {
|
|
563
602
|
console.log(`reading file: ${fullpath}`)
|
|
564
603
|
|
|
@@ -601,12 +640,7 @@ async function proxy_url(url) {
|
|
|
601
640
|
|
|
602
641
|
await braid_text.put(url, { version, parents, patches, peer: self.peer, merge_type: 'dt' })
|
|
603
642
|
|
|
604
|
-
await
|
|
605
|
-
version: file_last_version,
|
|
606
|
-
digest: sha256(self.file_last_text),
|
|
607
|
-
peer: self.peer,
|
|
608
|
-
local_edit_counter: self.local_edit_counter
|
|
609
|
-
}))
|
|
643
|
+
await write_meta_file()
|
|
610
644
|
} else {
|
|
611
645
|
add_to_version_cache(text, file_last_version)
|
|
612
646
|
|
|
@@ -620,7 +654,10 @@ async function proxy_url(url) {
|
|
|
620
654
|
self.file_last_stat = stat
|
|
621
655
|
self.file_ignore_until = Date.now() + 1000
|
|
622
656
|
}
|
|
623
|
-
if (file_needs_writing) {
|
|
657
|
+
if (file_needs_writing === 'just_meta_file') {
|
|
658
|
+
file_needs_writing = false
|
|
659
|
+
await write_meta_file()
|
|
660
|
+
} else if (file_needs_writing) {
|
|
624
661
|
file_needs_writing = false
|
|
625
662
|
let { version, body } = await braid_text.get(url, {})
|
|
626
663
|
if (!v_eq(version, file_last_version)) {
|
|
@@ -644,16 +681,10 @@ async function proxy_url(url) {
|
|
|
644
681
|
self.file_last_text = body
|
|
645
682
|
self.file_ignore_until = Date.now() + 1000
|
|
646
683
|
await require('fs').promises.writeFile(fullpath, self.file_last_text)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
await require('fs').promises.writeFile(meta_path, JSON.stringify({
|
|
650
|
-
version: file_last_version,
|
|
651
|
-
digest: sha256(self.file_last_text),
|
|
652
|
-
peer: self.peer,
|
|
653
|
-
local_edit_counter: self.local_edit_counter
|
|
654
|
-
}))
|
|
655
684
|
}
|
|
656
685
|
|
|
686
|
+
await write_meta_file()
|
|
687
|
+
|
|
657
688
|
if (await is_read_only(fullpath) !== self.file_read_only) {
|
|
658
689
|
self.file_ignore_until = Date.now() + 1000
|
|
659
690
|
await set_read_only(fullpath, self.file_read_only)
|
|
@@ -672,8 +703,90 @@ async function proxy_url(url) {
|
|
|
672
703
|
file_loop_pump_lock--
|
|
673
704
|
}
|
|
674
705
|
|
|
675
|
-
|
|
676
|
-
|
|
706
|
+
self.update_fork_point = async (event, parents) => {
|
|
707
|
+
var resource = await braid_text.get_resource(url)
|
|
708
|
+
|
|
709
|
+
// special case:
|
|
710
|
+
// if current fork point has all parents,
|
|
711
|
+
// then we can just remove those
|
|
712
|
+
// and add event
|
|
713
|
+
var fork_set = new Set(self.fork_point)
|
|
714
|
+
if (parents.every(p => fork_set.has(p))) {
|
|
715
|
+
parents.forEach(p => fork_set.delete(p))
|
|
716
|
+
fork_set.add(event)
|
|
717
|
+
self.fork_point = [...fork_set.values()]
|
|
718
|
+
} else {
|
|
719
|
+
// full-proof approach..
|
|
720
|
+
var looking_for = fork_set
|
|
721
|
+
looking_for.add(event)
|
|
722
|
+
|
|
723
|
+
self.fork_point = []
|
|
724
|
+
var shadow = new Set()
|
|
725
|
+
|
|
726
|
+
var bytes = resource.doc.toBytes()
|
|
727
|
+
var [_, events, parentss] = braid_text.dt_parse([...bytes])
|
|
728
|
+
for (var i = events.length - 1; i >= 0 && looking_for.size; i--) {
|
|
729
|
+
var e = events[i].join('-')
|
|
730
|
+
if (looking_for.has(e)) {
|
|
731
|
+
looking_for.delete(e)
|
|
732
|
+
if (!shadow.has(e)) self.fork_point.push(e)
|
|
733
|
+
shadow.add(e)
|
|
734
|
+
}
|
|
735
|
+
if (shadow.has(e))
|
|
736
|
+
parentss[i].forEach(p => shadow.add(p.join('-')))
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
self.fork_point.sort()
|
|
740
|
+
self.signal_file_needs_writing(true)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async function find_fork_point() {
|
|
744
|
+
console.log(`[find_fork_point] url: ${url}`)
|
|
745
|
+
|
|
746
|
+
// see if they have the fork point
|
|
747
|
+
if (self.fork_point) {
|
|
748
|
+
var r = await my_fetch({ method: "HEAD", version: self.fork_point })
|
|
749
|
+
if (r.ok) {
|
|
750
|
+
console.log(`[find_fork_point] they have our latest fork point, horray!`)
|
|
751
|
+
return self.fork_point
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// otherwise let's binary search for new fork point..
|
|
756
|
+
var resource = await braid_text.get_resource(url)
|
|
757
|
+
var bytes = resource.doc.toBytes()
|
|
758
|
+
var [_, events, __] = braid_text.dt_parse([...bytes])
|
|
759
|
+
events = events.map(x => x.join('-'))
|
|
760
|
+
|
|
761
|
+
var min = -1
|
|
762
|
+
var max = events.length
|
|
763
|
+
self.fork_point = []
|
|
764
|
+
while (min + 1 < max) {
|
|
765
|
+
var i = Math.floor((min + max)/2)
|
|
766
|
+
var version = [events[i]]
|
|
767
|
+
|
|
768
|
+
console.log(`min=${min}, max=${max}, i=${i}, version=${version}`)
|
|
769
|
+
|
|
770
|
+
var st = Date.now()
|
|
771
|
+
var r = await my_fetch({ method: "HEAD", version })
|
|
772
|
+
console.log(`fetched in ${Date.now() - st}`)
|
|
773
|
+
|
|
774
|
+
if (r.ok) {
|
|
775
|
+
min = i
|
|
776
|
+
self.fork_point = version
|
|
777
|
+
} else max = i
|
|
778
|
+
}
|
|
779
|
+
console.log(`[find_fork_point] settled on: ${JSON.stringify(self.fork_point)}`)
|
|
780
|
+
self.signal_file_needs_writing(true)
|
|
781
|
+
return self.fork_point
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (is_external_link) find_fork_point().then(fork_point => {
|
|
785
|
+
connect(fork_point)
|
|
786
|
+
send_new_stuff(fork_point)
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
function connect(fork_point) {
|
|
677
790
|
let a = new AbortController()
|
|
678
791
|
aborts.add(a)
|
|
679
792
|
self.reconnect = () => {
|
|
@@ -681,7 +794,7 @@ async function proxy_url(url) {
|
|
|
681
794
|
|
|
682
795
|
aborts.delete(a)
|
|
683
796
|
a.abort()
|
|
684
|
-
connect()
|
|
797
|
+
connect(fork_point)
|
|
685
798
|
}
|
|
686
799
|
|
|
687
800
|
console.log(`connecting to ${url}`)
|
|
@@ -695,12 +808,8 @@ async function proxy_url(url) {
|
|
|
695
808
|
subscribe: true,
|
|
696
809
|
retry: {
|
|
697
810
|
onRes: (res) => {
|
|
698
|
-
if (res.status !== 209)
|
|
699
|
-
log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
|
|
700
|
-
return console.log(
|
|
701
|
-
`FAILED TO CONNECT TO: ${url}\n` +
|
|
702
|
-
`GOT STATUS CODE: ${res.status}, expected 209.`)
|
|
703
|
-
}
|
|
811
|
+
if (res.status !== 209)
|
|
812
|
+
return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
|
|
704
813
|
|
|
705
814
|
console.log(`connected to ${url}`)
|
|
706
815
|
console.log(` editable = ${res.headers.get('editable')}`)
|
|
@@ -711,8 +820,9 @@ async function proxy_url(url) {
|
|
|
711
820
|
},
|
|
712
821
|
heartbeats: 120,
|
|
713
822
|
parents: async () => {
|
|
714
|
-
|
|
715
|
-
|
|
823
|
+
var x = fork_point || await find_fork_point()
|
|
824
|
+
fork_point = null
|
|
825
|
+
return x
|
|
716
826
|
},
|
|
717
827
|
peer: self.peer
|
|
718
828
|
}).then(x => {
|
|
@@ -730,6 +840,10 @@ async function proxy_url(url) {
|
|
|
730
840
|
|
|
731
841
|
await braid_text.put(url, { ...update, peer: self.peer, merge_type: 'dt' })
|
|
732
842
|
|
|
843
|
+
// the server is giving us this version,
|
|
844
|
+
// so they must have it,
|
|
845
|
+
// so let's add it to our fork point
|
|
846
|
+
await self.update_fork_point(update.version[0], update.parents)
|
|
733
847
|
|
|
734
848
|
self.signal_file_needs_writing()
|
|
735
849
|
finish_something()
|
|
@@ -738,107 +852,30 @@ async function proxy_url(url) {
|
|
|
738
852
|
}
|
|
739
853
|
|
|
740
854
|
// send them stuff we have but they don't
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
})
|
|
756
|
-
aborts.delete(a)
|
|
757
|
-
|
|
758
|
-
if (r.headers.get('editable') === 'false') {
|
|
759
|
-
console.log('do not send updates for read-only file: ' + url)
|
|
760
|
-
return
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (r.headers.get('version') == null) {
|
|
764
|
-
log_error(`Can't sync ${url} -- got no version from server`)
|
|
765
|
-
return console.log(`GOT NO VERSION FROM: ${url}`)
|
|
766
|
-
}
|
|
767
|
-
var parents = JSON.parse(`[${r.headers.get('version')}]`)
|
|
768
|
-
|
|
769
|
-
var bytes = (await braid_text.get_resource(url)).doc.toBytes()
|
|
770
|
-
var [_, versions, __] = braid_text.dt_parse([...bytes])
|
|
771
|
-
var agents = {}
|
|
772
|
-
for (var v of versions) agents[v[0]] = v[1]
|
|
773
|
-
|
|
774
|
-
function we_have_it(version) {
|
|
775
|
-
var m = version.match(/^(.*)-(\d+)$/s)
|
|
776
|
-
var agent = m[1]
|
|
777
|
-
var seq = 1 * m[2]
|
|
778
|
-
return (agents[agent] ?? -1) >= seq
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (parents.length && !parents.some(we_have_it)) {
|
|
782
|
-
var min = 0
|
|
783
|
-
var max = versions.length
|
|
784
|
-
var last_good_parents = []
|
|
785
|
-
while (min < max) {
|
|
786
|
-
var i = Math.ceil((min + max)/2)
|
|
787
|
-
parents = i ? [versions[i - 1].join('-')] : []
|
|
788
|
-
|
|
789
|
-
console.log(`min=${min}, max=${max}, i=${i}, parents=${parents}`)
|
|
790
|
-
|
|
791
|
-
var a = new AbortController()
|
|
792
|
-
aborts.add(a)
|
|
793
|
-
var st = Date.now()
|
|
794
|
-
var r = await braid_fetch(url, {
|
|
795
|
-
signal: a.signal,
|
|
796
|
-
method: "HEAD",
|
|
797
|
-
parents,
|
|
798
|
-
headers: {
|
|
799
|
-
Accept: 'text/plain',
|
|
800
|
-
...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname]),
|
|
801
|
-
},
|
|
802
|
-
retry: true
|
|
803
|
-
})
|
|
804
|
-
console.log(`fetched in ${Date.now() - st}`)
|
|
805
|
-
aborts.delete(a)
|
|
806
|
-
|
|
807
|
-
if (r.ok) {
|
|
808
|
-
min = i
|
|
809
|
-
last_good_parents = parents
|
|
810
|
-
} else {
|
|
811
|
-
max = i - 1
|
|
812
|
-
}
|
|
855
|
+
async function send_new_stuff(fork_point) {
|
|
856
|
+
var r = await my_fetch({ method: "HEAD" })
|
|
857
|
+
if (r.headers.get('editable') === 'false')
|
|
858
|
+
return console.log('do not send updates for read-only file: ' + url)
|
|
859
|
+
|
|
860
|
+
var in_parallel = create_parallel_promises(10)
|
|
861
|
+
braid_text.get(url, braid_text_get_options = {
|
|
862
|
+
parents: fork_point,
|
|
863
|
+
merge_type: 'dt',
|
|
864
|
+
peer: self.peer,
|
|
865
|
+
subscribe: async (u) => {
|
|
866
|
+
if (u.version.length) {
|
|
867
|
+
self.signal_file_needs_writing()
|
|
868
|
+
in_parallel(() => send_out({...u, peer: self.peer}))
|
|
813
869
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
console.log(`found good parents: ${parents}: ${url}`)
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
var in_parallel = create_parallel_promises(10)
|
|
820
|
-
braid_text.get(url, braid_text_get_options = {
|
|
821
|
-
parents,
|
|
822
|
-
merge_type: 'dt',
|
|
823
|
-
peer: self.peer,
|
|
824
|
-
subscribe: async (u) => {
|
|
825
|
-
if (u.version.length) {
|
|
826
|
-
self.signal_file_needs_writing()
|
|
827
|
-
in_parallel(() => send_out({...u, peer: self.peer}))
|
|
828
|
-
}
|
|
829
|
-
},
|
|
830
|
-
})
|
|
831
|
-
} catch (e) {
|
|
832
|
-
if (e?.name !== "AbortError") console.log(e)
|
|
833
|
-
}
|
|
834
|
-
finish_something()
|
|
870
|
+
},
|
|
871
|
+
})
|
|
835
872
|
}
|
|
836
873
|
|
|
837
874
|
// for config and errors file, listen for web changes
|
|
838
875
|
if (!is_external_link) braid_text.get(url, braid_text_get_options = {
|
|
839
876
|
merge_type: 'dt',
|
|
840
877
|
peer: self.peer,
|
|
841
|
-
subscribe: self.signal_file_needs_writing,
|
|
878
|
+
subscribe: () => self.signal_file_needs_writing(),
|
|
842
879
|
})
|
|
843
880
|
|
|
844
881
|
return self
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braidfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
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.73",
|
|
10
|
-
"braid-text": "^0.2.
|
|
10
|
+
"braid-text": "^0.2.23",
|
|
11
11
|
"chokidar": "^3.6.0"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|