Skip to content
Draft
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
208 changes: 207 additions & 1 deletion main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Plugin, PluginSettingTab, Setting, TFile, TFolder, Notice, MarkdownView, Modal, App } from 'obsidian';
import { Plugin, PluginSettingTab, Setting, TFile, TFolder, TAbstractFile, Notice, MarkdownView, Modal, App } from 'obsidian';
import { EditorView, ViewUpdate, ViewPlugin } from '@codemirror/view';
import { StateField } from '@codemirror/state';
import { EditorState } from '@codemirror/state';
Expand Down Expand Up @@ -305,6 +305,20 @@ export default class LockdownPlugin extends Plugin {
})
);

// Handle file/folder rename (move) to update lock registry
this.registerEvent(
this.app.vault.on('rename', (file, oldPath) => {
this.handleFileRename(file, oldPath);
})
);

// Handle file deletion to clean up lock registry
this.registerEvent(
this.app.vault.on('delete', (file) => {
this.handleFileDelete(file);
})
);

// Intercept file opening to show overlay for locked files
this.registerEvent(
this.app.workspace.on('file-open', (file) => {
Expand Down Expand Up @@ -1488,6 +1502,198 @@ export default class LockdownPlugin extends Plugin {
await this.unlockFolder(lockedFolderPath);
}

/**
* Handle file or folder rename/move events to update lock registry.
* This ensures locked files maintain their lock status when moved.
*/
private handleFileRename(file: TAbstractFile, oldPath: string): void {
const newPath = file.path;

if (file instanceof TFile) {
// Handle file rename/move
if (this.lockedFiles.has(oldPath)) {
// Update locked files set
this.lockedFiles.delete(oldPath);
this.lockedFiles.add(newPath);

// Update password hash
const passwordHash = this.passwordHashes.get(oldPath);
if (passwordHash) {
this.passwordHashes.delete(oldPath);
this.passwordHashes.set(newPath, passwordHash);
}

// Update cached password
const cachedPassword = this.filePasswords.get(oldPath);
if (cachedPassword) {
this.filePasswords.delete(oldPath);
this.filePasswords.set(newPath, cachedPassword);
}

// Update lock registry
this.lockRegistry.updateFilePath(oldPath, newPath);

// Save the updated state
void this.saveLockedFiles();

// Update UI
this.updateFileExplorerIndicators();

// If there's an overlay for the old path, update it
if (this.lockOverlayManager.has(oldPath)) {
this.removeLockOverlay(oldPath);
// Show overlay for new path after a short delay to ensure DOM is updated
setTimeout(() => {
if (this.isFileLocked(newPath)) {
this.showLockOverlay(newPath);
}
}, 100);
}
}
} else if (file instanceof TFolder) {
// Handle folder rename/move
const oldPrefix = oldPath + '/';
const newPrefix = newPath + '/';

// Update the locked folder itself if it was locked
if (this.lockedFolders.has(oldPath)) {
this.lockedFolders.delete(oldPath);
this.lockedFolders.add(newPath);
this.lockRegistry.updateFolderPath(oldPath, newPath);
}

// Update all locked files that were inside the renamed folder
const filesToUpdate: Array<{ oldFilePath: string; newFilePath: string }> = [];
for (const filePath of this.lockedFiles) {
if (filePath.startsWith(oldPrefix)) {
const relativePath = filePath.substring(oldPrefix.length);
const newFilePath = newPrefix + relativePath;
filesToUpdate.push({ oldFilePath: filePath, newFilePath });
}
}

for (const { oldFilePath, newFilePath } of filesToUpdate) {
// Update locked files set
this.lockedFiles.delete(oldFilePath);
this.lockedFiles.add(newFilePath);

// Update password hash
const passwordHash = this.passwordHashes.get(oldFilePath);
if (passwordHash) {
this.passwordHashes.delete(oldFilePath);
this.passwordHashes.set(newFilePath, passwordHash);
}

// Update cached password
const cachedPassword = this.filePasswords.get(oldFilePath);
if (cachedPassword) {
this.filePasswords.delete(oldFilePath);
this.filePasswords.set(newFilePath, cachedPassword);
}

// Update lock registry
this.lockRegistry.updateFilePath(oldFilePath, newFilePath);

// Update overlays if needed
if (this.lockOverlayManager.has(oldFilePath)) {
this.removeLockOverlay(oldFilePath);
}
}

// Update all locked subfolders
const foldersToUpdate: Array<{ oldFolderPath: string; newFolderPath: string }> = [];
for (const folderPath of this.lockedFolders) {
if (folderPath.startsWith(oldPrefix)) {
const relativePath = folderPath.substring(oldPrefix.length);
const newFolderPath = newPrefix + relativePath;
foldersToUpdate.push({ oldFolderPath: folderPath, newFolderPath });
}
}

for (const { oldFolderPath, newFolderPath } of foldersToUpdate) {
this.lockedFolders.delete(oldFolderPath);
this.lockedFolders.add(newFolderPath);
this.lockRegistry.updateFolderPath(oldFolderPath, newFolderPath);
}

// Also update child paths in lock registry
this.lockRegistry.updateChildFilePaths(oldPath, newPath);
this.lockRegistry.updateChildFolderPaths(oldPath, newPath);

// Save the updated state
void this.saveLockedFiles();

// Update UI
this.updateFileExplorerIndicators();
}
}

/**
* Handle file or folder deletion to clean up lock registry.
* This prevents stale entries from accumulating.
*/
private handleFileDelete(file: TAbstractFile): void {
const path = file.path;

if (file instanceof TFile) {
// Clean up file from lock registry
if (this.lockedFiles.has(path)) {
this.lockedFiles.delete(path);
this.passwordHashes.delete(path);
this.filePasswords.delete(path);

// Remove from lock overlay manager
this.removeLockOverlay(path);

// Save the updated state
void this.saveLockedFiles();

// Update UI
this.updateFileExplorerIndicators();
}
} else if (file instanceof TFolder) {
// Clean up folder from lock registry
if (this.lockedFolders.has(path)) {
this.lockedFolders.delete(path);
}

// Clean up any files that were in the deleted folder
const prefix = path + '/';
const filesToRemove: string[] = [];

for (const filePath of this.lockedFiles) {
if (filePath.startsWith(prefix)) {
filesToRemove.push(filePath);
}
}

for (const filePath of filesToRemove) {
this.lockedFiles.delete(filePath);
this.passwordHashes.delete(filePath);
this.filePasswords.delete(filePath);
this.removeLockOverlay(filePath);
}

// Clean up any subfolders that were locked
const foldersToRemove: string[] = [];
for (const folderPath of this.lockedFolders) {
if (folderPath.startsWith(prefix)) {
foldersToRemove.push(folderPath);
}
}

for (const folderPath of foldersToRemove) {
this.lockedFolders.delete(folderPath);
}

// Save the updated state
void this.saveLockedFiles();

// Update UI
this.updateFileExplorerIndicators();
}
}

createLockdownExtension() {
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for CodeMirror extension closure
const plugin = this;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

112 changes: 112 additions & 0 deletions src/infrastructure/storage/LockRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,118 @@ export class LockRegistry {
return Array.from(this.lockedFolders);
}

/**
* Update the path of a locked file when it is moved or renamed.
* This preserves the lock status and password hash across path changes.
* @param oldPath - The previous file path
* @param newPath - The new file path
* @returns true if the file was tracked and updated, false otherwise
*/
updateFilePath(oldPath: string, newPath: string): boolean {
if (!this.lockedFiles.has(oldPath)) {
return false;
}

// Move the file entry from old path to new path
this.lockedFiles.delete(oldPath);
this.lockedFiles.add(newPath);

// Move the password hash if it exists
const passwordHash = this.passwordHashes.get(oldPath);
if (passwordHash) {
this.passwordHashes.delete(oldPath);
this.passwordHashes.set(newPath, passwordHash);
}

return true;
}

/**
* Update the path of a locked folder when it is moved or renamed.
* This preserves the lock status across path changes.
* @param oldPath - The previous folder path
* @param newPath - The new folder path
* @returns true if the folder was tracked and updated, false otherwise
*/
updateFolderPath(oldPath: string, newPath: string): boolean {
if (!this.lockedFolders.has(oldPath)) {
return false;
}

// Move the folder entry from old path to new path
this.lockedFolders.delete(oldPath);
this.lockedFolders.add(newPath);

return true;
}

/**
* Update all locked file paths that are children of a renamed folder.
* @param oldFolderPath - The previous folder path
* @param newFolderPath - The new folder path
* @returns Number of files updated
*/
updateChildFilePaths(oldFolderPath: string, newFolderPath: string): number {
let updatedCount = 0;
const oldPrefix = oldFolderPath + '/';
const filesToUpdate: Array<{ oldPath: string; newPath: string }> = [];

// Find all locked files that are children of the old folder path
for (const filePath of this.lockedFiles) {
if (filePath.startsWith(oldPrefix)) {
const relativePath = filePath.substring(oldPrefix.length);
const newFilePath = newFolderPath + '/' + relativePath;
filesToUpdate.push({ oldPath: filePath, newPath: newFilePath });
}
}

// Update each file path
for (const { oldPath, newPath } of filesToUpdate) {
this.lockedFiles.delete(oldPath);
this.lockedFiles.add(newPath);

const passwordHash = this.passwordHashes.get(oldPath);
if (passwordHash) {
this.passwordHashes.delete(oldPath);
this.passwordHashes.set(newPath, passwordHash);
}

updatedCount++;
}

return updatedCount;
}

/**
* Update all locked folder paths that are children of a renamed folder.
* @param oldFolderPath - The previous folder path
* @param newFolderPath - The new folder path
* @returns Number of folders updated
*/
updateChildFolderPaths(oldFolderPath: string, newFolderPath: string): number {
let updatedCount = 0;
const oldPrefix = oldFolderPath + '/';
const foldersToUpdate: Array<{ oldPath: string; newPath: string }> = [];

// Find all locked folders that are children of the old folder path
for (const folderPath of this.lockedFolders) {
if (folderPath.startsWith(oldPrefix)) {
const relativePath = folderPath.substring(oldPrefix.length);
const newChildFolderPath = newFolderPath + '/' + relativePath;
foldersToUpdate.push({ oldPath: folderPath, newPath: newChildFolderPath });
}
}

// Update each folder path
for (const { oldPath, newPath } of foldersToUpdate) {
this.lockedFolders.delete(oldPath);
this.lockedFolders.add(newPath);
updatedCount++;
}

return updatedCount;
}

clear(): void {
this.lockedFiles.clear();
this.lockedFolders.clear();
Expand Down