From c275ad1e0e051f4c371d5ea9d5d27e74da187597 Mon Sep 17 00:00:00 2001 From: joshuakto <34743132+joshuakto@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:18:25 +0800 Subject: [PATCH 1/4] improved typing for readability and efficiency --- main.ts | 44 +++++++++++++------------ src/fit.ts | 46 ++++++++++++++++---------- src/fitPull.ts | 85 ++++++++++++++++++++++++++++--------------------- src/fitPush.ts | 59 ++++++++++++++++++---------------- src/utils.ts | 73 +++++++++++++++++++++++++++++------------- src/vaultOps.ts | 6 ++-- 6 files changed, 189 insertions(+), 124 deletions(-) diff --git a/main.ts b/main.ts index 5dcd56e..875e9ec 100644 --- a/main.ts +++ b/main.ts @@ -106,7 +106,7 @@ export default class FitPlugin extends Plugin { this.fitPullRibbonIconEl.removeClass('animate-icon') return }// early return to abort pull - await this.fitPull.pullRemoteToLocal(...[...checkResult, this.saveLocalStoreCallback]) + await this.fitPull.pullRemoteToLocal(checkResult, this.saveLocalStoreCallback) this.fitPullRibbonIconEl.removeClass('animate-icon') }); @@ -126,9 +126,7 @@ export default class FitPlugin extends Plugin { this.fitPushRibbonIconEl.removeClass('animate-icon') return } // early return if prepush checks not passed - const [changedFiles, latestRemoteCommitSha] = checksResult - await this.fitPush.pushChangedFilesToRemote( - changedFiles, latestRemoteCommitSha, this.saveLocalStoreCallback) + await this.fitPush.pushChangedFilesToRemote(checksResult, this.saveLocalStoreCallback) this.fitPushRibbonIconEl.removeClass('animate-icon') }); @@ -154,11 +152,13 @@ export default class FitPlugin extends Plugin { // Command for computing an inputed file path's local sha for debugging purposes this.addCommand({ id: 'recompute-local-sha', - name: 'Update local store with new local sha, essentially ignoring local changes when pulling/pushing (Debug)', + name: `Update local store with new local sha, essentially + ignoring local changes when pulling/pushing (Debug)`, callback: async () => { this.localStore.localSha = await this.fit.computeLocalSha() this.saveLocalStore() - new Notice("Local sha recomputed and stored, they will not be considered in future push/pull.") + new Notice(`Local sha recomputed and stored, they will + not be considered in future push/pull.`) } }); @@ -207,27 +207,29 @@ export default class FitPlugin extends Plugin { if (isMobile && !userSetting) { settings = Object.assign({}, DEFAULT_MOBILE_SETTINGS); } - const settingsObj: FitSettings = Object.keys(DEFAULT_SETTINGS).reduce((obj, key: keyof FitSettings) => { - if (settings.hasOwnProperty(key)) { - if (key == "verbose") { - obj[key] = Boolean(settings["verbose"]); - } else { - obj[key] = settings[key]; + const settingsObj: FitSettings = Object.keys(DEFAULT_SETTINGS).reduce( + (obj, key: keyof FitSettings) => { + if (settings.hasOwnProperty(key)) { + if (key == "verbose") { + obj[key] = Boolean(settings["verbose"]); + } else { + obj[key] = settings[key]; + } } - } - return obj; - }, {} as FitSettings); + return obj; + }, {} as FitSettings); this.settings = settingsObj } async loadLocalStore() { const localStore = Object.assign({}, DEFAULT_LOCAL_STORE, await this.loadData()); - const localStoreObj: LocalStores = Object.keys(DEFAULT_LOCAL_STORE).reduce((obj, key: keyof LocalStores) => { - if (localStore.hasOwnProperty(key)) { - obj[key] = localStore[key]; - } - return obj; - }, {} as LocalStores); + const localStoreObj: LocalStores = Object.keys(DEFAULT_LOCAL_STORE).reduce( + (obj, key: keyof LocalStores) => { + if (localStore.hasOwnProperty(key)) { + obj[key] = localStore[key]; + } + return obj; + }, {} as LocalStores); this.localStore = localStoreObj } diff --git a/src/fit.ts b/src/fit.ts index 199b878..bb332dd 100644 --- a/src/fit.ts +++ b/src/fit.ts @@ -1,6 +1,8 @@ import { LocalStores, FitSettings } from "main" import { Vault } from "obsidian" import { Octokit } from "@octokit/core" +import { LocalChange } from "./fitPush" +import { RECOGNIZED_BINARY_EXT } from "./utils" type TreeNode = {path: string, mode: string, type: string, sha: string | null} @@ -21,7 +23,7 @@ export interface IFit { getCommitTreeSha: (ref: string) => Promise getRemoteTreeSha: (tree_sha: string) => Promise<{[k:string]: string}> createBlob: (content: string, encoding: string) =>Promise - createTreeNodeFromFile: ({path, type, extension}: {path: string, type: string, extension?: string}) => Promise + createTreeNodeFromFile: ({path, status, extension}: LocalChange) => Promise createCommit: (treeSha: string, parentSha: string) =>Promise updateRef: (sha: string, ref: string) => Promise getBlob: (file_sha:string) =>Promise @@ -46,7 +48,9 @@ export class Fit implements IFit { this.loadLocalStore(localStores) this.vault = vault this.headers = { - "If-None-Match": '', // Hack to disable caching which leads to inconsistency for read after write https://github.com/octokit/octokit.js/issues/890 + // Hack to disable caching which leads to inconsistency for + // read after write https://github.com/octokit/octokit.js/issues/890 + "If-None-Match": '', 'X-GitHub-Api-Version': '2022-11-28' } } @@ -109,9 +113,11 @@ export class Fit implements IFit { return await this.getRef(ref) } - // ref Can be a commit SHA, branch name (heads/BRANCH_NAME), or tag name (tags/TAG_NAME), refers to https://git-scm.com/book/en/v2/Git-Internals-Git-References + // ref Can be a commit SHA, branch name (heads/BRANCH_NAME), or tag name (tags/TAG_NAME), + // refers to https://git-scm.com/book/en/v2/Git-Internals-Git-References async getCommitTreeSha(ref: string): Promise { - const {data: commit} = await this.octokit.request( `GET /repos/${this.owner}/${this.repo}/commits/${ref}`, { + const {data: commit} = await this.octokit.request( + `GET /repos/${this.owner}/${this.repo}/commits/${ref}`, { owner: this.owner, repo: this.repo, ref, @@ -121,7 +127,8 @@ export class Fit implements IFit { } async getTree(tree_sha: string): Promise { - const { data: tree } = await this.octokit.request(`GET /repos/${this.owner}/${this.repo}/git/trees/${tree_sha}`, { + const { data: tree } = await this.octokit.request( + `GET /repos/${this.owner}/${this.repo}/git/trees/${tree_sha}`, { owner: this.owner, repo: this.repo, tree_sha, @@ -135,7 +142,8 @@ export class Fit implements IFit { async getRemoteTreeSha(tree_sha: string): Promise<{[k:string]: string}> { const remoteTree = await this.getTree(tree_sha) const remoteSha = Object.fromEntries(remoteTree.map((node: TreeNode) : [string, string] | null=>{ - // currently ignoring directory changes, if you'd like to upload a new directory, a quick hack would be creating an empty file inside + // currently ignoring directory changes, if you'd like to upload a new directory, + // a quick hack would be creating an empty file inside if (node.type=="blob") { if (!node.path || !node.sha) { throw new Error("Path or sha not found for blob node in remote"); @@ -148,7 +156,8 @@ export class Fit implements IFit { } async createBlob(content: string, encoding: string): Promise { - const {data: blob} = await this.octokit.request(`POST /repos/${this.owner}/${this.repo}/git/blobs`, { + const {data: blob} = await this.octokit.request( + `POST /repos/${this.owner}/${this.repo}/git/blobs`, { owner: this.owner, repo: this.repo, content, @@ -159,8 +168,8 @@ export class Fit implements IFit { } - async createTreeNodeFromFile({path, type, extension}: {path: string, type: string, extension?: string}): Promise { - if (type === "deleted") { + async createTreeNodeFromFile({path, status, extension}: LocalChange): Promise { + if (status === "deleted") { return { path, mode: '100644', @@ -169,11 +178,13 @@ export class Fit implements IFit { } } if (!this.vault.adapter.exists(path)) { - throw new Error("Unexpected error: attempting to createBlob for non-existent file, please file an issue on github with info to reproduce the issue."); + throw new Error( + `Unexpected error: attempting to createBlob for non-existent file, + please file an issue on github with info to reproduce the issue.`); } let encoding: string; let content: string - if (extension && ["pdf", "png", "jpeg"].includes(extension)) { + if (extension && RECOGNIZED_BINARY_EXT.includes(extension)) { encoding = "base64" const fileArrayBuf = await this.vault.adapter.readBinary(path) const uint8Array = new Uint8Array(fileArrayBuf); @@ -199,7 +210,8 @@ export class Fit implements IFit { treeNodes: Array, base_tree_sha: string): Promise { - const {data: newTree} = await this.octokit.request(`POST /repos/${this.owner}/${this.repo}/git/trees`, { + const {data: newTree} = await this.octokit.request( + `POST /repos/${this.owner}/${this.repo}/git/trees`, { owner: this.owner, repo: this.repo, tree: treeNodes, @@ -209,10 +221,10 @@ export class Fit implements IFit { return newTree.sha } - // eslint-disable-next-line @typescript-eslint/no-explicit-any async createCommit(treeSha: string, parentSha: string): Promise { const message = `Commit from ${this.deviceName} on ${new Date().toLocaleString()}` - const { data: createdCommit } = await this.octokit.request(`POST /repos/${this.owner}/${this.repo}/git/commits` ,{ + const { data: createdCommit } = await this.octokit.request( + `POST /repos/${this.owner}/${this.repo}/git/commits` , { owner: this.owner, repo: this.repo, message, @@ -224,7 +236,8 @@ export class Fit implements IFit { } async updateRef(sha: string, ref = `heads/${this.branch}`): Promise { - const { data:updatedRef } = await this.octokit.request(`PATCH /repos/${this.owner}/${this.repo}/git/refs/${ref}`, { + const { data:updatedRef } = await this.octokit.request( + `PATCH /repos/${this.owner}/${this.repo}/git/refs/${ref}`, { owner: this.owner, repo: this.repo, ref, @@ -235,7 +248,8 @@ export class Fit implements IFit { } async getBlob(file_sha:string): Promise { - const { data: blob } = await this.octokit.request(`GET /repos/${this.owner}/${this.repo}/git/blobs/${file_sha}`, { + const { data: blob } = await this.octokit.request( + `GET /repos/${this.owner}/${this.repo}/git/blobs/${file_sha}`, { owner: this.owner, repo: this.repo, file_sha, diff --git a/src/fitPull.ts b/src/fitPull.ts index 1162670..685cbf6 100644 --- a/src/fitPull.ts +++ b/src/fitPull.ts @@ -1,13 +1,20 @@ import { VaultOperations } from "./vaultOps"; -import { ChangeType, compareSha } from "./utils"; +import { RemoteChangeType, compareSha } from "./utils"; import { Fit } from "./fit"; import { Notice } from "obsidian"; import { LocalStores } from "main"; +import { LocalChange } from "./fitPush"; export type RemoteChange = { path: string, - status: ChangeType, - currentSha : string + status: RemoteChangeType, + currentSha?: string +} + +type RemoteUpdate = { + remoteChanges: RemoteChange[], + remoteTreeSha: Record, + latestRemoteCommitSha: string, } export interface IFitPull { @@ -25,34 +32,34 @@ export class FitPull implements IFitPull { this.fit = fit } - async getLocalChanges(): Promise<{path: string, status: ChangeType}[]> { + async getLocalChanges(): Promise { if (!this.fit.localSha) { // assumes every local files are created if no localSha is found return this.vaultOps.vault.getFiles().map(f => { - return {path: f.path, status: "added" as ChangeType}}) + return {path: f.path, status: "created"}}) } const currentLocalSha = await this.fit.computeLocalSha() - const localChanges = compareSha(currentLocalSha, this.fit.localSha) + const localChanges = compareSha(currentLocalSha, this.fit.localSha, "local") return localChanges } - async getRemoteChanges( - latestRemoteCommitSha: string): - Promise<[{path: string, status: ChangeType, currentSha: string}[], {[k: string]: string}]> { - const remoteTreeSha = await this.fit.getRemoteTreeSha(latestRemoteCommitSha) + async getRemoteChanges(remoteTreeSha: {[k: string]: string}): Promise { if (!this.fit.lastFetchedRemoteSha) { Object.keys(remoteTreeSha).map(path=>{ - return {path, status: "added" as ChangeType} + return {path, status: "ADDED" as RemoteChangeType} }) } - const remoteChanges = compareSha(remoteTreeSha, this.fit.lastFetchedRemoteSha) - return [remoteChanges, remoteTreeSha] + const remoteChanges = compareSha( + remoteTreeSha, this.fit.lastFetchedRemoteSha, "remote") + return remoteChanges } - getClashedChanges(localChangePaths: Array, remoteChangePaths: Array) { - const clashedFiles: Array = localChangePaths.filter(path => remoteChangePaths.includes(path)) - const remoteOnly: Array = remoteChangePaths.filter(path => !localChangePaths.includes(path)) - return {clashedFiles, remoteOnly} + getClashedChanges(localChanges: LocalChange[], remoteChanges:RemoteChange[]): string[] { + const localChangePaths = localChanges.map(c=>c.path) + const remoteChangePaths = remoteChanges.map(c=>c.path) + const clashedFiles: string[] = localChangePaths.filter( + path => remoteChangePaths.includes(path)) + return clashedFiles } // return null if remote doesn't have updates otherwise, return the latestRemoteCommitSha @@ -64,26 +71,28 @@ export class FitPull implements IFitPull { return latestRemoteCommitSha } - async performPrePullChecks(): Promise { + async performPrePullChecks(): Promise { const latestRemoteCommitSha = await this.remoteHasUpdates() if (!latestRemoteCommitSha) { new Notice("Local copy already up to date") return null } const localChanges = await this.getLocalChanges() - const [remoteChanges, remoteSha] = await this.getRemoteChanges(latestRemoteCommitSha) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {clashedFiles, remoteOnly} = this.getClashedChanges(localChanges.map(c=>c.path), remoteChanges.map(c=>c.path)) + const remoteTreeSha = await this.fit.getRemoteTreeSha(latestRemoteCommitSha) + const remoteChanges = await this.getRemoteChanges(remoteTreeSha) + const clashedFiles = this.getClashedChanges(localChanges, remoteChanges) // TODO handle clashes without completely blocking pull if (clashedFiles.length > 0) { new Notice("Unsaved local changes clash with remote changes, aborting."); console.log("clashed files:") console.log(clashedFiles) return null + } else { + console.log(`No clash detected between local and remote changes, + writing the following changes to local:`) + console.log(remoteChanges) } - return [remoteChanges, remoteSha, latestRemoteCommitSha] + return {remoteChanges, remoteTreeSha, latestRemoteCommitSha} } // Get changes from remote, pathShaMap is coupled to the Fit plugin design @@ -95,25 +104,29 @@ export class FitPull implements IFitPull { return await Promise.all(remoteChanges) } - async pullRemoteToLocal( - remoteChanges: RemoteChange[], - remoteSha: {[k:string]: string}, - latestRemoteCommitSha: string, - saveLocalStoreCallback: (localStore: Partial) => Promise): - Promise { - const deleteFromLocal = remoteChanges.filter(c=>c.status=="removed").map(c=>c.path) - const changesToProcess = remoteChanges.filter(c=>c.status!="removed").reduce( + async prepareChangesToExecute(remoteChanges: RemoteChange[]) { + const deleteFromLocal = remoteChanges.filter(c=>c.status=="REMOVED").map(c=>c.path) + const changesToProcess = remoteChanges.filter(c=>c.status!="REMOVED").reduce( (acc, change) => { - acc[change.path] = change.currentSha; + acc[change.path] = change.currentSha as string; return acc; - }, {} as Record); + }, {} as Record); - const addToLocal = await this.getRemoteNonDeletionChangesContent(changesToProcess) + const addToLocal = await this.getRemoteNonDeletionChangesContent(changesToProcess) + return {addToLocal, deleteFromLocal} + } + + async pullRemoteToLocal( + remoteUpdate: RemoteUpdate, + saveLocalStoreCallback: (localStore: Partial) => Promise): + Promise { + const {remoteChanges, remoteTreeSha, latestRemoteCommitSha} = remoteUpdate + const {addToLocal, deleteFromLocal} = await this.prepareChangesToExecute(remoteChanges) // TODO: when there are clashing local changes, prompt user for confirmation before proceeding await this.vaultOps.updateLocalFiles(addToLocal, deleteFromLocal); await saveLocalStoreCallback({ - lastFetchedRemoteSha: remoteSha, + lastFetchedRemoteSha: remoteTreeSha, lastFetchedCommitSha: latestRemoteCommitSha, localSha: await this.fit.computeLocalSha() }) diff --git a/src/fitPush.ts b/src/fitPush.ts index f2ed861..d1b620e 100644 --- a/src/fitPush.ts +++ b/src/fitPush.ts @@ -6,12 +6,20 @@ import { warn } from "console"; import { LocalStores } from "main"; +export type LocalFileStatus = "deleted" | "created" | "changed" + export type LocalChange = { path: string, - type: string, + status: LocalFileStatus, extension? : string } +type LocalUpdate = { + localChanges: LocalChange[], + localTreeSha: Record, + parentCommitSha: string +} + export interface IFitPush { localSha: Record vaultOps: VaultOperations @@ -29,10 +37,10 @@ export class FitPush implements IFitPush { this.fit = fit } - async performPrePushChecks(): Promise { - const localSha = await this.fit.computeLocalSha() - const changedFiles = await this.getLocalChanges(localSha) - if (changedFiles.length == 0) { + async performPrePushChecks(): Promise { + const localTreeSha = await this.fit.computeLocalSha() + const localChanges = await this.getLocalChanges(localTreeSha) + if (localChanges.length == 0) { new Notice("No local changes detected.") return null } @@ -41,34 +49,31 @@ export class FitPush implements IFitPush { new Notice("Remote changed after last pull/write, please pull again.") return null } - return [changedFiles, latestRemoteCommitSha] + return {localChanges, localTreeSha, parentCommitSha: latestRemoteCommitSha} } - async getLocalChanges(currentLocalSha: {[k: string]: string}): Promise { - let changedFiles: Array<{path: string, type: string, extension?: string}>; + async getLocalChanges(currentLocalSha: Record): Promise { + let changedFiles: LocalChange[]; // const localSha = await this.fit.computeLocalSha() const files = this.vaultOps.vault.getFiles() // mark all files as changed if local sha for previous commit is not found if (!this.fit.localSha) { changedFiles = files.map(f=> {return { - path: f.path, type: 'changed', extension: f.extension}}) + path: f.path, status: 'changed', extension: f.extension}}) } else { - const localChanges = compareSha(currentLocalSha, this.fit.localSha) + const localChanges = compareSha(currentLocalSha, this.fit.localSha, "local") changedFiles = localChanges.flatMap(change=>{ - if (change.status == "removed") { - return {path: change.path, type: 'deleted'} + if (change.status == "deleted") { + return {path: change.path, status: change.status} } else { - // adopted getAbstractFileByPath for mobile compatiability, TODO: check whether additional checks needed to validate instance of TFile + // adopted getAbstractFileByPath for mobile compatiability + // TODO: check whether additional checks needed to validate instance of TFile const file = this.vaultOps.vault.getAbstractFileByPath(change.path) as TFile if (!file) { warn(`${file} included in local changes (added/modified) but not found`) return [] } - if (change.status == "added") { - return {path: change.path, type: 'created', extension: file.extension} - } else { - return {path: change.path, type: 'changed', extension: file.extension} - } + return {path: change.path, status: change.status, extension: file.extension} } }) } @@ -76,28 +81,28 @@ export class FitPush implements IFitPush { } async pushChangedFilesToRemote( - changedFiles: LocalChange[], - latestRemoteCommitSha: string, + localUpdate: LocalUpdate, saveLocalStoreCallback: (localStore: Partial) => Promise): Promise { - const treeNodes = await Promise.all(changedFiles.map((f) => { + const {localChanges, localTreeSha, parentCommitSha} = localUpdate; + const treeNodes = await Promise.all(localChanges.map((f) => { return this.fit.createTreeNodeFromFile(f) })) - const latestRemoteCommitTreeSha = await this.fit.getCommitTreeSha(latestRemoteCommitSha) + const latestRemoteCommitTreeSha = await this.fit.getCommitTreeSha(parentCommitSha) const createdTreeSha = await this.fit.createTree(treeNodes, latestRemoteCommitTreeSha) - const createdCommitSha = await this.fit.createCommit(createdTreeSha, latestRemoteCommitSha) + const createdCommitSha = await this.fit.createCommit(createdTreeSha, parentCommitSha) const updatedRefSha = await this.fit.updateRef(createdCommitSha) const updatedRemoteTreeSha = await this.fit.getRemoteTreeSha(updatedRefSha) await saveLocalStoreCallback({ lastFetchedRemoteSha: updatedRemoteTreeSha, lastFetchedCommitSha: createdCommitSha, - localSha: await this.fit.computeLocalSha() + localSha: localTreeSha }) - changedFiles.map(({path, type}): void=>{ - const typeToAction = {deleted: "deleted from", created: "added to", changed: "modified on"} - new Notice(`${path} ${typeToAction[type as keyof typeof typeToAction]} remote.`, 10000) + localChanges.map(({path, status}): void=>{ + const statusToAction = {deleted: "deleted from", created: "added to", changed: "modified on"} + new Notice(`${path} ${statusToAction[status]} remote.`, 10000) }) new Notice(`Successful pushed to ${this.fit.repo}`) } diff --git a/src/utils.ts b/src/utils.ts index 4d6a97e..66c849f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,31 +1,60 @@ -export type ChangeType = "added" | "changed" | "removed" - -// compare currentSha with storedSha and check for differences, files only in currentSha are considerd added, while files only in storedSha are considered removed -export function compareSha( - currentSha: {[k:string]:string}, storedSha: {[k:string]:string}): - Array<{path: string, status: ChangeType, currentSha: string}> { - const allPaths = Array.from(new Set([...Object.keys(currentSha), ...Object.keys(storedSha)])); - - return allPaths.reduce<{path: string, status: ChangeType, currentSha: string}[]>((changes, path) => { - const inCurrent = path in currentSha; - const inStored = path in storedSha; - - if (inCurrent && !inStored) { - changes.push({ path, status: 'added', currentSha: currentSha[path] }); - } else if (!inCurrent && inStored) { - changes.push({ path, status: 'removed', currentSha: currentSha[path] }); - } else if (inCurrent && inStored && currentSha[path] !== storedSha[path]) { - changes.push({ path, status: 'changed', currentSha: currentSha[path]}); +import { LocalFileStatus } from "./fitPush"; + +export type RemoteChangeType = "ADDED" | "MODIFIED" | "REMOVED" + +type Status = RemoteChangeType | LocalFileStatus + +type FileLocation = "remote" | "local" + +type ComparisonResult = { + path: string, + status: Env extends "local" ? LocalFileStatus: RemoteChangeType + currentSha?: string +} + +function getValueOrNull(obj: Record, key: string): string | null { + return obj.hasOwnProperty(key) ? obj[key] : null; +} + + +// compare currentSha with storedSha and check for differences, files only in currentSha +// are considerd added, while files only in storedSha are considered removed +export function compareSha( + currentShaMap: Record, + storedShaMap: Record, + env: Env): ComparisonResult[] { + const determineStatus = (currentSha: string | null, storedSha: string | null): Status | null => + { + if (currentSha && storedSha && currentSha !== storedSha) { + return env === "local" ? "changed" : "MODIFIED"; + } else if (currentSha && !storedSha) { + return env === "local" ? "created" : "ADDED"; + } else if (!currentSha && storedSha) { + return env === "local" ? "deleted" : "REMOVED"; } - // Unchanged files are implicitly handled by not adding them to the changes array - return changes; - }, []); + return null + } + + return Object.keys({ ...currentShaMap, ...storedShaMap }).flatMap((path): ComparisonResult[] => { + const [currentSha, storedSha] = [getValueOrNull(currentShaMap, path), getValueOrNull(storedShaMap, path)]; + const status = determineStatus(currentSha, storedSha); + if (status) { + return [{ + path, + status: status as Env extends "local" ? LocalFileStatus : RemoteChangeType, + currentSha: currentSha ?? undefined, + }]; + } + return []; + }); } +export const RECOGNIZED_BINARY_EXT = ["png", "jpg", "jpeg", "pdf"] + // Using file extension to determine encoding of files (works in most cases) export function getFileEncoding(path: string): string { const extension = path.match(/[^.]+$/)?.[0]; - const isBinary = extension && ["png", "jpg", "jpeg", "pdf"].includes(extension); + const isBinary = extension && RECOGNIZED_BINARY_EXT.includes(extension); if (isBinary) { return "base64" } diff --git a/src/vaultOps.ts b/src/vaultOps.ts index f8a1054..d8f8c57 100644 --- a/src/vaultOps.ts +++ b/src/vaultOps.ts @@ -18,7 +18,8 @@ export class VaultOperations implements IVaultOperations { } async deleteFromLocal(path: string): Promise { - // adopted getAbstractFileByPath for mobile compatiability, TODO: check whether additional checks needed to validate instance of TFile + // adopted getAbstractFileByPath for mobile compatiability + // TODO: check whether additional checks needed to validate instance of TFile const file = this.vault.getAbstractFileByPath(path) if (file) { await this.vault.delete(file); @@ -37,8 +38,9 @@ export class VaultOperations implements IVaultOperations { } async writeToLocal(path: string, content: string): Promise { - // adopted getAbstractFileByPath for mobile compatiability, TODO: check whether additional checks needed to validate instance of TFile + // adopted getAbstractFileByPath for mobile compatiability // temporary fix that works temporarily since path are garanteed to be for files not folders + // TODO: check whether additional checks needed to validate instance of TFile const file = this.vault.getAbstractFileByPath(path) as TFile if (file) { await this.vault.modifyBinary(file, base64ToArrayBuffer(content)) From 5d5ab6f7b4d0ab9cd19813db767e9be580e53355 Mon Sep 17 00:00:00 2001 From: joshuakto <34743132+joshuakto@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:38:50 +0800 Subject: [PATCH 2/4] removed use of TFile casting --- src/fitPush.ts | 8 +++++--- src/vaultOps.ts | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/fitPush.ts b/src/fitPush.ts index d1b620e..4c558b8 100644 --- a/src/fitPush.ts +++ b/src/fitPush.ts @@ -67,13 +67,15 @@ export class FitPush implements IFitPush { return {path: change.path, status: change.status} } else { // adopted getAbstractFileByPath for mobile compatiability - // TODO: check whether additional checks needed to validate instance of TFile - const file = this.vaultOps.vault.getAbstractFileByPath(change.path) as TFile + const file = this.vaultOps.vault.getAbstractFileByPath(change.path) if (!file) { warn(`${file} included in local changes (added/modified) but not found`) return [] } - return {path: change.path, status: change.status, extension: file.extension} + if (file instanceof TFile) { + return {path: change.path, status: change.status, extension: file.extension} + } + throw new Error(`Expected ${file.path} to be of type TFile but got type ${typeof file}`); } }) } diff --git a/src/vaultOps.ts b/src/vaultOps.ts index d8f8c57..dcbfede 100644 --- a/src/vaultOps.ts +++ b/src/vaultOps.ts @@ -19,14 +19,13 @@ export class VaultOperations implements IVaultOperations { async deleteFromLocal(path: string): Promise { // adopted getAbstractFileByPath for mobile compatiability - // TODO: check whether additional checks needed to validate instance of TFile const file = this.vault.getAbstractFileByPath(path) - if (file) { + if (file && file instanceof TFile) { await this.vault.delete(file); new Notice(`${path} deleted from local drive.`, this.noticeDuration); return - } - warn(`Attempting to delete ${path} from local but not found.`) + } + warn(`Attempting to delete ${path} from local but not successful, file is of type ${typeof file}.`) } async ensureFolderExists(path: string): Promise { @@ -39,14 +38,16 @@ export class VaultOperations implements IVaultOperations { async writeToLocal(path: string, content: string): Promise { // adopted getAbstractFileByPath for mobile compatiability - // temporary fix that works temporarily since path are garanteed to be for files not folders - // TODO: check whether additional checks needed to validate instance of TFile - const file = this.vault.getAbstractFileByPath(path) as TFile - if (file) { + // TODO: add capability for creating folder from remote + const file = this.vault.getAbstractFileByPath(path) + if (file && file instanceof TFile) { await this.vault.modifyBinary(file, base64ToArrayBuffer(content)) - } else { + } else if (!file) { this.ensureFolderExists(path) await this.vault.createBinary(path, base64ToArrayBuffer(content)) + } else { + throw new Error(`${path} writeToLocal operation unsuccessful, + vault abstractFile on ${path} is of type ${typeof file}`); } new Notice(`${path} ${file ? 'updated' : 'copied'} to local drive.`, this.noticeDuration); return From efa66929167d3e0d84667678d7e537cca66e7286 Mon Sep 17 00:00:00 2001 From: joshuakto <34743132+joshuakto@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:42:20 +0800 Subject: [PATCH 3/4] removed debugging tools for merging to main, updated manifest.json --- main.ts | 58 +----------------------------------- manifest.json | 2 +- src/pluginModal.ts | 73 ---------------------------------------------- 3 files changed, 2 insertions(+), 131 deletions(-) delete mode 100644 src/pluginModal.ts diff --git a/main.ts b/main.ts index 875e9ec..0edeeb4 100644 --- a/main.ts +++ b/main.ts @@ -1,5 +1,4 @@ -import { Notice, Platform, Plugin, base64ToArrayBuffer } from 'obsidian'; -import { ComputeFileLocalShaModal, DebugModal } from 'src/pluginModal'; +import { Notice, Platform, Plugin } from 'obsidian'; import { Fit } from 'src/fit'; import { FitPull } from 'src/fitPull'; import { FitPush } from 'src/fitPush'; @@ -49,7 +48,6 @@ const DEFAULT_LOCAL_STORE: LocalStores = { export default class FitPlugin extends Plugin { settings: FitSettings; - localStore: LocalStores fit: Fit; vaultOps: VaultOperations; @@ -57,8 +55,6 @@ export default class FitPlugin extends Plugin { fitPush: FitPush fitPullRibbonIconEl: HTMLElement fitPushRibbonIconEl: HTMLElement - - checkSettingsConfigured(): boolean { if ([" ", ""].includes(this.settings.pat)) { @@ -83,8 +79,6 @@ export default class FitPlugin extends Plugin { await this.saveLocalStore() } - - async onload() { await this.loadSettings(Platform.isMobile); await this.loadLocalStore(); @@ -133,56 +127,6 @@ export default class FitPlugin extends Plugin { // add class to ribbon element to afford styling, refer to styles.css this.fitPushRibbonIconEl.addClass('fit-push-ribbon-el'); - // This adds a status bar item to the bottom of the app. Does not work on mobile apps. - const statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); - - // Command for computing an inputed file path's local sha for debugging purposes - this.addCommand({ - id: 'compute-file-local-sha', - name: 'Compute local sha for file (Debug)', - callback: () => { - new ComputeFileLocalShaModal( - this.app, - async (queryFile) => console.log(await this.fit.computeFileLocalSha(queryFile)) - ).open(); - } - }); - - // Command for computing an inputed file path's local sha for debugging purposes - this.addCommand({ - id: 'recompute-local-sha', - name: `Update local store with new local sha, essentially - ignoring local changes when pulling/pushing (Debug)`, - callback: async () => { - this.localStore.localSha = await this.fit.computeLocalSha() - this.saveLocalStore() - new Notice(`Local sha recomputed and stored, they will - not be considered in future push/pull.`) - } - }); - - - // Command for computing an inputed file path's local sha for debugging purposes - this.addCommand({ - id: 'pull-file', - name: 'Pull a file from remote for debugging purpose (Debug)', - callback: async () => { - new DebugModal( - this.app, - async (debugInput) => { - console.log(debugInput) - console.log("Getting blob for ") - const fileSha = this.localStore.lastFetchedRemoteSha[debugInput] - const content = await this.fit.getBlob(fileSha) - this.vaultOps.vault.createBinary('testing123.md', base64ToArrayBuffer(content)) - console.log(content) - } - ).open(); - } - }); - - // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new FitSettingTab(this.app, this)); diff --git a/manifest.json b/manifest.json index 428d051..126dc23 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "fit", "name": "Fit", - "version": "1.0.4", + "version": "1.0.5", "minAppVersion": "0.15.0", "description": "File gIT - a barebone git system for files in obsidian", "author": "joshuakto", diff --git a/src/pluginModal.ts b/src/pluginModal.ts deleted file mode 100644 index 915a200..0000000 --- a/src/pluginModal.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { App, Modal, Setting } from "obsidian"; - -export class ComputeFileLocalShaModal extends Modal { - queryFile: string; - onSubmit: (result: string) => void; - - constructor(app: App, onSubmit: (result: string) => void) { - super(app); - this.onSubmit = onSubmit; - } - - onOpen() { - const {contentEl} = this; - contentEl.createEl("h1", { text: "Input the filename you want to compute local Sha for:" }); - new Setting(contentEl) - .setName("Name") - .addText((text) => - text.onChange((value) => { - this.queryFile = value - })); - - new Setting(contentEl) - .addButton((btn) => - btn - .setButtonText("Submit") - .setCta() - .onClick(() => { - this.close(); - this.onSubmit(this.queryFile); - })); - } - - onClose() { - const {contentEl} = this; - contentEl.empty(); - } -} - -export class DebugModal extends Modal { - debugInput: string; - onSubmit: (result: string) => void; - - constructor(app: App, onSubmit: (result: string) => void) { - super(app); - this.onSubmit = onSubmit; - } - - onOpen() { - const {contentEl} = this; - contentEl.createEl("h1", { text: "Debug input:" }); - new Setting(contentEl) - .setName("input") - .addText((text) => - text.onChange((value) => { - this.debugInput = value - })); - - new Setting(contentEl) - .addButton((btn) => - btn - .setButtonText("Submit") - .setCta() - .onClick(() => { - this.close(); - this.onSubmit(this.debugInput); - })); - } - - onClose() { - const {contentEl} = this; - contentEl.empty(); - } -} \ No newline at end of file From 80d24911da7817cede55be8e33194aa6d253fa68 Mon Sep 17 00:00:00 2001 From: joshuakto <34743132+joshuakto@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:42:46 +0800 Subject: [PATCH 4/4] 1.0.5 --- package-lock.json | 4 ++-- package.json | 2 +- versions.json | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00d675e..9c24589 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-sample-plugin", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-sample-plugin", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "dependencies": { "@octokit/core": "^6.0.1" diff --git a/package.json b/package.json index e040f24..f551ac1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-sample-plugin", - "version": "1.0.4", + "version": "1.0.5", "description": "This is a sample plugin for Obsidian (https://obsidian.md)", "main": "main.js", "scripts": { diff --git a/versions.json b/versions.json index 1d7eb49..22b4387 100644 --- a/versions.json +++ b/versions.json @@ -3,5 +3,6 @@ "1.0.1": "0.15.0", "1.0.2": "0.15.0", "1.0.3": "0.15.0", - "1.0.4": "0.15.0" + "1.0.4": "0.15.0", + "1.0.5": "0.15.0" } \ No newline at end of file