mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-26 17:46:26 +03:00
e6302f5d73
The only time we ever need to update a manifest value is when we update the expiration time. To make it easier to support updating expiration time across different version, only allow that one value to be updated. Change-Id: I621ec744019802edc65b059baa7f5cf0c476b0b1
246 lines
6.6 KiB
JavaScript
246 lines
6.6 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
goog.provide('shaka.offline.indexeddb.V2StorageCell');
|
|
|
|
goog.require('shaka.offline.indexeddb.DBConnection');
|
|
goog.require('shaka.util.Error');
|
|
|
|
|
|
/**
|
|
* The V2StorageCell is for all stores that follow the shakaExterns V2 offline
|
|
* types. This storage cell will work for both IndexedDB version 2 and 3 as
|
|
* both used the shakaExterns V2 offline types.
|
|
*
|
|
* @implements {shakaExtern.StorageCell}
|
|
*/
|
|
shaka.offline.indexeddb.V2StorageCell = class {
|
|
/**
|
|
* @param {IDBDatabase} connection
|
|
* @param {string} segmentStore
|
|
* @param {string} manifestStore
|
|
* @param {boolean} isFixedKey
|
|
*/
|
|
constructor(connection,
|
|
segmentStore,
|
|
manifestStore,
|
|
isFixedKey) {
|
|
/** @private {!shaka.offline.indexeddb.DBConnection} */
|
|
this.connection_ = new shaka.offline.indexeddb.DBConnection(connection);
|
|
|
|
/** @private {string} */
|
|
this.segmentStore_ = segmentStore;
|
|
/** @private {string} */
|
|
this.manifestStore_ = manifestStore;
|
|
|
|
/** @private {boolean} */
|
|
this.isFixedKey_ = isFixedKey;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
destroy() { return this.connection_.destroy(); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
hasFixedKeySpace() { return this.isFixedKey_; }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
addSegments(segments) { return this.add_(this.segmentStore_, segments); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
removeSegments(keys) { return this.remove_(this.segmentStore_, keys); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
getSegments(keys) { return this.get_(this.segmentStore_, keys); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
addManifests(manifests) { return this.add_(this.manifestStore_, manifests); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
updateManifestExpiration(key, newExpiration) {
|
|
let op = this.connection_.startReadWriteOperation(this.manifestStore_);
|
|
let store = op.store();
|
|
store.get(key).onsuccess = (e) => {
|
|
let found = e.target.result;
|
|
// If we can't find the value, then there is nothing for us to update.
|
|
if (found) {
|
|
found.expiration = newExpiration;
|
|
store.put(found, key);
|
|
}
|
|
};
|
|
|
|
return op.promise();
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
removeManifests(keys) { return this.remove_(this.manifestStore_, keys); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
getManifests(keys) { return this.get_(this.manifestStore_, keys); }
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
getAllManifests() {
|
|
let op = this.connection_.startReadOnlyOperation(this.manifestStore_);
|
|
let store = op.store();
|
|
|
|
let values = {};
|
|
|
|
store.openCursor().onsuccess = (event) => {
|
|
// When we reach the end of the data that the cursor is iterating
|
|
// over, |event.target.result| will be null to signal the end of the
|
|
// iteration.
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/continue
|
|
let cursor = event.target.result;
|
|
if (!cursor) {
|
|
return;
|
|
}
|
|
|
|
values[cursor.key] = cursor.value;
|
|
|
|
// Go to the next item in the store, which will cause |onsuccess| to be
|
|
// called again.
|
|
cursor.continue();
|
|
};
|
|
|
|
// Wait until the operation completes or else values may be missing from
|
|
// |values|.
|
|
return op.promise().then(() => values);
|
|
}
|
|
|
|
/**
|
|
* @param {string} storeName
|
|
* @param {!Array.<T>} values
|
|
* @return {!Promise.<!Array.<number>>}
|
|
* @template T
|
|
* @private
|
|
*/
|
|
add_(storeName, values) {
|
|
// In the case that this storage cell does not support add-operations, just
|
|
// reject the request immediately instead of allowing it to try.
|
|
if (this.isFixedKey_) {
|
|
return Promise.reject(new shaka.util.Error(
|
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
shaka.util.Error.Category.STORAGE,
|
|
shaka.util.Error.Code.NEW_KEY_OPERATION_NOT_SUPPORTED,
|
|
'Cannot add new value to ' + storeName));
|
|
}
|
|
|
|
let op = this.connection_.startReadWriteOperation(storeName);
|
|
let store = op.store();
|
|
|
|
/** @type {!Array.<number>} */
|
|
let keys = [];
|
|
|
|
// Write each segment out. When each request completes, the key will
|
|
// be in |event.target.result| as can be seen in
|
|
// https://w3c.github.io/IndexedDB/#key-generator-construct.
|
|
values.forEach((value) => {
|
|
let request = store.add(value);
|
|
request.onsuccess = (event) => {
|
|
let key = event.target.result;
|
|
keys.push(key);
|
|
};
|
|
});
|
|
|
|
// Wait until the operation completes or else |keys| will not be fully
|
|
// populated.
|
|
return op.promise().then(() => keys);
|
|
}
|
|
|
|
/**
|
|
* @param {string} storeName
|
|
* @param {!Array.<number>} keys
|
|
* @return {!Promise}
|
|
* @private
|
|
*/
|
|
remove_(storeName, keys) {
|
|
let op = this.connection_.startReadWriteOperation(storeName);
|
|
let store = op.store();
|
|
|
|
keys.forEach((key) => {
|
|
store.delete(key);
|
|
});
|
|
|
|
return op.promise();
|
|
}
|
|
|
|
/**
|
|
* @param {string} storeName
|
|
* @param {!Array.<number>} keys
|
|
* @return {!Promise.<!Array.<T>>}
|
|
* @template T
|
|
* @private
|
|
*/
|
|
get_(storeName, keys) {
|
|
let op = this.connection_.startReadOnlyOperation(storeName);
|
|
let store = op.store();
|
|
|
|
let values = {};
|
|
let missing = [];
|
|
|
|
// Use a map to store the objects so that we can reorder the results to
|
|
// match the order of |keys|.
|
|
keys.forEach((key) => {
|
|
let request = store.get(key);
|
|
request.onsuccess = () => {
|
|
// Make sure a defined value was found. Indexeddb treats no-value found
|
|
// as a success with an undefined result.
|
|
if (request.result == undefined) {
|
|
missing.push(key);
|
|
}
|
|
|
|
values[key] = request.result;
|
|
};
|
|
});
|
|
|
|
// Wait until the operation completes or else values may be missing from
|
|
// |values|. Use the original key list to convert the map to a list so that
|
|
// the order will match.
|
|
return op.promise().then(() => {
|
|
if (missing.length) {
|
|
return Promise.reject(new shaka.util.Error(
|
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
shaka.util.Error.Category.STORAGE,
|
|
shaka.util.Error.Code.KEY_NOT_FOUND,
|
|
'Could not find values for ' + missing
|
|
));
|
|
}
|
|
|
|
return keys.map((key) => values[key]);
|
|
});
|
|
}
|
|
};
|