Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations. pdsmoover.com
pds atproto migrations moo cow

missing blobs and better upload status

+52 -7
+2 -2
index.html
··· 169 169 </form> 170 170 <div x-show="askForPlcToken" class="section"> 171 171 <form @submit.prevent="await signPlcOperation()"> 172 - <h2>Please enter your PLCToken you received in an email</h2> 172 + <h2>Please enter your PLC Token you received in an email</h2> 173 173 <div class="form-group"> 174 - <label for="plc-token">PLCToken:</label> 174 + <label for="plc-token">PLC Token:</label> 175 175 <input type="text" id="plc-token" name="plc-token" x-model="plcToken" required> 176 176 </div> 177 177 <div x-show="error" x-text="error" class="error-message"></div>
+50 -5
src/pdsmoover.js
··· 35 35 constructor() { 36 36 this.oldAgent = null; 37 37 this.newAgent = null; 38 + this.missingBlobs = []; 38 39 } 39 40 40 41 /** ··· 77 78 await oldAgent.login({identifier: oldHandle, password: password, authFactorToken: twoFactorCode}); 78 79 } 79 80 80 - safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS'); 81 + safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); 81 82 const newAgent = new AtpAgent({service: newPdsUrl}); 82 83 const newHostDesc = await newAgent.com.atproto.server.describeServer(); 83 84 const newHostWebDid = newHostDesc.data.did; ··· 110 111 111 112 await newAgent.login({ 112 113 identifier: usersDid, 113 - password, 114 + password: password, 114 115 }); 115 116 116 117 safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); ··· 119 120 encoding: 'application/vnd.ipld.car', 120 121 }); 121 122 123 + let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 122 124 safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 123 125 124 126 let blobCursor = undefined; 125 127 let uploadedBlobs = 0; 126 128 do { 127 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs, ${uploadedBlobs}/${uploadedBlobs + 100}`); 128 - uploadedBlobs += 100; 129 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 130 + 129 131 const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 130 132 did: usersDid, 131 133 cursor: blobCursor, 132 134 limit: 100, 133 135 }); 136 + 134 137 for (const cid of listedBlobs.data.cids) { 135 138 try { 136 139 //TODO may move the status update here but would have it only update like every 10 ··· 141 144 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 142 145 encoding: blobRes.headers['content-type'], 143 146 }); 147 + uploadedBlobs++; 148 + if (uploadedBlobs % 10 === 0) { 149 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 150 + } 144 151 } catch (error) { 145 152 //TODO silently logging for now will do a missing blobs later 146 153 console.error(error); ··· 149 156 blobCursor = listedBlobs.data.cursor; 150 157 } while (blobCursor); 151 158 152 - //TODO NEED to do some checking on the missing blobs here 159 + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 160 + if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { 161 + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; 162 + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 163 + //Probably should be shared between main blob uploader, but eh 164 + let missingBlobCursor = undefined; 165 + let missingUploadedBlobs = 0; 166 + do { 167 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 168 + 169 + const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ 170 + did: usersDid, 171 + cursor: missingBlobCursor, 172 + limit: 100, 173 + }); 174 + 175 + for (const cid of missingBlobs.data.cids) { 176 + try { 177 + //TODO may move the status update here but would have it only update like every 10 178 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 179 + cid, 180 + }); 181 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 182 + encoding: blobRes.headers['content-type'], 183 + }); 184 + if (uploadedBlobs % 10 === 0) { 185 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); 186 + } 187 + uploadedBlobs++; 188 + } catch (error) { 189 + //TODO silently logging for now will do a missing blobs later 190 + console.error(error); 191 + this.missingBlobs.push(cid); 192 + } 193 + } 194 + missingBlobCursor = missingBlobs.data.cursor; 195 + } while (missingBlobCursor); 196 + 197 + } 153 198 154 199 const prefs = await oldAgent.app.bsky.actor.getPreferences(); 155 200 await newAgent.app.bsky.actor.putPreferences(prefs.data);