Rename RestoreFiles to GoldenRetriever

This commit is contained in:
Renée Kooi 2017-10-09 15:11:53 +02:00
parent f87fe03466
commit 172575fb79
No known key found for this signature in database
GPG key ID: 30516CF2A8E63718
13 changed files with 268 additions and 247 deletions

View file

@ -26,7 +26,7 @@ const Dashboard = require('uppy/lib/plugins/Dashboard')
const Webcam = require('uppy/lib/plugins/Webcam')
const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
const Tus10 = require('uppy/lib/plugins/Tus10')
const uppy = Uppy({ autoProceed: false })
.use(Dashboard, { trigger: '#select-files' })
.use(Webcam, { target: Dashboard })
@ -106,7 +106,7 @@ If you like, you can also use a pre-built bundle, for example from [unpkg CDN](h
- `ProgressBar` — minimal progress bar that fills itself when upload progresses
- `StatusBar` — more detailed progress, pause/resume/cancel buttons, percentage, speed, uploaded/total sizes (included by default with `Dashboard`)
- `Informer` — send notifications like “smile” before taking a selfie or “upload failed” when all is lost (also included by default with `Dashboard`)
- `RestoreFiles` — restores files after a browser crash, like its nothing
- `GoldenRetriever` — restores files after a browser crash, like its nothing
- `ReduxDevTools` — for your emerging [time traveling](https://github.com/gaearon/redux-devtools) needs
- `GoogleDrive` — select files from [Google Drive](https://www.google.com/drive/)
- `Dropbox` — select files from [Dropbox](https://www.dropbox.com/)

View file

@ -1,17 +1,17 @@
const Uppy = require('../../src/core/Core.js')
const Uppy = require('../../src/core')
const Dashboard = require('../../src/plugins/Dashboard')
// const GoogleDrive = require('../../src/plugins/GoogleDrive')
const Dropbox = require('../../src/plugins/Dropbox')
const Instagram = require('../../src/plugins/Instagram')
// const Webcam = require('../../src/plugins/Webcam')
const Tus10 = require('../../src/plugins/Tus10')
// const Multipart = require('../../src/plugins/Multipart')
// const XHRUpload = require('../../src/plugins/XHRUpload')
// const FileInput = require('../../src/plugins/FileInput')
const MetaData = require('../../src/plugins/MetaData')
// const Informer = require('../../src/plugins/Informer')
// const StatusBar = require('../../src/plugins/StatusBar')
// const DragDrop = require('../../src/plugins/DragDrop')
// const RestoreFiles = require('../../src/plugins/RestoreFiles')
// const GoldenRetriever = require('../../src/plugins/GoldenRetriever')
const PROTOCOL = location.protocol === 'https:' ? 'https' : 'http'
const TUS_ENDPOINT = PROTOCOL + '://master.tus.io/files/'
@ -68,7 +68,7 @@ const uppy = Uppy({
{ id: 'caption', name: 'Caption', value: 'none', placeholder: 'describe what the image is about' }
]
})
// .use(RestoreFiles, {serviceWorker: true})
// .use(GoldenRetriever, {serviceWorker: true})
.run()
uppy.on('core:success', (fileList) => {

View file

@ -1,8 +1,8 @@
const Uppy = require('uppy/lib/core')
const Dashboard = require('uppy/lib/plugins/Dashboard')
const RestoreFiles = require('uppy/lib/plugins/RestoreFiles')
const GoldenRetriever = require('uppy/lib/plugins/GoldenRetriever')
// Initialise two Uppy instances with the RestoreFiles plugin,
// Initialise two Uppy instances with the GoldenRetriever plugin,
// but with different `id`s.
const a = Uppy({
id: 'a',
@ -13,7 +13,7 @@ const a = Uppy({
inline: true,
maxWidth: 400
})
.use(RestoreFiles, { serviceWorker: false })
.use(GoldenRetriever, { serviceWorker: false })
.run()
const b = Uppy({
@ -25,7 +25,7 @@ const b = Uppy({
inline: true,
maxWidth: 400
})
.use(RestoreFiles, { serviceWorker: false })
.use(GoldenRetriever, { serviceWorker: false })
.run()
window.a = a

View file

@ -30,7 +30,7 @@ const Transloadit = require('./plugins/Transloadit')
const AwsS3 = require('./plugins/AwsS3')
// Other?
const RestoreFiles = require('./plugins/RestoreFiles')
const GoldenRetriever = require('./plugins/GoldenRetriever')
const ReduxDevTools = require('./plugins/ReduxDevTools')
const ReduxStore = require('./plugins/Redux')
@ -53,7 +53,22 @@ module.exports = {
Dashboard,
MetaData,
Webcam,
RestoreFiles,
GoldenRetriever,
ReduxDevTools,
ReduxStore
}
Object.defineProperty(module.exports, 'RestoreFiles', {
enumerable: true,
configurable: true,
get: () => {
console.warn('Uppy.RestoreFiles is deprecated and will be removed in v0.22. Use Uppy.GoldenRetriever instead.')
Object.defineProperty(module.exports, 'RestoreFiles', {
enumerable: true,
configurable: true,
writable: true,
value: GoldenRetriever
})
return GoldenRetriever
}
})

View file

@ -0,0 +1,234 @@
const Plugin = require('../Plugin')
const ServiceWorkerStore = require('./ServiceWorkerStore')
const IndexedDBStore = require('./IndexedDBStore')
const MetaDataStore = require('./MetaDataStore')
/**
* The Golden Retriever plugin restores selected files and resumes uploads
* after a closed tab or a browser crash!
*
* Uses localStorage, IndexedDB and ServiceWorker to do its magic, read more:
* https://uppy.io/blog/2017/07/golden-retriever/
*/
module.exports = class GoldenRetriever extends Plugin {
constructor (core, opts) {
super(core, opts)
this.type = 'debugger'
this.id = 'GoldenRetriever'
this.title = 'Restore Files'
const defaultOptions = {
expires: 24 * 60 * 60 * 1000, // 24 hours
serviceWorker: false
}
this.opts = Object.assign({}, defaultOptions, opts)
this.MetaDataStore = new MetaDataStore({
expires: this.opts.expires,
storeName: core.getID()
})
this.ServiceWorkerStore = null
if (this.opts.serviceWorker) {
this.ServiceWorkerStore = new ServiceWorkerStore({ storeName: core.getID() })
}
this.IndexedDBStore = new IndexedDBStore(Object.assign(
{ expires: this.opts.expires },
opts.indexedDB || {},
{ storeName: core.getID() }))
this.saveFilesStateToLocalStorage = this.saveFilesStateToLocalStorage.bind(this)
this.loadFilesStateFromLocalStorage = this.loadFilesStateFromLocalStorage.bind(this)
this.loadFileBlobsFromServiceWorker = this.loadFileBlobsFromServiceWorker.bind(this)
this.loadFileBlobsFromIndexedDB = this.loadFileBlobsFromIndexedDB.bind(this)
this.onBlobsLoaded = this.onBlobsLoaded.bind(this)
}
loadFilesStateFromLocalStorage () {
const savedState = this.MetaDataStore.load()
if (savedState) {
this.core.log('Recovered some state from Local Storage')
this.core.setState(savedState)
}
}
/**
* Get file objects that are currently waiting: they've been selected,
* but aren't yet being uploaded.
*/
getWaitingFiles () {
const waitingFiles = {}
const allFiles = this.core.state.files
Object.keys(allFiles).forEach((fileID) => {
const file = this.core.getFile(fileID)
if (!file.progress || !file.progress.uploadStarted) {
waitingFiles[fileID] = file
}
})
return waitingFiles
}
/**
* Get file objects that are currently being uploaded. If a file has finished
* uploading, but the other files in the same batch have not, the finished
* file is also returned.
*/
getUploadingFiles () {
const uploadingFiles = {}
const { currentUploads } = this.core.state
if (currentUploads) {
const uploadIDs = Object.keys(currentUploads)
uploadIDs.forEach((uploadID) => {
const filesInUpload = currentUploads[uploadID].fileIDs
filesInUpload.forEach((fileID) => {
uploadingFiles[fileID] = this.core.getFile(fileID)
})
})
}
return uploadingFiles
}
saveFilesStateToLocalStorage () {
const filesToSave = Object.assign(
this.getWaitingFiles(),
this.getUploadingFiles()
)
this.MetaDataStore.save({
currentUploads: this.core.state.currentUploads,
files: filesToSave
})
}
loadFileBlobsFromServiceWorker () {
this.ServiceWorkerStore.list().then((blobs) => {
const numberOfFilesRecovered = Object.keys(blobs).length
const numberOfFilesTryingToRecover = Object.keys(this.core.state.files).length
if (numberOfFilesRecovered === numberOfFilesTryingToRecover) {
this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Service Worker!`)
this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
this.onBlobsLoaded(blobs)
} else {
this.core.log('Failed to recover blobs from Service Worker, trying IndexedDB now...')
this.loadFileBlobsFromIndexedDB()
}
})
}
loadFileBlobsFromIndexedDB () {
this.IndexedDBStore.list().then((blobs) => {
const numberOfFilesRecovered = Object.keys(blobs).length
if (numberOfFilesRecovered > 0) {
this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Indexed DB!`)
this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
return this.onBlobsLoaded(blobs)
}
this.core.log('Couldnt recover anything from IndexedDB :(')
})
}
onBlobsLoaded (blobs) {
const obsoleteBlobs = []
const updatedFiles = Object.assign({}, this.core.state.files)
Object.keys(blobs).forEach((fileID) => {
const originalFile = this.core.getFile(fileID)
if (!originalFile) {
obsoleteBlobs.push(fileID)
return
}
const cachedData = blobs[fileID]
const updatedFileData = {
data: cachedData,
isRestored: true
}
const updatedFile = Object.assign({}, originalFile, updatedFileData)
updatedFiles[fileID] = updatedFile
this.core.generatePreview(updatedFile)
})
this.core.setState({
files: updatedFiles
})
this.core.emit('core:restored')
if (obsoleteBlobs.length) {
this.deleteBlobs(obsoleteBlobs).then(() => {
this.core.log(`[GoldenRetriever] cleaned up ${obsoleteBlobs.length} old files`)
})
}
}
deleteBlobs (fileIDs) {
const promises = []
fileIDs.forEach((id) => {
if (this.ServiceWorkerStore) {
promises.push(this.ServiceWorkerStore.delete(id))
}
if (this.IndexedDBStore) {
promises.push(this.IndexedDBStore.delete(id))
}
})
return Promise.all(promises)
}
install () {
this.loadFilesStateFromLocalStorage()
if (Object.keys(this.core.state.files).length > 0) {
if (this.ServiceWorkerStore) {
this.core.log('Attempting to load files from Service Worker...')
this.loadFileBlobsFromServiceWorker()
} else {
this.core.log('Attempting to load files from Indexed DB...')
this.loadFileBlobsFromIndexedDB()
}
}
this.core.on('core:file-added', (file) => {
if (file.isRemote) return
if (this.ServiceWorkerStore) {
this.ServiceWorkerStore.put(file).catch((err) => {
this.core.log('Could not store file', 'error')
this.core.log(err)
})
}
this.IndexedDBStore.put(file).catch((err) => {
this.core.log('Could not store file', 'error')
this.core.log(err)
})
})
this.core.on('core:file-removed', (fileID) => {
if (this.ServiceWorkerStore) this.ServiceWorkerStore.delete(fileID)
this.IndexedDBStore.delete(fileID)
})
this.core.on('core:success', (fileIDs) => {
this.deleteBlobs(fileIDs).then(() => {
this.core.log(`[GoldenRetriever] removed ${fileIDs.length} files that finished uploading`)
})
})
this.core.on('core:state-update', this.saveFilesStateToLocalStorage)
this.core.on('core:restored', () => {
// start all uploads again when file blobs are restored
const { currentUploads } = this.core.getState()
if (currentUploads) {
Object.keys(currentUploads).forEach((uploadId) => {
this.core.restore(uploadId, currentUploads[uploadId])
})
}
})
}
}

View file

@ -1,234 +1,5 @@
const Plugin = require('../Plugin')
const ServiceWorkerStore = require('./ServiceWorkerStore')
const IndexedDBStore = require('./IndexedDBStore')
const MetaDataStore = require('./MetaDataStore')
const GoldenRetriever = require('../GoldenRetriever')
/**
* Restore Files plugin restores selected files and resumes uploads
* after a closed tab or a browser crash!
*
* Uses localStorage, IndexedDB and ServiceWorker to do its magic, read more:
* https://uppy.io/blog/2017/07/golden-retriever/
*/
module.exports = class RestoreFiles extends Plugin {
constructor (core, opts) {
super(core, opts)
this.type = 'debugger'
this.id = 'RestoreFiles'
this.title = 'Restore Files'
console.warn('Using `uppy/lib/plugins/RestoreFiles` is deprecated and will be removed in v0.22. Please use `uppy/lib/plugins/GoldenRetriever` instead.')
const defaultOptions = {
expires: 24 * 60 * 60 * 1000, // 24 hours
serviceWorker: false
}
this.opts = Object.assign({}, defaultOptions, opts)
this.MetaDataStore = new MetaDataStore({
expires: this.opts.expires,
storeName: core.getID()
})
this.ServiceWorkerStore = null
if (this.opts.serviceWorker) {
this.ServiceWorkerStore = new ServiceWorkerStore({ storeName: core.getID() })
}
this.IndexedDBStore = new IndexedDBStore(Object.assign(
{ expires: this.opts.expires },
opts.indexedDB || {},
{ storeName: core.getID() }))
this.saveFilesStateToLocalStorage = this.saveFilesStateToLocalStorage.bind(this)
this.loadFilesStateFromLocalStorage = this.loadFilesStateFromLocalStorage.bind(this)
this.loadFileBlobsFromServiceWorker = this.loadFileBlobsFromServiceWorker.bind(this)
this.loadFileBlobsFromIndexedDB = this.loadFileBlobsFromIndexedDB.bind(this)
this.onBlobsLoaded = this.onBlobsLoaded.bind(this)
}
loadFilesStateFromLocalStorage () {
const savedState = this.MetaDataStore.load()
if (savedState) {
this.core.log('Recovered some state from Local Storage')
this.core.setState(savedState)
}
}
/**
* Get file objects that are currently waiting: they've been selected,
* but aren't yet being uploaded.
*/
getWaitingFiles () {
const waitingFiles = {}
const allFiles = this.core.state.files
Object.keys(allFiles).forEach((fileID) => {
const file = this.core.getFile(fileID)
if (!file.progress || !file.progress.uploadStarted) {
waitingFiles[fileID] = file
}
})
return waitingFiles
}
/**
* Get file objects that are currently being uploaded. If a file has finished
* uploading, but the other files in the same batch have not, the finished
* file is also returned.
*/
getUploadingFiles () {
const uploadingFiles = {}
const { currentUploads } = this.core.state
if (currentUploads) {
const uploadIDs = Object.keys(currentUploads)
uploadIDs.forEach((uploadID) => {
const filesInUpload = currentUploads[uploadID].fileIDs
filesInUpload.forEach((fileID) => {
uploadingFiles[fileID] = this.core.getFile(fileID)
})
})
}
return uploadingFiles
}
saveFilesStateToLocalStorage () {
const filesToSave = Object.assign(
this.getWaitingFiles(),
this.getUploadingFiles()
)
this.MetaDataStore.save({
currentUploads: this.core.state.currentUploads,
files: filesToSave
})
}
loadFileBlobsFromServiceWorker () {
this.ServiceWorkerStore.list().then((blobs) => {
const numberOfFilesRecovered = Object.keys(blobs).length
const numberOfFilesTryingToRecover = Object.keys(this.core.state.files).length
if (numberOfFilesRecovered === numberOfFilesTryingToRecover) {
this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Service Worker!`)
this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
this.onBlobsLoaded(blobs)
} else {
this.core.log('Failed to recover blobs from Service Worker, trying IndexedDB now...')
this.loadFileBlobsFromIndexedDB()
}
})
}
loadFileBlobsFromIndexedDB () {
this.IndexedDBStore.list().then((blobs) => {
const numberOfFilesRecovered = Object.keys(blobs).length
if (numberOfFilesRecovered > 0) {
this.core.log(`Successfully recovered ${numberOfFilesRecovered} blobs from Indexed DB!`)
this.core.info(`Successfully recovered ${numberOfFilesRecovered} files`, 'success', 3000)
return this.onBlobsLoaded(blobs)
}
this.core.log('Couldnt recover anything from IndexedDB :(')
})
}
onBlobsLoaded (blobs) {
const obsoleteBlobs = []
const updatedFiles = Object.assign({}, this.core.state.files)
Object.keys(blobs).forEach((fileID) => {
const originalFile = this.core.getFile(fileID)
if (!originalFile) {
obsoleteBlobs.push(fileID)
return
}
const cachedData = blobs[fileID]
const updatedFileData = {
data: cachedData,
isRestored: true
}
const updatedFile = Object.assign({}, originalFile, updatedFileData)
updatedFiles[fileID] = updatedFile
this.core.generatePreview(updatedFile)
})
this.core.setState({
files: updatedFiles
})
this.core.emit('core:restored')
if (obsoleteBlobs.length) {
this.deleteBlobs(obsoleteBlobs).then(() => {
this.core.log(`RestoreFiles: cleaned up ${obsoleteBlobs.length} old files`)
})
}
}
deleteBlobs (fileIDs) {
const promises = []
fileIDs.forEach((id) => {
if (this.ServiceWorkerStore) {
promises.push(this.ServiceWorkerStore.delete(id))
}
if (this.IndexedDBStore) {
promises.push(this.IndexedDBStore.delete(id))
}
})
return Promise.all(promises)
}
install () {
this.loadFilesStateFromLocalStorage()
if (Object.keys(this.core.state.files).length > 0) {
if (this.ServiceWorkerStore) {
this.core.log('Attempting to load files from Service Worker...')
this.loadFileBlobsFromServiceWorker()
} else {
this.core.log('Attempting to load files from Indexed DB...')
this.loadFileBlobsFromIndexedDB()
}
}
this.core.on('core:file-added', (file) => {
if (file.isRemote) return
if (this.ServiceWorkerStore) {
this.ServiceWorkerStore.put(file).catch((err) => {
this.core.log('Could not store file', 'error')
this.core.log(err)
})
}
this.IndexedDBStore.put(file).catch((err) => {
this.core.log('Could not store file', 'error')
this.core.log(err)
})
})
this.core.on('core:file-removed', (fileID) => {
if (this.ServiceWorkerStore) this.ServiceWorkerStore.delete(fileID)
this.IndexedDBStore.delete(fileID)
})
this.core.on('core:success', (fileIDs) => {
this.deleteBlobs(fileIDs).then(() => {
this.core.log(`RestoreFiles: removed ${fileIDs.length} files that finished uploading`)
})
})
this.core.on('core:state-update', this.saveFilesStateToLocalStorage)
this.core.on('core:restored', () => {
// start all uploads again when file blobs are restored
const { currentUploads } = this.core.getState()
if (currentUploads) {
Object.keys(currentUploads).forEach((uploadId) => {
this.core.restore(uploadId, currentUploads[uploadId])
})
}
})
}
}
module.exports = GoldenRetriever

View file

@ -5,22 +5,23 @@ permalink: docs/golden-retriever/
order: 40
---
Golden Retriever plugin, also known and used as `RestoreFiles`, saves selected files in your browser cache (Local Storage for metadata, then Service Worker for all blobs + IndexedDB for small blobs), so that if the browser crashes, Uppy can restore everything and continue uploading like nothing happened. Read more about it [on the blog](https://uppy.io/blog/2017/07/golden-retriever/).
Golden Retriever plugin saves selected files in your browser cache (Local Storage for metadata, then Service Worker for all blobs + IndexedDB for small blobs), so that if the browser crashes, Uppy can restore everything and continue uploading like nothing happened. Read more about it [on the blog](https://uppy.io/blog/2017/07/golden-retriever/).
1\. Bundle your own service worker `sw.js` file with Uppy Golden Retrievers service worker. If youre using Browserify, just bundle it separately, for Webpack there is a plugin [serviceworker-webpack-plugin](https://github.com/oliviertassinari/serviceworker-webpack-plugin).
```js
// sw.js
require('uppy/lib/RestoreFiles/ServiceWorker.js')
require('uppy/lib/plugins/GoldenRetriever/ServiceWorker.js')
```
2\. Register it in your app entry point:
```js
// you app.js entry point
const GoldenRetriever = require('uppy/lib/plugins/GoldenRetriever')
uppy.use(RestoreFiles, {serviceWorker: true})
uppy.use(GoldenRetriever, {serviceWorker: true})
uppy.run()
if ('serviceWorker' in navigator) {