Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 1x 4x 1x 1x 1x 1x 1x 11x 11x 11x 3x 6x 6x 1x 1x 1x 1x 7x 3x 3x 1x 1x 2x 2x 1x 1x 3x 3x 1x 2x 2x 1x 2x 2x 2x 2x 2x 2x 2x 2x | import * as disc from "../fdc.js";
import { GoogleDriveLoader } from "./google-drive.js";
import { toast } from "./toast.js";
import { errorText } from "./reporting.js";
import { replaceOrAddExtension } from "../archive.js";
import { describeDriveFile } from "./media-catalogue.js";
import { failedFor, LoadFailures } from "../loader.js";
import { DiscLayout } from "../disc.js";
const FluxBlankSectorExtension = ".dsd";
const saveFailed = (name) => (error) =>
toast(
`Google Drive would not take changes to ${name} (${errorText(error)}). Use the drive's Save button to keep a copy.`,
{ title: "Google Drive", quietKey: "quietDriveSaveFailed" },
);
const isUnauthorised = (error) => error?.status === 401 || error?.result?.error?.code === 401;
/** @returns {import("../disc.js").Disc} a blank sector image with every track written out, ready to save as flux */
function formattedDisc(name) {
const sectorName = replaceOrAddExtension(name, FluxBlankSectorExtension);
const data = disc.blankImage(disc.guessDiscTypeFromName(sectorName), name);
return disc.discFor(sectorName, data, undefined, DiscLayout.contiguous);
}
/**
* Google Drive as a media source: signing in, listing the user's discs,
* loading one, and creating a new one there.
*/
export class GoogleDriveSource {
constructor({ media, loader = new GoogleDriveLoader() }) {
this.googleDrive = loader;
media.addSource("drive", (cat, layout) => this.load(cat, layout));
media.addLister("gdrive", async () =>
this.connected ? (await this.withToken(() => this.googleDrive.listFiles())).map(describeDriveFile) : [],
);
}
/** Runs a Drive call; a 401 means the token has lapsed, so the account is connected no longer. */
async withToken(call) {
try {
return await call();
} catch (error) {
const unauthorised = isUnauthorised(error);
Eif (unauthorised) this.googleDrive.authorized = false;
// gapi rejects with a response object; its message is a level down.
const thrown =
error instanceof Error ? error : new Error(error?.result?.error?.message ?? errorText(error));
throw unauthorised ? failedFor(thrown, LoadFailures.unauthorised) : thrown;
}
}
get connected() {
return !!this.googleDrive.authorized;
}
/** Loads the Google client and signs in; false when either is refused. Needs a user gesture. */
async connect() {
try {
await this.googleDrive.initialise();
} catch (error) {
toast(`Google Drive is unavailable: ${errorText(error)}`, { title: "Google Drive" });
return false;
}
try {
return await this.googleDrive.authorize(false);
} catch (error) {
toast(`There was an error accessing your Google Drive account: ${errorText(error)}`, {
title: "Google Drive",
});
return false;
}
}
/**
* A disc from the Drive, once signed in. Signing in needs a click, so a
* load from elsewhere (the URL at startup, say) can only ask for one.
*/
async load(cat, layout) {
Iif (!(await this.googleDrive.initialise())) throw new Error("Google Drive is not available");
if (!(await this.googleDrive.authorize(true)))
throw failedFor(
new Error("Google Drive is not connected; use Connect Google Drive in the media window first"),
LoadFailures.unauthorised,
);
const loaded = await this.withToken(() => this.googleDrive.load(cat.id, layout, saveFailed(cat.name)));
if (!loaded.savesChanges) {
toast(`${cat.name} is read only on Google Drive, so changes to it are not written back.`, {
title: "Google Drive",
quietKey: "quietDriveReadOnly",
});
}
return loaded;
}
/**
* Makes a blank, formatted disc on the Drive. An HFE is a double-sided DFS
* disc, formatted and empty, recorded as flux.
*
* @returns {Promise<{ref: string, disc: import("../disc.js").Disc}>} the disc, and how the URL names it
*/
async createBlank(name, layout) {
const discType = disc.guessDiscTypeFromName(name);
const data = discType.isFluxImage ? discType.saver(formattedDisc(name)) : disc.blankImage(discType, name);
return this.createFrom(name, data, layout);
}
/** Puts an image on the Drive under `name`, as a disc that saves its changes there. */
async createFrom(name, data, layout) {
const fullName = replaceOrAddExtension(name, disc.guessDiscTypeFromName(name).extension);
const result = await this.withToken(() =>
this.googleDrive.create(fullName, data, layout, saveFailed(fullName)),
);
return { ref: `gd:${result.fileId}/${fullName}`, disc: result.disc };
}
}
|