mirror of
https://github.com/abraunegg/onedrive
synced 2024-05-18 13:46:41 +02:00
parent
3f08e271af
commit
98624267c5
34
README.md
34
README.md
|
@ -264,6 +264,38 @@ Enter the response uri:
|
|||
|
||||
```
|
||||
|
||||
### Testing your configuration
|
||||
You are able to test your configuration by utilising the `--dry-run` CLI option. No files will be downloaded, uploaded or removed, however the application will display what 'would' have occurred. For example:
|
||||
```text
|
||||
onedrive --synchronize --verbose --dry-run
|
||||
DRY-RUN Configured. Output below shows what 'would' have occurred.
|
||||
Loading config ...
|
||||
Using Config Dir: /home/user/.config/onedrive
|
||||
Initializing the OneDrive API ...
|
||||
Opening the item database ...
|
||||
All operations will be performed in: /home/user/OneDrive
|
||||
Initializing the Synchronization Engine ...
|
||||
Account Type: personal
|
||||
Default Drive ID: <redacted>
|
||||
Default Root ID: <redacted>
|
||||
Remaining Free Space: 5368709120
|
||||
Fetching details for OneDrive Root
|
||||
OneDrive Root exists in the database
|
||||
Syncing changes from OneDrive ...
|
||||
Applying changes of Path ID: <redacted>
|
||||
Uploading differences of .
|
||||
Processing root
|
||||
The directory has not changed
|
||||
Uploading new items of .
|
||||
OneDrive Client requested to create remote path: ./newdir
|
||||
The requested directory to create was not found on OneDrive - creating remote directory: ./newdir
|
||||
Successfully created the remote directory ./newdir on OneDrive
|
||||
Uploading new file ./newdir/newfile.txt ... done.
|
||||
Remaining free space: 5368709076
|
||||
Applying changes of Path ID: <redacted>
|
||||
```
|
||||
|
||||
**Note:** `--dry-run` can only be used with `--synchronize`. It cannot be used with `--monitor` and will be ignored.
|
||||
|
||||
### Show your configuration
|
||||
To validate your configuration the application will use, utilise the following:
|
||||
|
@ -664,6 +696,8 @@ Options:
|
|||
Only download remote changes
|
||||
--disable-upload-validation
|
||||
Disable upload validation when uploading to OneDrive
|
||||
--dry-run
|
||||
Perform a trial sync with no changes made
|
||||
--enable-logging
|
||||
Enable client activity to a separate log file
|
||||
--force-http-1.1
|
||||
|
|
|
@ -51,6 +51,9 @@ Display the sync status of the client \- no sync will be performed.
|
|||
\fB\-d \-\-download\-only\fP
|
||||
Only download remote changes
|
||||
.TP
|
||||
\fB\-\-dry\-run\fP
|
||||
Perform a trial sync with no changes made. Can ONLY be used with --synchronize. Will be ignored for --monitor
|
||||
.TP
|
||||
\fB\-\-enable\-logging\fP
|
||||
Enable client activity to a separate log file
|
||||
.TP
|
||||
|
|
|
@ -7,6 +7,7 @@ final class Config
|
|||
public string refreshTokenFilePath;
|
||||
public string deltaLinkFilePath;
|
||||
public string databaseFilePath;
|
||||
public string databaseFilePathDryRun;
|
||||
public string uploadStateFilePath;
|
||||
public string syncListFilePath;
|
||||
|
||||
|
@ -19,6 +20,7 @@ final class Config
|
|||
refreshTokenFilePath = configDirName ~ "/refresh_token";
|
||||
deltaLinkFilePath = configDirName ~ "/delta_link";
|
||||
databaseFilePath = configDirName ~ "/items.sqlite3";
|
||||
databaseFilePathDryRun = configDirName ~ "/items-dryrun.sqlite3";
|
||||
uploadStateFilePath = configDirName ~ "/resume_upload";
|
||||
userConfigFilePath = configDirName ~ "/config";
|
||||
syncListFilePath = configDirName ~ "/sync_list";
|
||||
|
|
62
src/main.d
62
src/main.d
|
@ -43,6 +43,8 @@ int main(string[] args)
|
|||
// Does the user want to disable upload validation - https://github.com/abraunegg/onedrive/issues/205
|
||||
// SharePoint will associate some metadata from the library the file is uploaded to directly in the file - thus change file size & checksums
|
||||
bool disableUploadValidation = false;
|
||||
// Perform only a dry run - not applicable for --monitor mode
|
||||
bool dryRun = false;
|
||||
// Do we enable a log file
|
||||
bool enableLogFile = false;
|
||||
// Force the use of HTTP 1.1 to overcome curl => 7.62.0 where some operations are now sent via HTTP/2
|
||||
|
@ -102,6 +104,7 @@ int main(string[] args)
|
|||
"display-sync-status", "Display the sync status of the client - no sync will be performed.", &displaySyncStatus,
|
||||
"download-only|d", "Only download remote changes", &downloadOnly,
|
||||
"disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation,
|
||||
"dry-run", "Perform a trial sync with no changes made", &dryRun,
|
||||
"enable-logging", "Enable client activity to a separate log file", &enableLogFile,
|
||||
"force-http-1.1", "Force the use of HTTP 1.1 for all operations", &forceHTTP11,
|
||||
"get-O365-drive-id", "Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library", &o365SharedLibraryName,
|
||||
|
@ -144,6 +147,8 @@ int main(string[] args)
|
|||
bool debugHttpSubmit;
|
||||
// Are we able to reach the OneDrive Service
|
||||
bool online = false;
|
||||
// simulateNoRefreshTokenFile in case of --dry-run & --logout
|
||||
bool simulateNoRefreshTokenFile = false;
|
||||
|
||||
// Determine the users home directory.
|
||||
// Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
|
||||
|
@ -203,6 +208,11 @@ int main(string[] args)
|
|||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// dry-run notification
|
||||
if (dryRun) {
|
||||
log.log("DRY-RUN Configured. Output below shows what 'would' have occurred.");
|
||||
}
|
||||
|
||||
// load application configuration
|
||||
log.vlog("Loading config ...");
|
||||
log.vlog("Using Config Dir: ", configDirName);
|
||||
|
@ -214,6 +224,16 @@ int main(string[] args)
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// dry-run database setup
|
||||
if (dryRun) {
|
||||
// Make a copy of the original items.sqlite3 for use as the dry run copy if it exists
|
||||
if (exists(cfg.databaseFilePath)) {
|
||||
// copy the file
|
||||
log.vdebug("Copying items.sqlite3 to items-dryrun.sqlite3 to use for dry run operations");
|
||||
copy(cfg.databaseFilePath,cfg.databaseFilePathDryRun);
|
||||
}
|
||||
}
|
||||
|
||||
// command line parameters to override default 'config' & take precedence
|
||||
// Set the client to skip specific directories if .nosync is found AND ONLY if --check-for-nosync was passed in
|
||||
if (checkNoSync) {
|
||||
|
@ -285,18 +305,27 @@ int main(string[] args)
|
|||
|
||||
// upgrades
|
||||
if (exists(configDirName ~ "/items.db")) {
|
||||
remove(configDirName ~ "/items.db");
|
||||
if (!dryRun) {
|
||||
safeRemove(configDirName ~ "/items.db");
|
||||
}
|
||||
log.logAndNotify("Database schema changed, resync needed");
|
||||
resync = true;
|
||||
}
|
||||
|
||||
if (resync || logout) {
|
||||
log.vlog("Deleting the saved status ...");
|
||||
if (!dryRun) {
|
||||
safeRemove(cfg.databaseFilePath);
|
||||
safeRemove(cfg.deltaLinkFilePath);
|
||||
safeRemove(cfg.uploadStateFilePath);
|
||||
}
|
||||
if (logout) {
|
||||
if (!dryRun) {
|
||||
safeRemove(cfg.refreshTokenFilePath);
|
||||
} else {
|
||||
// simulate file being removed / unavailable
|
||||
simulateNoRefreshTokenFile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +390,7 @@ int main(string[] args)
|
|||
}
|
||||
|
||||
// Initialize OneDrive, check for authorization
|
||||
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11);
|
||||
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11, dryRun, simulateNoRefreshTokenFile);
|
||||
oneDrive.printAccessToken = printAccessToken;
|
||||
if (!oneDrive.init()) {
|
||||
log.error("Could not initialize the OneDrive API");
|
||||
|
@ -390,9 +419,17 @@ int main(string[] args)
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// initialize system
|
||||
// Initialize the item database
|
||||
log.vlog("Opening the item database ...");
|
||||
if (!dryRun) {
|
||||
// Load the items.sqlite3 file as the database
|
||||
log.vdebug("Using database file: ", cfg.databaseFilePath);
|
||||
itemDb = new ItemDatabase(cfg.databaseFilePath);
|
||||
} else {
|
||||
// Load the items-dryrun.sqlite3 file as the database
|
||||
log.vdebug("Using database file: ", cfg.databaseFilePathDryRun);
|
||||
itemDb = new ItemDatabase(cfg.databaseFilePathDryRun);
|
||||
}
|
||||
|
||||
log.vlog("All operations will be performed in: ", syncDir);
|
||||
if (!exists(syncDir)) {
|
||||
|
@ -416,9 +453,9 @@ int main(string[] args)
|
|||
selectiveSync.load(cfg.syncListFilePath);
|
||||
selectiveSync.setMask(cfg.getValue("skip_file"));
|
||||
|
||||
// Initialise the sync engine
|
||||
// Initialize the sync engine
|
||||
log.logAndNotify("Initializing the Synchronization Engine ...");
|
||||
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync);
|
||||
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync, dryRun);
|
||||
|
||||
try {
|
||||
if (!initSyncEngine(sync)) {
|
||||
|
@ -612,8 +649,21 @@ int main(string[] args)
|
|||
}
|
||||
}
|
||||
|
||||
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
||||
// Workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
||||
oneDrive.http.shutdown();
|
||||
|
||||
// Make sure the .wal file is incorporated into the main db before we exit
|
||||
destroy(itemDb);
|
||||
|
||||
// --dry-run temp database cleanup
|
||||
if (dryRun) {
|
||||
if (exists(cfg.databaseFilePathDryRun)) {
|
||||
// remove the file
|
||||
log.vdebug("Removing items-dryrun.sqlite3 as dry run operations complete");
|
||||
safeRemove(cfg.databaseFilePathDryRun);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import progress;
|
|||
import config;
|
||||
static import log;
|
||||
shared bool debugResponse = false;
|
||||
shared bool dryRun = false;
|
||||
shared bool simulateNoRefreshTokenFile = false;
|
||||
|
||||
private immutable {
|
||||
// Client Identifier
|
||||
|
@ -64,7 +66,7 @@ final class OneDriveApi
|
|||
// if true, every new access token is printed
|
||||
bool printAccessToken;
|
||||
|
||||
this(Config cfg, bool debugHttp, bool forceHTTP11)
|
||||
this(Config cfg, bool debugHttp, bool forceHTTP11, bool dryRun, bool simulateNoRefreshTokenFile)
|
||||
{
|
||||
this.cfg = cfg;
|
||||
http = HTTP();
|
||||
|
@ -104,6 +106,14 @@ final class OneDriveApi
|
|||
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
|
||||
http.handle.set(CurlOption.http_version,2);
|
||||
}
|
||||
|
||||
// Do we set the dryRun handlers?
|
||||
if (dryRun) {
|
||||
.dryRun = true;
|
||||
}
|
||||
if (simulateNoRefreshTokenFile) {
|
||||
.simulateNoRefreshTokenFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool init()
|
||||
|
@ -117,12 +127,28 @@ final class OneDriveApi
|
|||
}
|
||||
} catch (Exception e) {}
|
||||
|
||||
if (!.dryRun) {
|
||||
// original code
|
||||
try {
|
||||
refreshToken = readText(cfg.refreshTokenFilePath);
|
||||
} catch (FileException e) {
|
||||
return authorize();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// --dry-run
|
||||
if (!.simulateNoRefreshTokenFile) {
|
||||
try {
|
||||
refreshToken = readText(cfg.refreshTokenFilePath);
|
||||
} catch (FileException e) {
|
||||
return authorize();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// --dry-run & --logout
|
||||
return authorize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool authorize()
|
||||
|
@ -361,7 +387,9 @@ final class OneDriveApi
|
|||
accessToken = "bearer " ~ response["access_token"].str();
|
||||
refreshToken = response["refresh_token"].str();
|
||||
accessTokenExpiration = Clock.currTime() + dur!"seconds"(response["expires_in"].integer());
|
||||
if (!.dryRun) {
|
||||
std.file.write(cfg.refreshTokenFilePath, refreshToken);
|
||||
}
|
||||
if (printAccessToken) writeln("New access token: ", accessToken);
|
||||
}
|
||||
|
||||
|
|
236
src/sync.d
236
src/sync.d
|
@ -186,6 +186,8 @@ final class SyncEngine
|
|||
private string[] skippedItems;
|
||||
// list of items to delete after the changes has been downloaded
|
||||
private string[2][] idsToDelete;
|
||||
// list of items we fake created when running --dry-run
|
||||
private string[2][] idsFaked;
|
||||
// default drive id
|
||||
private string defaultDriveId;
|
||||
// default root id
|
||||
|
@ -200,8 +202,10 @@ final class SyncEngine
|
|||
private bool downloadFailed = false;
|
||||
// initialization has been done
|
||||
private bool initDone = false;
|
||||
// sync engine dryRun flag
|
||||
private bool dryRun = false;
|
||||
|
||||
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync)
|
||||
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync, bool dryRun)
|
||||
{
|
||||
assert(onedrive && itemdb && selectiveSync);
|
||||
this.cfg = cfg;
|
||||
|
@ -209,6 +213,7 @@ final class SyncEngine
|
|||
this.itemdb = itemdb;
|
||||
this.selectiveSync = selectiveSync;
|
||||
// session = UploadSession(onedrive, cfg.uploadStateFilePath);
|
||||
this.dryRun = dryRun;
|
||||
}
|
||||
|
||||
void reset()
|
||||
|
@ -221,7 +226,6 @@ final class SyncEngine
|
|||
// Set accountType, defaultDriveId, defaultRootId & remainingFreeSpace once and reuse where possible
|
||||
JSONValue oneDriveDetails;
|
||||
|
||||
|
||||
if (initDone) {
|
||||
return;
|
||||
}
|
||||
|
@ -825,8 +829,17 @@ final class SyncEngine
|
|||
if (itemdb.idInLocalDatabase(item.driveId, item.id)){
|
||||
oldPath = itemdb.computePath(item.driveId, item.id);
|
||||
if (!isItemSynced(oldItem, oldPath)) {
|
||||
log.vlog("The local item is unsynced, renaming");
|
||||
if (exists(oldPath)) safeRename(oldPath);
|
||||
if (exists(oldPath)) {
|
||||
auto ext = extension(oldPath);
|
||||
auto newPath = path.chomp(ext) ~ "-" ~ deviceName ~ ext;
|
||||
log.vlog("The local item is unsynced, renaming: ", oldPath, " -> ", newPath);
|
||||
if (!dryRun) {
|
||||
safeRename(oldPath);
|
||||
} else {
|
||||
log.vdebug("DRY-RUN: Skipping local file rename");
|
||||
// Expectation here is that there is a new file locally (newPath) however as we don't create this, the "new file" will not be uploaded as it does not exist
|
||||
}
|
||||
}
|
||||
cached = false;
|
||||
}
|
||||
}
|
||||
|
@ -866,8 +879,14 @@ final class SyncEngine
|
|||
return;
|
||||
} else {
|
||||
// TODO: force remote sync by deleting local item
|
||||
log.vlog("The local item is out of sync, renaming...");
|
||||
auto ext = extension(path);
|
||||
auto newPath = path.chomp(ext) ~ "-" ~ deviceName ~ ext;
|
||||
log.vlog("The local item is out of sync, renaming: ", path, " -> ", newPath);
|
||||
if (!dryRun) {
|
||||
safeRename(path);
|
||||
} else {
|
||||
log.vdebug("DRY-RUN: Skipping local file rename");
|
||||
}
|
||||
}
|
||||
}
|
||||
final switch (item.type) {
|
||||
|
@ -877,7 +896,12 @@ final class SyncEngine
|
|||
case ItemType.dir:
|
||||
case ItemType.remote:
|
||||
log.log("Creating directory: ", path);
|
||||
if (!dryRun) {
|
||||
mkdirRecurse(path);
|
||||
} else {
|
||||
// we dont create the directory, but we need to track that we 'faked it'
|
||||
idsFaked ~= [item.driveId, item.id];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -945,6 +969,8 @@ final class SyncEngine
|
|||
}
|
||||
|
||||
auto fileSize = fileDetails["size"].integer;
|
||||
|
||||
if (!dryRun) {
|
||||
try {
|
||||
onedrive.downloadById(item.driveId, item.id, path, fileSize);
|
||||
} catch (OneDriveException e) {
|
||||
|
@ -975,9 +1001,11 @@ final class SyncEngine
|
|||
downloadFailed = true;
|
||||
return;
|
||||
}
|
||||
setTimes(path, item.mtime, item.mtime);
|
||||
}
|
||||
|
||||
writeln("done.");
|
||||
log.fileOnly("Downloading file ", path, " ... done.");
|
||||
setTimes(path, item.mtime, item.mtime);
|
||||
}
|
||||
|
||||
// returns true if the given item corresponds to the local one
|
||||
|
@ -1024,11 +1052,14 @@ final class SyncEngine
|
|||
if (!itemdb.selectById(i[0], i[1], item)) continue; // check if the item is in the db
|
||||
string path = itemdb.computePath(i[0], i[1]);
|
||||
log.log("Trying to delete item ", path);
|
||||
if (!dryRun) {
|
||||
// Actually process the database entry removal
|
||||
itemdb.deleteById(item.driveId, item.id);
|
||||
if (item.remoteDriveId != null) {
|
||||
// delete the linked remote folder
|
||||
itemdb.deleteById(item.remoteDriveId, item.remoteId);
|
||||
}
|
||||
}
|
||||
bool needsRemoval = false;
|
||||
if (exists(path)) {
|
||||
// path exists on the local system
|
||||
|
@ -1047,6 +1078,7 @@ final class SyncEngine
|
|||
}
|
||||
if (needsRemoval) {
|
||||
log.log("Deleting item ", path);
|
||||
if (!dryRun) {
|
||||
if (isFile(path)) {
|
||||
remove(path);
|
||||
} else {
|
||||
|
@ -1064,9 +1096,14 @@ final class SyncEngine
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
// clean up idsToDelete
|
||||
idsToDelete.length = 0;
|
||||
assumeSafeAppend(idsToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
// scan the given directory for differences and new items
|
||||
void scanForDifferences(string path)
|
||||
|
@ -1079,14 +1116,32 @@ final class SyncEngine
|
|||
}
|
||||
log.vlog("Uploading new items of ", path);
|
||||
uploadNewItems(path);
|
||||
|
||||
// clean up idsToDelete only if --dry-run is set
|
||||
if (dryRun) {
|
||||
idsToDelete.length = 0;
|
||||
assumeSafeAppend(idsToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadDifferences(Item item)
|
||||
{
|
||||
log.vlog("Processing ", item.name);
|
||||
// see if this item.id we were supposed to have deleted
|
||||
// match early and return
|
||||
if (dryRun) {
|
||||
foreach (i; idsToDelete) {
|
||||
if (i[1] == item.id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.vlog("Processing ", item.name);
|
||||
bool unwanted = false;
|
||||
string path;
|
||||
bool unwanted = selectiveSync.isNameExcluded(item.name);
|
||||
|
||||
// Is item.name or the path excluded
|
||||
unwanted = selectiveSync.isNameExcluded(item.name);
|
||||
if (!unwanted) {
|
||||
path = itemdb.computePath(item.driveId, item.id);
|
||||
unwanted = selectiveSync.isPathExcluded(path);
|
||||
|
@ -1145,6 +1200,9 @@ final class SyncEngine
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Directory does not exist locally
|
||||
// If we are in a --dry-run situation - this directory may never have existed as we never downloaded it
|
||||
if (!dryRun) {
|
||||
log.vlog("The directory has been deleted locally");
|
||||
if (noRemoteDelete) {
|
||||
// do not process remote directory delete
|
||||
|
@ -1152,6 +1210,32 @@ final class SyncEngine
|
|||
} else {
|
||||
uploadDeleteItem(item, path);
|
||||
}
|
||||
} else {
|
||||
// we are in a --dry-run situation, directory appears to have deleted locally - this directory may never have existed as we never downloaded it ..
|
||||
// Check if path does not exist in database
|
||||
if (!itemdb.selectByPath(path, defaultDriveId, item)) {
|
||||
// Path not found in database
|
||||
log.vlog("The directory has been deleted locally");
|
||||
if (noRemoteDelete) {
|
||||
// do not process remote directory delete
|
||||
log.vlog("Skipping remote directory delete as --upload-only & --no-remote-delete configured");
|
||||
} else {
|
||||
uploadDeleteItem(item, path);
|
||||
}
|
||||
} else {
|
||||
// Path was found in the database
|
||||
// Did we 'fake create it' as part of --dry-run ?
|
||||
foreach (i; idsFaked) {
|
||||
if (i[1] == item.id) {
|
||||
log.vdebug("Matched faked dir which is 'supposed' to exist but not created due to --dry-run use");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// item.id did not match a 'faked' download new directory creation
|
||||
log.vlog("The directory has been deleted locally");
|
||||
uploadDeleteItem(item, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1195,9 +1279,10 @@ final class SyncEngine
|
|||
string eTag = item.eTag;
|
||||
if (!testFileHash(path, item)) {
|
||||
log.vlog("The file content has changed");
|
||||
write("Uploading file ", path, " ... ");
|
||||
write("Uploading modified file ", path, " ... ");
|
||||
JSONValue response;
|
||||
|
||||
if (!dryRun) {
|
||||
// Are we using OneDrive Personal or OneDrive Business?
|
||||
// To solve 'Multiple versions of file shown on website after single upload' (https://github.com/abraunegg/onedrive/issues/2)
|
||||
// check what 'account type' this is as this issue only affects OneDrive Business so we need some extra logic here
|
||||
|
@ -1220,7 +1305,7 @@ final class SyncEngine
|
|||
// The file is currently checked out or locked for editing by another user
|
||||
// We cant upload this file at this time
|
||||
writeln(" skipped.");
|
||||
log.fileOnly("Uploading file ", path, " ... skipped.");
|
||||
log.fileOnly("Uploading modified file ", path, " ... skipped.");
|
||||
write("", path, " is currently checked out or locked for editing by another user.");
|
||||
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
|
||||
return;
|
||||
|
@ -1272,7 +1357,7 @@ final class SyncEngine
|
|||
// The file is currently checked out or locked for editing by another user
|
||||
// We cant upload this file at this time
|
||||
writeln(" skipped.");
|
||||
log.fileOnly("Uploading file ", path, " ... skipped.");
|
||||
log.fileOnly("Uploading modified file ", path, " ... skipped.");
|
||||
writeln("", path, " is currently checked out or locked for editing by another user.");
|
||||
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
|
||||
return;
|
||||
|
@ -1283,14 +1368,25 @@ final class SyncEngine
|
|||
// As the session.upload includes the last modified time, save the response
|
||||
saveItem(response);
|
||||
}
|
||||
log.fileOnly("Uploading file ", path, " ... done.");
|
||||
log.fileOnly("Uploading modified file ", path, " ... done.");
|
||||
// use the cTag instead of the eTag because OneDrive may update the metadata of files AFTER they have been uploaded via simple upload
|
||||
eTag = response["cTag"].str;
|
||||
} else {
|
||||
// we are --dry-run - simulate the file upload
|
||||
writeln("done.");
|
||||
response = createFakeResponse(path);
|
||||
// Log action to log file
|
||||
log.fileOnly("Uploading modified file ", path, " ... done.");
|
||||
saveItem(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (accountType == "personal"){
|
||||
// If Personal, call to update the modified time as stored on OneDrive
|
||||
if (!dryRun) {
|
||||
uploadLastModifiedTime(item.driveId, item.id, eTag, localModifiedTime.toUTC());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.vlog("The file has not changed");
|
||||
}
|
||||
|
@ -1492,6 +1588,7 @@ final class SyncEngine
|
|||
// The directory was not found
|
||||
log.vlog("The requested directory to create was not found on OneDrive - creating remote directory: ", path);
|
||||
|
||||
if (!dryRun) {
|
||||
// Perform the database lookup
|
||||
enforce(itemdb.selectByPath(dirName(path), parent.driveId, parent), "The parent item id is not in the database");
|
||||
JSONValue driveItem = [
|
||||
|
@ -1511,8 +1608,15 @@ final class SyncEngine
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// save the created directory
|
||||
saveItem(response);
|
||||
} else {
|
||||
// Simulate a successful 'directory create' & save it to the dryRun database copy
|
||||
// The simulated response has to pass 'makeItem' as part of saveItem
|
||||
auto fakeResponse = createFakeResponse(path);
|
||||
saveItem(fakeResponse);
|
||||
}
|
||||
|
||||
log.vlog("Successfully created the remote directory ", path, " on OneDrive");
|
||||
return;
|
||||
}
|
||||
|
@ -1558,7 +1662,7 @@ final class SyncEngine
|
|||
Item parent;
|
||||
// Check the database for the parent
|
||||
//enforce(itemdb.selectByPath(dirName(path), defaultDriveId, parent), "The parent item is not in the local database");
|
||||
if (itemdb.selectByPath(dirName(path), defaultDriveId, parent)) {
|
||||
if ((dryRun) || (itemdb.selectByPath(dirName(path), defaultDriveId, parent))) {
|
||||
// Maximum file size upload
|
||||
// https://support.microsoft.com/en-au/help/3125202/restrictions-and-limitations-when-you-sync-files-and-folders
|
||||
// 1. OneDrive Business say's 15GB
|
||||
|
@ -1587,9 +1691,10 @@ final class SyncEngine
|
|||
} catch (OneDriveException e) {
|
||||
if (e.httpStatusCode == 404) {
|
||||
// The file was not found on OneDrive, need to upload it
|
||||
write("Uploading file ", path, " ...");
|
||||
write("Uploading new file ", path, " ...");
|
||||
JSONValue response;
|
||||
|
||||
if (!dryRun) {
|
||||
// Resolve https://github.com/abraunegg/onedrive/issues/37
|
||||
if (thisFileSize == 0){
|
||||
// We can only upload zero size files via simpleFileUpload regardless of account type
|
||||
|
@ -1655,7 +1760,7 @@ final class SyncEngine
|
|||
}
|
||||
|
||||
// Log action to log file
|
||||
log.fileOnly("Uploading file ", path, " ... done.");
|
||||
log.fileOnly("Uploading new file ", path, " ... done.");
|
||||
|
||||
// The file was uploaded
|
||||
ulong uploadFileSize = response["size"].integer;
|
||||
|
@ -1699,6 +1804,15 @@ final class SyncEngine
|
|||
saveItem(response);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// we are --dry-run - simulate the file upload
|
||||
writeln(" done.");
|
||||
response = createFakeResponse(path);
|
||||
// Log action to log file
|
||||
log.fileOnly("Uploading new file ", path, " ... done.");
|
||||
saveItem(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.httpStatusCode >= 500) {
|
||||
|
@ -1725,9 +1839,10 @@ final class SyncEngine
|
|||
if (localFileModifiedTime > remoteFileModifiedTime){
|
||||
// local file is newer
|
||||
log.vlog("Requested file to upload is newer than existing file on OneDrive");
|
||||
write("Uploading file ", path, " ...");
|
||||
write("Uploading modified file ", path, " ...");
|
||||
JSONValue response;
|
||||
|
||||
if (!dryRun) {
|
||||
if (accountType == "personal"){
|
||||
// OneDrive Personal account upload handling
|
||||
if (getSize(path) <= thresholdFileSize) {
|
||||
|
@ -1750,12 +1865,22 @@ final class SyncEngine
|
|||
writeln(" done.");
|
||||
saveItem(response);
|
||||
}
|
||||
} else {
|
||||
// we are --dry-run - simulate the file upload
|
||||
writeln(" done.");
|
||||
response = createFakeResponse(path);
|
||||
// Log action to log file
|
||||
log.fileOnly("Uploading modified file ", path, " ... done.");
|
||||
saveItem(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log action to log file
|
||||
log.fileOnly("Uploading file ", path, " ... done.");
|
||||
log.fileOnly("Uploading modified file ", path, " ... done.");
|
||||
|
||||
} else {
|
||||
// Save the details of the file that we got from OneDrive
|
||||
// --dry-run safe
|
||||
log.vlog("Updating the local database with details for this file: ", path);
|
||||
saveItem(fileDetailsFromOneDrive);
|
||||
}
|
||||
|
@ -1779,7 +1904,8 @@ final class SyncEngine
|
|||
private void uploadDeleteItem(Item item, string path)
|
||||
{
|
||||
log.log("Deleting item from OneDrive: ", path);
|
||||
|
||||
if (!dryRun) {
|
||||
// we are not in a --dry-run situation, process deletion to OneDrive
|
||||
if ((item.driveId == "") && (item.id == "") && (item.eTag == "")){
|
||||
// These are empty ... we cannot delete if this is empty ....
|
||||
log.vdebug("item.driveId, item.id & item.eTag are empty ... need to query OneDrive for values");
|
||||
|
@ -1832,6 +1958,7 @@ final class SyncEngine
|
|||
itemdb.deleteById(item.remoteDriveId, item.remoteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadLastModifiedTime(const(char)[] driveId, const(char)[] id, const(char)[] eTag, SysTime mtime)
|
||||
{
|
||||
|
@ -2136,4 +2263,75 @@ final class SyncEngine
|
|||
writeln("No pending remote changes - in sync");
|
||||
}
|
||||
}
|
||||
|
||||
// Create a fake OneDrive response suitable for use with saveItem
|
||||
JSONValue createFakeResponse(string path) {
|
||||
import std.digest.sha;
|
||||
// Generate a simulated JSON response which can be used
|
||||
// At a minimum we need:
|
||||
// 1. eTag
|
||||
// 2. cTag
|
||||
// 3. fileSystemInfo
|
||||
// 4. file or folder. if file, hash of file
|
||||
// 5. id
|
||||
// 6. name
|
||||
// 7. parent reference
|
||||
|
||||
SysTime mtime = timeLastModified(path).toUTC();
|
||||
|
||||
// real id / eTag / cTag are different format for personal / business account
|
||||
auto sha1 = new SHA1Digest();
|
||||
ubyte[] hash1 = sha1.digest(path);
|
||||
|
||||
JSONValue fakeResponse;
|
||||
|
||||
if (isDir(path)) {
|
||||
// path is a directory
|
||||
fakeResponse = [
|
||||
"id": JSONValue(toHexString(hash1)),
|
||||
"cTag": JSONValue(toHexString(hash1)),
|
||||
"eTag": JSONValue(toHexString(hash1)),
|
||||
"fileSystemInfo": JSONValue([
|
||||
"createdDateTime": mtime.toISOExtString(),
|
||||
"lastModifiedDateTime": mtime.toISOExtString()
|
||||
]),
|
||||
"name": JSONValue(baseName(path)),
|
||||
"parentReference": JSONValue([
|
||||
"driveId": JSONValue(defaultDriveId),
|
||||
"driveType": JSONValue(accountType),
|
||||
"id": JSONValue(defaultRootId)
|
||||
]),
|
||||
"folder": JSONValue("")
|
||||
];
|
||||
} else {
|
||||
// path is a file
|
||||
// compute file hash - both business and personal responses use quickXorHash
|
||||
string quickXorHash = computeQuickXorHash(path);
|
||||
|
||||
fakeResponse = [
|
||||
"id": JSONValue(toHexString(hash1)),
|
||||
"cTag": JSONValue(toHexString(hash1)),
|
||||
"eTag": JSONValue(toHexString(hash1)),
|
||||
"fileSystemInfo": JSONValue([
|
||||
"createdDateTime": mtime.toISOExtString(),
|
||||
"lastModifiedDateTime": mtime.toISOExtString()
|
||||
]),
|
||||
"name": JSONValue(baseName(path)),
|
||||
"parentReference": JSONValue([
|
||||
"driveId": JSONValue(defaultDriveId),
|
||||
"driveType": JSONValue(accountType),
|
||||
"id": JSONValue(defaultRootId)
|
||||
]),
|
||||
"file": JSONValue([
|
||||
"hashes":JSONValue([
|
||||
"quickXorHash": JSONValue(quickXorHash)
|
||||
])
|
||||
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
log.vdebug("Generated Fake OneDrive Response: ", fakeResponse);
|
||||
return fakeResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import std.uri;
|
|||
import qxor;
|
||||
static import log;
|
||||
|
||||
private string deviceName;
|
||||
shared string deviceName;
|
||||
|
||||
static this()
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue