123456module exchange78import compress.zlib9import src.events10import src.history11import src.remote12import src.storage13141516pub fn fetch_missing(store &storage.Store, mut backend remote.Backend, repo string, ids []string) !int {17 mut fetched := Fetched{}18 strips := known_strips(store, fetched)!19 mut wanted := ids.clone()20 mut seen := map[string]bool{}21 for wanted.len > 0 {22 id := wanted.pop()23 if id == '' || id in seen || history.by_id(store, id) != none {24 continue25 }26 if !valid_save_id(id) {27 return error('remote history names invalid save ID ${id}')28 }29 seen[id] = true30 object := backend.get(save_key(repo, id))!31 if !object.found {32 return error('the remote does not have save ${id} either')33 }34 mut record := decode_save(object.body)!35 validate_save(record, id)!36 drop_unsafe_paths(mut record)37 validate_hashes(record.id, record.files)!38 for file in record.files {39 if !store.has_object(file.hash)! && file.hash !in fetched.repo_objects40 && !strips.any(reaches(it, id) && covers(it.path, file.path)) {41 blob := backend.get(object_key(repo, file.hash))!42 if !blob.found {43 return error('the remote is missing object ${file.hash} that save ${id} needs')44 }45 fetched.repo_objects[file.hash] = blob.body46 }47 }48 fetched.saves << record49 wanted << record.parent50 wanted << record.restores51 }52 53 mut ordered := []SaveRecord{}54 mut placed := map[string]bool{}55 for ordered.len < fetched.saves.len {56 before := ordered.len57 for record in fetched.saves {58 if record.id !in placed && (record.parent !in seen || record.parent in placed) {59 ordered << record60 placed[record.id] = true61 }62 }63 if ordered.len == before {64 return error('the remote history has a parent cycle')65 }66 }67 fetched.saves = ordered68 store.begin()!69 write_fetched_records(store, fetched) or {70 store.rollback()71 return err72 }73 store.end()!74 return ordered.len75}76777879pub fn resend(store &storage.Store, mut backend remote.Backend, repo string, hashes []string, save_ids []string) !int {80 81 82 if unapplied_strips(store, mut backend, repo)!.len > 0 {83 return error(strip_not_applied)84 }85 mut uploaded := Uploaded{}86 upload_copies(store, mut backend, repo, hashes, save_ids, mut uploaded) or {87 return partway(uploaded.done(), after_failed_upload(store, mut backend, repo, uploaded,88 err))89 }90 91 if undo_late_strips(store, mut backend, repo, uploaded) or {92 return partway(uploaded.done(), err)93 } {94 return partway(uploaded.done(), error(strip_not_applied))95 }96 return uploaded.objects.len + uploaded.saves.len97}9899fn upload_copies(store &storage.Store, mut backend remote.Backend, repo string, hashes []string, save_ids []string, mut uploaded Uploaded) ! {100 for hash in hashes {101 key := object_key(repo, hash)102 existing := backend.get(key)!103 if existing.found {104 105 106 if data := zlib.decompress(existing.body) {107 if storage.hash_hex(data) == hash {108 continue109 }110 }111 backend.delete(key)!112 uploaded.deleted++113 }114 store.get_object(hash)!115 if backend.put_if_absent(key, store.object_blob(hash)!)! {116 uploaded.objects[hash] = true117 }118 }119 for id in save_ids {120 save := history.by_id(store, id) or { return error('save ${id} is missing here too') }121 for file in history.files_of(store, id)! {122 store.get_object(file.hash)!123 if backend.put_if_absent(object_key(repo, file.hash), store.object_blob(file.hash)!)! {124 uploaded.objects[file.hash] = true125 }126 }127 if backend.put_if_absent(save_key(repo, id), encode_save(save_record(store, save)!).bytes())! {128 uploaded.saves << id129 }130 }131}132133134135136pub fn settle_unsettled(store &storage.Store, mut backend remote.Backend, repo string) ! {137 if unapplied_strips(store, mut backend, repo)!.len > 0 {138 return error(strip_not_applied)139 }140 mut needed := map[string]bool{}141 for key in backend.list('${repo}/saves')! {142 object := backend.get(key)!143 if object.found {144 for file in decode_save(object.body)!.files {145 needed[file.hash] = true146 }147 }148 }149 mut deleted := 0150 for hash in unsettled(store)! {151 if hash !in needed && storage.valid_hash(hash) && !store.has_object(hash)!152 && store.paths_of_object(hash)!.len == 0 {153 backend.delete(object_key(repo, hash)) or {154 return partway(done_text('deleted', deleted, 'remote object'), err)155 }156 deleted++157 }158 }159 store.remove_state(unsettled_key) or {160 return partway(done_text('deleted', deleted, 'remote object'), err)161 }162}163164165166pub fn reapply_strips(store &storage.Store) !int {167 store.begin()!168 removed := reapply_valid_strips(store) or {169 store.rollback()170 return err171 }172 store.end()!173 return removed174}175176177178fn reapply_valid_strips(store &storage.Store) !int {179 mut removed := 0180 for event in events.strip_events(store)!.filter(events.valid_strip(it)) {181 hashes := stripped_hashes(store, event.path, event.save_id)!182 removed += remove_path(store, event.path, event.save_id)!183 store.drop_unused(hashes)!184 }185 return removed186}