@mieweb/wikigdrive
Version:
Google Drive to MarkDown synchronization
152 lines (129 loc) • 4.64 kB
text/typescript
import process from 'node:process';
import { FileContentService } from '../../utils/FileContentService.ts';
import { TreeItem } from '../../model/TreeItem.ts';
import { DirectoryScanner, RESERVED_NAMES } from './DirectoryScanner.ts';
import { MimeTypes } from '../../model/GoogleFile.ts';
import { FileId } from '../../model/model.ts';
type CallBack<K> = (treeItem: K) => boolean;
export type TreeItemTuple = [TreeItem?, string?];
export class MarkdownTreeProcessor {
private driveTree: TreeItem[] = [];
constructor(private driveFileSystem: FileContentService) {
}
async load() {
this.driveTree = await this.driveFileSystem.readJson('.tree.json') || [];
}
async save() {
if (this.driveTree[0]) {
this.driveTree[0]['wikigdrive'] = process.env.GIT_SHA;
}
await this.driveFileSystem.writeJson('.tree.json', this.driveTree);
}
async regenerateTree(rootFolderId: FileId): Promise<void> {
this.driveTree = await this.internalRegenerateTree(this.driveFileSystem, rootFolderId);
}
private async internalRegenerateTree(contentFileService: FileContentService, parentId?: string): Promise<Array<TreeItem>> {
const scanner = new DirectoryScanner();
const files = await scanner.scan(contentFileService);
const retVal = [];
for (const realFileName in files) {
if (RESERVED_NAMES.includes(realFileName)) {
continue;
}
if (realFileName.endsWith('.debug.xml')) {
continue;
}
const file = files[realFileName];
if (file.mimeType === MimeTypes.FOLDER_MIME) {
const subFilesService = await contentFileService.getSubFileService(realFileName);
const item: TreeItem = {
id: file.id,
title: file.title,
path: contentFileService.getVirtualPath() + realFileName,
realFileName: realFileName,
fileName: file.fileName,
mimeType: file.mimeType,
modifiedTime: file.modifiedTime,
version: file.version,
conflicting: file.type === 'conflict' ? file.conflicting : undefined,
redirectTo: file.type === 'redir' ? file.redirectTo : undefined,
lastAuthor: 'lastAuthor' in file ? file.lastAuthor : undefined,
parentId,
children: await this.internalRegenerateTree(subFilesService, file.id)
};
retVal.push(item);
} else {
const item: TreeItem = {
id: file.id,
title: file.title,
path: contentFileService.getVirtualPath() + realFileName,
fileName: file.fileName,
realFileName: realFileName,
mimeType: file.mimeType,
modifiedTime: file.modifiedTime,
version: file.version,
conflicting: file.type === 'conflict' ? file.conflicting : undefined,
redirectTo: file.type === 'redir' ? file.redirectTo : undefined,
lastAuthor: 'lastAuthor' in file ? file.lastAuthor : undefined,
parentId
};
retVal.push(item);
}
}
return retVal;
}
async findById(fileId: FileId): Promise<TreeItemTuple> {
return await this.findInTree(item => item.id === fileId, this.driveTree);
}
async findByPath(path: string) {
return await this.findInTree(item => item.path === path, this.driveTree);
}
private async findInTree(callBack: CallBack<TreeItem>, children: Array<TreeItem>, curPath = ''): Promise<TreeItemTuple> {
for (const file of children) {
const part = file['realFileName'];
if (callBack(file)) {
return [file, curPath ? curPath + '/' + part : part];
}
}
for (const file of children) {
if (file.mimeType !== MimeTypes.FOLDER_MIME) {
continue;
}
if (file.children) {
const part = file['realFileName'];
const tuple = await this.findInTree(callBack, file.children, curPath ? curPath + '/' + part : part);
if (tuple?.length > 0) {
return tuple;
}
}
}
return [];
}
async walkTree(callBack: CallBack<TreeItem>) {
await this.findInTree(callBack, this.driveTree);
}
async getRootItem(driveId: FileId): Promise<TreeItemTuple> {
return [{
path: '/',
fileName: '/',
realFileName: '/',
title: '/',
mimeType: MimeTypes.FOLDER_MIME,
id: driveId,
parentId: driveId,
children: this.driveTree
}, '/'];
}
isEmpty() {
return this.driveTree.length === 0;
}
getTree() {
return this.driveTree;
}
getTreeVersion() {
if (this.driveTree.length < 1) {
return null;
}
return this.driveTree[0]['wikigdrive'] || null;
}
}