Remove lots of unnecessary code

This commit is contained in:
Silvano Cerza 2025-01-14 17:02:16 +01:00
parent 4085c2f5c5
commit 56a9fb29cd
8 changed files with 22 additions and 536 deletions

View file

@ -1,14 +0,0 @@
import { type Event } from "./types";
import SyncManager from "src/sync-manager";
export default class EventsConsumer {
constructor(private readonly syncManager: SyncManager) {}
async process(event: Event): Promise<void> {
if (event.type == "create" || event.type == "modify") {
await this.syncManager.uploadFile(event.file);
} else if (event.type == "delete") {
await this.syncManager.deleteFile(event.filePath);
}
}
}

View file

@ -1,5 +1,4 @@
import { Vault, requestUrl, normalizePath } from "obsidian";
import MetadataStore, { Metadata } from "../metadata-store";
import { requestUrl } from "obsidian";
export type RepoContent = {
files: { [key: string]: GetTreeResponseItem };
@ -132,62 +131,4 @@ export default class GithubClient {
});
return res.json;
}
/**
* Create or edit a remote file to GitHub.
*
* @param remoteFilePath Path to remote file
* @param fileContent Content of the file
* @param sha SHA of the file
*/
async uploadFile(
remoteFilePath: string,
fileContent: ArrayBuffer,
sha: string | null,
) {
await requestUrl({
url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}`,
method: "PUT",
headers: this.headers(),
body: JSON.stringify({
message: `Edit ${remoteFilePath}`,
branch: this.branch,
content: Buffer.from(fileContent).toString("base64"),
sha: sha,
}),
});
}
/**
* Gets the SHA of a file in the remote repo.
*
* @param remoteFilePath Path to remote file
* @returns sha of the file as string
*/
async getFileSha(remoteFilePath: string) {
const res = await requestUrl({
url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}?ref=${this.branch}`,
headers: this.headers(),
});
return res.json.sha;
}
/**
* Delete a single file from GitHub.
*
* @param remoteFilePath Path to remote file
* @param sha SHA of the file
*/
async deleteFile(remoteFilePath: string, sha: string) {
await requestUrl({
url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}`,
method: "DELETE",
headers: this.headers(),
body: JSON.stringify({
message: `Delete ${remoteFilePath}`,
branch: this.branch,
sha: sha,
}),
});
}
}

View file

@ -1,7 +1,6 @@
import { EventRef, Plugin, FileView } from "obsidian";
import { GitHubSyncSettings, DEFAULT_SETTINGS } from "./settings/settings";
import GitHubSyncSettingsTab from "./settings/tab";
import { UploadDialog } from "./views/upload-all-files-dialog/view";
import SyncManager from "./sync-manager";
export default class GitHubSyncPlugin extends Plugin {
@ -9,10 +8,7 @@ export default class GitHubSyncPlugin extends Plugin {
syncManager: SyncManager;
statusBarItem: HTMLElement | null = null;
downloadAllRibbonIcon: HTMLElement | null = null;
uploadModifiedFilesRibbonIcon: HTMLElement | null = null;
uploadCurrentFileRibbonIcon: HTMLElement | null = null;
uploadAllFilesRibbonIcon: HTMLElement | null = null;
activeLeafChangeListener: EventRef | null = null;
vaultCreateListener: EventRef | null = null;
@ -56,71 +52,21 @@ export default class GitHubSyncPlugin extends Plugin {
this.showStatusBarItem();
}
if (this.settings.showDownloadRibbonButton) {
this.showDownloadAllRibbonIcon();
}
if (this.settings.showUploadModifiedFilesRibbonButton) {
this.showUploadModifiedFilesRibbonIcon();
}
if (this.settings.showUploadActiveFileRibbonButton) {
this.showUploadActiveFileRibbonIcon();
}
if (this.settings.showUploadAllFilesRibbonButton) {
this.showUploadAllFilesRibbonIcon();
if (this.settings.showSyncRibbonButton) {
this.showSyncRibbonIcon();
}
});
this.addCommand({
id: "download-all-files",
name: "Download all files to GitHub",
repeatable: false,
icon: "arrow-down-from-line",
callback: async () => {
await this.syncManager.downloadAllFiles();
this.updateStatusBarItem();
},
});
this.addCommand({
id: "upload-modified-files",
name: "Upload modified files to GitHub",
id: "sync-files",
name: "Sync with GitHub",
repeatable: false,
icon: "refresh-cw",
callback: async () => {
await this.syncManager.uploadModifiedFiles();
await this.syncManager.sync();
this.updateStatusBarItem();
},
});
this.addCommand({
id: "upload-active-file",
name: "Upload active file to GitHub",
repeatable: false,
icon: "arrow-up",
callback: async () => {
const activeView = this.app.workspace.getActiveViewOfType(FileView);
if (!activeView) {
return;
}
const activeFile = activeView.file;
if (!activeFile) {
return;
}
// await this.syncManager.uploadFile(activeFile);
this.updateStatusBarItem();
},
});
this.addCommand({
id: "upload-all-files",
name: "Upload all files to GitHub",
repeatable: false,
icon: "arrow-up-from-line",
callback: async () => await this.openUploadAllFilesDialog(),
});
}
async onunload() {
@ -129,16 +75,6 @@ export default class GitHubSyncPlugin extends Plugin {
console.log("GitHubSyncPlugin unloaded");
}
/**
* Opens dialog to upload all file in the tracked folder.
* This doesn't take into account the state of the files and uploads
* ALL files whether they've been modified or not.
*/
private async openUploadAllFilesDialog() {
new UploadDialog(this).open();
this.updateStatusBarItem();
}
showStatusBarItem() {
if (this.statusBarItem) {
return;
@ -190,34 +126,15 @@ export default class GitHubSyncPlugin extends Plugin {
this.statusBarItem.setText(`GitHub: ${state}`);
}
showDownloadAllRibbonIcon() {
if (this.downloadAllRibbonIcon) {
return;
}
this.downloadAllRibbonIcon = this.addRibbonIcon(
"arrow-down-from-line",
"Download all files from GitHub",
async () => {
await this.syncManager.downloadAllFiles();
this.updateStatusBarItem();
},
);
}
hideDownloadAllRibbonIcon() {
this.downloadAllRibbonIcon?.remove();
this.downloadAllRibbonIcon = null;
}
showUploadModifiedFilesRibbonIcon() {
showSyncRibbonIcon() {
if (this.uploadModifiedFilesRibbonIcon) {
return;
}
this.uploadModifiedFilesRibbonIcon = this.addRibbonIcon(
"refresh-cw",
"Upload modified files to GitHub",
"Sync with GitHub",
async () => {
await this.syncManager.uploadModifiedFiles();
await this.syncManager.sync();
this.updateStatusBarItem();
},
);
@ -228,50 +145,6 @@ export default class GitHubSyncPlugin extends Plugin {
this.uploadModifiedFilesRibbonIcon = null;
}
showUploadActiveFileRibbonIcon() {
if (this.uploadCurrentFileRibbonIcon) {
return;
}
this.uploadCurrentFileRibbonIcon = this.addRibbonIcon(
"arrow-up",
"Upload current file to GitHub",
async () => {
const activeView = this.app.workspace.getActiveViewOfType(FileView);
if (!activeView) {
return;
}
const activeFile = activeView.file;
if (!activeFile) {
return;
}
// await this.syncManager.uploadFile(activeFile);
this.updateStatusBarItem();
},
);
}
hideUploadActiveFileRibbonIcon() {
this.uploadCurrentFileRibbonIcon?.remove();
this.uploadCurrentFileRibbonIcon = null;
}
showUploadAllFilesRibbonIcon() {
if (this.uploadAllFilesRibbonIcon) {
return;
}
this.uploadAllFilesRibbonIcon = this.addRibbonIcon(
"arrow-up-from-line",
"Upload all files to GitHub",
async () => await this.openUploadAllFilesDialog(),
);
}
hideUploadAllFilesRibbonIcon() {
this.uploadAllFilesRibbonIcon?.remove();
this.uploadAllFilesRibbonIcon = null;
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}

View file

@ -10,10 +10,7 @@ export interface GitHubSyncSettings {
syncOnStartup: boolean;
conflictHandling: "ignore" | "ask" | "overwrite";
showStatusBarItem: boolean;
showDownloadRibbonButton: boolean;
showUploadModifiedFilesRibbonButton: boolean;
showUploadActiveFileRibbonButton: boolean;
showUploadAllFilesRibbonButton: boolean;
showSyncRibbonButton: boolean;
}
export const DEFAULT_SETTINGS: GitHubSyncSettings = {
@ -28,8 +25,5 @@ export const DEFAULT_SETTINGS: GitHubSyncSettings = {
syncOnStartup: false,
conflictHandling: "ask",
showStatusBarItem: true,
showDownloadRibbonButton: true,
showUploadModifiedFilesRibbonButton: true,
showUploadActiveFileRibbonButton: true,
showUploadAllFilesRibbonButton: true,
showSyncRibbonButton: true,
};

View file

@ -218,73 +218,20 @@ export default class GitHubSyncSettingsTab extends PluginSettingTab {
});
new Setting(containerEl)
.setName("Show download all files button")
.setDesc(
"Displays a ribbon button to download all files from the remote repository",
)
.setName("Show sync button")
.setDesc("Displays a ribbon button to sync files")
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.showDownloadRibbonButton)
.setValue(this.plugin.settings.showSyncRibbonButton)
.onChange((value) => {
this.plugin.settings.showDownloadRibbonButton = value;
this.plugin.settings.showSyncRibbonButton = value;
this.plugin.saveSettings();
if (value) {
this.plugin.showDownloadAllRibbonIcon();
} else {
this.plugin.hideDownloadAllRibbonIcon();
}
});
});
new Setting(containerEl)
.setName("Show upload modified files button")
.setDesc("Displays a ribbon button to upload modified files")
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.showUploadModifiedFilesRibbonButton)
.onChange((value) => {
this.plugin.settings.showUploadModifiedFilesRibbonButton = value;
this.plugin.saveSettings();
if (value) {
this.plugin.showUploadModifiedFilesRibbonIcon();
this.plugin.showSyncRibbonIcon();
} else {
this.plugin.hideUploadModifiedFilesRibbonIcon();
}
});
});
new Setting(containerEl)
.setName("Show upload active file button")
.setDesc("Displays a ribbon button to upload active file")
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.showUploadActiveFileRibbonButton)
.onChange((value) => {
this.plugin.settings.showUploadActiveFileRibbonButton = value;
this.plugin.saveSettings();
if (value) {
this.plugin.showUploadActiveFileRibbonIcon();
} else {
this.plugin.hideUploadActiveFileRibbonIcon();
}
});
});
new Setting(containerEl)
.setName("Show upload all files button")
.setDesc("Displays a ribbon button to upload all files")
.addToggle((toggle) => {
toggle
.setValue(this.plugin.settings.showUploadAllFilesRibbonButton)
.onChange((value) => {
this.plugin.settings.showUploadAllFilesRibbonButton = value;
this.plugin.saveSettings();
if (value) {
this.plugin.showUploadAllFilesRibbonIcon();
} else {
this.plugin.hideUploadAllFilesRibbonIcon();
}
});
});
}
}

View file

@ -5,7 +5,6 @@ import GithubClient, {
} from "./github/client";
import MetadataStore, { FileMetadata, Metadata } from "./metadata-store";
import EventsListener from "./events/listener";
import EventsConsumer from "./events/consumer";
import { GitHubSyncSettings } from "./settings/settings";
interface SyncAction {
@ -17,7 +16,6 @@ export default class SyncManager {
private metadataStore: MetadataStore;
private client: GithubClient;
private eventsListener: EventsListener;
private eventsConsumer: EventsConsumer;
private syncIntervalId: number | null = null;
constructor(
@ -39,7 +37,6 @@ export default class SyncManager {
this.settings.localContentDir,
this.settings.repoContentDir,
);
this.eventsConsumer = new EventsConsumer(this);
}
async sync() {
@ -296,6 +293,7 @@ export default class SyncManager {
// but not the same happens when the file is modified locally.
// So if sha are different and the local file is newer we can't
// know for sure which version should be kept.
return;
}
if (remoteFile.deleted && !localFile.deleted) {
@ -321,6 +319,10 @@ export default class SyncManager {
}
if (remoteFile.lastModified > localFile.lastModified) {
console.log("Downloading");
console.log(filePath);
console.log(`localFile.lastModified: ${localFile.lastModified}`);
console.log(`remoteFile.lastModified: ${remoteFile.lastModified}`);
actions.push({ type: "download", filePath: filePath });
} else if (localFile.lastModified > remoteFile.lastModified) {
console.log("Uploading");
@ -402,22 +404,6 @@ export default class SyncManager {
await this.client.updateBranchHead(commitSha);
}
async uploadFile(filePath: string) {
const { remotePath, sha } = this.metadataStore.data.files[filePath];
const normalizedFilePath = normalizePath(filePath);
if (!(await this.vault.adapter.exists(normalizedFilePath))) {
throw new Error(`Can't find file ${filePath}`);
}
const fileContent = await this.vault.adapter.readBinary(normalizedFilePath);
await this.client.uploadFile(remotePath, fileContent, sha);
// Reset dirty state
this.metadataStore.data.files[filePath].dirty = false;
// Gets the new SHA of the file
const newSha = await this.client.getFileSha(remotePath);
this.metadataStore.data.files[filePath].sha = newSha;
this.metadataStore.save();
}
async downloadFile(file: GetTreeResponseItem, lastModified: number) {
const url = file.url;
const destinationFile = file.path.replace(
@ -460,52 +446,6 @@ export default class SyncManager {
this.metadataStore.save();
}
async deleteRemoteFile(filePath: string) {
const { remotePath, sha } = this.metadataStore.data.files[filePath];
if (!sha) {
// File was never uploaded, no need to delete it
// This is unlikely to happen but we handle it anyway
return;
}
await this.client.deleteFile(remotePath, sha);
this.metadataStore.save();
}
async deleteFile(filePath: string) {
const { remotePath, sha } = this.metadataStore.data.files[filePath];
if (!sha) {
// File was never uploaded, no need to delete it
return;
}
await this.client.deleteFile(remotePath, sha);
this.metadataStore.save();
}
async downloadAllFiles() {
const { files } = await this.client.getRepoContent();
await Promise.all(
Object.keys(files)
.filter((filePath: string) =>
filePath.startsWith(this.settings.repoContentDir),
)
.map(
async (filePath: string) =>
await this.downloadFile(files[filePath], Date.now()),
),
);
}
async uploadModifiedFiles() {
// We upload files sequentially to avoid conflicts.
// GitHub rejects commits if they're made in fast succession, thus
// forcing us to retry the failed upload.
// So parallelization is not an option.
for (const event of this.eventsListener.flush()) {
await this.eventsConsumer.process(event);
}
}
async loadMetadata() {
await this.metadataStore.load();
if (Object.keys(this.metadataStore.data.files).length === 0) {
@ -577,8 +517,7 @@ export default class SyncManager {
this.syncIntervalId = window.setInterval(
() => this.sync(),
// Sync interval is set in minutes but setInterval expects milliseconds
// minutes * 60 * 1000,
10000,
minutes * 60 * 1000,
);
return this.syncIntervalId;
}

View file

@ -1,161 +0,0 @@
import { usePlugin } from "../hooks";
import { useState, useMemo, useEffect } from "react";
const UploadDialogQuestion = ({
fileCount,
onCancel,
startUpload: onUpload,
}: {
fileCount: number;
onCancel: () => void;
startUpload: () => Promise<void>;
}) => {
return (
<div
style={{
display: "flex",
flexFlow: "column",
}}
>
<h1 style={{ width: "fit-content" }}>Upload files to GitHub</h1>
<p style={{ width: "fit-content" }}>
Do you want to upload{" "}
<span style={{ fontWeight: "bold" }}>{fileCount}</span> files? This
might take a while.
</p>
<div className="modal-button-container">
<button onClick={onCancel}>Cancel</button>
<button
onClick={async () => await onUpload()}
style={{
color: "var(--text-on-accent)",
backgroundColor: "var(--interactive-accent)",
}}
>
Upload
</button>
</div>
</div>
);
};
const UploadDialogUploading = ({
completedCount,
fileCount,
onCancel,
}: {
completedCount: number;
fileCount: number;
onCancel: () => void;
}) => {
const progress = (completedCount * 100) / fileCount;
return (
<div>
<h1 style={{ width: "fit-content" }}>
{progress === 100 ? "All files uploaded" : "Uploading…"}
</h1>
<div className="setting-progress-bar">
<div
className="setting-progress-bar-inner"
style={{
width: `${progress}%`,
}}
/>
</div>
<p>
Uploaded {completedCount} file{completedCount === 1 ? "" : "s"} of{" "}
{fileCount}
</p>
<div className="modal-button-container">
{/*
We use onCancel for both cases as it won't have any effect other
than closing the modal if upload is done
*/}
<button onClick={onCancel}>{progress === 100 ? "Ok" : "Cancel"}</button>
</div>
</div>
);
};
const UploadDialogContent = ({ onCancel }: { onCancel: () => void }) => {
const plugin = usePlugin();
if (!plugin) {
// Unlikely to happen, makes TS happy though
throw new Error("Plugin is not initialized");
}
const [uploading, setUploading] = useState(false);
const [completedCount, setCompletedCount] = useState(0);
const files = useMemo(
// This is not the most efficient way to get all files in the
// folder to sync but it's quick to code.
// I'll enhance it in the future.
() =>
plugin?.app.vault
.getFiles()
.filter((file) =>
file.path.startsWith(plugin.settings.localContentDir),
),
[],
);
const abortController = useMemo(() => new AbortController(), []);
// Stop the upload when the component is unmounted, otherwise
// we keep uploading even when the modal is closed.
useEffect(() => {
return () => {
abortController.abort();
};
}, []);
const startUploading = async () => {
setUploading(true);
// We upload files sequentially to avoid conflicts.
// GitHub rejects commits if they're made in fast succession, thus
// forcing us to retry the failed upload.
// So parallelization is not an option.
for (const file of files) {
if (abortController.signal.aborted) {
return;
}
await plugin.syncManager.uploadFile(file);
if (abortController.signal.aborted) {
// Check abort state only after the uploaded file metadata
// is updated, otherwise we risk having outdated SHAs
return;
}
setCompletedCount((count) => count + 1);
}
};
return (
<div
style={{
display: "flex",
flexFlow: "column",
}}
>
{!uploading && (
<UploadDialogQuestion
fileCount={files.length}
onCancel={onCancel}
startUpload={startUploading}
/>
)}
{uploading && (
<UploadDialogUploading
completedCount={completedCount}
fileCount={files.length}
onCancel={() => {
abortController.abort();
onCancel();
}}
/>
)}
</div>
);
};
export default UploadDialogContent;

View file

@ -1,33 +0,0 @@
import { Modal } from "obsidian";
import { Root, createRoot } from "react-dom/client";
import GitHubSyncPlugin from "src/main";
import { PluginContext } from "../hooks";
import UploadDialogContent from "./component";
/**
* Dialog shown when user tries to upload all local files to GitHub.
*/
export class UploadDialog extends Modal {
root: Root | null = null;
constructor(private plugin: GitHubSyncPlugin) {
super(plugin.app);
}
onOpen() {
this.root = createRoot(this.modalEl);
this.root.render(
<PluginContext.Provider value={this.plugin}>
<UploadDialogContent onCancel={() => this.close()} />
</PluginContext.Provider>,
);
}
onClose() {
const { contentEl } = this;
// It's important to unmount the component so upload stops
// in case it's running
this.root?.unmount();
contentEl.empty();
}
}