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

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) {}
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);
}

View file

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

View file

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