IndexedDBStore.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB
  2. const isSupported = !!indexedDB
  3. const DB_NAME = 'uppy-blobs'
  4. const STORE_NAME = 'files' // maybe have a thumbnail store in the future
  5. const DB_VERSION = 2
  6. function connect (dbName) {
  7. const request = indexedDB.open(dbName, DB_VERSION)
  8. return new Promise((resolve, reject) => {
  9. request.onupgradeneeded = (event) => {
  10. const db = event.target.result
  11. const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' })
  12. store.createIndex('store', 'store', { unique: false })
  13. store.transaction.oncomplete = () => {
  14. resolve(db)
  15. }
  16. }
  17. request.onsuccess = (event) => {
  18. resolve(event.target.result)
  19. }
  20. request.onerror = reject
  21. })
  22. }
  23. function waitForRequest (request) {
  24. return new Promise((resolve, reject) => {
  25. request.onsuccess = (event) => {
  26. resolve(event.target.result)
  27. }
  28. request.onerror = reject
  29. })
  30. }
  31. class IndexedDBStore {
  32. constructor (core, opts) {
  33. this.opts = Object.assign({
  34. dbName: DB_NAME,
  35. storeName: 'default',
  36. maxFileSize: 10 * 1024 * 1024, // 10 MB
  37. maxTotalSize: 300 * 1024 * 1024 // 300 MB
  38. }, opts)
  39. this.name = this.opts.storeName
  40. this.ready = connect(this.opts.dbName)
  41. }
  42. key (fileID) {
  43. return `${this.name}!${fileID}`
  44. }
  45. /**
  46. * List all file blobs currently in the store.
  47. */
  48. list () {
  49. return this.ready.then((db) => {
  50. const transaction = db.transaction([STORE_NAME], 'readonly')
  51. const store = transaction.objectStore(STORE_NAME)
  52. const request = store.index('store')
  53. .getAll(IDBKeyRange.only(this.name))
  54. return waitForRequest(request)
  55. }).then((files) => {
  56. const result = {}
  57. files.forEach((file) => {
  58. result[file.fileID] = {
  59. id: file.fileID,
  60. data: file.data
  61. }
  62. })
  63. return result
  64. })
  65. }
  66. /**
  67. * Get one file blob from the store.
  68. */
  69. get (fileID) {
  70. return this.ready.then((db) => {
  71. const transaction = db.transaction([STORE_NAME], 'readonly')
  72. const request = transaction.objectStore(STORE_NAME)
  73. .get(this.key(fileID))
  74. return waitForRequest(request)
  75. }).then((result) => ({
  76. id: result.data.fileID,
  77. data: result.data.data
  78. }))
  79. }
  80. /**
  81. * Get the total size of all stored files.
  82. *
  83. * @private
  84. */
  85. getSize () {
  86. return this.ready.then((db) => {
  87. const transaction = db.transaction([STORE_NAME], 'readonly')
  88. const store = transaction.objectStore(STORE_NAME)
  89. const request = store.index('store')
  90. .openCursor(IDBKeyRange.only(this.name))
  91. return new Promise((resolve, reject) => {
  92. let size = 0
  93. request.onsuccess = (event) => {
  94. const cursor = event.target.result
  95. if (cursor) {
  96. size += cursor.value.data.size
  97. cursor.continue()
  98. } else {
  99. resolve(size)
  100. }
  101. }
  102. request.onerror = () => {
  103. reject(new Error('Could not retrieve stored blobs size'))
  104. }
  105. })
  106. })
  107. }
  108. /**
  109. * Save a file in the store.
  110. */
  111. put (file) {
  112. if (file.data.size > this.opts.maxFileSize) {
  113. return Promise.reject(new Error('File is too big to store.'))
  114. }
  115. return this.getSize().then((size) => {
  116. if (size > this.opts.maxTotalSize) {
  117. return Promise.reject(new Error('No space left'))
  118. }
  119. return this.ready
  120. }).then((db) => {
  121. const transaction = db.transaction([STORE_NAME], 'readwrite')
  122. const request = transaction.objectStore(STORE_NAME).add({
  123. id: this.key(file.id),
  124. fileID: file.id,
  125. store: this.name,
  126. data: file.data
  127. })
  128. return waitForRequest(request)
  129. })
  130. }
  131. /**
  132. * Delete a file blob from the store.
  133. */
  134. delete (fileID) {
  135. return this.ready.then((db) => {
  136. const transaction = db.transaction([STORE_NAME], 'readwrite')
  137. const request = transaction.objectStore(STORE_NAME)
  138. .delete(this.key(fileID))
  139. return waitForRequest(request)
  140. })
  141. }
  142. }
  143. IndexedDBStore.isSupported = isSupported
  144. module.exports = IndexedDBStore