Skip to content

Commit

Permalink
fix(offline): Clean up orphaned segments on abort
Browse files Browse the repository at this point in the history
If a storage operation is aborted (via API, not via closing the page),
this will now be cleaned up from the database.  More work is needed to
find and remove orphaned segments in the database.

Related to shaka-project#4166 and follow-up to PR shaka-project#4176.
  • Loading branch information
joeyparrish committed Apr 29, 2022
1 parent c1c9613 commit e5409cc
Showing 1 changed file with 54 additions and 56 deletions.
110 changes: 54 additions & 56 deletions lib/offline/storage.js
Expand Up @@ -485,42 +485,54 @@ shaka.offline.Storage = class {

const usingBgFetch = false; // TODO: Get.

if (this.getManifestIsEncrypted_(manifest) && usingBgFetch &&
!this.getManifestIncludesInitData_(manifest)) {
// Background fetch can't make DRM sessions, so if we have to get the
// init data from the init segments, download those first before anything
// else.
await download(toDownload.filter((info) => info.isInitSegment), true);
this.ensureNotDestroyed_();
toDownload = toDownload.filter((info) => !info.isInitSegment);

// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

manifestDB =
(await shaka.offline.Storage.assignSegmentsToManifest(
manifestId, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_())) || manifestDB;
this.ensureNotDestroyed_();
}
try {
if (this.getManifestIsEncrypted_(manifest) && usingBgFetch &&
!this.getManifestIncludesInitData_(manifest)) {
// Background fetch can't make DRM sessions, so if we have to get the
// init data from the init segments, download those first before
// anything else.
await download(toDownload.filter((info) => info.isInitSegment), true);
this.ensureNotDestroyed_();
toDownload = toDownload.filter((info) => !info.isInitSegment);

if (!usingBgFetch) {
await download(toDownload, false);
this.ensureNotDestroyed_();
// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

manifestDB =
(await shaka.offline.Storage.assignSegmentsToManifest(
manifestId, pendingManifestUpdates, pendingDataSize,
() => this.ensureNotDestroyed_())) || manifestDB;
this.ensureNotDestroyed_();
await shaka.offline.Storage.assignSegmentsToManifest(
storage, manifestId, manifestDB, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_());
this.ensureNotDestroyed_();
}

goog.asserts.assert(
!manifestDB.isIncomplete, 'The manifest should be complete by now');
} else {
// TODO: Send the request to the service worker. Don't await the result.
if (!usingBgFetch) {
await download(toDownload, false);
this.ensureNotDestroyed_();

// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

await shaka.offline.Storage.assignSegmentsToManifest(
storage, manifestId, manifestDB, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_());
this.ensureNotDestroyed_();

goog.asserts.assert(
!manifestDB.isIncomplete, 'The manifest should be complete by now');
} else {
// TODO: Send the request to the service worker. Don't await the result.
}
} catch (error) {
const dataKeys = Object.values(pendingManifestUpdates);
// Remove these pending segments that are not yet linked to the manifest.
await storage.removeSegments(dataKeys, (key) => {});

throw error;
}
}

Expand Down Expand Up @@ -550,32 +562,21 @@ shaka.offline.Storage = class {
* It is up to the caller to ensure that this method is not called
* concurrently on the same manifest.
*
* @param {shaka.extern.StorageCell} storage
* @param {number} manifestId
* @param {!shaka.extern.ManifestDB} manifestDB
* @param {!Object.<string, number>} manifestUpdates
* @param {number} dataSizeUpdate
* @param {function()} throwIfAbortedFn A function that should throw if the
* download has been aborted.
* @return {!Promise.<?shaka.extern.ManifestDB>}
* @return {!Promise}
*/
static async assignSegmentsToManifest(
manifestId, manifestUpdates, dataSizeUpdate, throwIfAbortedFn) {
/** @type {shaka.offline.StorageMuxer} */
const muxer = new shaka.offline.StorageMuxer();

storage, manifestId, manifestDB, manifestUpdates, dataSizeUpdate,
throwIfAbortedFn) {
let manifestUpdated = false;
let activeHandle;
/** @type {!shaka.extern.ManifestDB} */
let manifestDB;

try {
await muxer.init();
activeHandle = await muxer.getActive();

// Load the manifest.
const manifests = await activeHandle.cell.getManifests([manifestId]);
throwIfAbortedFn();
manifestDB = manifests[0];

// Assign the stored data to the manifest.
let complete = true;
for (const stream of manifestDB.streams) {
Expand Down Expand Up @@ -616,26 +617,23 @@ shaka.offline.Storage = class {
manifestDB.isIncomplete = false;
}

// Re-store the manifest.
await activeHandle.cell.updateManifest(manifestId, manifestDB);
// Update the manifest.
await storage.updateManifest(manifestId, manifestDB);
manifestUpdated = true;
throwIfAbortedFn();
} catch (e) {
await shaka.offline.Storage.cleanStoredManifest(manifestId);

if (activeHandle && !manifestUpdated) {
if (!manifestUpdated) {
const dataKeys = Object.values(manifestUpdates);
// The cleanStoredManifest method will not "see" any segments that have
// been downloaded but not assigned to the manifest yet. So un-store
// them separately.
await activeHandle.cell.removeSegments(dataKeys, (key) => {});
await storage.removeSegments(dataKeys, (key) => {});
}

throw e;
} finally {
await muxer.destroy();
}
return manifestDB;
}

/**
Expand Down

0 comments on commit e5409cc

Please sign in to comment.