mirror of
https://github.com/abraunegg/onedrive
synced 2024-05-19 22:26:37 +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
|
### 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
72
src/main.d
72
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
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
806
src/sync.d
806
src/sync.d
File diff suppressed because it is too large
Load diff
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue