Initial code commit for onedrive client version v2.5.0-alpha-1

Add initial code for onedrive client version v2.5.0-alpha-1, adding in support for OneDrive Business Shared Folders
This commit is contained in:
abraunegg 2023-09-22 05:34:42 +10:00
parent 3502e0cee4
commit 4bd9ae5092
7 changed files with 548 additions and 285 deletions

View file

@ -41,7 +41,7 @@ class ClientSideFiltering {
// Load the Business Shared Items file if it exists // Load the Business Shared Items file if it exists
if (exists(appConfig.businessSharedItemsFilePath)){ if (exists(appConfig.businessSharedItemsFilePath)){
loadSyncList(appConfig.businessSharedItemsFilePath); loadBusinessSharedItems(appConfig.businessSharedItemsFilePath);
} }
// Configure skip_dir, skip_file, skip-dir-strict-match & skip_dotfiles from config entries // Configure skip_dir, skip_file, skip-dir-strict-match & skip_dotfiles from config entries

View file

@ -55,7 +55,7 @@ class ApplicationConfig {
// - Identify as ISV and include Company Name, App Name separated by a pipe character and then adding Version number separated with a slash character // - Identify as ISV and include Company Name, App Name separated by a pipe character and then adding Version number separated with a slash character
//immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ strip(import("version")); //immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ strip(import("version"));
immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ "v2.5.0-alpha-0"; immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ "v2.5.0-alpha-1";
// HTTP Struct items, used for configuring HTTP() // HTTP Struct items, used for configuring HTTP()
// Curl Timeout Handling // Curl Timeout Handling
@ -146,20 +146,18 @@ class ApplicationConfig {
private string userConfigFilePath = ""; private string userConfigFilePath = "";
// - Store the system 'config' file path // - Store the system 'config' file path
private string systemConfigFilePath = ""; private string systemConfigFilePath = "";
// - What is the 'config' file path that will be used?
private string applicableConfigFilePath = "";
// - Store the 'sync_list' file path // - Store the 'sync_list' file path
string syncListFilePath = ""; string syncListFilePath = "";
// - Store the 'business_shared_items' file path // - Store the 'business_shared_items' file path
string businessSharedItemsFilePath = ""; string businessSharedItemsFilePath = "";
// - What is the 'config' file path that will be used?
private string applicableConfigFilePath = "";
// Hash files so that we can detect when the configuration has changed, in items that will require a --resync // Hash files so that we can detect when the configuration has changed, in items that will require a --resync
private string configHashFile = ""; private string configHashFile = "";
private string configBackupFile = ""; private string configBackupFile = "";
private string syncListHashFile = ""; private string syncListHashFile = "";
private string businessSharedItemsHashFile = ""; private string businessSharedItemsHashFile = "";
// hash file permission values (set via initialize function)
private int convertedPermissionValue;
// Store the actual 'runtime' hash // Store the actual 'runtime' hash
private string currentConfigHash = ""; private string currentConfigHash = "";
@ -178,6 +176,10 @@ class ApplicationConfig {
private string configFileDriveId = ""; // Default here is that no drive id is specified private string configFileDriveId = ""; // Default here is that no drive id is specified
private bool configFileSkipDotfiles = false; private bool configFileSkipDotfiles = false;
private bool configFileSkipSymbolicLinks = false; private bool configFileSkipSymbolicLinks = false;
private bool configFileSyncBusinessSharedItems = false;
// File permission values (set via initialize function)
private int convertedPermissionValue;
// Array of values that are the actual application runtime configuration // Array of values that are the actual application runtime configuration
// The values stored in these array's are the actual application configuration which can then be accessed by getValue & setValue // The values stored in these array's are the actual application configuration which can then be accessed by getValue & setValue
@ -323,7 +325,7 @@ class ApplicationConfig {
// Print in debug the application version as soon as possible // Print in debug the application version as soon as possible
//log.vdebug("Application Version: ", strip(import("version"))); //log.vdebug("Application Version: ", strip(import("version")));
string tempVersion = "v2.5.0-alpha-0" ~ " GitHub version: " ~ strip(import("version")); string tempVersion = "v2.5.0-alpha-1" ~ " GitHub version: " ~ strip(import("version"));
log.vdebug("Application Version: ", tempVersion); log.vdebug("Application Version: ", tempVersion);
// EXPAND USERS HOME DIRECTORY // EXPAND USERS HOME DIRECTORY
@ -448,9 +450,14 @@ class ApplicationConfig {
userConfigFilePath = buildNormalizedPath(configDirName ~ "/config"); userConfigFilePath = buildNormalizedPath(configDirName ~ "/config");
// - What is the full path for the system 'config' file if it is required // - What is the full path for the system 'config' file if it is required
systemConfigFilePath = buildNormalizedPath(systemConfigDirName ~ "/config"); systemConfigFilePath = buildNormalizedPath(systemConfigDirName ~ "/config");
// - What is the full path for the 'business_shared_items' // - What is the full path for the 'business_shared_items'
businessSharedItemsFilePath = buildNormalizedPath(configDirName ~ "/business_shared_items"); businessSharedItemsFilePath = buildNormalizedPath(configDirName ~ "/business_shared_items");
// To determine if any configuration items has changed, where a --resync would be required, we need to have a hash file for the following items // To determine if any configuration items has changed, where a --resync would be required, we need to have a hash file for the following items
// - 'config.backup' file // - 'config.backup' file
// - applicable 'config' file // - applicable 'config' file
@ -707,6 +714,12 @@ class ApplicationConfig {
if (key == "skip_symlinks") { if (key == "skip_symlinks") {
configFileSkipSymbolicLinks = true; configFileSkipSymbolicLinks = true;
} }
// sync_business_shared_items tracking for change
if (key == "sync_business_shared_items") {
configFileSyncBusinessSharedItems = true;
}
} else { } else {
auto pp = key in stringValues; auto pp = key in stringValues;
if (pp) { if (pp) {
@ -1084,12 +1097,6 @@ class ApplicationConfig {
"version", "version",
"Print the version and exit", "Print the version and exit",
&tmpBol, &tmpBol,
"list-shared-folders",
"List OneDrive Business Shared Items",
&boolValues["list_business_shared_items"],
"sync-shared-folders",
"Sync OneDrive Business Shared Items",
&boolValues["sync_business_shared_items"],
"with-editing-perms", "with-editing-perms",
"Create a read-write shareable link for an existing file on OneDrive when used with --create-share-link <file>", "Create a read-write shareable link for an existing file on OneDrive when used with --create-share-link <file>",
&boolValues["with_editing_perms"] &boolValues["with_editing_perms"]
@ -1119,7 +1126,7 @@ class ApplicationConfig {
// Display application version // Display application version
//writeln("onedrive version = ", strip(import("version"))); //writeln("onedrive version = ", strip(import("version")));
string tempVersion = "v2.5.0-alpha-0" ~ " GitHub version: " ~ strip(import("version")); string tempVersion = "v2.5.0-alpha-1" ~ " GitHub version: " ~ strip(import("version"));
writeln("onedrive version = ", tempVersion); writeln("onedrive version = ", tempVersion);
// Display all of the pertinent configuration options // Display all of the pertinent configuration options
@ -1188,7 +1195,7 @@ class ApplicationConfig {
writeln("Config option 'ip_protocol_version' = ", getValueLong("ip_protocol_version")); writeln("Config option 'ip_protocol_version' = ", getValueLong("ip_protocol_version"));
// Is sync_list configured ? // Is sync_list configured ?
writeln("Config option 'sync_root_files' = ", getValueBool("sync_root_files")); writeln("\nConfig option 'sync_root_files' = ", getValueBool("sync_root_files"));
if (exists(syncListFilePath)){ if (exists(syncListFilePath)){
writeln("Selective sync 'sync_list' configured = true"); writeln("Selective sync 'sync_list' configured = true");
@ -1206,9 +1213,10 @@ class ApplicationConfig {
} }
// Is sync_business_shared_items enabled and configured ? // Is sync_business_shared_items enabled and configured ?
writeln("Config option 'sync_business_shared_items' = ", getValueBool("sync_business_shared_items")); writeln("\nConfig option 'sync_business_shared_items' = ", getValueBool("sync_business_shared_items"));
if (exists(businessSharedItemsFilePath)){ if (exists(businessSharedItemsFilePath)){
writeln("Business Shared Items configured = true"); writeln("Selective Business Shared Items configured = true");
writeln("sync_business_shared_items contents:"); writeln("sync_business_shared_items contents:");
// Output the sync_business_shared_items contents // Output the sync_business_shared_items contents
auto businessSharedItemsFileList = File(businessSharedItemsFilePath, "r"); auto businessSharedItemsFileList = File(businessSharedItemsFilePath, "r");
@ -1218,11 +1226,13 @@ class ApplicationConfig {
writeln(line); writeln(line);
} }
} else { } else {
writeln("Business Shared Items configured = false"); writeln("Selective Business Shared Items configured = false");
} }
// Are webhooks enabled? // Are webhooks enabled?
writeln("Config option 'webhook_enabled' = ", getValueBool("webhook_enabled")); writeln("\nConfig option 'webhook_enabled' = ", getValueBool("webhook_enabled"));
if (getValueBool("webhook_enabled")) { if (getValueBool("webhook_enabled")) {
writeln("Config option 'webhook_public_url' = ", getValueString("webhook_public_url")); writeln("Config option 'webhook_public_url' = ", getValueString("webhook_public_url"));
writeln("Config option 'webhook_listening_host' = ", getValueString("webhook_listening_host")); writeln("Config option 'webhook_listening_host' = ", getValueString("webhook_listening_host"));
@ -1292,13 +1302,14 @@ class ApplicationConfig {
// Configuration File Flags // Configuration File Flags
bool configFileOptionsDifferent = false; bool configFileOptionsDifferent = false;
bool syncListFileDifferent = false; bool syncListFileDifferent = false;
bool businessSharedItemsFileDifferent = false;
bool syncDirDifferent = false; bool syncDirDifferent = false;
bool skipFileDifferent = false; bool skipFileDifferent = false;
bool skipDirDifferent = false; bool skipDirDifferent = false;
bool skipDotFilesDifferent = false; bool skipDotFilesDifferent = false;
bool skipSymbolicLinksDifferent = false; bool skipSymbolicLinksDifferent = false;
bool driveIdDifferent = false; bool driveIdDifferent = false;
bool syncBusinessSharedItemsDifferent = false;
bool businessSharedItemsFileDifferent = false;
// Create the required initial hash files // Create the required initial hash files
createRequiredInitialConfigurationHashFiles(); createRequiredInitialConfigurationHashFiles();
@ -1334,6 +1345,7 @@ class ApplicationConfig {
// # skip_dir = "" // # skip_dir = ""
// # skip_dotfiles = "" // # skip_dotfiles = ""
// # skip_symlinks = "" // # skip_symlinks = ""
// # sync_business_shared_items = ""
string[string] backupConfigStringValues; string[string] backupConfigStringValues;
backupConfigStringValues["drive_id"] = ""; backupConfigStringValues["drive_id"] = "";
backupConfigStringValues["sync_dir"] = ""; backupConfigStringValues["sync_dir"] = "";
@ -1341,6 +1353,7 @@ class ApplicationConfig {
backupConfigStringValues["skip_dir"] = ""; backupConfigStringValues["skip_dir"] = "";
backupConfigStringValues["skip_dotfiles"] = ""; backupConfigStringValues["skip_dotfiles"] = "";
backupConfigStringValues["skip_symlinks"] = ""; backupConfigStringValues["skip_symlinks"] = "";
backupConfigStringValues["sync_business_shared_items"] = "";
// bool flags to trigger if the entries that trigger a --resync were found in the backup config file // bool flags to trigger if the entries that trigger a --resync were found in the backup config file
// if these were not in the backup file, they may have been added ... thus new, thus we need to double check the existing // if these were not in the backup file, they may have been added ... thus new, thus we need to double check the existing
@ -1351,6 +1364,7 @@ class ApplicationConfig {
bool skip_dir_present = false; bool skip_dir_present = false;
bool skip_dotfiles_present = false; bool skip_dotfiles_present = false;
bool skip_symlinks_present = false; bool skip_symlinks_present = false;
bool sync_business_shared_items_present = false;
// Common debug message if an element is different // Common debug message if an element is different
string configOptionModifiedMessage = " was modified since the last time the application was successfully run, --resync required"; string configOptionModifiedMessage = " was modified since the last time the application was successfully run, --resync required";
@ -1421,6 +1435,14 @@ class ApplicationConfig {
configFileOptionsDifferent = true; configFileOptionsDifferent = true;
} }
} }
if (key == "sync_business_shared_items") {
sync_business_shared_items_present = true;
if (c.front.dup != to!string(getValueBool("sync_business_shared_items"))) {
log.vdebug(key, configOptionModifiedMessage);
configFileOptionsDifferent = true;
}
}
} }
} }
} }
@ -1433,12 +1455,13 @@ class ApplicationConfig {
// Were any of the items that trigger a --resync not in the existing backup 'config' file .. thus newly added? // Were any of the items that trigger a --resync not in the existing backup 'config' file .. thus newly added?
if ((!drive_id_present) || (!sync_dir_present) || (! skip_file_present) || (!skip_dir_present) || (!skip_dotfiles_present) || (!skip_symlinks_present)) { if ((!drive_id_present) || (!sync_dir_present) || (! skip_file_present) || (!skip_dir_present) || (!skip_dotfiles_present) || (!skip_symlinks_present)) {
log.vdebug("drive_id present in config backup: ", drive_id_present); log.vdebug("drive_id present in config backup: ", drive_id_present);
log.vdebug("sync_dir present in config backup: ", sync_dir_present); log.vdebug("sync_dir present in config backup: ", sync_dir_present);
log.vdebug("skip_file present in config backup: ", skip_file_present); log.vdebug("skip_file present in config backup: ", skip_file_present);
log.vdebug("skip_dir present in config backup: ", skip_dir_present); log.vdebug("skip_dir present in config backup: ", skip_dir_present);
log.vdebug("skip_dotfiles present in config backup: ", skip_dotfiles_present); log.vdebug("skip_dotfiles present in config backup: ", skip_dotfiles_present);
log.vdebug("skip_symlinks present in config backup: ", skip_symlinks_present); log.vdebug("skip_symlinks present in config backup: ", skip_symlinks_present);
log.vdebug("sync_business_shared_items present in config backup: ", sync_business_shared_items_present);
if ((!drive_id_present) && (configFileDriveId != "")) { if ((!drive_id_present) && (configFileDriveId != "")) {
writeln("drive_id newly added ... --resync needed"); writeln("drive_id newly added ... --resync needed");
@ -1475,6 +1498,12 @@ class ApplicationConfig {
configFileOptionsDifferent = true; configFileOptionsDifferent = true;
skipSymbolicLinksDifferent = true; skipSymbolicLinksDifferent = true;
} }
if ((!sync_business_shared_items_present) && (configFileSyncBusinessSharedItems)) {
writeln("sync_business_shared_items newly added ... --resync needed");
configFileOptionsDifferent = true;
syncBusinessSharedItemsDifferent = true;
}
} }
} else { } else {
// no backup to check // no backup to check
@ -1543,16 +1572,20 @@ class ApplicationConfig {
} }
// Did any of the config files or CLI options trigger a --resync requirement? // Did any of the config files or CLI options trigger a --resync requirement?
log.vdebug("configFileOptionsDifferent: ", configFileOptionsDifferent); log.vdebug("configFileOptionsDifferent: ", configFileOptionsDifferent);
log.vdebug("syncListFileDifferent: ", syncListFileDifferent); // Options
log.vdebug("driveIdDifferent: ", driveIdDifferent);
log.vdebug("syncDirDifferent: ", syncDirDifferent);
log.vdebug("skipFileDifferent: ", skipFileDifferent);
log.vdebug("skipDirDifferent: ", skipDirDifferent);
log.vdebug("skipDotFilesDifferent: ", skipDotFilesDifferent);
log.vdebug("skipSymbolicLinksDifferent: ", skipSymbolicLinksDifferent);
log.vdebug("syncBusinessSharedItemsDifferent: ", syncBusinessSharedItemsDifferent);
// Files
log.vdebug("syncListFileDifferent: ", syncListFileDifferent);
log.vdebug("businessSharedItemsFileDifferent: ", businessSharedItemsFileDifferent); log.vdebug("businessSharedItemsFileDifferent: ", businessSharedItemsFileDifferent);
log.vdebug("syncDirDifferent: ", syncDirDifferent);
log.vdebug("skipFileDifferent: ", skipFileDifferent);
log.vdebug("skipDirDifferent: ", skipDirDifferent);
log.vdebug("driveIdDifferent: ", driveIdDifferent);
log.vdebug("skipDotFilesDifferent: ", skipDotFilesDifferent);
if ((configFileOptionsDifferent) || (syncListFileDifferent) || (businessSharedItemsFileDifferent) || (syncDirDifferent) || (skipFileDifferent) || (skipDirDifferent) || (driveIdDifferent) || (skipDotFilesDifferent) || (skipSymbolicLinksDifferent) ) { if ((configFileOptionsDifferent) || (syncListFileDifferent) || (businessSharedItemsFileDifferent) || (syncDirDifferent) || (skipFileDifferent) || (skipDirDifferent) || (driveIdDifferent) || (skipDotFilesDifferent) || (skipSymbolicLinksDifferent) || (syncBusinessSharedItemsDifferent) ) {
// set the flag // set the flag
resyncRequired = true; resyncRequired = true;
} }
@ -1594,6 +1627,8 @@ class ApplicationConfig {
// Hash file should only be readable by the user who created it - 0600 permissions needed // Hash file should only be readable by the user who created it - 0600 permissions needed
syncListHashFile.setAttributes(convertedPermissionValue); syncListHashFile.setAttributes(convertedPermissionValue);
} }
// Update 'update business_shared_items' files // Update 'update business_shared_items' files
if (exists(businessSharedItemsFilePath)) { if (exists(businessSharedItemsFilePath)) {
// update business_shared_folders hash // update business_shared_folders hash
@ -1602,6 +1637,7 @@ class ApplicationConfig {
// Hash file should only be readable by the user who created it - 0600 permissions needed // Hash file should only be readable by the user who created it - 0600 permissions needed
businessSharedItemsHashFile.setAttributes(convertedPermissionValue); businessSharedItemsHashFile.setAttributes(convertedPermissionValue);
} }
} else { } else {
// --dry-run scenario ... technically we should not be making any local file changes ....... // --dry-run scenario ... technically we should not be making any local file changes .......
log.log("DRY RUN: Not updating hash files as --dry-run has been used"); log.log("DRY RUN: Not updating hash files as --dry-run has been used");

View file

@ -28,6 +28,7 @@ struct Item {
string driveId; string driveId;
string id; string id;
string name; string name;
string remoteName;
ItemType type; ItemType type;
string eTag; string eTag;
string cTag; string cTag;
@ -43,11 +44,15 @@ struct Item {
// Construct an Item struct from a JSON driveItem // Construct an Item struct from a JSON driveItem
Item makeDatabaseItem(JSONValue driveItem) { Item makeDatabaseItem(JSONValue driveItem) {
log.vdebug("Starting this function: ", getFunctionName!({}));
Item item = { Item item = {
id: driveItem["id"].str, id: driveItem["id"].str,
name: "name" in driveItem ? driveItem["name"].str : null, // name may be missing for deleted files in OneDrive Biz name: "name" in driveItem ? driveItem["name"].str : null, // name may be missing for deleted files in OneDrive Business
eTag: "eTag" in driveItem ? driveItem["eTag"].str : null, // eTag is not returned for the root in OneDrive Biz eTag: "eTag" in driveItem ? driveItem["eTag"].str : null, // eTag is not returned for the root in OneDrive Business
cTag: "cTag" in driveItem ? driveItem["cTag"].str : null, // cTag is missing in old files (and all folders in OneDrive Biz) cTag: "cTag" in driveItem ? driveItem["cTag"].str : null, // cTag is missing in old files (and all folders in OneDrive Business)
remoteName: "actualOnlineName" in driveItem ? driveItem["actualOnlineName"].str : null, // actualOnlineName is only used with OneDrive Business Shared Folders
}; };
// OneDrive API Change: https://github.com/OneDrive/onedrive-api-docs/issues/834 // OneDrive API Change: https://github.com/OneDrive/onedrive-api-docs/issues/834
@ -164,6 +169,7 @@ final class ItemDatabase {
string insertItemStmt; string insertItemStmt;
string updateItemStmt; string updateItemStmt;
string selectItemByIdStmt; string selectItemByIdStmt;
string selectItemByRemoteIdStmt;
string selectItemByParentIdStmt; string selectItemByParentIdStmt;
string deleteItemByIdStmt; string deleteItemByIdStmt;
bool databaseInitialised = false; bool databaseInitialised = false;
@ -205,7 +211,11 @@ final class ItemDatabase {
db.exec("PRAGMA recursive_triggers = TRUE"); db.exec("PRAGMA recursive_triggers = TRUE");
// Set the journal mode for databases associated with the current connection // Set the journal mode for databases associated with the current connection
// https://www.sqlite.org/pragma.html#pragma_journal_mode // https://www.sqlite.org/pragma.html#pragma_journal_mode
db.exec("PRAGMA journal_mode = WAL");
//db.exec("PRAGMA journal_mode = WAL");
// Automatic indexing is enabled by default as of version 3.7.17 // Automatic indexing is enabled by default as of version 3.7.17
// https://www.sqlite.org/pragma.html#pragma_automatic_index // https://www.sqlite.org/pragma.html#pragma_automatic_index
// PRAGMA automatic_index = boolean; // PRAGMA automatic_index = boolean;
@ -223,12 +233,12 @@ final class ItemDatabase {
db.exec("PRAGMA locking_mode = EXCLUSIVE"); db.exec("PRAGMA locking_mode = EXCLUSIVE");
insertItemStmt = " insertItemStmt = "
INSERT OR REPLACE INTO item (driveId, id, name, type, eTag, cTag, mtime, parentId, quickXorHash, sha256Hash, remoteDriveId, remoteId, syncStatus, size) INSERT OR REPLACE INTO item (driveId, id, name, remoteName, type, eTag, cTag, mtime, parentId, quickXorHash, sha256Hash, remoteDriveId, remoteId, syncStatus, size)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)
"; ";
updateItemStmt = " updateItemStmt = "
UPDATE item UPDATE item
SET name = ?3, type = ?4, eTag = ?5, cTag = ?6, mtime = ?7, parentId = ?8, quickXorHash = ?9, sha256Hash = ?10, remoteDriveId = ?11, remoteId = ?12, syncStatus = ?13, size = ?14 SET name = ?3, remoteName = ?4, type = ?5, eTag = ?6, cTag = ?7, mtime = ?8, parentId = ?9, quickXorHash = ?10, sha256Hash = ?11, remoteDriveId = ?12, remoteId = ?13, syncStatus = ?14, size = ?15
WHERE driveId = ?1 AND id = ?2 WHERE driveId = ?1 AND id = ?2
"; ";
selectItemByIdStmt = " selectItemByIdStmt = "
@ -236,6 +246,11 @@ final class ItemDatabase {
FROM item FROM item
WHERE driveId = ?1 AND id = ?2 WHERE driveId = ?1 AND id = ?2
"; ";
selectItemByRemoteIdStmt = "
SELECT *
FROM item
WHERE remoteDriveId = ?1 AND remoteId = ?2
";
selectItemByParentIdStmt = "SELECT * FROM item WHERE driveId = ? AND parentId = ?"; selectItemByParentIdStmt = "SELECT * FROM item WHERE driveId = ? AND parentId = ?";
deleteItemByIdStmt = "DELETE FROM item WHERE driveId = ? AND id = ?"; deleteItemByIdStmt = "DELETE FROM item WHERE driveId = ? AND id = ?";
@ -252,6 +267,7 @@ final class ItemDatabase {
driveId TEXT NOT NULL, driveId TEXT NOT NULL,
id TEXT NOT NULL, id TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
remoteName TEXT,
type TEXT NOT NULL, type TEXT NOT NULL,
eTag TEXT, eTag TEXT,
cTag TEXT, cTag TEXT,
@ -334,6 +350,18 @@ final class ItemDatabase {
return false; return false;
} }
bool selectByRemoteId(const(char)[] remoteDriveId, const(char)[] remoteId, out Item item) {
auto p = db.prepare(selectItemByRemoteIdStmt);
p.bind(1, remoteDriveId);
p.bind(2, remoteId);
auto r = p.exec();
if (!r.empty) {
item = buildItem(r);
return true;
}
return false;
}
// returns true if an item id is in the database // returns true if an item id is in the database
bool idInLocalDatabase(const(string) driveId, const(string)id) { bool idInLocalDatabase(const(string) driveId, const(string)id) {
auto p = db.prepare(selectItemByIdStmt); auto p = db.prepare(selectItemByIdStmt);
@ -420,6 +448,7 @@ final class ItemDatabase {
bind(1, driveId); bind(1, driveId);
bind(2, id); bind(2, id);
bind(3, name); bind(3, name);
bind(4, remoteName);
string typeStr = null; string typeStr = null;
final switch (type) with (ItemType) { final switch (type) with (ItemType) {
case file: typeStr = "file"; break; case file: typeStr = "file"; break;
@ -427,41 +456,60 @@ final class ItemDatabase {
case remote: typeStr = "remote"; break; case remote: typeStr = "remote"; break;
case unknown: typeStr = "unknown"; break; case unknown: typeStr = "unknown"; break;
} }
bind(4, typeStr); bind(5, typeStr);
bind(5, eTag); bind(6, eTag);
bind(6, cTag); bind(7, cTag);
bind(7, mtime.toISOExtString()); bind(8, mtime.toISOExtString());
bind(8, parentId); bind(9, parentId);
bind(9, quickXorHash); bind(10, quickXorHash);
bind(10, sha256Hash); bind(11, sha256Hash);
bind(11, remoteDriveId); bind(12, remoteDriveId);
bind(12, remoteId); bind(13, remoteId);
bind(13, syncStatus); bind(14, syncStatus);
bind(14, size); bind(15, size);
} }
} }
private Item buildItem(Statement.Result result) { private Item buildItem(Statement.Result result) {
assert(!result.empty, "The result must not be empty"); assert(!result.empty, "The result must not be empty");
assert(result.front.length == 15, "The result must have 15 columns"); assert(result.front.length == 16, "The result must have 16 columns");
Item item = { Item item = {
// column 0: driveId
// column 1: id
// column 2: name
// column 3: remoteName - only used when there is a difference in the local name & remote shared folder name
// column 4: type
// column 5: eTag
// column 6: cTag
// column 7: mtime
// column 8: parentId
// column 9: quickXorHash
// column 10: sha256Hash
// column 11: remoteDriveId
// column 12: remoteId
// column 13: deltaLink
// column 14: syncStatus
// column 15: size
driveId: result.front[0].dup, driveId: result.front[0].dup,
id: result.front[1].dup, id: result.front[1].dup,
name: result.front[2].dup, name: result.front[2].dup,
// Column 3 is type - not set here remoteName: result.front[3].dup,
eTag: result.front[4].dup, // Column 4 is type - not set here
cTag: result.front[5].dup, eTag: result.front[5].dup,
mtime: SysTime.fromISOExtString(result.front[6]), cTag: result.front[6].dup,
parentId: result.front[7].dup, mtime: SysTime.fromISOExtString(result.front[7]),
quickXorHash: result.front[8].dup, parentId: result.front[8].dup,
sha256Hash: result.front[9].dup, quickXorHash: result.front[9].dup,
remoteDriveId: result.front[10].dup, sha256Hash: result.front[10].dup,
remoteId: result.front[11].dup, remoteDriveId: result.front[11].dup,
// Column 12 is deltaLink - not set here remoteId: result.front[12].dup,
syncStatus: result.front[13].dup, // Column 13 is deltaLink - not set here
size: result.front[14].dup syncStatus: result.front[14].dup,
size: result.front[15].dup
}; };
switch (result.front[3]) { switch (result.front[4]) {
case "file": item.type = ItemType.file; break; case "file": item.type = ItemType.file; break;
case "dir": item.type = ItemType.dir; break; case "dir": item.type = ItemType.dir; break;
case "remote": item.type = ItemType.remote; break; case "remote": item.type = ItemType.remote; break;

View file

@ -112,7 +112,7 @@ int main(string[] cliArgs) {
// Print the version and exit // Print the version and exit
if (printVersion) { if (printVersion) {
//writeln("onedrive ", strip(import("version"))); //writeln("onedrive ", strip(import("version")));
string tempVersion = "v2.5.0-alpha-0" ~ " GitHub version: " ~ strip(import("version")); string tempVersion = "v2.5.0-alpha-1" ~ " GitHub version: " ~ strip(import("version"));
writeln(tempVersion); writeln(tempVersion);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View file

@ -284,7 +284,7 @@ class OneDriveApi {
} else { } else {
// Try and read the value from the appConfig if it is set, rather than trying to read the value from disk // Try and read the value from the appConfig if it is set, rather than trying to read the value from disk
if (!appConfig.refreshToken.empty) { if (!appConfig.refreshToken.empty) {
log.vdebug("read token from appConfig"); log.vdebug("Read token from appConfig");
refreshToken = strip(appConfig.refreshToken); refreshToken = strip(appConfig.refreshToken);
authorised = true; authorised = true;
} else { } else {
@ -494,6 +494,12 @@ class OneDriveApi {
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta
JSONValue viewChangesByItemId(string driveId, string id, string deltaLink) { JSONValue viewChangesByItemId(string driveId, string id, string deltaLink) {
checkAccessTokenExpired(); checkAccessTokenExpired();
// If Business Account add addIncludeFeatureRequestHeader() which should add Prefer: Include-Feature=AddToOneDrive
if ((appConfig.accountType != "personal") && ( appConfig.getValueBool("sync_business_shared_items"))) {
addIncludeFeatureRequestHeader();
}
string url; string url;
// configure deltaLink to query // configure deltaLink to query
if (deltaLink.empty) { if (deltaLink.empty) {
@ -507,6 +513,12 @@ class OneDriveApi {
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children
JSONValue listChildren(string driveId, string id, string nextLink) { JSONValue listChildren(string driveId, string id, string nextLink) {
checkAccessTokenExpired(); checkAccessTokenExpired();
// If Business Account add addIncludeFeatureRequestHeader() which should add Prefer: Include-Feature=AddToOneDrive
if ((appConfig.accountType != "personal") && ( appConfig.getValueBool("sync_business_shared_items"))) {
addIncludeFeatureRequestHeader();
}
string url; string url;
// configure URL to query // configure URL to query
if (nextLink.empty) { if (nextLink.empty) {
@ -677,6 +689,11 @@ class OneDriveApi {
curlEngine.http.addRequestHeader("Authorization", accessToken); curlEngine.http.addRequestHeader("Authorization", accessToken);
} }
private void addIncludeFeatureRequestHeader() {
log.vdebug("Adding 'Include-Feature=AddToOneDrive' API request header as 'sync_business_shared_items' config option is enabled");
curlEngine.http.addRequestHeader("Prefer", "Include-Feature=AddToOneDrive");
}
private void acquireToken(char[] postData) { private void acquireToken(char[] postData) {
JSONValue response; JSONValue response;
@ -684,11 +701,16 @@ class OneDriveApi {
response = post(tokenUrl, postData); response = post(tokenUrl, postData);
} catch (OneDriveException e) { } catch (OneDriveException e) {
// an error was generated // an error was generated
if (e.httpStatusCode >= 500) { if ((e.httpStatusCode == 400) || (e.httpStatusCode == 401)) {
// There was a HTTP 5xx Server Side Error - retry // Handle an unauthorised client
acquireToken(postData); handleClientUnauthorised(e.httpStatusCode, e.msg);
} else { } else {
displayOneDriveErrorMessage(e.msg, getFunctionName!({})); if (e.httpStatusCode >= 500) {
// There was a HTTP 5xx Server Side Error - retry
acquireToken(postData);
} else {
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
module util; module util;
// What does this module require to function? // What does this module require to function?
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
import std.base64; import std.base64;
import std.conv; import std.conv;
import std.digest.crc; import std.digest.crc;
@ -341,7 +342,7 @@ bool containsASCIIHTMLCodes(string path) {
return m.empty; return m.empty;
} }
/**
// Parse and display error message received from OneDrive // Parse and display error message received from OneDrive
void displayOneDriveErrorMessage(string message, string callingFunction) { void displayOneDriveErrorMessage(string message, string callingFunction) {
writeln(); writeln();
@ -412,85 +413,40 @@ void displayOneDriveErrorMessage(string message, string callingFunction) {
log.vdebug("Raw Error Data: ", message); log.vdebug("Raw Error Data: ", message);
log.vdebug("JSON Message: ", errorMessage); log.vdebug("JSON Message: ", errorMessage);
} }
**/
// Alpha-0 Testing ..... // Common code for handling when a client is unauthorised
void displayOneDriveErrorMessage(string message, string callingFunction) { void handleClientUnauthorised(int httpStatusCode, string message) {
writeln(); // Split the lines of the error message
log.log("ERROR: Microsoft OneDrive API returned an error with the following message:");
auto errorArray = splitLines(message); auto errorArray = splitLines(message);
log.log(" Error Message: ", errorArray[0]);
// Extract 'message' as the reason // Extract 'message' as the reason
JSONValue errorMessage = parseJSON(replace(message, errorArray[0], "")); JSONValue errorMessage = parseJSON(replace(message, errorArray[0], ""));
log.vdebug("errorMessage: ", errorMessage);
// What is the reason for the error if (httpStatusCode == 400) {
if (errorMessage.type() == JSONType.object) { // bad request or a new auth token is needed
// configure the error reason // configure the error reason
string errorReason; writeln();
string requestDate; string[] errorReason = splitLines(errorMessage["error_description"].str);
string requestId; log.errorAndNotify(errorReason[0]);
writeln();
// set the reason for the error log.errorAndNotify("ERROR: You will need to issue a --reauth and re-authorise this client to obtain a fresh auth token.");
try { writeln();
// Use error_description as reason
errorReason = errorMessage["error_description"].str;
} catch (JSONException e) {
// we dont want to do anything here
}
// set the reason for the error
try {
// Use ["error"]["message"] as reason
errorReason = errorMessage["error"]["message"].str;
} catch (JSONException e) {
// we dont want to do anything here
}
// Display the error reason
if (errorReason.startsWith("<!DOCTYPE")) {
// a HTML Error Reason was given
log.log(" Error Reason: A HTML Error response was provided. Use debug logging (--verbose --verbose) to view this error");
log.log(errorReason);
} else {
// a non HTML Error Reason was given
log.log(" Error Reason: ", errorReason);
}
// Get the date of request if available
try {
// Use ["error"]["innerError"]["date"] as date
requestDate = errorMessage["error"]["innerError"]["date"].str;
} catch (JSONException e) {
// we dont want to do anything here
}
// Get the request-id if available
try {
// Use ["error"]["innerError"]["request-id"] as request-id
requestId = errorMessage["error"]["innerError"]["request-id"].str;
} catch (JSONException e) {
// we dont want to do anything here
}
// Display the date and request id if available
if (requestDate != "") log.error(" Error Timestamp: ", requestDate);
if (requestId != "") log.error(" API Request ID: ", requestId);
} }
// Where in the code was this error generated if (httpStatusCode == 401) {
log.log(" Calling Function: ", callingFunction);
// Extra Debug if we are using --verbose --verbose writeln("CODING TO DO: Triggered a 401 HTTP unauthorised response");
log.log("Raw Error Data: ", message);
log.log("JSON Message: ", errorMessage); writeln();
log.errorAndNotify("ERROR: Check your configuration as your refresh_token may be empty or invalid. You may need to issue a --reauth and re-authorise this client.");
writeln();
}
// Must exit here
exit(EXIT_FAILURE);
} }
// Parse and display error message received from the local file system // Parse and display error message received from the local file system
void displayFileSystemErrorMessage(string message, string callingFunction) { void displayFileSystemErrorMessage(string message, string callingFunction) {
writeln(); writeln();
@ -504,7 +460,7 @@ void displayFileSystemErrorMessage(string message, string callingFunction) {
ulong localActualFreeSpace = to!ulong(getAvailableDiskSpace(".")); ulong localActualFreeSpace = to!ulong(getAvailableDiskSpace("."));
if (localActualFreeSpace == 0) { if (localActualFreeSpace == 0) {
// force exit // force exit
exit(-1); exit(EXIT_FAILURE);
} }
} }
@ -820,4 +776,8 @@ bool hasLocalPath(const ref JSONValue item) {
bool hasETag(const ref JSONValue item) { bool hasETag(const ref JSONValue item) {
return ("eTag" in item) != null; return ("eTag" in item) != null;
}
bool hasSharedElement(const ref JSONValue item) {
return ("eTag" in item) != null;
} }