@@ -20,6 +20,7 @@ declare const IS_BROWSER: boolean
2020
2121const LOCK_INTERVAL = 2 * 60 * 1000 // Lock every 2mins while syncing
2222const LOCK_TIMEOUT = 15 * 60 * 1000 // Override lock 0.25h after last time lock has been set
23+ const PUT_FILE_SIZE_RETRIES = 2
2324export default class WebDavAdapter extends CachingAdapter {
2425 private lockingInterval : any
2526 private lockingPromise : Promise < any >
@@ -220,13 +221,7 @@ export default class WebDavAdapter extends CachingAdapter {
220221 if ( response . status === 200 ) {
221222 let xmlDocText = response . data
222223 if ( IS_BROWSER ) {
223- let fileSize = null
224- try {
225- fileSize = await this . getFileSize ( fullUrl )
226- } catch ( e ) {
227- console . warn ( e )
228- Logger . log ( 'Error getting file size: ' + e . message )
229- }
224+ const fileSize = await this . getRemoteFileSizeOrNull ( fullUrl )
230225
231226 if ( fileSize === null || Number . isNaN ( fileSize ) ) {
232227 throw new FileSizeUnknown ( )
@@ -378,14 +373,63 @@ export default class WebDavAdapter extends CachingAdapter {
378373 const ciphertext = await Crypto . encryptAES ( this . server . passphrase , xbel , salt )
379374 xbel = JSON . stringify ( { ciphertext, salt} )
380375 }
381- await this . uploadFile ( fullUrl , this . server . bookmark_file_type === 'xbel' ? 'application/xml' : 'text/html' , xbel )
376+ await this . uploadBookmarkFile ( fullUrl , this . server . bookmark_file_type === 'xbel' ? 'application/xml' : 'text/html' , xbel )
382377 } else {
383378 Logger . log ( 'No changes to the server version necessary' )
384379 }
385380
386381 await this . freeLock ( )
387382 }
388383
384+ async getRemoteFileSizeOrNull ( url ) {
385+ try {
386+ return await this . getFileSize ( url )
387+ } catch ( e ) {
388+ if ( e instanceof CancelledSyncError ) {
389+ throw e
390+ }
391+ console . warn ( e )
392+ Logger . log ( 'Error getting file size: ' + e . message )
393+ return null
394+ }
395+ }
396+
397+ getContentByteLength ( data ) {
398+ return new TextEncoder ( ) . encode ( data ) . length
399+ }
400+
401+ async verifyUploadedFileSize ( url , expectedByteLength ) {
402+ const fileSize = await this . getRemoteFileSizeOrNull ( url )
403+
404+ if ( fileSize === null || Number . isNaN ( fileSize ) ) {
405+ throw new FileSizeUnknown ( )
406+ }
407+
408+ if ( fileSize !== expectedByteLength ) {
409+ Logger . log ( 'Uploaded file size mismatch: ' + fileSize + ' != ' + expectedByteLength )
410+ throw new FileSizeMismatch ( )
411+ }
412+ }
413+
414+ async uploadBookmarkFile ( url , content_type , data ) {
415+ const expectedByteLength = this . getContentByteLength ( data )
416+
417+ for ( let attempt = 0 ; attempt <= PUT_FILE_SIZE_RETRIES ; attempt ++ ) {
418+ try {
419+ await this . uploadFile ( url , content_type , data )
420+ await this . verifyUploadedFileSize ( url , expectedByteLength )
421+ return
422+ } catch ( e ) {
423+ const isLastAttempt = attempt === PUT_FILE_SIZE_RETRIES
424+ const shouldRetry = e instanceof FileSizeMismatch || e instanceof FileSizeUnknown
425+ if ( ! shouldRetry || isLastAttempt ) {
426+ throw e
427+ }
428+ Logger . log ( 'Uploaded file size verification failed. Retrying upload (' + ( attempt + 2 ) + '/' + ( PUT_FILE_SIZE_RETRIES + 1 ) + ')' )
429+ }
430+ }
431+ }
432+
389433 async uploadFile ( url , content_type , data ) {
390434 if ( IS_BROWSER ) {
391435 return this . uploadFileWeb ( url , content_type , data )
0 commit comments