Migrate to SQLCipher for messages/cache
Quite a few other fixes, including: - Sending to contact with no avatar yet (not synced from mobile) - Left pane doesn't update quickly or at all on new message - Left pane doesn't show sent or error status Also: - Contributing.md: Ensure set of linux dev dependencies is complete
This commit is contained in:
		
					parent
					
						
							
								fc461c82ce
							
						
					
				
			
			
				commit
				
					
						3105b77475
					
				
			
		
					 29 changed files with 2006 additions and 716 deletions
				
			
		
							
								
								
									
										167
									
								
								js/modules/migrate_to_sql.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								js/modules/migrate_to_sql.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,167 @@ | |||
| /* global window, IDBKeyRange */ | ||||
| 
 | ||||
| const { includes, isFunction, isString, last } = require('lodash'); | ||||
| const { saveMessages, saveUnprocesseds } = require('./data'); | ||||
| const { | ||||
|   getMessageExportLastIndex, | ||||
|   setMessageExportLastIndex, | ||||
|   getUnprocessedExportLastIndex, | ||||
|   setUnprocessedExportLastIndex, | ||||
| } = require('./settings'); | ||||
| 
 | ||||
| module.exports = { | ||||
|   migrateToSQL, | ||||
| }; | ||||
| 
 | ||||
| async function migrateToSQL({ db, clearStores, handleDOMException }) { | ||||
|   if (!db) { | ||||
|     throw new Error('Need db for IndexedDB connection!'); | ||||
|   } | ||||
|   if (!isFunction(clearStores)) { | ||||
|     throw new Error('Need clearStores function!'); | ||||
|   } | ||||
|   if (!isFunction(handleDOMException)) { | ||||
|     throw new Error('Need handleDOMException function!'); | ||||
|   } | ||||
| 
 | ||||
|   window.log.info('migrateToSQL: start'); | ||||
| 
 | ||||
|   let lastIndex = await getMessageExportLastIndex(db); | ||||
|   let complete = false; | ||||
| 
 | ||||
|   while (!complete) { | ||||
|     // eslint-disable-next-line no-await-in-loop
 | ||||
|     const status = await migrateStoreToSQLite({ | ||||
|       db, | ||||
|       save: saveMessages, | ||||
|       storeName: 'messages', | ||||
|       handleDOMException, | ||||
|       lastIndex, | ||||
|     }); | ||||
| 
 | ||||
|     ({ complete, lastIndex } = status); | ||||
| 
 | ||||
|     // eslint-disable-next-line no-await-in-loop
 | ||||
|     await setMessageExportLastIndex(db, lastIndex); | ||||
|   } | ||||
|   window.log.info('migrateToSQL: migrate of messages complete'); | ||||
| 
 | ||||
|   lastIndex = await getUnprocessedExportLastIndex(db); | ||||
|   complete = false; | ||||
| 
 | ||||
|   while (!complete) { | ||||
|     // eslint-disable-next-line no-await-in-loop
 | ||||
|     const status = await migrateStoreToSQLite({ | ||||
|       db, | ||||
|       save: saveUnprocesseds, | ||||
|       storeName: 'unprocessed', | ||||
|       handleDOMException, | ||||
|       lastIndex, | ||||
|     }); | ||||
| 
 | ||||
|     ({ complete, lastIndex } = status); | ||||
| 
 | ||||
|     // eslint-disable-next-line no-await-in-loop
 | ||||
|     await setUnprocessedExportLastIndex(db, lastIndex); | ||||
|   } | ||||
|   window.log.info('migrateToSQL: migrate of unprocessed complete'); | ||||
| 
 | ||||
|   await clearStores(['messages', 'unprocessed']); | ||||
| 
 | ||||
|   window.log.info('migrateToSQL: complete'); | ||||
| } | ||||
| 
 | ||||
| async function migrateStoreToSQLite({ | ||||
|   db, | ||||
|   save, | ||||
|   storeName, | ||||
|   handleDOMException, | ||||
|   lastIndex = null, | ||||
|   batchSize = 20, | ||||
| }) { | ||||
|   if (!db) { | ||||
|     throw new Error('Need db for IndexedDB connection!'); | ||||
|   } | ||||
|   if (!isFunction(save)) { | ||||
|     throw new Error('Need save function!'); | ||||
|   } | ||||
|   if (!isString(storeName)) { | ||||
|     throw new Error('Need storeName!'); | ||||
|   } | ||||
|   if (!isFunction(handleDOMException)) { | ||||
|     throw new Error('Need handleDOMException for error handling!'); | ||||
|   } | ||||
| 
 | ||||
|   if (!includes(db.objectStoreNames, storeName)) { | ||||
|     return { | ||||
|       complete: true, | ||||
|       count: 0, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const queryPromise = new Promise((resolve, reject) => { | ||||
|     const items = []; | ||||
|     const transaction = db.transaction(storeName, 'readonly'); | ||||
|     transaction.onerror = () => { | ||||
|       handleDOMException( | ||||
|         'migrateToSQLite transaction error', | ||||
|         transaction.error, | ||||
|         reject | ||||
|       ); | ||||
|     }; | ||||
|     transaction.oncomplete = () => {}; | ||||
| 
 | ||||
|     const store = transaction.objectStore(storeName); | ||||
|     const excludeLowerBound = true; | ||||
|     const range = lastIndex | ||||
|       ? IDBKeyRange.lowerBound(lastIndex, excludeLowerBound) | ||||
|       : undefined; | ||||
|     const request = store.openCursor(range); | ||||
|     request.onerror = () => { | ||||
|       handleDOMException( | ||||
|         'migrateToSQLite: request error', | ||||
|         request.error, | ||||
|         reject | ||||
|       ); | ||||
|     }; | ||||
|     request.onsuccess = event => { | ||||
|       const cursor = event.target.result; | ||||
| 
 | ||||
|       if (!cursor || !cursor.value) { | ||||
|         return resolve({ | ||||
|           complete: true, | ||||
|           items, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       const item = cursor.value; | ||||
|       items.push(item); | ||||
| 
 | ||||
|       if (items.length >= batchSize) { | ||||
|         return resolve({ | ||||
|           complete: false, | ||||
|           items, | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       return cursor.continue(); | ||||
|     }; | ||||
|   }); | ||||
| 
 | ||||
|   const { items, complete } = await queryPromise; | ||||
| 
 | ||||
|   if (items.length) { | ||||
|     // We need to pass forceSave parameter, because these items already have an
 | ||||
|     //   id key. Normally, this call would be interpreted as an update request.
 | ||||
|     await save(items, { forceSave: true }); | ||||
|   } | ||||
| 
 | ||||
|   const lastItem = last(items); | ||||
|   const id = lastItem ? lastItem.id : null; | ||||
| 
 | ||||
|   return { | ||||
|     complete, | ||||
|     count: items.length, | ||||
|     lastIndex: id, | ||||
|   }; | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Scott Nonnenberg
				Scott Nonnenberg