gdrive-syncer 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/envSync.js +75 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdrive-syncer",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Google Drive Syncer",
5
5
  "main": "./index.js",
6
6
  "bin": "./run.js",
package/src/envSync.js CHANGED
@@ -1242,54 +1242,19 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1242
1242
  }
1243
1243
  }
1244
1244
 
1245
- // Get local files first for delta sync comparison
1245
+ // Get local files
1246
1246
  fs.ensureDirSync(envDir);
1247
1247
  const localItems = listLocalFilesRecursive(envDir, pattern, ignore, '', true);
1248
1248
  const localFiles = localItems.filter((f) => !f.isFolder);
1249
1249
  const localFolders = localItems.filter((f) => f.isFolder);
1250
1250
 
1251
- // Build local file lookup map for delta sync
1252
- const localFileMap = new Map();
1253
- for (const file of localFiles) {
1254
- localFileMap.set(file.relativePath, file);
1255
- }
1256
-
1257
- // Delta sync: Determine which files need to be downloaded for comparison
1258
- // Skip files where metadata indicates no change (same size AND local is not older)
1259
- s.message('Analyzing file changes (delta sync)...');
1260
- const filesToDownload = [];
1261
- const unchangedFiles = [];
1262
-
1263
- for (const driveFile of driveFiles) {
1264
- const localFile = localFileMap.get(driveFile.relativePath);
1265
-
1266
- if (!localFile) {
1267
- // File only exists on Drive - need to download for diff
1268
- filesToDownload.push(driveFile);
1269
- } else {
1270
- // File exists in both places - check if metadata indicates change
1271
- const sizeMatch = driveFile.sizeBytes === localFile.sizeBytes;
1272
- const driveTime = driveFile.modifiedTime ? driveFile.modifiedTime.getTime() : 0;
1273
- const localTime = localFile.modifiedTime ? localFile.modifiedTime.getTime() : 0;
1274
-
1275
- // Consider unchanged if: same size AND Drive is not newer
1276
- // (allow 1 second tolerance for time comparison)
1277
- const timeMatch = driveTime <= localTime + 1000;
1278
-
1279
- if (sizeMatch && timeMatch) {
1280
- // Metadata suggests unchanged - skip download
1281
- unchangedFiles.push(driveFile.relativePath);
1282
- } else {
1283
- // Metadata differs - need to download for accurate comparison
1284
- filesToDownload.push(driveFile);
1285
- }
1286
- }
1287
- }
1251
+ // Always download ALL Drive files for content-based comparison (no delta sync heuristics)
1252
+ // Accuracy is paramount - metadata (size/time) comparisons are unreliable because:
1253
+ // - Drive sizes from CLI output are human-readable approximations (lossy)
1254
+ // - Local mtime vs Drive modifiedTime are not comparable across systems
1255
+ const filesToDownload = [...driveFiles];
1288
1256
 
1289
- // Download only changed files to temp for comparison
1290
- s.message(
1291
- `Downloading ${filesToDownload.length} file(s) for comparison (${unchangedFiles.length} unchanged)...`
1292
- );
1257
+ s.message(`Downloading ${filesToDownload.length} file(s) for comparison...`);
1293
1258
 
1294
1259
  // Prepare destination directories first
1295
1260
  for (const file of filesToDownload) {
@@ -1304,14 +1269,60 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1304
1269
  destination: path.join(tempDir, path.dirname(file.relativePath)),
1305
1270
  overwrite: true,
1306
1271
  },
1272
+ relativePath: file.relativePath,
1307
1273
  }));
1274
+ let downloadResults = [];
1308
1275
  if (downloads.length > 0) {
1309
- await gdrive.downloadParallel(downloads, 5, (completed, total) => {
1276
+ downloadResults = await gdrive.downloadParallel(downloads, 5, (completed, total) => {
1310
1277
  s.message(`Downloading files for comparison... (${completed}/${total})`);
1311
1278
  });
1312
1279
  }
1313
1280
 
1314
- s.stop(color.green(`Found ${driveFiles.length} file(s) on Drive (${unchangedFiles.length} skipped via delta sync)`));
1281
+ // Check for failed downloads and retry them individually
1282
+ const failedDownloads = [];
1283
+ for (let i = 0; i < downloadResults.length; i++) {
1284
+ if (downloadResults[i].code !== 0) {
1285
+ failedDownloads.push(downloads[i]);
1286
+ }
1287
+ }
1288
+
1289
+ if (failedDownloads.length > 0) {
1290
+ s.message(`Retrying ${failedDownloads.length} failed download(s)...`);
1291
+ for (const dl of failedDownloads) {
1292
+ const retryResult = await gdrive.downloadAsync(dl.fileId, dl.options);
1293
+ if (retryResult.code !== 0) {
1294
+ // Verify file actually exists on disk despite error code
1295
+ const expectedPath = path.join(tempDir, dl.relativePath);
1296
+ if (!fs.existsSync(expectedPath)) {
1297
+ s.stop(color.red('Download failures detected'));
1298
+ log.error(`Failed to download: ${dl.relativePath}`);
1299
+ if (retryResult.stderr) log.error(retryResult.stderr);
1300
+ log.error('Diff results would be incomplete. Aborting to avoid showing incorrect diff.');
1301
+ return;
1302
+ }
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ // Final verification: ensure every file we expected to download actually exists
1308
+ const missingFiles = [];
1309
+ for (const file of filesToDownload) {
1310
+ const expectedPath = path.join(tempDir, file.relativePath);
1311
+ if (!fs.existsSync(expectedPath)) {
1312
+ missingFiles.push(file.relativePath);
1313
+ }
1314
+ }
1315
+
1316
+ if (missingFiles.length > 0) {
1317
+ s.stop(color.red('Some files could not be downloaded'));
1318
+ for (const f of missingFiles) {
1319
+ log.error(`Missing: ${f}`);
1320
+ }
1321
+ log.error('Diff results would be incomplete. Aborting to avoid showing incorrect diff.');
1322
+ return;
1323
+ }
1324
+
1325
+ s.stop(color.green(`Found ${driveFiles.length} file(s) on Drive, downloaded all for comparison`));
1315
1326
 
1316
1327
  // Compare files using relativePath
1317
1328
  const changes = {
@@ -1325,26 +1336,19 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1325
1336
  const driveFilePaths = new Set(driveFiles.map((f) => f.relativePath));
1326
1337
  const localFilePaths = new Set(localFiles.map((f) => f.relativePath));
1327
1338
  const localFolderPaths = new Set(localFolders.map((f) => f.relativePath));
1328
- const unchangedSet = new Set(unchangedFiles);
1329
1339
 
1330
- // Check local files
1340
+ // Check local files - always compare content for accuracy
1341
+ // All Drive files are guaranteed to exist in tempDir (verified above)
1331
1342
  for (const localFile of localFiles) {
1332
1343
  const localFilePath = path.join(envDir, localFile.relativePath);
1333
1344
  const driveFilePath = path.join(tempDir, localFile.relativePath);
1334
1345
 
1335
1346
  if (driveFilePaths.has(localFile.relativePath)) {
1336
- // File exists on both
1337
- if (unchangedSet.has(localFile.relativePath)) {
1338
- // Delta sync determined this file is unchanged - skip comparison
1339
- continue;
1340
- }
1341
- // Check for modifications (only for files we downloaded)
1342
- if (fs.existsSync(driveFilePath)) {
1343
- const localContent = fs.readFileSync(localFilePath, 'utf-8');
1344
- const driveContent = fs.readFileSync(driveFilePath, 'utf-8');
1345
- if (localContent !== driveContent) {
1346
- changes.modified.push(localFile.relativePath);
1347
- }
1347
+ // File exists on both - compare actual content
1348
+ const localContent = fs.readFileSync(localFilePath, 'utf-8');
1349
+ const driveContent = fs.readFileSync(driveFilePath, 'utf-8');
1350
+ if (localContent !== driveContent) {
1351
+ changes.modified.push(localFile.relativePath);
1348
1352
  }
1349
1353
  } else {
1350
1354
  changes.localOnly.push(localFile.relativePath);
@@ -1519,11 +1523,13 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1519
1523
  }
1520
1524
 
1521
1525
  const downloadSpinner = spinner();
1522
- downloadSpinner.start('Downloading files...');
1523
-
1526
+ const totalToDownload = changes.modified.length + changes.driveOnly.length;
1524
1527
  let downloaded = 0;
1525
1528
 
1529
+ downloadSpinner.start(`Downloading files... (0/${totalToDownload})`);
1530
+
1526
1531
  for (const relativePath of changes.modified) {
1532
+ downloadSpinner.message(`Downloading (${downloaded + 1}/${totalToDownload}): ${relativePath}`);
1527
1533
  const srcPath = path.join(tempDir, relativePath);
1528
1534
  const destPath = path.join(envDir, relativePath);
1529
1535
  fs.ensureDirSync(path.dirname(destPath));
@@ -1532,6 +1538,7 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1532
1538
  }
1533
1539
 
1534
1540
  for (const relativePath of changes.driveOnly) {
1541
+ downloadSpinner.message(`Downloading (${downloaded + 1}/${totalToDownload}): ${relativePath}`);
1535
1542
  const srcPath = path.join(tempDir, relativePath);
1536
1543
  const destPath = path.join(envDir, relativePath);
1537
1544
  fs.ensureDirSync(path.dirname(destPath));
@@ -1542,11 +1549,13 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1542
1549
  downloadSpinner.stop(color.green(`Downloaded ${downloaded} file(s)`));
1543
1550
  } else if (action === 'upload') {
1544
1551
  const uploadSpinner = spinner();
1545
- uploadSpinner.start('Uploading files...');
1546
-
1552
+ const totalToUpload = changes.modified.length + changes.localOnly.length;
1553
+ let completed = 0;
1547
1554
  let uploaded = 0;
1548
1555
  let replaced = 0;
1549
1556
 
1557
+ uploadSpinner.start(`Uploading files... (0/${totalToUpload})`);
1558
+
1550
1559
  // Build a map of relativePath -> driveFile for quick lookup
1551
1560
  const driveFileMap = new Map();
1552
1561
  for (const df of driveFiles) {
@@ -1554,14 +1563,17 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1554
1563
  }
1555
1564
 
1556
1565
  for (const relativePath of changes.modified) {
1566
+ uploadSpinner.message(`Replacing (${completed + 1}/${totalToUpload}): ${relativePath}`);
1557
1567
  const driveFile = driveFileMap.get(relativePath);
1558
1568
  if (driveFile) {
1559
1569
  gdrive.update(driveFile.id, path.join(envDir, relativePath));
1560
1570
  replaced++;
1571
+ completed++;
1561
1572
  }
1562
1573
  }
1563
1574
 
1564
1575
  for (const relativePath of changes.localOnly) {
1576
+ uploadSpinner.message(`Uploading (${completed + 1}/${totalToUpload}): ${relativePath}`);
1565
1577
  const localFilePath = path.join(envDir, relativePath);
1566
1578
  const folderPath = path.dirname(relativePath);
1567
1579
 
@@ -1573,6 +1585,7 @@ const runSyncOperation = async (syncConfig, action, projectRoot, backupPath, con
1573
1585
 
1574
1586
  gdrive.upload(localFilePath, { parent: parentId });
1575
1587
  uploaded++;
1588
+ completed++;
1576
1589
  }
1577
1590
 
1578
1591
  uploadSpinner.stop(color.green(`Replaced: ${replaced}, New: ${uploaded}`));