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
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

View file

@ -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

View file

@ -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";

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
// 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
@ -202,6 +207,11 @@ int main(string[] args)
std.stdio.write("onedrive ", import("version"));
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 ...");
@ -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 ...");
safeRemove(cfg.databaseFilePath);
safeRemove(cfg.deltaLinkFilePath);
safeRemove(cfg.uploadStateFilePath);
if (!dryRun) {
safeRemove(cfg.databaseFilePath);
safeRemove(cfg.deltaLinkFilePath);
safeRemove(cfg.uploadStateFilePath);
}
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
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 ...");
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);
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;
}

View file

@ -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) {}
try {
refreshToken = readText(cfg.refreshTokenFilePath);
} catch (FileException e) {
return authorize();
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();
}
}
return true;
}
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());
std.file.write(cfg.refreshTokenFilePath, refreshToken);
if (!.dryRun) {
std.file.write(cfg.refreshTokenFilePath, refreshToken);
}
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;
static import log;
private string deviceName;
shared string deviceName;
static this()
{