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 seen[id] = true27 object := backend.get(save_key(repo, id))!28 if !object.found {29 return error('the remote does not have save ${id} either')30 }31 record := decode_save(object.body)!32 if record.id != id {33 return error('remote save ${id} holds the record of ${record.id}')34 }35 36 37 if record.created_at < 0 || record.size_bytes < 0 {38 return error('remote save ${id} has invalid metadata')39 }40 for file in record.files {41 if !store.has_object(file.hash)! && file.hash !in fetched.repo_objects42 && !strips.any(reaches(it, id) && covers(it.path, file.path)) {43 blob := backend.get(object_key(repo, file.hash))!44 if !blob.found {45 return error('the remote is missing object ${file.hash} that save ${id} needs')46 }47 fetched.repo_objects[file.hash] = blob.body48 }49 }50 fetched.saves << record51 wanted << record.parent52 wanted << record.restores53 }54 55 mut ordered := []SaveRecord{}56 mut placed := map[string]bool{}57 for ordered.len < fetched.saves.len {58 before := ordered.len59 for record in fetched.saves {60 if record.id !in placed && (record.parent !in seen || record.parent in placed) {61 ordered << record62 placed[record.id] = true63 }64 }65 if ordered.len == before {66 return error('the remote history has a parent cycle')67 }68 }69 fetched.saves = ordered70 store.begin()!71 write_fetched_records(store, fetched) or {72 store.rollback()73 return err74 }75 store.end()!76 return ordered.len77}78798081pub fn resend(store &storage.Store, mut backend remote.Backend, repo string, hashes []string, save_ids []string) !int {82 83 84 if unapplied_strips(store, mut backend, repo)!.len > 0 {85 return error(strip_not_applied)86 }87 mut uploaded := Uploaded{}88 upload_copies(store, mut backend, repo, hashes, save_ids, mut uploaded) or {89 return partway(uploaded.done(), after_failed_upload(store, mut backend, repo, uploaded,90 err))91 }92 93 if undo_late_strips(store, mut backend, repo, uploaded) or {94 return partway(uploaded.done(), err)95 } {96 return partway(uploaded.done(), error(strip_not_applied))97 }98 return uploaded.objects.len + uploaded.saves.len99}100101fn upload_copies(store &storage.Store, mut backend remote.Backend, repo string, hashes []string, save_ids []string, mut uploaded Uploaded) ! {102 for hash in hashes {103 key := object_key(repo, hash)104 existing := backend.get(key)!105 if existing.found {106 107 108 if data := zlib.decompress(existing.body) {109 if storage.hash_hex(data) == hash {110 continue111 }112 }113 backend.delete(key)!114 uploaded.deleted++115 }116 store.get_object(hash)!117 if backend.put_if_absent(key, store.object_blob(hash)!)! {118 uploaded.objects[hash] = true119 }120 }121 for id in save_ids {122 save := history.by_id(store, id) or { return error('save ${id} is missing here too') }123 for file in history.files_of(store, id)! {124 store.get_object(file.hash)!125 if backend.put_if_absent(object_key(repo, file.hash), store.object_blob(file.hash)!)! {126 uploaded.objects[file.hash] = true127 }128 }129 if backend.put_if_absent(save_key(repo, id), encode_save(save_record(store, save)!).bytes())! {130 uploaded.saves << id131 }132 }133}134135136137138pub fn settle_unsettled(store &storage.Store, mut backend remote.Backend, repo string) ! {139 if unapplied_strips(store, mut backend, repo)!.len > 0 {140 return error(strip_not_applied)141 }142 mut needed := map[string]bool{}143 for key in backend.list('${repo}/saves')! {144 object := backend.get(key)!145 if object.found {146 for file in decode_save(object.body)!.files {147 needed[file.hash] = true148 }149 }150 }151 mut deleted := 0152 for hash in unsettled(store)! {153 if hash !in needed && storage.valid_hash(hash) && !store.has_object(hash)!154 && store.paths_of_object(hash)!.len == 0 {155 backend.delete(object_key(repo, hash)) or {156 return partway(done_text('deleted', deleted, 'remote object'), err)157 }158 deleted++159 }160 }161 store.remove_state(unsettled_key) or {162 return partway(done_text('deleted', deleted, 'remote object'), err)163 }164}165166167168pub fn reapply_strips(store &storage.Store) !int {169 store.begin()!170 removed := reapply_valid_strips(store) or {171 store.rollback()172 return err173 }174 store.end()!175 return removed176}177178179180fn reapply_valid_strips(store &storage.Store) !int {181 mut removed := 0182 for event in events.strip_events(store)!.filter(events.valid_strip(it)) {183 hashes := stripped_hashes(store, event.path, event.save_id)!184 removed += remove_path(store, event.path, event.save_id)!185 store.drop_unused(hashes)!186 }187 return removed188}