Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/StorageAccessor/DirectVault.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type { Stat } from "obsidian";
import { FileType, StorageAccessorTypes } from "../storage.ts";
import { normalizePath, type Stat } from "obsidian";
import { StorageAccessor } from "./StorageAccessor.ts";
import { FileType, StorageAccessorTypes } from "./storage-accessor-types.ts";


export class DirectVault extends StorageAccessor {
type = StorageAccessorTypes.DIRECT;

sep = "/"; // Always use / as separator on vault.

normalizePath(path: string): string {
return normalizePath(path);
}

async createFolder(absolutePath: string): Promise<void> {
await this.app.vault.adapter.mkdir(absolutePath);
}
Expand Down
36 changes: 36 additions & 0 deletions src/StorageAccessor/ExternalVaultFilesystem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// eslint-disable-next-line import/no-nodejs-modules -- Electron file reads return Node Buffers.
import { Buffer } from "node:buffer";
import { ExternalVaultFilesystem } from "./ExternalVaultFilesystem.ts";
import type { StorageAccessorHost } from "./storage-accessor-types.ts";

declare const Deno: {
test: (name: string, fn: () => void | Promise<void>) => void;
};

function assertEquals<T>(actual: T, expected: T, message: string) {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(`${message}\nactual=${JSON.stringify(actual)}\nexpected=${JSON.stringify(expected)}`);
}
}

Deno.test("ExternalVaultFilesystem: reads only the visible Node Buffer bytes", async () => {
const backing = Buffer.from([99, 1, 2, 3, 88]);
const visible = backing.subarray(1, 4);
const plugin = {
app: {
vault: {
adapter: {
fsPromises: {
readFile: async () => visible,
},
},
},
},
} as unknown as StorageAccessorHost;
const storage = new ExternalVaultFilesystem(plugin);

const result = await storage._readBinary("archive.bin");
if (result === false) throw new Error("Expected binary data");

assertEquals([...new Uint8Array(result)], [1, 2, 3], "Buffer byte offset and length should be preserved");
});
9 changes: 6 additions & 3 deletions src/StorageAccessor/ExternalVaultFilesystem.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { Stat } from "obsidian";
import { type FsAPI, FileType, StorageAccessorTypes } from "../storage.ts";
// eslint-disable-next-line import/no-nodejs-modules -- Electron exposes the Node filesystem API used here.
import type { promises } from "node:fs";
import { StorageAccessor } from "./StorageAccessor.ts";
import { FileType, StorageAccessorTypes } from "./storage-accessor-types.ts";
import { toArrayBuffer } from "../util.ts";

type FsAPI = Pick<typeof promises, "mkdir" | "writeFile" | "readFile" | "stat" | "rm">;

export class ExternalVaultFilesystem extends StorageAccessor {
type = StorageAccessorTypes.EXTERNAL;
Expand All @@ -21,7 +24,7 @@ export class ExternalVaultFilesystem extends StorageAccessor {
await this.fsPromises.mkdir(absolutePath, { recursive: true });
}

async ensureDirectory(fullPath: string) {
override async ensureDirectory(fullPath: string) {
const delimiter = this.sep;
const pathElements = fullPath.split(delimiter);
pathElements.pop();
Expand All @@ -42,7 +45,7 @@ export class ExternalVaultFilesystem extends StorageAccessor {

async _readBinary(path: string): Promise<ArrayBuffer | false> {
const buffer = await this.fsPromises.readFile(path);
return toArrayBuffer(buffer.buffer)
return toArrayBuffer(buffer);
}

async deleteBinary(path: string): Promise<boolean> {
Expand Down
8 changes: 6 additions & 2 deletions src/StorageAccessor/NormalVault.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { TFile, TFolder, type Stat } from "obsidian";
import { FileType, StorageAccessorTypes } from "../storage.ts";
import { normalizePath, TFile, TFolder, type Stat } from "obsidian";
import { StorageAccessor } from "./StorageAccessor.ts";
import { FileType, StorageAccessorTypes } from "./storage-accessor-types.ts";


export class NormalVault extends StorageAccessor {
type = StorageAccessorTypes.NORMAL;

sep = "/"; // Always use / as separator on vault.

normalizePath(path: string): string {
return normalizePath(path);
}

async createFolder(absolutePath: string): Promise<void> {
await this.app.vault.createFolder(absolutePath);
}
Expand Down
10 changes: 7 additions & 3 deletions src/StorageAccessor/S3Bucket.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { S3 } from "@aws-sdk/client-s3";
import type { Stat } from "obsidian";
import { normalizePath, type Stat } from "obsidian";
import { ObsHttpHandler } from "../ObsHttpHandler.ts";
import { FileType, StorageAccessorTypes } from "../storage.ts";
import { StorageAccessor } from "./StorageAccessor.ts";
import { FileType, StorageAccessorTypes } from "./storage-accessor-types.ts";
import { toArrayBuffer } from "../util.ts";


export class S3Bucket extends StorageAccessor {
type = StorageAccessorTypes.S3;
sep = "/";

normalizePath(path: string): string {
return normalizePath(path);
}

createFolder(absolutePath: string): Promise<void> {
// S3 does not have folder concept. So, we don't need to create folder.
return Promise.resolve();
}
ensureDirectory(fullPath: string): Promise<void> {
override ensureDirectory(fullPath: string): Promise<void> {
return Promise.resolve();
}

Expand Down
20 changes: 12 additions & 8 deletions src/StorageAccessor/StorageAccessor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { normalizePath, type Stat } from "obsidian";
import type DiffZipBackupPlugin from "../../main.ts";
import { type StorageAccessorType, FileType, decryptCompatOpenSSL, encryptCompatOpenSSL } from "../storage.ts";
import type { Stat } from "obsidian";
import { OpenSSLCompat } from "octagonal-wheels/encryption";
import {
type StorageAccessorHost,
type StorageAccessorType,
FileType,
} from "./storage-accessor-types.ts";
import { toArrayBuffer } from "../util.ts";

const decryptCompatOpenSSL = OpenSSLCompat.CBC.decryptCBC;
const encryptCompatOpenSSL = OpenSSLCompat.CBC.encryptCBC;

export abstract class StorageAccessor {
abstract type: StorageAccessorType;
abstract sep: string;
public plugin: DiffZipBackupPlugin;
public plugin: StorageAccessorHost;
get app() {
return this.plugin.app;
}
Expand All @@ -21,7 +27,7 @@ export abstract class StorageAccessor {
}
public isLocal: boolean = false;

constructor(plugin: DiffZipBackupPlugin, basePath?: string, isLocal?: boolean) {
constructor(plugin: StorageAccessorHost, basePath?: string, isLocal?: boolean) {
this.basePath = basePath || "";
this.plugin = plugin;
this.isLocal = isLocal || false;
Expand Down Expand Up @@ -73,9 +79,7 @@ export abstract class StorageAccessor {
abstract _readBinary(path: string, preventUseCache?: boolean): Promise<ArrayBuffer | false>;
abstract deleteBinary(path: string): Promise<boolean>;

normalizePath(path: string): string {
return normalizePath(path);
}
abstract normalizePath(path: string): string;
abstract stat(path: string): Promise<false | Stat>;

async ensureDirectory(fullPath: string) {
Expand Down
30 changes: 30 additions & 0 deletions src/StorageAccessor/storage-accessor-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { App } from "obsidian";
import type { DiffZipBackupSettings } from "../types.ts";

/**
* DiffZip-owned context supplied to storage accessor implementations.
*
* This is an internal composition shape, not a platform-neutral storage contract.
*/
export interface StorageAccessorHost {
readonly app: App;
readonly settings: DiffZipBackupSettings;
}

/** File kind reported by a storage accessor. */
export enum FileType {
Missing,
File,
Folder,
}

/** Stable storage accessor identifiers. */
export const StorageAccessorTypes = {
NORMAL: "normal",
DIRECT: "direct",
EXTERNAL: "external",
S3: "s3",
} as const;

/** Identifier for one DiffZip storage accessor implementation. */
export type StorageAccessorType = typeof StorageAccessorTypes[keyof typeof StorageAccessorTypes];
37 changes: 9 additions & 28 deletions src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,21 @@
* Abstract class for storage accessors and its implementations.
*/
import type DiffZipBackupPlugin from "../main.ts";
// Because of this is a type-only import.
// eslint-disable-next-line import/no-nodejs-modules -- type only import of promises
import type { promises } from "node:fs";
import { OpenSSLCompat } from "octagonal-wheels/encryption";
import { NormalVault } from "./StorageAccessor/NormalVault.ts";
import { DirectVault } from "./StorageAccessor/DirectVault.ts";
import { ExternalVaultFilesystem } from "./StorageAccessor/ExternalVaultFilesystem.ts";
import { S3Bucket } from "./StorageAccessor/S3Bucket.ts";
import type { StorageAccessor } from "./StorageAccessor/StorageAccessor.ts";
export const decryptCompatOpenSSL = OpenSSLCompat.CBC.decryptCBC;
export const encryptCompatOpenSSL = OpenSSLCompat.CBC.encryptCBC;
import {
StorageAccessorTypes,
type StorageAccessorType,
} from "./StorageAccessor/storage-accessor-types.ts";

export enum FileType {
"Missing",
"File",
"Folder",
}

export type FsAPI = {
mkdir: typeof promises.mkdir;
writeFile: typeof promises.writeFile;
readFile: typeof promises.readFile;
stat: typeof promises.stat;
rm: typeof promises.rm;
};

export const StorageAccessorTypes = {
NORMAL: "normal",
DIRECT: "direct",
EXTERNAL: "external",
S3: "s3",
} as const;

export type StorageAccessorType = typeof StorageAccessorTypes[keyof typeof StorageAccessorTypes];
export {
FileType,
StorageAccessorTypes,
type StorageAccessorType,
} from "./StorageAccessor/storage-accessor-types.ts";

export function getStorageTypeForBackupAccess(plugin: DiffZipBackupPlugin): StorageAccessorType {
if (plugin.isDesktopMode) {
Expand Down
Loading