Implement --dry-run (#337)

* Implement new feature --dry-run
This commit is contained in:
abraunegg 2019-03-11 17:57:47 +11:00 committed by GitHub
parent 3f08e271af
commit 98624267c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 638 additions and 323 deletions

View file

@ -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 ### Show your configuration
To validate your configuration the application will use, utilise the following: To validate your configuration the application will use, utilise the following:
@ -664,6 +696,8 @@ Options:
Only download remote changes Only download remote changes
--disable-upload-validation --disable-upload-validation
Disable upload validation when uploading to OneDrive Disable upload validation when uploading to OneDrive
--dry-run
Perform a trial sync with no changes made
--enable-logging --enable-logging
Enable client activity to a separate log file Enable client activity to a separate log file
--force-http-1.1 --force-http-1.1

View file

@ -51,6 +51,9 @@ Display the sync status of the client \- no sync will be performed.
\fB\-d \-\-download\-only\fP \fB\-d \-\-download\-only\fP
Only download remote changes Only download remote changes
.TP .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 \fB\-\-enable\-logging\fP
Enable client activity to a separate log file Enable client activity to a separate log file
.TP .TP

View file

@ -7,6 +7,7 @@ final class Config
public string refreshTokenFilePath; public string refreshTokenFilePath;
public string deltaLinkFilePath; public string deltaLinkFilePath;
public string databaseFilePath; public string databaseFilePath;
public string databaseFilePathDryRun;
public string uploadStateFilePath; public string uploadStateFilePath;
public string syncListFilePath; public string syncListFilePath;
@ -19,6 +20,7 @@ final class Config
refreshTokenFilePath = configDirName ~ "/refresh_token"; refreshTokenFilePath = configDirName ~ "/refresh_token";
deltaLinkFilePath = configDirName ~ "/delta_link"; deltaLinkFilePath = configDirName ~ "/delta_link";
databaseFilePath = configDirName ~ "/items.sqlite3"; databaseFilePath = configDirName ~ "/items.sqlite3";
databaseFilePathDryRun = configDirName ~ "/items-dryrun.sqlite3";
uploadStateFilePath = configDirName ~ "/resume_upload"; uploadStateFilePath = configDirName ~ "/resume_upload";
userConfigFilePath = configDirName ~ "/config"; userConfigFilePath = configDirName ~ "/config";
syncListFilePath = configDirName ~ "/sync_list"; syncListFilePath = configDirName ~ "/sync_list";

View file

@ -43,6 +43,8 @@ int main(string[] args)
// Does the user want to disable upload validation - https://github.com/abraunegg/onedrive/issues/205 // 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 // 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; bool disableUploadValidation = false;
// Perform only a dry run - not applicable for --monitor mode
bool dryRun = false;
// Do we enable a log file // Do we enable a log file
bool enableLogFile = false; 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 // 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, "display-sync-status", "Display the sync status of the client - no sync will be performed.", &displaySyncStatus,
"download-only|d", "Only download remote changes", &downloadOnly, "download-only|d", "Only download remote changes", &downloadOnly,
"disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation, "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, "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, "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, "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; bool debugHttpSubmit;
// Are we able to reach the OneDrive Service // Are we able to reach the OneDrive Service
bool online = false; bool online = false;
// simulateNoRefreshTokenFile in case of --dry-run & --logout
bool simulateNoRefreshTokenFile = false;
// Determine the users home directory. // 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 // Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
@ -202,6 +207,11 @@ int main(string[] args)
std.stdio.write("onedrive ", import("version")); std.stdio.write("onedrive ", import("version"));
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
// dry-run notification
if (dryRun) {
log.log("DRY-RUN Configured. Output below shows what 'would' have occurred.");
}
// load application configuration // load application configuration
log.vlog("Loading config ..."); log.vlog("Loading config ...");
@ -214,6 +224,16 @@ int main(string[] args)
return EXIT_FAILURE; 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 // 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 // Set the client to skip specific directories if .nosync is found AND ONLY if --check-for-nosync was passed in
if (checkNoSync) { if (checkNoSync) {
@ -285,18 +305,27 @@ int main(string[] args)
// upgrades // upgrades
if (exists(configDirName ~ "/items.db")) { if (exists(configDirName ~ "/items.db")) {
remove(configDirName ~ "/items.db"); if (!dryRun) {
safeRemove(configDirName ~ "/items.db");
}
log.logAndNotify("Database schema changed, resync needed"); log.logAndNotify("Database schema changed, resync needed");
resync = true; resync = true;
} }
if (resync || logout) { if (resync || logout) {
log.vlog("Deleting the saved status ..."); log.vlog("Deleting the saved status ...");
safeRemove(cfg.databaseFilePath); if (!dryRun) {
safeRemove(cfg.deltaLinkFilePath); safeRemove(cfg.databaseFilePath);
safeRemove(cfg.uploadStateFilePath); safeRemove(cfg.deltaLinkFilePath);
safeRemove(cfg.uploadStateFilePath);
}
if (logout) { if (logout) {
safeRemove(cfg.refreshTokenFilePath); 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 // Initialize OneDrive, check for authorization
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11); oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11, dryRun, simulateNoRefreshTokenFile);
oneDrive.printAccessToken = printAccessToken; oneDrive.printAccessToken = printAccessToken;
if (!oneDrive.init()) { if (!oneDrive.init()) {
log.error("Could not initialize the OneDrive API"); log.error("Could not initialize the OneDrive API");
@ -390,9 +419,17 @@ int main(string[] args)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// initialize system // Initialize the item database
log.vlog("Opening the item database ..."); log.vlog("Opening the item database ...");
itemDb = new ItemDatabase(cfg.databaseFilePath); 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); log.vlog("All operations will be performed in: ", syncDir);
if (!exists(syncDir)) { if (!exists(syncDir)) {
@ -416,9 +453,9 @@ int main(string[] args)
selectiveSync.load(cfg.syncListFilePath); selectiveSync.load(cfg.syncListFilePath);
selectiveSync.setMask(cfg.getValue("skip_file")); selectiveSync.setMask(cfg.getValue("skip_file"));
// Initialise the sync engine // Initialize the sync engine
log.logAndNotify("Initializing the Synchronization 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 { try {
if (!initSyncEngine(sync)) { 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(); 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; return EXIT_SUCCESS;
} }

View file

@ -8,6 +8,8 @@ import progress;
import config; import config;
static import log; static import log;
shared bool debugResponse = false; shared bool debugResponse = false;
shared bool dryRun = false;
shared bool simulateNoRefreshTokenFile = false;
private immutable { private immutable {
// Client Identifier // Client Identifier
@ -64,7 +66,7 @@ final class OneDriveApi
// if true, every new access token is printed // if true, every new access token is printed
bool printAccessToken; bool printAccessToken;
this(Config cfg, bool debugHttp, bool forceHTTP11) this(Config cfg, bool debugHttp, bool forceHTTP11, bool dryRun, bool simulateNoRefreshTokenFile)
{ {
this.cfg = cfg; this.cfg = cfg;
http = HTTP(); http = HTTP();
@ -104,6 +106,14 @@ final class OneDriveApi
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1 // Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
http.handle.set(CurlOption.http_version,2); http.handle.set(CurlOption.http_version,2);
} }
// Do we set the dryRun handlers?
if (dryRun) {
.dryRun = true;
}
if (simulateNoRefreshTokenFile) {
.simulateNoRefreshTokenFile = true;
}
} }
bool init() bool init()
@ -117,12 +127,28 @@ final class OneDriveApi
} }
} catch (Exception e) {} } catch (Exception e) {}
try { if (!.dryRun) {
refreshToken = readText(cfg.refreshTokenFilePath); // original code
} catch (FileException e) { try {
return authorize(); 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();
}
} }
return true;
} }
bool authorize() bool authorize()
@ -361,7 +387,9 @@ final class OneDriveApi
accessToken = "bearer " ~ response["access_token"].str(); accessToken = "bearer " ~ response["access_token"].str();
refreshToken = response["refresh_token"].str(); refreshToken = response["refresh_token"].str();
accessTokenExpiration = Clock.currTime() + dur!"seconds"(response["expires_in"].integer()); accessTokenExpiration = Clock.currTime() + dur!"seconds"(response["expires_in"].integer());
std.file.write(cfg.refreshTokenFilePath, refreshToken); if (!.dryRun) {
std.file.write(cfg.refreshTokenFilePath, refreshToken);
}
if (printAccessToken) writeln("New access token: ", accessToken); if (printAccessToken) writeln("New access token: ", accessToken);
} }

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@ import std.uri;
import qxor; import qxor;
static import log; static import log;
private string deviceName; shared string deviceName;
static this() static this()
{ {