Source: lib/offline/storage_muxer.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.offline.StorageCellHandle');
  7. goog.provide('shaka.offline.StorageCellPath');
  8. goog.provide('shaka.offline.StorageMuxer');
  9. goog.require('shaka.log');
  10. goog.require('shaka.util.Error');
  11. goog.require('shaka.util.IDestroyable');
  12. /**
  13. * @typedef {{
  14. * mechanism: string,
  15. * cell: string
  16. * }}
  17. *
  18. * @property {string} mechanism
  19. * The name of the mechanism that holds the cell.
  20. * @property {string} cell
  21. * The name of the cell in the mechanism.
  22. */
  23. shaka.offline.StorageCellPath;
  24. /**
  25. * @typedef {{
  26. * path: shaka.offline.StorageCellPath,
  27. * cell: !shaka.extern.StorageCell
  28. * }}
  29. *
  30. * @property {shaka.offline.StorageCellPath} path
  31. * The path that maps to the cell.
  32. * @property {shaka.extern.StorageCell} cell
  33. * The storage cell that the path points to within the storage muxer.
  34. */
  35. shaka.offline.StorageCellHandle;
  36. // TODO: revisit this when Closure Compiler supports partially-exported classes.
  37. /**
  38. * StorageMuxer is responsible for managing StorageMechanisms and addressing
  39. * cells. The primary purpose of the muxer is to give the caller the correct
  40. * cell for the operations they want to perform.
  41. *
  42. * |findActive| will be used when the caller wants a cell that supports
  43. * add-operations. This will be used when saving new content to storage.
  44. *
  45. * |findAll| will be used when the caller want to look at all the content
  46. * in storage.
  47. *
  48. * |resolvePath| will be used to convert a path (from |findActive| and
  49. * |findAll|) into a cell, which it then returns.
  50. *
  51. * @implements {shaka.util.IDestroyable}
  52. * @export
  53. */
  54. shaka.offline.StorageMuxer = class {
  55. /** */
  56. constructor() {
  57. /**
  58. * A key in this map is the name given when registering a StorageMechanism.
  59. *
  60. * @private {!Map<string, !shaka.extern.StorageMechanism>}
  61. */
  62. this.mechanisms_ = new Map();
  63. }
  64. // TODO: revisit this when the compiler supports partially-exported classes.
  65. /**
  66. * Free all resources used by the muxer, mechanisms, and cells. This should
  67. * not affect the stored content.
  68. *
  69. * @override
  70. * @export
  71. */
  72. destroy() {
  73. /** @type {!Array<!Promise>} */
  74. const destroys = [];
  75. for (const mechanism of this.mechanisms_.values()) {
  76. destroys.push(mechanism.destroy());
  77. }
  78. // Empty the map so that subsequent calls will be no-ops.
  79. this.mechanisms_.clear();
  80. return Promise.all(destroys);
  81. }
  82. /**
  83. * Initialize the storage muxer. This must be called before any other calls.
  84. * This will initialize the muxer to use all mechanisms that have been
  85. * registered with |StorageMuxer.register|.
  86. *
  87. * @return {!Promise}
  88. */
  89. init() {
  90. // Add the new instance of each mechanism to the muxer.
  91. const registry = shaka.offline.StorageMuxer.getRegistry_();
  92. registry.forEach((factory, name) => {
  93. const mech = factory();
  94. if (mech) {
  95. this.mechanisms_.set(name, mech);
  96. } else {
  97. shaka.log.info(
  98. 'Skipping ' + name + ' as it is not supported on this platform');
  99. }
  100. });
  101. /** @type {!Array<!Promise>} */
  102. const initPromises = [];
  103. for (const mechanism of this.mechanisms_.values()) {
  104. initPromises.push(mechanism.init());
  105. }
  106. return Promise.all(initPromises);
  107. }
  108. /**
  109. * Get a promise that will resolve with a storage cell that supports
  110. * add-operations. If no cell can be found, the promise will be rejected.
  111. *
  112. * @return {shaka.offline.StorageCellHandle}
  113. */
  114. getActive() {
  115. /** @type {?shaka.offline.StorageCellHandle} */
  116. let handle = null;
  117. this.mechanisms_.forEach((mechanism, mechanismName) => {
  118. mechanism.getCells().forEach((cell, cellName) => {
  119. // If this cell is not useful to us or we already have a handle, then
  120. // we don't need to make a new handle.
  121. if (cell.hasFixedKeySpace() || handle) {
  122. return;
  123. }
  124. const path = {
  125. mechanism: mechanismName,
  126. cell: cellName,
  127. };
  128. handle = {
  129. path: path,
  130. cell: cell,
  131. };
  132. });
  133. });
  134. if (handle) {
  135. return /** @type {shaka.offline.StorageCellHandle} */(handle);
  136. }
  137. throw new shaka.util.Error(
  138. shaka.util.Error.Severity.CRITICAL,
  139. shaka.util.Error.Category.STORAGE,
  140. shaka.util.Error.Code.MISSING_STORAGE_CELL,
  141. 'Could not find a cell that supports add-operations');
  142. }
  143. /**
  144. * @param {function(!shaka.offline.StorageCellPath,
  145. * !shaka.extern.StorageCell)} callback
  146. */
  147. forEachCell(callback) {
  148. this.mechanisms_.forEach((mechanism, mechanismName) => {
  149. mechanism.getCells().forEach((cell, cellName) => {
  150. const path = {
  151. mechanism: mechanismName,
  152. cell: cellName,
  153. };
  154. callback(path, cell);
  155. });
  156. });
  157. }
  158. /**
  159. * Get a specific storage cell. The promise will resolve with the storage
  160. * cell if it is found. If the storage cell is not found, the promise will
  161. * be rejected.
  162. *
  163. * @param {string} mechanismName
  164. * @param {string} cellName
  165. * @return {!shaka.extern.StorageCell}
  166. */
  167. getCell(mechanismName, cellName) {
  168. const mechanism = this.mechanisms_.get(mechanismName);
  169. if (!mechanism) {
  170. throw new shaka.util.Error(
  171. shaka.util.Error.Severity.CRITICAL,
  172. shaka.util.Error.Category.STORAGE,
  173. shaka.util.Error.Code.MISSING_STORAGE_CELL,
  174. 'Could not find mechanism with name ' + mechanismName);
  175. }
  176. const cell = mechanism.getCells().get(cellName);
  177. if (!cell) {
  178. throw new shaka.util.Error(
  179. shaka.util.Error.Severity.CRITICAL,
  180. shaka.util.Error.Category.STORAGE,
  181. shaka.util.Error.Code.MISSING_STORAGE_CELL,
  182. 'Could not find cell with name ' + cellName);
  183. }
  184. return cell;
  185. }
  186. /**
  187. * @param {function(!shaka.extern.EmeSessionStorageCell)} callback
  188. */
  189. forEachEmeSessionCell(callback) {
  190. this.mechanisms_.forEach((mechanism, name) => {
  191. callback(mechanism.getEmeSessionCell());
  192. });
  193. }
  194. /**
  195. * Gets an arbitrary EME session cell that can be used for storing new session
  196. * info.
  197. *
  198. * @return {!shaka.extern.EmeSessionStorageCell}
  199. */
  200. getEmeSessionCell() {
  201. const mechanisms = Array.from(this.mechanisms_.keys());
  202. if (!mechanisms.length) {
  203. throw new shaka.util.Error(
  204. shaka.util.Error.Severity.CRITICAL,
  205. shaka.util.Error.Category.STORAGE,
  206. shaka.util.Error.Code.STORAGE_NOT_SUPPORTED,
  207. 'No supported storage mechanisms found');
  208. }
  209. return this.mechanisms_.get(mechanisms[0]).getEmeSessionCell();
  210. }
  211. /**
  212. * Find the cell that the path points to. A path is made up of a mount point
  213. * and a cell id. If a cell can be found, the cell will be returned. If no
  214. * cell is found, null will be returned.
  215. *
  216. * @param {shaka.offline.StorageCellPath} path
  217. * @return {shaka.extern.StorageCell}
  218. */
  219. resolvePath(path) {
  220. const mechanism = this.mechanisms_.get(path.mechanism);
  221. if (!mechanism) {
  222. return null;
  223. }
  224. return mechanism.getCells().get(path.cell);
  225. }
  226. /**
  227. * This will erase all previous content from storage. Using paths obtained
  228. * before calling |erase| is discouraged, as cells may have changed during a
  229. * erase.
  230. *
  231. * @return {!Promise}
  232. */
  233. async erase() {
  234. // If we have initialized, we will use the existing mechanism instances.
  235. /** @type {!Array<!shaka.extern.StorageMechanism>} */
  236. const mechanisms = Array.from(this.mechanisms_.values());
  237. const alreadyInitialized = mechanisms.length > 0;
  238. // If we have not initialized, we should still be able to erase. This is
  239. // critical to our ability to wipe the DB in case of a version mismatch.
  240. // If there are no instances, create temporary ones and destroy them later.
  241. if (!alreadyInitialized) {
  242. const registry = shaka.offline.StorageMuxer.getRegistry_();
  243. registry.forEach((factory, name) => {
  244. const mech = factory();
  245. if (mech) {
  246. mechanisms.push(mech);
  247. }
  248. });
  249. }
  250. // Erase all storage mechanisms.
  251. await Promise.all(mechanisms.map((m) => m.erase()));
  252. // If we were erasing temporary instances, destroy them, too.
  253. if (!alreadyInitialized) {
  254. await Promise.all(mechanisms.map((m) => m.destroy()));
  255. }
  256. }
  257. /**
  258. * Register a storage mechanism for use with the default storage muxer. This
  259. * will have no effect on any storage muxer already in main memory.
  260. *
  261. * @param {string} name
  262. * @param {function():shaka.extern.StorageMechanism} factory
  263. * @export
  264. */
  265. static register(name, factory) {
  266. shaka.offline.StorageMuxer.registry_.set(name, factory);
  267. }
  268. /**
  269. * Unregister a storage mechanism for use with the default storage muxer. This
  270. * will have no effect on any storage muxer already in main memory.
  271. *
  272. * @param {string} name The name that the storage mechanism was registered
  273. * under.
  274. * @export
  275. */
  276. static unregister(name) {
  277. shaka.offline.StorageMuxer.registry_.delete(name);
  278. }
  279. /**
  280. * Check if there is support for storage on this platform. It is assumed that
  281. * if there are any mechanisms registered, it means that storage is supported
  282. * on this platform. We do not check if the mechanisms have any cells.
  283. *
  284. * @return {boolean}
  285. */
  286. static support() {
  287. const registry = shaka.offline.StorageMuxer.getRegistry_();
  288. // Make sure that we will have SOME mechanisms created by creating a
  289. // mechanism and immediately destroying it.
  290. for (const create of registry.values()) {
  291. const instance = create();
  292. if (instance) {
  293. instance.destroy();
  294. return true;
  295. }
  296. }
  297. return false;
  298. }
  299. /**
  300. * Replace the mechanism map used by the muxer. This should only be used
  301. * in testing.
  302. *
  303. * @param {Map<string, function(): shaka.extern.StorageMechanism>} map
  304. */
  305. static overrideSupport(map) {
  306. shaka.offline.StorageMuxer.override_ = map;
  307. }
  308. /**
  309. * Undo a previous call to |overrideSupport|.
  310. */
  311. static clearOverride() {
  312. shaka.offline.StorageMuxer.override_ = null;
  313. }
  314. /**
  315. * Get the registry. If the support has been disabled, this will always
  316. * an empty registry. Reading should always be done via |getRegistry_|.
  317. *
  318. * @return {!Map<string, function(): shaka.extern.StorageMechanism>}
  319. * @private
  320. */
  321. static getRegistry_() {
  322. const override = shaka.offline.StorageMuxer.override_;
  323. const registry = shaka.offline.StorageMuxer.registry_;
  324. if (COMPILED) {
  325. return registry;
  326. } else {
  327. return override || registry;
  328. }
  329. }
  330. };
  331. /**
  332. * @private {Map<string, function(): shaka.extern.StorageMechanism>}
  333. */
  334. shaka.offline.StorageMuxer.override_ = null;
  335. /**
  336. * @private {!Map<string, function(): shaka.extern.StorageMechanism>}
  337. */
  338. shaka.offline.StorageMuxer.registry_ = new Map();