Skip to content

Commit

Permalink
Merge pull request #10 from frontend-engineering/fix-pasted-img
Browse files Browse the repository at this point in the history
Fix backslash path issue and attachment images sync issue
  • Loading branch information
walkthunder authored Oct 25, 2023
2 parents 88b26ab + d2831f9 commit 693a0fb
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const publishFiles = async (
if (exportedFile) {
externalFiles.push(...exportedFile.downloads.map((d: Downloadable) => {
const afterPath = exportedFile.exportToFolder.join(d.relativeDownloadPath);
const fileKey = (d.relativeDownloadPath.asString + '/' + d.filename).replace(/^\.\//, '');
const fileKey = Path.localToWebPath((d.relativeDownloadPath.asString + '/' + d.filename).replace(/^\.\//, ''));

const mdName = (file.path.endsWith('.md') && (fileKey?.replace(/\.html$/ig, '') === file.path.replace(/\.md$/igm, ''))) ? file.path : undefined
Object.assign(d, {
Expand Down
80 changes: 74 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from "./localdb";
import { RemoteClient, ServerDomain } from "./remote";
import { InvioSettingTab, DEFAULT_SETTINGS } from "./settings";
import { fetchMetadataFile, parseRemoteItems, SyncStatusType, RemoteSrcPrefix } from "./sync";
import { fetchMetadataFile, parseRemoteItems, SyncStatusType, RemoteSrcPrefix, RemoteAttPrefix } from "./sync";
import { doActualSync, getSyncPlan, isPasswordOk } from "./sync";
import { messyConfigToNormal, normalConfigToMessy } from "./configPersist";
import { ObsConfigDirFileType, listFilesInObsFolder } from "./obsFolderLister";
Expand Down Expand Up @@ -123,6 +123,7 @@ export default class InvioPlugin extends Plugin {
contents,
...file.stat
}
// TODO: Set max memory limit
log.info('file snapshot: ', this.recentSyncedFiles);
}

Expand Down Expand Up @@ -264,14 +265,12 @@ export default class InvioPlugin extends Plugin {
client.serviceType,
this.settings.password
);
console.log('parseRemoteItems result: ', remoteStates, metadataFile);
const origMetadataOnRemote = await fetchMetadataFile(
metadataFile,
client,
this.app.vault,
this.settings.password
);
console.log('fetchMetadataFile result: ', origMetadataOnRemote);

getNotice(
loadingModal,
Expand Down Expand Up @@ -473,6 +472,72 @@ export default class InvioPlugin extends Plugin {
const syncedFile = this.app.vault.getAbstractFileByPath(pathName);
if (syncedFile instanceof TFile) {
this.addRecentSyncedFile(syncedFile);
const meta = this.app.metadataCache.getFileCache(syncedFile);
if (meta?.embeds) {
// @ts-ignore
const attachmentFolderPath = app.vault.getConfig('attachmentFolderPath');
const attachmentFolderPrefix = attachmentFolderPath?.replace(/\/$/, '');
const attachmentList = await this.app.vault.adapter.list(attachmentFolderPrefix + '/');

const localAttachmentFiles: string[] = attachmentList.files;
log.info('local dir list: ', localAttachmentFiles);

// TODO: For all embeding formats.
const embedImages = meta.embeds
.filter((em: any) => em.link?.startsWith('Pasted image '))
.map(em => em.link);

log.info('embed list: ', embedImages);

const getLinkWithPrefix = (link: string) => `${attachmentFolderPrefix}/${link}`.replace(/^\//, '')
// TODO: Remove deleted attachment files
if (decision === 'uploadLocalToRemote') {
const diff = embedImages.filter(link => {
const exist = localAttachmentFiles.find(f => f === getLinkWithPrefix(link));
return exist;
})
await Promise.all(diff.map(async link => {
log.info('uploading attachment: ', link);
view?.info(`uploading attachment: ${link}`);

return client.uploadToRemote(
getLinkWithPrefix(link),
RemoteAttPrefix,
this.app.vault,
false,
'',
'',
null,
false,
null,
`${RemoteAttPrefix}/${link}`
)
}))
} else {
const diff: string[] = embedImages.map(link => {
const exist = localAttachmentFiles.find(f => f === getLinkWithPrefix(link));
return exist ? null : link;
})
.filter(l => !!l);
await Promise.all(diff.map(async link => {
view?.info(`downloading attachment: ${link}`);
log.info('downloading attachment: ', link);
return client.downloadFromRemote(
link,
RemoteAttPrefix,
this.app.vault,
0,
'',
'',
false,
getLinkWithPrefix(link)
)
.catch(err => {
log.error('sync attachment failed: ', err);
})
}))
}
}
}
}
// TODO: Get remote link, but need remote domain first
Expand Down Expand Up @@ -525,13 +590,16 @@ export default class InvioPlugin extends Plugin {
// Force Mode - Publish all docs
if (triggerSource === 'force') {
pubPathList.push(...allFiles.map(file => file.path));
pubPathList = pubPathList.filter((p, idx) => pubPathList.indexOf(p) === idx);
pubPathList = pubPathList
.filter((p, idx) => pubPathList.indexOf(p) === idx)
}
if (pubPathList?.length === 0) {
if (unPubList?.length > 0) {
// Need to update left tree links for unpublish means link deduction
const indexFile = allFiles.find(file => file.name === 'index.md') || allFiles[0];
pubPathList.push(indexFile.path);
const indexFile = allFiles.find(file => file.name === 'index.md') || allFiles.filter(file => !file.name.endsWith('.conflict.md'))[0];
if (indexFile) {
pubPathList.push(indexFile.path);
}
}
}
await publishFiles(client, this.app, pubPathList, allFiles, '', this.settings, triggerSource, view, (pathName: string, status: string, meta?: any) => {
Expand Down
92 changes: 49 additions & 43 deletions src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
THostConfig,
} from "./baseTypes";
import * as s3 from "./remoteForS3";

import { Path, WEB_PATH_SPLITER } from './utils/path';
import { log } from "./moreOnLog";
import { RemoteSrcPrefix } from "./sync";

Expand Down Expand Up @@ -74,57 +74,63 @@ export class RemoteClient {
}

getUseHostSlugPath(key: string) {
let localPath = ''
if (!this.useHost) {
return key;
}
const hasPrefix = key?.startsWith(RemoteSrcPrefix);
const paths = key?.split('/');
if (paths?.length > 0) {
let dir = hasPrefix ? paths[1] : paths[0];
if (dir === 'p') {
paths.splice(0, 1);
dir = hasPrefix ? paths[1] : paths[0];
}

if (dir !== this.localWatchDir) {
throw new Error('NeedSwitchProject');
}
if (hasPrefix) {
paths[1] = this.getUseHostSlug();
} else {
paths[0] = this.getUseHostSlug();
localPath = key;
} else {
const hasPrefix = key?.startsWith(RemoteSrcPrefix);
const paths = Path.splitString(key);
if (paths?.length > 0) {
let dir = hasPrefix ? paths[1] : paths[0];
if (dir === 'p') {
paths.splice(0, 1);
dir = hasPrefix ? paths[1] : paths[0];
}

if (dir !== this.localWatchDir) {
throw new Error('NeedSwitchProject');
}
if (hasPrefix) {
paths[1] = this.getUseHostSlug();
} else {
paths[0] = this.getUseHostSlug();
}
localPath = Path.joinString(paths);
}
return paths.join('/');
}
return '';

return Path.localToWebPath(localPath);
}

getUseHostLocalPath(slug: string) {
let webPath = ''
if (!this.useHost) {
return slug;
}
const hasPrefix = slug?.startsWith(RemoteSrcPrefix);
const paths = slug?.split('/');
let encrypted = false;
if (paths?.length > 0) {
let dir = hasPrefix ? paths[1] : paths[0];
if (dir === 'p') {
encrypted = true;
dir = hasPrefix ? paths[2] : paths[1];
}
const getSlug = this.getUseHostSlug();
if (dir !== getSlug?.replace(/^p\//, '')) {
throw new Error('NeedSwitchProject');
}
if (hasPrefix) {
paths[encrypted ? 2 : 1] = this.getUseHostDirname();
} else {
paths[encrypted ? 1 : 0] = this.getUseHostDirname();
webPath = slug
} else {
const hasPrefix = slug?.startsWith(RemoteSrcPrefix);
const paths = slug?.split(WEB_PATH_SPLITER);
let encrypted = false;
if (paths?.length > 0) {
let dir = hasPrefix ? paths[1] : paths[0];
if (dir === 'p') {
encrypted = true;
dir = hasPrefix ? paths[2] : paths[1];
}
const getSlug = this.getUseHostSlug();
if (dir !== getSlug?.replace(/^p\//, '')) {
throw new Error('NeedSwitchProject');
}
if (hasPrefix) {
paths[encrypted ? 2 : 1] = this.getUseHostDirname();
} else {
paths[encrypted ? 1 : 0] = this.getUseHostDirname();
}
webPath = Path.joinString(paths);
log.info('get local path: ', webPath)
}
log.info('get local path: ', paths.join('/'));
return paths.join('/');
}
return '';

return Path.webToLocalPath(webPath)
}

uploadToRemote = async (
Expand Down
19 changes: 10 additions & 9 deletions src/remoteForS3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
mkdirpInVault,
} from "./misc";
import Utils from './utils';
import { Path } from './utils/path';
export { S3Client } from "@aws-sdk/client-s3";

import { log } from "./moreOnLog";
Expand Down Expand Up @@ -339,11 +340,11 @@ export const uploadToRemote = async (
rawContent: string | ArrayBuffer = "",
remoteKey?: string
) => {
let uploadFile = prefix + fileOrFolderPath;
let uploadFileKey = Path.localToWebPath(prefix + fileOrFolderPath);
if (password !== "") {
uploadFile = prefix + remoteEncryptedKey;
uploadFileKey = Path.localToWebPath(prefix + remoteEncryptedKey);
}
const isFolder = fileOrFolderPath.endsWith("/");
const isFolder = Path.isFolderOrDir(fileOrFolderPath);

if (isFolder && isRecursively) {
throw Error("upload function doesn't implement recursive function yet!");
Expand All @@ -356,12 +357,12 @@ export const uploadToRemote = async (
await s3Client.send(
new PutObjectCommand({
Bucket: s3Config.s3BucketName,
Key: uploadFile,
Key: uploadFileKey,
Body: "",
ContentType: contentType,
})
);
return await getRemoteMeta(s3Client, s3Config, uploadFile);
return await getRemoteMeta(s3Client, s3Config, uploadFileKey);
} else {
// file
// we ignore isRecursively parameter here
Expand Down Expand Up @@ -397,7 +398,7 @@ export const uploadToRemote = async (
leavePartsOnError: false,
params: {
Bucket: s3Config.s3BucketName,
Key: remoteKey || uploadFile,
Key: remoteKey || uploadFileKey,
Body: body,
ContentType: contentType,
},
Expand All @@ -407,7 +408,7 @@ export const uploadToRemote = async (
});
await upload.done();

return await getRemoteMeta(s3Client, s3Config, remoteKey || uploadFile);
return await getRemoteMeta(s3Client, s3Config, remoteKey || uploadFileKey);
}
};

Expand Down Expand Up @@ -508,7 +509,7 @@ export const downloadFromRemote = async (
skipSaving: boolean = false,
renamedTo?: string
) => {
const isFolder = fileOrFolderPath.endsWith("/");
const isFolder = Path.isFolderOrDir(fileOrFolderPath);

if (!skipSaving) {
await mkdirpInVault(fileOrFolderPath, vault);
Expand Down Expand Up @@ -536,7 +537,7 @@ export const downloadFromRemote = async (
localContent = await decryptArrayBuffer(remoteContent, password);
}
if (!skipSaving) {
await vault.adapter.writeBinary(renamedTo || fileOrFolderPath, localContent, {
await vault.adapter.writeBinary(Path.webToLocalPath(renamedTo || fileOrFolderPath), localContent, {
mtime: mtime,
});
}
Expand Down
8 changes: 6 additions & 2 deletions src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { Utils } from './utils/utils';
import { log } from "./moreOnLog";

export const RemoteSrcPrefix = 'op-remote-source-raw/'
export const RemoteAttPrefix = 'op-remote-attach-p/'
// 影响到pub产物变动的decision
const RemoteFileTouchedDecisions = ['uploadLocalDelHistToRemote', 'uploadLocalToRemote'];
const LocalFileTouchedDecisions = ['downloadRemoteToLocal', 'keepRemoteDelHist'];
Expand Down Expand Up @@ -1292,10 +1293,13 @@ const dispatchOperationToActual = async (
if (r.remoteUnsync && r.existLocal) {
// TODO: Add a hook to alert users the risk of data lost on the local
log.info('rename last changed version from local for backup: ', r.key)
const renamed = r.key.replace(/\.md$/ig, '.conflict.md');
const conflictKey = () => '.' + Math.random().toFixed(4).slice(2) + '.conflict.md'
let renamed = r.key.replace(/\.md$/ig, conflictKey());

if (vault.adapter.exists(renamed)) {
renamed = renamed.replace('.conflict.md', conflictKey())
}
await vault.adapter.rename(r.key, renamed);

await Utils.appendFile(vault, renamed, ConflictDescriptionTxt);
}

Expand Down
24 changes: 23 additions & 1 deletion src/utils/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const isWindows: boolean = (typeof process.platform === 'string' ?
process.platform :
parseOSFromUA(navigator.userAgent)) === "win32";

const PATH_SPLITER = isWindows ? '\\' : '/';
export const PATH_SPLITER = isWindows ? '\\' : '/';
export const WEB_PATH_SPLITER = '/';

export class Path
{
Expand Down Expand Up @@ -243,6 +244,27 @@ export class Path
return path.replaceAll(" ", "-").replaceAll(/-{2,}/g, "-").replace(".-", "-").toLowerCase();
}

static localToWebPath(path: string): string {
return path.replaceAll(PATH_SPLITER, '/')
}

static webToLocalPath(path: string): string {
if (isWindows) {
return path.replaceAll('/', PATH_SPLITER);
}
return path;
}
static splitString(path: string): string[] {
if (!path) return [];
return path.split(PATH_SPLITER);
}
static joinString(paths: string[]): string {
return paths.join(PATH_SPLITER);
}
static isFolderOrDir(path: string): boolean {
return path.endsWith(PATH_SPLITER) || path.endsWith(WEB_PATH_SPLITER);
}

joinString(...paths: string[]): Path
{
return this.copy.reparse(Path.joinStringPaths(this.asString, ...paths));
Expand Down

0 comments on commit 693a0fb

Please sign in to comment.