diff --git a/main.js b/main.js index 28cb07d..f8f866c 100644 --- a/main.js +++ b/main.js @@ -158,6 +158,10 @@ var DriveApi = class { const { id } = resp.json; return id; } + async moveFile(driveId, oldParentId, newParentId) { + const params = new URLSearchParams({ addParents: newParentId, removeParents: oldParentId, fields: "id" }); + await this.request("PATCH", `${BASE}/files/${driveId}?${params}`); + } async renameFile(driveId, newName) { await this.request( "PATCH", @@ -191,7 +195,7 @@ var DriveApi = class { pageToken: token, pageSize: "1000", includeRemoved: "true", - fields: "nextPageToken,newStartPageToken,changes(fileId,removed,file(id,name,mimeType,modifiedTime,trashed))" + fields: "nextPageToken,newStartPageToken,changes(fileId,removed,file(id,name,mimeType,modifiedTime,size,trashed))" }); const resp = await this.request("GET", `${BASE}/changes?${params}`); const data = resp.json; @@ -756,6 +760,14 @@ var Syncer = class { const mimeType = fromPath(path); const cached2 = this.index.get(path); if (cached2 && !cached2.isFolder) { + if (op === "create") { + const targetParentId = await this.index.resolveParentFolder(path); + const meta = await this.drive.getFileMeta(cached2.driveId); + const currentParentId = meta.parents?.[0]; + if (currentParentId && currentParentId !== targetParentId) { + await this.drive.moveFile(cached2.driveId, currentParentId, targetParentId); + } + } await this.drive.updateFile(cached2.driveId, bytes, mimeType, mtime, this.settings.keepRevisions); this.index.set(path, { ...cached2, driveMtime: mtime, syncedAt: Date.now() }); } else { @@ -777,11 +789,14 @@ var Syncer = class { if (cached2) { try { await this.drive.deleteFile(cached2.driveId); - } catch (e) { + this.index.delete(path); + result.deleted.push(path); + } catch (err) { + result.errors.push({ path, error: `Drive delete failed: ${err instanceof Error ? err.message : String(err)}` }); } - this.index.delete(path); + } else { + result.deleted.push(path); } - result.deleted.push(path); } async handleConflict(path, driveMtime, result) { const entry = this.index.get(path); diff --git a/manifest.json b/manifest.json index 53d1d83..fa95fc7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "neogdsync", "name": "NeoGDSync", - "version": "0.2.0", + "version": "0.2.1", "minAppVersion": "1.6.0", "description": "Lightweight Google Drive sync with path-based index, conflict detection, and smart/push/pull modes.", "author": "LM", diff --git a/package.json b/package.json index 0e9ee2f..e686d1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neogdsync", - "version": "0.2.0", + "version": "0.2.1", "description": "", "main": "index.js", "scripts": { diff --git a/src/driveApi.js b/src/driveApi.js new file mode 100644 index 0000000..8ec53c4 --- /dev/null +++ b/src/driveApi.js @@ -0,0 +1,140 @@ +/** Google Drive API v3 wrapper */ +import { requestUrl } from 'obsidian'; +import { getAccessToken } from './auth'; +const BASE = 'https://www.googleapis.com/drive/v3'; +const UPLOAD = 'https://www.googleapis.com/upload/drive/v3'; +const FOLDER_MIME = 'application/vnd.google-apps.folder'; +async function driveRequest(method, url, body, headers, refreshToken) { + const token = refreshToken ? await getAccessToken(refreshToken) : ''; + const resp = await requestUrl({ + url, + method, + headers: { Authorization: `Bearer ${token}`, ...headers }, + body, + throw: false, + }); + if (resp.status >= 400) { + const txt = resp.text.slice(0, 200); + throw new Error(`Drive ${method} ${url} → ${resp.status}: ${txt}`); + } + return resp; +} +export class DriveApi { + constructor(refreshToken) { + this.refreshToken = refreshToken; + } + request(method, url, body, headers) { + return driveRequest(method, url, body, headers, this.refreshToken); + } + // ── Folder operations ────────────────────────────────────────── + async listChildren(folderId) { + var _a; + const results = []; + let pageToken; + do { + const params = new URLSearchParams({ + q: `'${folderId}' in parents and trashed=false`, + fields: 'nextPageToken,files(id,name,mimeType,modifiedTime,size)', + pageSize: '1000', + }); + if (pageToken) + params.set('pageToken', pageToken); + const resp = await this.request('GET', `${BASE}/files?${params}`); + const data = resp.json; + results.push(...((_a = data.files) !== null && _a !== void 0 ? _a : [])); + pageToken = data.nextPageToken; + } while (pageToken); + return results; + } + async createFolder(name, parentId) { + const resp = await this.request('POST', `${BASE}/files?fields=id`, JSON.stringify({ name, mimeType: FOLDER_MIME, parents: [parentId] }), { 'Content-Type': 'application/json' }); + const { id } = resp.json; + return id; + } + // ── File operations ──────────────────────────────────────────── + async uploadFile(name, parentId, content, mimeType, modifiedTime, keepRevision = false) { + const boundary = 'neogdsync_boundary'; + const meta = JSON.stringify({ name, parents: [parentId], modifiedTime }); + const body = buildMultipart(boundary, meta, content, mimeType); + const params = new URLSearchParams({ uploadType: 'multipart', fields: 'id' }); + if (keepRevision) + params.set('keepRevisionForever', 'true'); + const resp = await this.request('POST', `${UPLOAD}/files?${params}`, body.buffer, { 'Content-Type': `multipart/related; boundary=${boundary}` }); + const { id } = resp.json; + return id; + } + async updateFile(driveId, content, mimeType, modifiedTime, keepRevision = false) { + const boundary = 'neogdsync_boundary'; + const meta = JSON.stringify({ modifiedTime }); + const body = buildMultipart(boundary, meta, content, mimeType); + const params = new URLSearchParams({ uploadType: 'multipart', fields: 'id' }); + if (keepRevision) + params.set('keepRevisionForever', 'true'); + const resp = await this.request('PATCH', `${UPLOAD}/files/${driveId}?${params}`, body.buffer, { 'Content-Type': `multipart/related; boundary=${boundary}` }); + const { id } = resp.json; + return id; + } + async moveFile(driveId, oldParentId, newParentId) { + const params = new URLSearchParams({ addParents: newParentId, removeParents: oldParentId, fields: 'id' }); + await this.request('PATCH', `${BASE}/files/${driveId}?${params}`); + } + async renameFile(driveId, newName) { + await this.request('PATCH', `${BASE}/files/${driveId}?fields=id`, JSON.stringify({ name: newName }), { 'Content-Type': 'application/json' }); + } + async deleteFile(driveId) { + await this.request('DELETE', `${BASE}/files/${driveId}`); + } + async downloadFile(driveId) { + const resp = await this.request('GET', `${BASE}/files/${driveId}?alt=media`); + return resp.arrayBuffer; + } + async getFileMeta(driveId) { + const resp = await this.request('GET', `${BASE}/files/${driveId}?fields=id,name,mimeType,modifiedTime,parents,size`); + return resp.json; + } + async getChanges(pageToken) { + var _a, _b; + const changes = []; + let token = pageToken; + while (token) { + const params = new URLSearchParams({ + pageToken: token, + pageSize: '1000', + includeRemoved: 'true', + fields: 'nextPageToken,newStartPageToken,changes(fileId,removed,file(id,name,mimeType,modifiedTime,size,trashed))', + }); + const resp = await this.request('GET', `${BASE}/changes?${params}`); + const data = resp.json; + changes.push(...((_a = data.changes) !== null && _a !== void 0 ? _a : [])); + token = (_b = data.nextPageToken) !== null && _b !== void 0 ? _b : ''; + if (data.newStartPageToken) { + return { changes, newToken: data.newStartPageToken }; + } + } + return { changes, newToken: pageToken }; + } + async getStartPageToken() { + const resp = await this.request('GET', `${BASE}/changes/startPageToken`); + const { startPageToken } = resp.json; + return startPageToken; + } + async listRevisions(driveId) { + var _a; + const resp = await this.request('GET', `${BASE}/files/${driveId}/revisions?fields=revisions(id,modifiedTime,size)`); + const data = resp.json; + return (_a = data.revisions) !== null && _a !== void 0 ? _a : []; + } +} +// ── helpers ──────────────────────────────────────────────────── +function buildMultipart(boundary, meta, content, mime) { + const enc = new TextEncoder(); + const header = enc.encode(`--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${meta}\r\n` + + `--${boundary}\r\nContent-Type: ${mime}\r\n\r\n`); + const footer = enc.encode(`\r\n--${boundary}--`); + const body = new Uint8Array(header.byteLength + content.byteLength + footer.byteLength); + body.set(header, 0); + body.set(new Uint8Array(content), header.byteLength); + body.set(footer, header.byteLength + content.byteLength); + return body; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJpdmVBcGkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJkcml2ZUFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxrQ0FBa0M7QUFFbEMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUN0QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBR3hDLE1BQU0sSUFBSSxHQUFHLHFDQUFxQyxDQUFDO0FBQ25ELE1BQU0sTUFBTSxHQUFHLDRDQUE0QyxDQUFDO0FBQzVELE1BQU0sV0FBVyxHQUFHLG9DQUFvQyxDQUFDO0FBRXpELEtBQUssVUFBVSxZQUFZLENBQ3pCLE1BQWMsRUFDZCxHQUFXLEVBQ1gsSUFBMkIsRUFDM0IsT0FBZ0MsRUFDaEMsWUFBcUI7SUFFckIsTUFBTSxLQUFLLEdBQUcsWUFBWSxDQUFDLENBQUMsQ0FBQyxNQUFNLGNBQWMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ3JFLE1BQU0sSUFBSSxHQUFHLE1BQU0sVUFBVSxDQUFDO1FBQzVCLEdBQUc7UUFDSCxNQUFNO1FBQ04sT0FBTyxFQUFFLEVBQUUsYUFBYSxFQUFFLFVBQVUsS0FBSyxFQUFFLEVBQUUsR0FBRyxPQUFPLEVBQUU7UUFDekQsSUFBSTtRQUNKLEtBQUssRUFBRSxLQUFLO0tBQ2IsQ0FBQyxDQUFDO0lBQ0gsSUFBSSxJQUFJLENBQUMsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNwQyxNQUFNLElBQUksS0FBSyxDQUFDLFNBQVMsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUNELE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVELE1BQU0sT0FBTyxRQUFR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S0FBSyxRQUFRLDREQUE0RCxJQUFJLE1BQU07UUFDbkYsS0FBSyxRQUFRLHFCQUFxQixJQUFJLFVBQVUsQ0FDakQsQ0FBQztJQUNGLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsU0FBUyxRQUFRLElBQUksQ0FBQyxDQUFDO0lBQ2pELE1BQU0sSUFBSSxHQUFHLElBQUksVUFBVSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEdBQUcsT0FBTyxDQUFDLFVBQVUsR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDeEYsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDcEIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsRUFBRSxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDckQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLFVBQVUsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDekQsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqIEdvb2dsZSBEcml2ZSBBUEkgdjMgd3JhcHBlciAqL1xuXG5pbXBvcnQgeyByZXF1ZXN0VXJsIH0gZnJvbSAnb2JzaWRpYW4nO1xuaW1wb3J0IHsgZ2V0QWNjZXNzVG9rZW4gfSBmcm9tICcuL2F1dGgnO1xuaW1wb3J0IHsgRHJpdmVGaWxlSW5mbywgRHJpdmVDaGFuZ2UsIERyaXZlUmV2aXNpb24gfSBmcm9tICcuL3R5cGVzJztcblxuY29uc3QgQkFTRSA9ICdodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9kcml2ZS92Myc7XG5jb25zdCBVUExPQUQgPSAnaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vdXBsb2FkL2RyaXZlL3YzJztcbmNvbnN0IEZPTERFUl9NSU1FID0gJ2FwcGxpY2F0aW9uL3ZuZC5nb29nbGUtYXBwcy5mb2xkZXInO1xuXG5hc3luYyBmdW5jdGlvbiBkcml2ZVJlcXVlc3QoXG4gIG1ldGhvZDogc3RyaW5nLFxuICB1cmw6IHN0cmluZyxcbiAgYm9keT86IHN0cmluZyB8IEFycmF5QnVmZmVyLFxuICBoZWFkZXJzPzogUmVjb3JkPHN0cmluZywgc3RyaW5nPixcbiAgcmVmcmVzaFRva2VuPzogc3RyaW5nLFxuKTogUHJvbWlzZTx7IHN0YXR1czogbnVtYmVyOyBqc29uOiB1bmtub3duOyB0ZXh0OiBzdHJpbmc7IGFycmF5QnVmZmVyOiBBcnJheUJ1ZmZlciB9PiB7XG4gIGNvbnN0IHRva2VuID0gcmVmcmVzaFRva2VuID8gYXdhaXQgZ2V0QWNjZXNzVG9rZW4ocmVmcmVzaFRva2VuKSA6ICcnO1xuICBjb25zdCByZXNwID0gYXdhaXQgcmVxdWVzdFVybCh7XG4gICAgdXJsLFxuICAgIG1ldGhvZCxcbiAgICBoZWFkZXJzOiB7IEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHt0b2tlbn1gLCAuLi5oZWFkZXJzIH0sXG4gICAgYm9keSxcbiAgICB0aHJvdzogZmFsc2UsXG4gIH0pO1xuICBpZiAocmVzcC5zdGF0dXMgPj0gNDAwKSB7XG4gICAgY29uc3QgdHh0ID0gcmVzcC50ZXh0LnNsaWNlKDAsIDIwMCk7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBEcml2ZSAke21ldGhvZH0gJHt1cmx9IOKGkiAke3Jlc3Auc3RhdHVzfTogJHt0eHR9YCk7XG4gIH1cbiAgcmV0dXJuIHJlc3A7XG59XG5cbmV4cG9ydCBjbGFzcyBEcml2ZUFwaSB7XG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgcmVmcmVzaFRva2VuOiBzdHJpbmcpIHt9XG5cbiAgcHJpdmF0ZSByZXF1ZXN0KG1ldGhvZDogc3RyaW5nLCB1cmw6IHN0cmluZywgYm9keT86IHN0cmluZyB8IEFycmF5QnVmZmVyLCBoZWFkZXJzPzogUmVjb3JkPHN0cmluZywgc3RyaW5nPikge1xuICAgIHJldHVybiBkcml2ZVJlcXVlc3QobWV0aG9kLCB1cmwsIGJvZHksIGhlYWRlcnMsIHRoaXMucmVmcmVzaFRva2VuKTtcbiAgfVxuXG4gIC8vIOKUgOKUgCBGb2xkZXIgb3BlcmF0aW9ucyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuICBhc3luYyBsaXN0Q2hpbGRyZW4oZm9sZGVySWQ6IHN0cmluZyk6IFByb21pc2U8RHJpdmVGaWxlSW5mb1tdPiB7XG4gICAgY29uc3QgcmVzdWx0czogRHJpdmVGaWxlSW5mb1tdID0gW107XG4gICAgbGV0IHBhZ2VUb2tlbjogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIGRvIHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IG5ldyBVUkxTZWFyY2hQYXJhbXMoe1xuICAgICAgICBxOiBgJyR7Zm9sZGVySWR9JyBpbiBwYXJlbnRzIGFuZCB0cmFzaGVkPWZhbHNlYCxcbiAgICAgICAgZmllbGRzOiAnbmV4dFBhZ2VUb2tlbixmaWxlcyhpZCxuYW1lLG1pbWVUeXBlLG1vZGlmaWVkVGltZSxzaXplKScsXG4gICAgICAgIHBhZ2VTaXplOiAnMTAwMCcsXG4gICAgICB9KTtcbiAgICAgIGlmIChwYWdlVG9rZW4pIHBhcmFtcy5zZXQoJ3BhZ2VUb2tlbicsIHBhZ2VUb2tlbik7XG4gICAgICBjb25zdCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KCdHRVQnLCBgJHtCQVNFfS9maWxlcz8ke3BhcmFtc31gKTtcbiAgICAgIGNvbnN0IGRhdGEgPSByZXNwLmpzb24gYXMgeyBmaWxlcz86IERyaXZlRmlsZUluZm9bXTsgbmV4dFBhZ2VUb2tlbj86IHN0cmluZyB9O1xuICAgICAgcmVzdWx0cy5wdXNoKC4uLihkYXRhLmZpbGVzID8/IFtdKSk7XG4gICAgICBwYWdlVG9rZW4gPSBkYXRhLm5leHRQYWdlVG9rZW47XG4gICAgfSB3aGlsZSAocGFnZVRva2VuKTtcbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfVxuXG4gIGFzeW5jIGNyZWF0ZUZvbGRlcihuYW1lOiBzdHJpbmcsIHBhcmVudElkOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHJlc3AgPSBhd2FpdCB0aGlzLnJlcXVlc3QoXG4gICAgICAnUE9TVCcsXG4gICAgICBgJHtCQVNFfS9maWxlcz9maWVsZHM9aWRgLFxuICAgICAgSlNPTi5zdHJpbmdpZnkoeyBuYW1lLCBtaW1lVHlwZTogRk9MREVSX01JTUUsIHBhcmVudHM6IFtwYXJlbnRJZF0gfSksXG4gICAgICB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICApO1xuICAgIGNvbnN0IHsgaWQgfSA9IHJlc3AuanNvbiBhcyB7IGlkOiBzdHJpbmcgfTtcbiAgICByZXR1cm4gaWQ7XG4gIH1cblxuICAvLyDilIDilIAgRmlsZSBvcGVyYXRpb25zIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIGFzeW5jIHVwbG9hZEZpbGUoXG4gICAgbmFtZTogc3RyaW5nLFxuICAgIHBhcmVudElkOiBzdHJpbmcsXG4gICAgY29udGVudDogQXJyYXlCdWZmZXIsXG4gICAgbWltZVR5cGU6IHN0cmluZyxcbiAgICBtb2RpZmllZFRpbWU6IHN0cmluZyxcbiAgICBrZWVwUmV2aXNpb24gPSBmYWxzZSxcbiAgKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBib3VuZGFyeSA9ICduZW9nZHN5bmNfYm91bmRhcnknO1xuICAgIGNvbnN0IG1ldGEgPSBKU09OLnN0cmluZ2lmeSh7IG5hbWUsIHBhcmVudHM6IFtwYXJlbnRJZF0sIG1vZGlmaWVkVGltZSB9KTtcbiAgICBjb25zdCBib2R5ID0gYnVpbGRNdWx0aXBhcnQoYm91bmRhcnksIG1ldGEsIGNvbnRlbnQsIG1pbWVUeXBlKTtcbiAgICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHsgdXBsb2FkVHlwZTogJ211bHRpcGFydCcsIGZpZWxkczogJ2lkJyB9KTtcbiAgICBpZiAoa2VlcFJldmlzaW9uKSBwYXJhbXMuc2V0KCdrZWVwUmV2aXNpb25Gb3JldmVyJywgJ3RydWUnKTtcbiAgICBjb25zdCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KFxuICAgICAgJ1BPU1QnLFxuICAgICAgYCR7VVBMT0FEfS9maWxlcz8ke3BhcmFtc31gLFxuICAgICAgYm9keS5idWZmZXIsXG4gICAgICB7ICdDb250ZW50LVR5cGUnOiBgbXVsdGlwYXJ0L3JlbGF0ZWQ7IGJvdW5kYXJ5PSR7Ym91bmRhcnl9YCB9LFxuICAgICk7XG4gICAgY29uc3QgeyBpZCB9ID0gcmVzcC5qc29uIGFzIHsgaWQ6IHN0cmluZyB9O1xuICAgIHJldHVybiBpZDtcbiAgfVxuXG4gIGFzeW5jIHVwZGF0ZUZpbGUoXG4gICAgZHJpdmVJZDogc3RyaW5nLFxuICAgIGNvbnRlbnQ6IEFycmF5QnVmZmVyLFxuICAgIG1pbWVUeXBlOiBzdHJpbmcsXG4gICAgbW9kaWZpZWRUaW1lOiBzdHJpbmcsXG4gICAga2VlcFJldmlzaW9uID0gZmFsc2UsXG4gICk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgYm91bmRhcnkgPSAnbmVvZ2RzeW5jX2JvdW5kYXJ5JztcbiAgICBjb25zdCBtZXRhID0gSlNPTi5zdHJpbmdpZnkoeyBtb2RpZmllZFRpbWUgfSk7XG4gICAgY29uc3QgYm9keSA9IGJ1aWxkTXVsdGlwYXJ0KGJvdW5kYXJ5LCBtZXRhLCBjb250ZW50LCBtaW1lVHlwZSk7XG4gICAgY29uc3QgcGFyYW1zID0gbmV3IFVSTFNlYXJjaFBhcmFtcyh7IHVwbG9hZFR5cGU6ICdtdWx0aXBhcnQnLCBmaWVsZHM6ICdpZCcgfSk7XG4gICAgaWYgKGtlZXBSZXZpc2lvbikgcGFyYW1zLnNldCgna2VlcFJldmlzaW9uRm9yZXZlcicsICd0cnVlJyk7XG4gICAgY29uc3QgcmVzcCA9IGF3YWl0IHRoaXMucmVxdWVzdChcbiAgICAgICdQQVRDSCcsXG4gICAgICBgJHtVUExPQUR9L2ZpbGVzLyR7ZHJpdmVJZH0/JHtwYXJhbXN9YCxcbiAgICAgIGJvZHkuYnVmZmVyLFxuICAgICAgeyAnQ29udGVudC1UeXBlJzogYG11bHRpcGFydC9yZWxhdGVkOyBib3VuZGFyeT0ke2JvdW5kYXJ5fWAgfSxcbiAgICApO1xuICAgIGNvbnN0IHsgaWQgfSA9IHJlc3AuanNvbiBhcyB7IGlkOiBzdHJpbmcgfTtcbiAgICByZXR1cm4gaWQ7XG4gIH1cblxuICBhc3luYyByZW5hbWVGaWxlKGRyaXZlSWQ6IHN0cmluZywgbmV3TmFtZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgdGhpcy5yZXF1ZXN0KFxuICAgICAgJ1BBVENIJyxcbiAgICAgIGAke0JBU0V9L2ZpbGVzLyR7ZHJpdmVJZH0/ZmllbGRzPWlkYCxcbiAgICAgIEpTT04uc3RyaW5naWZ5KHsgbmFtZTogbmV3TmFtZSB9KSxcbiAgICAgIHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICk7XG4gIH1cblxuICBhc3luYyBkZWxldGVGaWxlKGRyaXZlSWQ6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMucmVxdWVzdCgnREVMRVRFJywgYCR7QkFTRX0vZmlsZXMvJHtkcml2ZUlkfWApO1xuICB9XG5cbiAgYXN5bmMgZG93bmxvYWRGaWxlKGRyaXZlSWQ6IHN0cmluZyk6IFByb21pc2U8QXJyYXlCdWZmZXI+IHtcbiAgICBjb25zdCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KCdHRVQnLCBgJHtCQVNFfS9maWxlcy8ke2RyaXZlSWR9P2FsdD1tZWRpYWApO1xuICAgIHJldHVybiByZXNwLmFycmF5QnVmZmVyO1xuICB9XG5cbiAgYXN5bmMgZ2V0RmlsZU1ldGEoZHJpdmVJZDogc3RyaW5nKTogUHJvbWlzZTxEcml2ZUZpbGVJbmZvPiB7XG4gICAgY29uc3QgcmVzcCA9IGF3YWl0IHRoaXMucmVxdWVzdCgnR0VUJywgYCR7QkFTRX0vZmlsZXMvJHtkcml2ZUlkfT9maWVsZHM9aWQsbmFtZSxtaW1lVHlwZSxtb2RpZmllZFRpbWUscGFyZW50cyxzaXplYCk7XG4gICAgcmV0dXJuIHJlc3AuanNvbiBhcyBEcml2ZUZpbGVJbmZvO1xuICB9XG5cbiAgYXN5bmMgZ2V0Q2hhbmdlcyhwYWdlVG9rZW46IHN0cmluZyk6IFByb21pc2U8eyBjaGFuZ2VzOiBEcml2ZUNoYW5nZVtdOyBuZXdUb2tlbjogc3RyaW5nIH0+IHtcbiAgICBjb25zdCBjaGFuZ2VzOiBEcml2ZUNoYW5nZVtdID0gW107XG4gICAgbGV0IHRva2VuID0gcGFnZVRva2VuO1xuICAgIHdoaWxlICh0b2tlbikge1xuICAgICAgY29uc3QgcGFyYW1zID0gbmV3IFVSTFNlYXJjaFBhcmFtcyh7XG4gICAgICAgIHBhZ2VUb2tlbjogdG9rZW4sXG4gICAgICAgIHBhZ2VTaXplOiAnMTAwMCcsXG4gICAgICAgIGluY2x1ZGVSZW1vdmVkOiAndHJ1ZScsXG4gICAgICAgIGZpZWxkczogJ25leHRQYWdlVG9rZW4sbmV3U3RhcnRQYWdlVG9rZW4sY2hhbmdlcyhmaWxlSWQscmVtb3ZlZCxmaWxlKGlkLG5hbWUsbWltZVR5cGUsbW9kaWZpZWRUaW1lKSknLFxuICAgICAgfSk7XG4gICAgICBjb25zdCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KCdHRVQnLCBgJHtCQVNFfS9jaGFuZ2VzPyR7cGFyYW1zfWApO1xuICAgICAgY29uc3QgZGF0YSA9IHJlc3AuanNvbiBhcyB7XG4gICAgICAgIGNoYW5nZXM/OiBEcml2ZUNoYW5nZVtdO1xuICAgICAgICBuZXh0UGFnZVRva2VuPzogc3RyaW5nO1xuICAgICAgICBuZXdTdGFydFBhZ2VUb2tlbj86IHN0cmluZztcbiAgICAgIH07XG4gICAgICBjaGFuZ2VzLnB1c2goLi4uKGRhdGEuY2hhbmdlcyA/PyBbXSkpO1xuICAgICAgdG9rZW4gPSBkYXRhLm5leHRQYWdlVG9rZW4gPz8gJyc7XG4gICAgICBpZiAoZGF0YS5uZXdTdGFydFBhZ2VUb2tlbikge1xuICAgICAgICByZXR1cm4geyBjaGFuZ2VzLCBuZXdUb2tlbjogZGF0YS5uZXdTdGFydFBhZ2VUb2tlbiB9O1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4geyBjaGFuZ2VzLCBuZXdUb2tlbjogcGFnZVRva2VuIH07XG4gIH1cblxuICBhc3luYyBnZXRTdGFydFBhZ2VUb2tlbigpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHJlc3AgPSBhd2FpdCB0aGlzLnJlcXVlc3QoJ0dFVCcsIGAke0JBU0V9L2NoYW5nZXMvc3RhcnRQYWdlVG9rZW5gKTtcbiAgICBjb25zdCB7IHN0YXJ0UGFnZVRva2VuIH0gPSByZXNwLmpzb24gYXMgeyBzdGFydFBhZ2VUb2tlbjogc3RyaW5nIH07XG4gICAgcmV0dXJuIHN0YXJ0UGFnZVRva2VuO1xuICB9XG5cbiAgYXN5bmMgbGlzdFJldmlzaW9ucyhkcml2ZUlkOiBzdHJpbmcpOiBQcm9taXNlPERyaXZlUmV2aXNpb25bXT4ge1xuICAgIGNvbnN0IHJlc3AgPSBhd2FpdCB0aGlzLnJlcXVlc3QoJ0dFVCcsIGAke0JBU0V9L2ZpbGVzLyR7ZHJpdmVJZH0vcmV2aXNpb25zP2ZpZWxkcz1yZXZpc2lvbnMoaWQsbW9kaWZpZWRUaW1lLHNpemUpYCk7XG4gICAgY29uc3QgZGF0YSA9IHJlc3AuanNvbiBhcyB7IHJldmlzaW9ucz86IERyaXZlUmV2aXNpb25bXSB9O1xuICAgIHJldHVybiBkYXRhLnJldmlzaW9ucyA/PyBbXTtcbiAgfVxufVxuXG4vLyDilIDilIAgaGVscGVycyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuZnVuY3Rpb24gYnVpbGRNdWx0aXBhcnQoYm91bmRhcnk6IHN0cmluZywgbWV0YTogc3RyaW5nLCBjb250ZW50OiBBcnJheUJ1ZmZlciwgbWltZTogc3RyaW5nKTogVWludDhBcnJheSB7XG4gIGNvbnN0IGVuYyA9IG5ldyBUZXh0RW5jb2RlcigpO1xuICBjb25zdCBoZWFkZXIgPSBlbmMuZW5jb2RlKFxuICAgIGAtLSR7Ym91bmRhcnl9XFxyXFxuQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29uOyBjaGFyc2V0PVVURi04XFxyXFxuXFxyXFxuJHttZXRhfVxcclxcbmAgK1xuICAgIGAtLSR7Ym91bmRhcnl9XFxyXFxuQ29udGVudC1UeXBlOiAke21pbWV9XFxyXFxuXFxyXFxuYCxcbiAgKTtcbiAgY29uc3QgZm9vdGVyID0gZW5jLmVuY29kZShgXFxyXFxuLS0ke2JvdW5kYXJ5fS0tYCk7XG4gIGNvbnN0IGJvZHkgPSBuZXcgVWludDhBcnJheShoZWFkZXIuYnl0ZUxlbmd0aCArIGNvbnRlbnQuYnl0ZUxlbmd0aCArIGZvb3Rlci5ieXRlTGVuZ3RoKTtcbiAgYm9keS5zZXQoaGVhZGVyLCAwKTtcbiAgYm9keS5zZXQobmV3IFVpbnQ4QXJyYXkoY29udGVudCksIGhlYWRlci5ieXRlTGVuZ3RoKTtcbiAgYm9keS5zZXQoZm9vdGVyLCBoZWFkZXIuYnl0ZUxlbmd0aCArIGNvbnRlbnQuYnl0ZUxlbmd0aCk7XG4gIHJldHVybiBib2R5O1xufVxuIl19 \ No newline at end of file diff --git a/src/driveApi.ts b/src/driveApi.ts index 9d5e4b7..2b4683b 100644 --- a/src/driveApi.ts +++ b/src/driveApi.ts @@ -115,6 +115,11 @@ export class DriveApi { return id; } + async moveFile(driveId: string, oldParentId: string, newParentId: string): Promise { + const params = new URLSearchParams({ addParents: newParentId, removeParents: oldParentId, fields: 'id' }); + await this.request('PATCH', `${BASE}/files/${driveId}?${params}`); + } + async renameFile(driveId: string, newName: string): Promise { await this.request( 'PATCH', @@ -156,7 +161,7 @@ export class DriveApi { pageToken: token, pageSize: '1000', includeRemoved: 'true', - fields: 'nextPageToken,newStartPageToken,changes(fileId,removed,file(id,name,mimeType,modifiedTime,trashed))', + fields: 'nextPageToken,newStartPageToken,changes(fileId,removed,file(id,name,mimeType,modifiedTime,size,trashed))', }); const resp = await this.request('GET', `${BASE}/changes?${params}`); const data = resp.json as { diff --git a/src/syncer.js b/src/syncer.js index 14aaf66..96bbade 100644 --- a/src/syncer.js +++ b/src/syncer.js @@ -182,6 +182,14 @@ export class Syncer { const mimeType = mime.fromPath(path); const cached = this.index.get(path); if (cached && !cached.isFolder) { + if (op === 'create') { + const targetParentId = await this.index.resolveParentFolder(path); + const meta = await this.drive.getFileMeta(cached.driveId); + const currentParentId = meta.parents?.[0]; + if (currentParentId && currentParentId !== targetParentId) { + await this.drive.moveFile(cached.driveId, currentParentId, targetParentId); + } + } await this.drive.updateFile(cached.driveId, bytes, mimeType, mtime, this.settings.keepRevisions); this.index.set(path, { ...cached, driveMtime: mtime, syncedAt: Date.now() }); } @@ -197,11 +205,16 @@ export class Syncer { if (cached) { try { await this.drive.deleteFile(cached.driveId); + this.index.delete(path); + result.deleted.push(path); + } + catch (err) { + result.errors.push({ path, error: `Drive delete failed: ${err instanceof Error ? err.message : String(err)}` }); } - catch ( /* already gone */_a) { /* already gone */ } - this.index.delete(path); } - result.deleted.push(path); + else { + result.deleted.push(path); + } } async handleConflict(path, driveMtime, result) { const entry = this.index.get(path); diff --git a/src/syncer.ts b/src/syncer.ts index 3f37179..65c7e5a 100644 --- a/src/syncer.ts +++ b/src/syncer.ts @@ -80,7 +80,7 @@ export class Syncer { for (const c of changes) { const localPath = driveIdToPath.get(c.fileId); if (localPath) { - driveChanged.set(localPath, { removed: c.removed, mtime: c.file?.modifiedTime }); + driveChanged.set(localPath, { removed: c.removed || c.file?.trashed === true, mtime: c.file?.modifiedTime }); } } @@ -267,6 +267,16 @@ export class Syncer { const mimeType = mime.fromPath(path); const cached = this.index.get(path); if (cached && !cached.isFolder) { + // Cross-folder rename: 'create' op + existing driveId means index.rename already ran. + // Drive.updateFile only updates content — we must also move the file to the new parent. + if (op === 'create') { + const targetParentId = await this.index.resolveParentFolder(path); + const meta = await this.drive.getFileMeta(cached.driveId); + const currentParentId = meta.parents?.[0]; + if (currentParentId && currentParentId !== targetParentId) { + await this.drive.moveFile(cached.driveId, currentParentId, targetParentId); + } + } await this.drive.updateFile(cached.driveId, bytes, mimeType, mtime, this.settings.keepRevisions); this.index.set(path, { ...cached, driveMtime: mtime, syncedAt: Date.now() }); } else { @@ -282,10 +292,16 @@ export class Syncer { private async handleDelete(path: string, result: SyncResult): Promise { const cached = this.index.get(path); if (cached) { - try { await this.drive.deleteFile(cached.driveId); } catch { /* already gone */ } - this.index.delete(path); + try { + await this.drive.deleteFile(cached.driveId); + this.index.delete(path); + result.deleted.push(path); + } catch (err: unknown) { + result.errors.push({ path, error: `Drive delete failed: ${err instanceof Error ? err.message : String(err)}` }); + } + } else { + result.deleted.push(path); } - result.deleted.push(path); } private async handleConflict(path: string, driveMtime: string, result: SyncResult): Promise { diff --git a/versions.json b/versions.json index 6154d93..165d387 100644 --- a/versions.json +++ b/versions.json @@ -8,5 +8,6 @@ "0.1.18": "1.6.0", "0.1.19": "1.6.0", "0.1.20": "1.6.0", - "0.2.0": "1.6.0" + "0.2.0": "1.6.0", + "0.2.1": "1.6.0" }