From 16fdd928b6f6719190d7cb34367ce26ca0217e63 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Sat, 29 May 2021 06:29:12 +1000 Subject: [PATCH 01/16] Update INSTALL.md * Fix typo --- docs/INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index e4f57a21..3034375d 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -135,7 +135,7 @@ sudo apt install libnotify-dev ``` ### Dependencies: CentOS 6.x / RHEL 6.x -CentOS 6.x and RHEL 6.x reached End of Linfe status on November 30th 2020 and is no longer supported. +CentOS 6.x and RHEL 6.x reached End of Life status on November 30th 2020 and is no longer supported. ### Dependencies: Fedora < Version 18 / CentOS 7.x / RHEL 7.x ```text From 761cf3eb878fd370576329127055eec06b975672 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Mon, 7 Jun 2021 08:26:36 +1000 Subject: [PATCH 02/16] Support DMD 2.097.0 as compiler (#1505) * Support DMD 2.097.0 as compiler and resolve deprecation messages --- src/log.d | 1 + src/notifications/dnotify.d | 2 +- src/qxor.d | 176 ++++++++++++++++++------------------ src/sync.d | 3 + 4 files changed, 93 insertions(+), 89 deletions(-) diff --git a/src/log.d b/src/log.d index 02281bd2..638c719f 100644 --- a/src/log.d +++ b/src/log.d @@ -154,6 +154,7 @@ void notify(T...)(T args) private void logfileWriteLine(T...)(T args) { + static import std.exception; // Write to log file string logFileName = .logFilePath ~ .username ~ ".onedrive.log"; auto currentTime = Clock.currTime(); diff --git a/src/notifications/dnotify.d b/src/notifications/dnotify.d index 81ebd229..1cc09356 100644 --- a/src/notifications/dnotify.d +++ b/src/notifications/dnotify.d @@ -163,7 +163,7 @@ class Notification { this(in char[] summary, in char[] body_, in char[] icon="") in { assert(is_initted(), "call dnotify.init() before using Notification"); } - body { + do { this.summary = summary; this.body_ = body_; this.icon = icon; diff --git a/src/qxor.d b/src/qxor.d index 0e521a49..63e8f0f5 100644 --- a/src/qxor.d +++ b/src/qxor.d @@ -1,88 +1,88 @@ -import std.algorithm; -import std.digest; - -// implementation of the QuickXorHash algorithm in D -// https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/code-snippets/quickxorhash.md -struct QuickXor -{ - private immutable int widthInBits = 160; - private immutable size_t lengthInBytes = (widthInBits - 1) / 8 + 1; - private immutable size_t lengthInQWords = (widthInBits - 1) / 64 + 1; - private immutable int bitsInLastCell = widthInBits % 64; // 32 - private immutable int shift = 11; - - private ulong[lengthInQWords] _data; - private ulong _lengthSoFar; - private int _shiftSoFar; - - nothrow @safe void put(scope const(ubyte)[] array...) - { - int vectorArrayIndex = _shiftSoFar / 64; - int vectorOffset = _shiftSoFar % 64; - immutable size_t iterations = min(array.length, widthInBits); - - for (size_t i = 0; i < iterations; i++) { - immutable bool isLastCell = vectorArrayIndex == _data.length - 1; - immutable int bitsInVectorCell = isLastCell ? bitsInLastCell : 64; - - if (vectorOffset <= bitsInVectorCell - 8) { - for (size_t j = i; j < array.length; j += widthInBits) { - _data[vectorArrayIndex] ^= cast(ulong) array[j] << vectorOffset; - } - } else { - int index1 = vectorArrayIndex; - int index2 = isLastCell ? 0 : (vectorArrayIndex + 1); - ubyte low = cast(ubyte) (bitsInVectorCell - vectorOffset); - - ubyte xoredByte = 0; - for (size_t j = i; j < array.length; j += widthInBits) { - xoredByte ^= array[j]; - } - - _data[index1] ^= cast(ulong) xoredByte << vectorOffset; - _data[index2] ^= cast(ulong) xoredByte >> low; - } - - vectorOffset += shift; - if (vectorOffset >= bitsInVectorCell) { - vectorArrayIndex = isLastCell ? 0 : vectorArrayIndex + 1; - vectorOffset -= bitsInVectorCell; - } - } - - _shiftSoFar = cast(int) (_shiftSoFar + shift * (array.length % widthInBits)) % widthInBits; - _lengthSoFar += array.length; - - } - - nothrow @safe void start() - { - _data = _data.init; - _shiftSoFar = 0; - _lengthSoFar = 0; - } - - nothrow @trusted ubyte[lengthInBytes] finish() - { - ubyte[lengthInBytes] tmp; - tmp[0 .. lengthInBytes] = (cast(ubyte*) _data)[0 .. lengthInBytes]; - for (size_t i = 0; i < 8; i++) { - tmp[lengthInBytes - 8 + i] ^= (cast(ubyte*) &_lengthSoFar)[i]; - } - return tmp; - } -} - -unittest -{ - assert(isDigest!QuickXor); -} - -unittest -{ - QuickXor qxor; - qxor.put(cast(ubyte[]) "The quick brown fox jumps over the lazy dog"); - assert(qxor.finish().toHexString() == "6CC4A56F2B26C492FA4BBE57C1F31C4193A972BE"); -} - -alias QuickXorDigest = WrapperDigest!(QuickXor); +import std.algorithm; +import std.digest; + +// implementation of the QuickXorHash algorithm in D +// https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/code-snippets/quickxorhash.md +struct QuickXor +{ + private enum int widthInBits = 160; + private enum size_t lengthInBytes = (widthInBits - 1) / 8 + 1; + private enum size_t lengthInQWords = (widthInBits - 1) / 64 + 1; + private enum int bitsInLastCell = widthInBits % 64; // 32 + private enum int shift = 11; + + private ulong[lengthInQWords] _data; + private ulong _lengthSoFar; + private int _shiftSoFar; + + nothrow @safe void put(scope const(ubyte)[] array...) + { + int vectorArrayIndex = _shiftSoFar / 64; + int vectorOffset = _shiftSoFar % 64; + immutable size_t iterations = min(array.length, widthInBits); + + for (size_t i = 0; i < iterations; i++) { + immutable bool isLastCell = vectorArrayIndex == _data.length - 1; + immutable int bitsInVectorCell = isLastCell ? bitsInLastCell : 64; + + if (vectorOffset <= bitsInVectorCell - 8) { + for (size_t j = i; j < array.length; j += widthInBits) { + _data[vectorArrayIndex] ^= cast(ulong) array[j] << vectorOffset; + } + } else { + int index1 = vectorArrayIndex; + int index2 = isLastCell ? 0 : (vectorArrayIndex + 1); + ubyte low = cast(ubyte) (bitsInVectorCell - vectorOffset); + + ubyte xoredByte = 0; + for (size_t j = i; j < array.length; j += widthInBits) { + xoredByte ^= array[j]; + } + + _data[index1] ^= cast(ulong) xoredByte << vectorOffset; + _data[index2] ^= cast(ulong) xoredByte >> low; + } + + vectorOffset += shift; + if (vectorOffset >= bitsInVectorCell) { + vectorArrayIndex = isLastCell ? 0 : vectorArrayIndex + 1; + vectorOffset -= bitsInVectorCell; + } + } + + _shiftSoFar = cast(int) (_shiftSoFar + shift * (array.length % widthInBits)) % widthInBits; + _lengthSoFar += array.length; + + } + + nothrow @safe void start() + { + _data = _data.init; + _shiftSoFar = 0; + _lengthSoFar = 0; + } + + nothrow @trusted ubyte[lengthInBytes] finish() + { + ubyte[lengthInBytes] tmp; + tmp[0 .. lengthInBytes] = (cast(ubyte*) _data)[0 .. lengthInBytes]; + for (size_t i = 0; i < 8; i++) { + tmp[lengthInBytes - 8 + i] ^= (cast(ubyte*) &_lengthSoFar)[i]; + } + return tmp; + } +} + +unittest +{ + assert(isDigest!QuickXor); +} + +unittest +{ + QuickXor qxor; + qxor.put(cast(ubyte[]) "The quick brown fox jumps over the lazy dog"); + assert(qxor.finish().toHexString() == "6CC4A56F2B26C492FA4BBE57C1F31C4193A972BE"); +} + +alias QuickXorDigest = WrapperDigest!(QuickXor); diff --git a/src/sync.d b/src/sync.d index 707cb5d9..f0806120 100644 --- a/src/sync.d +++ b/src/sync.d @@ -2585,6 +2585,7 @@ final class SyncEngine // downloads a File resource private void downloadFileItem(const ref Item item, const(string) path) { + static import std.exception; assert(item.type == ItemType.file); write("Downloading file ", path, " ... "); JSONValue fileDetails; @@ -3825,6 +3826,7 @@ final class SyncEngine // upload new items to OneDrive private void uploadNewItems(const(string) path) { + static import std.utf; import std.range : walkLength; import std.uni : byGrapheme; // https://support.microsoft.com/en-us/help/3125202/restrictions-and-limitations-when-you-sync-files-and-folders @@ -6515,6 +6517,7 @@ final class SyncEngine // Query itemdb.computePath() and catch potential assert when DB consistency issue occurs string computeItemPath(string thisDriveId, string thisItemId) { + static import core.exception; string calculatedPath; log.vdebug("Attempting to calculate local filesystem path for ", thisDriveId, " and ", thisItemId); try { From 1d4a2066830a1870cb788b1ab9ff9a26aaab47e2 Mon Sep 17 00:00:00 2001 From: Yannis Milo <2601428+acidrop@users.noreply.github.com> Date: Thu, 10 Jun 2021 19:14:57 +0100 Subject: [PATCH 03/16] Update Docker.md (#1514) "-it" must be added to the command line in order to be able to paste the link to the response uri. Without adding "-it' in the command line parameters, docker just exists. --- docs/Docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Docker.md b/docs/Docker.md index 3c686a9d..c3f00766 100644 --- a/docs/Docker.md +++ b/docs/Docker.md @@ -205,7 +205,7 @@ docker container run -e ONEDRIVE_RESYNC=1 -e ONEDRIVE_VERBOSE=1 -v onedrive_conf **Perform a --logout and re-authenticate:** ```bash -docker container run -e ONEDRIVE_LOGOUT=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest +docker container run -it -e ONEDRIVE_LOGOUT=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest ``` ## Build instructions From 6201938750ae9fdb96478ed6630bb2feae1294aa Mon Sep 17 00:00:00 2001 From: abraunegg Date: Tue, 15 Jun 2021 07:03:30 +1000 Subject: [PATCH 04/16] Update lock.yml Run lock once a day, 19 mins after midnight, rather than every hour to avoid service abuse --- .github/workflows/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 32f90b6d..fd5c8625 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,7 +2,7 @@ name: 'Lock Threads' on: schedule: - - cron: '0 * * * *' + - cron: '19 0 * * *' jobs: lock: From 13b37094f79499052c427513627b2d8cd40e6436 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Fri, 18 Jun 2021 07:15:32 +1000 Subject: [PATCH 05/16] Update INSTALL.md * Fix typo --- docs/INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 3034375d..d1d814a1 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -259,7 +259,7 @@ sudo zypper install libnotify-devel ## Compilation & Installation ### High Level Steps -1. Install the platfrom dependancies for your Linux OS +1. Install the platform dependancies for your Linux OS 2. Activate your DMD or LDC compiler 3. Clone the GitHub repository, run configure and make, then install 4. Deactivate your DMD or LDC compiler From 65d289d2cdf953e8d7b8e12841974c2c0d1a7ec5 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Tue, 29 Jun 2021 17:58:05 +1000 Subject: [PATCH 06/16] Update known-issues.md * Update 'Application 'stops' running without any visible reason' item with additional issues and some further debugging which can be done --- docs/known-issues.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/known-issues.md b/docs/known-issues.md index 6f245f3e..e40a9532 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -17,11 +17,13 @@ Technically, the client is 'working' correctly, as, when moving files, you are ' If the tracking of moving data to new local directories is requried, it is better to run the client in service mode (`--monitor`) rather than in standalone mode, as the 'move' of files can then be handled at the point when it occurs, so that the data is moved to the new location on OneDrive without the need to be deleted and re-uploaded. ## Application 'stops' running without any visible reason -**Issue Tracker:** [#494](https://github.com/abraunegg/onedrive/issues/494), [#753](https://github.com/abraunegg/onedrive/issues/753), [#792](https://github.com/abraunegg/onedrive/issues/792), [#884](https://github.com/abraunegg/onedrive/issues/884) +**Issue Tracker:** [#494](https://github.com/abraunegg/onedrive/issues/494), [#753](https://github.com/abraunegg/onedrive/issues/753), [#792](https://github.com/abraunegg/onedrive/issues/792), [#884](https://github.com/abraunegg/onedrive/issues/884), [#1162](https://github.com/abraunegg/onedrive/issues/1162), [#1408](https://github.com/abraunegg/onedrive/issues/1408), [#1520](https://github.com/abraunegg/onedrive/issues/1520), [#1526](https://github.com/abraunegg/onedrive/issues/1526) **Description:** -When running the client and performing an upload or download operation, the application just stops working without any reason or explanation. +When running the client and performing an upload or download operation, the application just stops working without any reason or explanation. If `echo $?` is used after the application has exited without visible reason, an error level of 141 may be provided. + +Additionally, this issue has mainly been seen when the client is operating against Microsoft's Europe Data Centre's. **Explanation:** From e236c7cf1251d46022a0e0e837a0d5a91c2b60ec Mon Sep 17 00:00:00 2001 From: abraunegg Date: Thu, 1 Jul 2021 06:18:48 +1000 Subject: [PATCH 07/16] Handle OneDrive API Bad Request response when querying if file exists (#1535) * Add exception handling for when the API returns a 400 error when attempting to query a path on OneDrive. If the path generates a 'bad request' response, this needs to be correctly handled. --- src/sync.d | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/sync.d b/src/sync.d index f0806120..98c12701 100644 --- a/src/sync.d +++ b/src/sync.d @@ -4394,14 +4394,24 @@ final class SyncEngine // test if the local path exists on OneDrive fileDetailsFromOneDrive = onedrive.getPathDetailsByDriveId(parent.driveId, path); } catch (OneDriveException e) { - // A 404 is the expected response if the file was not present + // log that we generated an exception log.vdebug("fileDetailsFromOneDrive = onedrive.getPathDetailsByDriveId(parent.driveId, path); generated a OneDriveException"); - if (e.httpStatusCode == 401) { - // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - log.vlog("Skipping item - OneDrive returned a 'HTTP 401 - Unauthorized' when attempting to query if file exists"); + // OneDrive returned a 'HTTP/1.1 400 Bad Request' + // If the 'path', when encoded, cannot be interpreted by the OneDrive API, the API will generate a 400 error + if (e.httpStatusCode == 400) { + log.log("Skipping uploading this new file: ", buildNormalizedPath(absolutePath(path))); + log.vlog("Skipping item - OneDrive returned a 'HTTP 400 - Bad Request' when attempting to query if file exists"); + log.error("ERROR: To resolve, rename this local file: ", buildNormalizedPath(absolutePath(path))); + uploadFailed = true; return; } - + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' + if (e.httpStatusCode == 401) { + log.vlog("Skipping item - OneDrive returned a 'HTTP 401 - Unauthorized' when attempting to query if file exists"); + uploadFailed = true; + return; + } + // A 404 is the expected response if the file was not present if (e.httpStatusCode == 404) { // The file was not found on OneDrive, need to upload it // Check if file should be skipped based on skip_size config @@ -4727,7 +4737,7 @@ final class SyncEngine return; } } - + // OneDrive returned a '429 - Too Many Requests' if (e.httpStatusCode == 429) { // HTTP request returned status code 429 (Too Many Requests). We need to leverage the response Retry-After HTTP header to ensure minimum delay until the throttle is removed. handleOneDriveThrottleRequest(); @@ -4737,9 +4747,8 @@ final class SyncEngine // return back to original call return; } - + // OneDrive returned a 'HTTP 5xx Server Side Error' - gracefully handling error - error message already logged if (e.httpStatusCode >= 500) { - // OneDrive returned a 'HTTP 5xx Server Side Error' - gracefully handling error - error message already logged uploadFailed = true; return; } From fc5d7f9327af85569906eee0e3d2e37448dc878c Mon Sep 17 00:00:00 2001 From: abraunegg Date: Tue, 6 Jul 2021 18:11:53 +1000 Subject: [PATCH 08/16] Fix application crash and incorrect handling of --single-directory when syncing a OneDrive Business Shared Folder due to using 'Add Shortcut to My Files' (#1542) * When syncing OneDrive Business Shared Folders and using --single-directory, select correct driveId and itemId for the remote directory that needs to be synced * Normally, the 'remoteItem' field will contain 'fileSystemInfo' however, if the user uses the 'Add Shortcut ..' option in OneDrive WebUI to create a 'link', this object, whilst remote, does not have 'fileSystemInfo' in the expected place, this leading to a application crash --- src/onedrive.d | 16 ++++++- src/sync.d | 126 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 119 insertions(+), 23 deletions(-) diff --git a/src/onedrive.d b/src/onedrive.d index 1714ddaf..391d2214 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -682,7 +682,8 @@ final class OneDriveApi return get(url); } - // Return the requested details of the specified path on the specified drive id + // Return the requested details of the specified path on the specified drive id and path + // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online JSONValue getPathDetailsByDriveId(const(char)[] driveId, const(string) path) { checkAccessTokenExpired(); @@ -693,6 +694,19 @@ final class OneDriveApi url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size"; return get(url); } + + // Return the requested details of the specified path on the specified drive id and item id + // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online + JSONValue getPathDetailsByDriveIdAndItemId(const(char)[] driveId, const(char)[] itemId) + { + checkAccessTokenExpired(); + const(char)[] url; + // string driveByIdUrl = "https://graph.microsoft.com/v1.0/drives/"; + // Required format: /drives/{drive-id}/items/{item-id} + url = driveByIdUrl ~ driveId ~ "/items/" ~ itemId; + url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size"; + return get(url); + } // Return the requested details of the specified id // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get diff --git a/src/sync.d b/src/sync.d index 98c12701..51b3a3fd 100644 --- a/src/sync.d +++ b/src/sync.d @@ -131,7 +131,16 @@ private Item makeItem(const ref JSONValue driveItem) // https://github.com/abraunegg/onedrive/issues/11 if (isItemRemote(driveItem)) { // remoteItem is a OneDrive object that exists on a 'different' OneDrive drive id, when compared to account default - item.mtime = SysTime.fromISOExtString(driveItem["remoteItem"]["fileSystemInfo"]["lastModifiedDateTime"].str); + // Normally, the 'remoteItem' field will contain 'fileSystemInfo' however, if the user uses the 'Add Shortcut ..' option in OneDrive WebUI + // to create a 'link', this object, whilst remote, does not have 'fileSystemInfo' in the expected place, thus leading to a application crash + // See: https://github.com/abraunegg/onedrive/issues/1533 + if ("fileSystemInfo" in driveItem["remoteItem"]) { + // 'fileSystemInfo' is in 'remoteItem' which will be the majority of cases + item.mtime = SysTime.fromISOExtString(driveItem["remoteItem"]["fileSystemInfo"]["lastModifiedDateTime"].str); + } else { + // is a remote item, but 'fileSystemInfo' is missing from 'remoteItem' + item.mtime = SysTime.fromISOExtString(driveItem["fileSystemInfo"]["lastModifiedDateTime"].str); + } } else { // item exists on account default drive id item.mtime = SysTime.fromISOExtString(driveItem["fileSystemInfo"]["lastModifiedDateTime"].str); @@ -788,6 +797,7 @@ final class SyncEngine string driveId = defaultDriveId; string rootId = defaultRootId; string folderId; + string itemId; JSONValue onedrivePathDetails; // Check OneDrive Business Shared Folders, if configured to do so @@ -795,29 +805,36 @@ final class SyncEngine log.vlog("Attempting to sync OneDrive Business Shared Folders"); // query OneDrive Business Shared Folders shared with me JSONValue graphQuery = onedrive.getSharedWithMe(); - if (graphQuery.type() == JSONType.object) { // valid response from OneDrive foreach (searchResult; graphQuery["value"].array) { string sharedFolderName = searchResult["name"].str; // Compare this to values in business_shared_folders if(selectiveSync.isSharedFolderMatched(sharedFolderName)){ - // Folder matches a user configured sync entry - string[] allowedPath; - allowedPath ~= sharedFolderName; + // Matched sharedFolderName to item in business_shared_folders + log.vdebug("Matched sharedFolderName in business_shared_folders: ", sharedFolderName); // But is this shared folder what we are looking for as part of --single-directory? - if (selectiveSync.isPathIncluded(path,allowedPath)) { + // User could be using 'directory' or 'directory/directory1/directory2/directory3/' + // Can we find 'sharedFolderName' in the given 'path' + if (canFind(path, sharedFolderName)) { + // Found 'sharedFolderName' in the given 'path' + log.vdebug("Matched 'sharedFolderName' in the given 'path'"); + // What was the matched folder JSON + log.vdebug("Matched sharedFolderName in business_shared_folders JSON: ", searchResult); // Path we want to sync is on a OneDrive Business Shared Folder // Set the correct driveId driveId = searchResult["remoteItem"]["parentReference"]["driveId"].str; + // Set this items id + itemId = searchResult["remoteItem"]["id"].str; log.vdebug("Updated the driveId to a new value: ", driveId); + log.vdebug("Updated the itemId to a new value: ", itemId); // Keep the driveIDsArray with unique entries only if (!canFind(driveIDsArray, driveId)) { // Add this drive id to the array to search with driveIDsArray ~= driveId; } - } - } + } + } } } else { // Log that an invalid JSON object was returned @@ -828,13 +845,54 @@ final class SyncEngine // Test if the path we are going to sync from actually exists on OneDrive log.vlog("Getting path details from OneDrive ..."); try { - onedrivePathDetails = onedrive.getPathDetailsByDriveId(driveId, path); + // Need to use different calls here - one call for majority, another if this is a OneDrive Business Shared Folder + if (!syncBusinessFolders){ + // Not a OneDrive Business Shared Folder + log.vdebug("Calling onedrive.getPathDetailsByDriveId(driveId, path) with: ", driveId, ", ", path); + onedrivePathDetails = onedrive.getPathDetailsByDriveId(driveId, path); + } else { + // OneDrive Business Shared Folder - Use another API call using the folders correct driveId and itemId + log.vdebug("Calling onedrive.getPathDetailsByDriveIdAndItemId(driveId, itemId) with: ", driveId, ", ", itemId); + onedrivePathDetails = onedrive.getPathDetailsByDriveIdAndItemId(driveId, itemId); + } } catch (OneDriveException e) { log.vdebug("onedrivePathDetails = onedrive.getPathDetails(path) generated a OneDriveException"); if (e.httpStatusCode == 404) { - // The directory was not found - log.error("ERROR: The requested single directory to sync was not found on OneDrive"); - return; + // The directory was not found + if (syncBusinessFolders){ + // 404 was returned when trying to use a specific driveId and itemId .. which 'should' work .... but didnt + // Try the query with the path as a backup failsafe + log.vdebug("Calling onedrive.getPathDetailsByDriveId(driveId, path) as backup with: ", driveId, ", ", path); + try { + // try calling using the path + onedrivePathDetails = onedrive.getPathDetailsByDriveId(driveId, path); + } catch (OneDriveException e) { + + if (e.httpStatusCode == 404) { + log.error("ERROR: The requested single directory to sync was not found on OneDrive - Check folder permissions and sharing status with folder owner"); + return; + } + + if (e.httpStatusCode == 429) { + // HTTP request returned status code 429 (Too Many Requests). We need to leverage the response Retry-After HTTP header to ensure minimum delay until the throttle is removed. + handleOneDriveThrottleRequest(); + // Retry original request by calling function again to avoid replicating any further error handling + log.vdebug("Retrying original request that generated the OneDrive HTTP 429 Response Code (Too Many Requests) - calling applyDifferencesSingleDirectory(path);"); + applyDifferencesSingleDirectory(path); + // return back to original call + return; + } + + if (e.httpStatusCode >= 500) { + // OneDrive returned a 'HTTP 5xx Server Side Error' - gracefully handling error - error message already logged + return; + } + } + } else { + // Not a OneDrive Business Shared folder operation + log.error("ERROR: The requested single directory to sync was not found on OneDrive"); + return; + } } if (e.httpStatusCode == 429) { @@ -2959,14 +3017,26 @@ final class SyncEngine log.vdebug("Processing DB entries for this driveId: ", driveId); // Database scan of every item in DB for the given driveId based on the root parent for that drive if ((syncBusinessFolders) && (driveId != defaultDriveId)) { - // There could be multiple shared folders all from this same driveId - foreach(dbItem; itemdb.selectByDriveId(driveId)) { - // Does it still exist on disk in the location the DB thinks it is - uploadDifferences(dbItem); + // There could be multiple shared folders all from this same driveId - are we doing a single directory sync? + if (cfg.getValueString("single_directory") != ""){ + // Limit the local filesystem check to just the requested directory + if (itemdb.selectByPath(path, driveId, item)) { + // Does it still exist on disk in the location the DB thinks it is + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); + uploadDifferences(item); + } + } else { + // check everything associated with each driveId we know about + foreach(dbItem; itemdb.selectByDriveId(driveId)) { + // Does it still exist on disk in the location the DB thinks it is + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); + uploadDifferences(dbItem); + } } } else { if (itemdb.selectByPath(path, driveId, item)) { // Does it still exist on disk in the location the DB thinks it is + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); uploadDifferences(item); } } @@ -3047,16 +3117,26 @@ final class SyncEngine log.vdebug("Processing DB entries for this driveId: ", driveId); // Database scan of every item in DB for the given driveId based on the root parent for that drive if ((syncBusinessFolders) && (driveId != defaultDriveId)) { - // There could be multiple shared folders all from this same driveId - foreach(dbItem; itemdb.selectByDriveId(driveId)) { - // Does it still exist on disk in the location the DB thinks it is - log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); - uploadDifferences(dbItem); + // There could be multiple shared folders all from this same driveId - are we doing a single directory sync? + if (cfg.getValueString("single_directory") != ""){ + // Limit the local filesystem check to just the requested directory + if (itemdb.selectByPath(path, driveId, item)) { + // Does it still exist on disk in the location the DB thinks it is + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); + uploadDifferences(item); + } + } else { + // check everything associated with each driveId we know about + foreach(dbItem; itemdb.selectByDriveId(driveId)) { + // Does it still exist on disk in the location the DB thinks it is + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); + uploadDifferences(dbItem); + } } } else { if (itemdb.selectByPath(path, driveId, item)) { // Does it still exist on disk in the location the DB thinks it is - log.vdebug("Calling uploadDifferences(item) as item is present in local cache DB"); + log.vdebug("Calling uploadDifferences(dbItem) as item is present in local cache DB"); uploadDifferences(item); } } @@ -5367,6 +5447,8 @@ final class SyncEngine // Log that we skipping adding item to the local DB and the reason why log.vdebug("Skipping adding to database as --upload-only & --remove-source-files configured"); } else { + // What is the JSON item we are trying to create a DB record with? + log.vdebug("Createing DB item from this JSON: ", jsonItem); // Takes a JSON input and formats to an item which can be used by the database Item item = makeItem(jsonItem); // Add to the local database From 54a65757094d570090fb3952a3d23b6287676357 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Wed, 7 Jul 2021 06:26:52 +1000 Subject: [PATCH 09/16] Fix application crash due to invalid UTF-8 sequence in the pathname for the application configuration (#1551) * Catch a Invalid UTF-8 handling error when attempting to initialise the application. This is caused by the 'path' to the application configuration (typically ~/.config/onedrive) contains a bad UTF-8 character thus cannot be read / initialised --- src/onedrive.d | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/onedrive.d b/src/onedrive.d index 391d2214..a64009d5 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -375,6 +375,7 @@ final class OneDriveApi bool init() { + static import std.utf; // detail what we are using for applicaion identification log.vdebug("clientId = ", clientId); log.vdebug("companyName = ", companyName); @@ -400,6 +401,11 @@ final class OneDriveApi log.error("Cannot authorize with Microsoft OneDrive Service"); return false; } + } catch (std.utf.UTFException e) { + // path contains characters which generate a UTF exception + log.error("Cannot read refreshToken from: ", cfg.refreshTokenFilePath); + log.error(" Error Reason:", e.msg); + return false; } return true; } else { @@ -409,6 +415,11 @@ final class OneDriveApi refreshToken = readText(cfg.refreshTokenFilePath); } catch (FileException e) { return authorize(); + } catch (std.utf.UTFException e) { + // path contains characters which generate a UTF exception + log.error("Cannot read refreshToken from: ", cfg.refreshTokenFilePath); + log.error(" Error Reason:", e.msg); + return false; } return true; } else { From 7171b83f5b2bb9729d7a4f7ba60f4c9d12ec80e0 Mon Sep 17 00:00:00 2001 From: c-maia <57997540+c-maia@users.noreply.github.com> Date: Sat, 10 Jul 2021 22:37:57 +0100 Subject: [PATCH 10/16] Fix error message when deleting a large number of files (#1559) "delete" word appears twice in the error message. This commit removes the extra "delete". --- src/sync.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync.d b/src/sync.d index 51b3a3fd..a25dead8 100644 --- a/src/sync.d +++ b/src/sync.d @@ -5285,7 +5285,7 @@ final class SyncEngine flagAsBigDelete = true; if (!cfg.getValueBool("force")) { log.error("ERROR: An attempt to remove a large volume of data from OneDrive has been detected. Exiting client to preserve data on OneDrive"); - log.error("ERROR: To delete delete a large volume of data use --force or increase the config value 'classify_as_big_delete' to a larger value"); + log.error("ERROR: To delete a large volume of data use --force or increase the config value 'classify_as_big_delete' to a larger value"); // Must exit here to preserve data on OneDrive exit(-1); } From 34f7a379f675bdd581a1d0746555292245e34b4b Mon Sep 17 00:00:00 2001 From: abraunegg Date: Sun, 11 Jul 2021 13:21:36 +1000 Subject: [PATCH 11/16] Fix Docker build process to source GOSU keys from updated GPG key location (#1562) * Fix Docker build process to source GOSU keys from updated GPG key location --- contrib/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 521adaf7..019ae9d6 100644 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -5,7 +5,7 @@ RUN yum install -y make git gcc libcurl-devel sqlite-devel pkg-config && \ yum install -y http://downloads.dlang.org/releases/2.x/2.092.1/dmd-2.092.1-0.fedora.x86_64.rpm && \ rm -rf /var/cache/yum/ && \ # gosu installation - gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \ + gpg --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \ && curl -o /usr/local/bin/gosu -SL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" \ && curl -o /usr/local/bin/gosu.asc -SL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64.asc" \ && gpg --verify /usr/local/bin/gosu.asc \ From 1fbe1d34a71844284ec0fc243483f8c65bb751ac Mon Sep 17 00:00:00 2001 From: abraunegg Date: Mon, 12 Jul 2021 05:59:32 +1000 Subject: [PATCH 12/16] Fix application crash due to a conversion overflow when calculating file offset for session uploads (#1558) * Fix an unhandled application crash when calculating the required offset for a file fragment size when resuming a session upload --- src/onedrive.d | 4 +++- src/upload.d | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/onedrive.d b/src/onedrive.d index a64009d5..64ec162f 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -769,6 +769,7 @@ final class OneDriveApi auto file = File(filepath, "rb"); file.seek(offset); string contentRange = "bytes " ~ to!string(offset) ~ "-" ~ to!string(offset + offsetSize - 1) ~ "/" ~ to!string(fileSize); + log.vdebugNewLine("contentRange: ", contentRange); // function scopes scope(exit) { @@ -789,7 +790,8 @@ final class OneDriveApi http.url = uploadUrl; http.addRequestHeader("Content-Range", contentRange); http.onSend = data => file.rawRead(data).length; - http.contentLength = offsetSize; + // convert offsetSize to ulong + http.contentLength = to!ulong(offsetSize); auto response = perform(); // TODO: retry on 5xx errors checkHttpCode(response); diff --git a/src/upload.d b/src/upload.d index f49d0cc1..012598a0 100644 --- a/src/upload.d +++ b/src/upload.d @@ -173,6 +173,7 @@ struct UploadSession Progress p = new Progress(iteration); p.title = "Uploading"; long fragmentCount = 0; + long fragSize = 0; // Initialise the download bar at 0% p.next(); @@ -181,7 +182,23 @@ struct UploadSession fragmentCount++; log.vdebugNewLine("Fragment: ", fragmentCount, " of ", iteration); p.next(); - long fragSize = fragmentSize < fileSize - offset ? fragmentSize : fileSize - offset; + log.vdebugNewLine("fragmentSize: ", fragmentSize, "offset: ", offset, " fileSize: ", fileSize ); + fragSize = fragmentSize < fileSize - offset ? fragmentSize : fileSize - offset; + log.vdebugNewLine("Using fragSize: ", fragSize); + + // fragSize must not be a negative value + if (fragSize < 0) { + // Session upload will fail + // not a JSON object - fragment upload failed + log.vlog("File upload session failed - invalid calculation of fragment size"); + if (exists(sessionFilePath)) { + remove(sessionFilePath); + } + // set response to null as error + response = null; + return response; + } + // If the resume upload fails, we need to check for a return code here try { response = onedrive.uploadFragment( From 4b56a6103ed45c84ee010563c59a9497bf2644af Mon Sep 17 00:00:00 2001 From: abraunegg Date: Mon, 12 Jul 2021 08:06:11 +1000 Subject: [PATCH 13/16] Fix Docker Alpine build failing due to filesystem permissions issue (#1564) * Fix Docker Alpine build failing due to filesystem permissions issue --- contrib/docker/Dockerfile-alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile-alpine b/contrib/docker/Dockerfile-alpine index e9356984..9b4c569c 100644 --- a/contrib/docker/Dockerfile-alpine +++ b/contrib/docker/Dockerfile-alpine @@ -1,5 +1,5 @@ # -*-Dockerfile-*- -FROM alpine +FROM alpine:3.13 RUN apk add \ alpine-sdk gnupg xz curl-dev sqlite-dev binutils-gold \ autoconf automake ldc go From 6d92bc821059f267abd521fb28f70d0b4b55b766 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Tue, 13 Jul 2021 18:31:48 +1000 Subject: [PATCH 14/16] Fix that Business Shared Folders with parentheses are ignored (#1560) * Fix issue where matching a OneDrive Business Shared Folder that contains '()' is not matched correctly --- src/selective.d | 8 ++++++++ src/sync.d | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/selective.d b/src/selective.d index 8dc444a3..6b589d4d 100644 --- a/src/selective.d +++ b/src/selective.d @@ -179,6 +179,14 @@ final class SelectiveSync if (!name.matchFirst(businessSharedFoldersList).empty) { return true; } else { + // try a direct comparison just in case + foreach (userFolder; businessSharedFoldersList) { + if (userFolder == name) { + // direct match + log.vdebug("'matchFirst' failed to match, however direct comparison was matched: ", name); + return true; + } + } return false; } } diff --git a/src/sync.d b/src/sync.d index a25dead8..f58971e3 100644 --- a/src/sync.d +++ b/src/sync.d @@ -660,6 +660,8 @@ final class SyncEngine if (isItemFolder(searchResult)) { // item returned is a shared folder, not a shared file sharedFolderName = searchResult["name"].str; + // Output Shared Folder Name early + log.vdebug("Shared Folder Name: ", sharedFolderName); // Compare this to values in business_shared_folders if(selectiveSync.isSharedFolderMatched(sharedFolderName)){ // Folder name matches what we are looking for @@ -671,7 +673,7 @@ final class SyncEngine // "what if" there are 2 or more folders shared with me have the "same" name? // The folder name will be the same, but driveId will be different // This will then cause these 'shared folders' to cross populate data, which may not be desirable - log.vdebug("Shared Folder Name: ", sharedFolderName); + log.vdebug("Shared Folder Name: MATCHED to any entry in 'business_shared_folders'"); log.vdebug("Parent Drive Id: ", searchResult["remoteItem"]["parentReference"]["driveId"].str); log.vdebug("Shared Item Id: ", searchResult["remoteItem"]["id"].str); Item databaseItem; @@ -753,6 +755,8 @@ final class SyncEngine } } } + } else { + log.vdebug("Shared Folder Name: NO MATCH to any entry in 'business_shared_folders'"); } } else { // not a folder, is this a file? @@ -807,8 +811,24 @@ final class SyncEngine JSONValue graphQuery = onedrive.getSharedWithMe(); if (graphQuery.type() == JSONType.object) { // valid response from OneDrive + string sharedFolderName; foreach (searchResult; graphQuery["value"].array) { - string sharedFolderName = searchResult["name"].str; + // set sharedFolderName + sharedFolderName = searchResult["name"].str; + // Configure additional logging items for this array element + string sharedByName; + string sharedByEmail; + + // Extra details for verbose logging + if ("sharedBy" in searchResult["remoteItem"]["shared"]) { + if ("displayName" in searchResult["remoteItem"]["shared"]["sharedBy"]["user"]) { + sharedByName = searchResult["remoteItem"]["shared"]["sharedBy"]["user"]["displayName"].str; + } + if ("email" in searchResult["remoteItem"]["shared"]["sharedBy"]["user"]) { + sharedByEmail = searchResult["remoteItem"]["shared"]["sharedBy"]["user"]["email"].str; + } + } + // Compare this to values in business_shared_folders if(selectiveSync.isSharedFolderMatched(sharedFolderName)){ // Matched sharedFolderName to item in business_shared_folders @@ -833,6 +853,15 @@ final class SyncEngine // Add this drive id to the array to search with driveIDsArray ~= driveId; } + + // Log who shared this to assist with sync data correlation + if ((sharedByName != "") && (sharedByEmail != "")) { + log.vlog("OneDrive Business Shared Folder - Shared By: ", sharedByName, " (", sharedByEmail, ")"); + } else { + if (sharedByName != "") { + log.vlog("OneDrive Business Shared Folder - Shared By: ", sharedByName); + } + } } } } From 72905b951fd35cb797308df9dd12b61ff3615d4b Mon Sep 17 00:00:00 2001 From: nrandon Date: Tue, 13 Jul 2021 20:13:00 +0100 Subject: [PATCH 15/16] Fix Alpine Docker lock to 3.13 (#1568) This issue relates to a build issue on the official docker build system where Alpine Linux 3.14 will not currently build so fix the Alpine version to 3.13. Make sure build and runtime image used in building the docker container are the same Alpine version to remove runtime issues. --- contrib/docker/Dockerfile-alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/Dockerfile-alpine b/contrib/docker/Dockerfile-alpine index 9b4c569c..5ff4c4b6 100644 --- a/contrib/docker/Dockerfile-alpine +++ b/contrib/docker/Dockerfile-alpine @@ -12,7 +12,7 @@ RUN cd /usr/src/onedrive/ && \ make && \ make install -FROM alpine +FROM alpine:3.13 ENTRYPOINT ["/entrypoint.sh"] RUN apk add --no-cache \ bash libcurl libgcc shadow sqlite-libs ldc-runtime && \ From b08690f1ace0475c9e5b3656bd107be14718fd13 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Wed, 14 Jul 2021 05:34:45 +1000 Subject: [PATCH 16/16] Release files for 2.4.13 (#1569) * Release files for 2.4.13 --- CHANGELOG.md | 16 +++++++++++++++ configure | 20 +++++++++---------- configure.ac | 2 +- ...e-2.4.12.ebuild => onedrive-2.4.13.ebuild} | 0 contrib/spec/onedrive.spec.in | 2 +- 5 files changed, 28 insertions(+), 12 deletions(-) rename contrib/gentoo/{onedrive-2.4.12.ebuild => onedrive-2.4.13.ebuild} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c18086..fec08307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 2.4.13 - 2021-7-14 +### Fixed +* Support DMD 2.097.0 as compiler +* Fix to handle OneDrive API Bad Request response when querying if file exists +* Fix application crash and incorrect handling of --single-directory when syncing a OneDrive Business Shared Folder due to using 'Add Shortcut to My Files' +* Fix application crash due to invalid UTF-8 sequence in the pathname for the application configuration +* Fix error message when deleting a large number of files +* Fix Docker build process to source GOSU keys from updated GPG key location +* Fix application crash due to a conversion overflow when calculating file offset for session uploads +* Fix Docker Alpine build failing due to filesystem permissions issue due to Docker build system and Alpine Linux 3.14 incompatibility +* Fix that Business Shared Folders with parentheses are ignored + +### Updated +* Updated Lock Bot to run daily +* Updated documentation (various) + ## 2.4.12 - 2021-5-28 ### Fixed * Fix an unhandled Error 412 when uploading modified files to OneDrive Business Accounts diff --git a/configure b/configure index 88a1e9f2..1d06f284 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for onedrive v2.4.13-dev. +# Generated by GNU Autoconf 2.69 for onedrive v2.4.13. # # Report bugs to . # @@ -579,8 +579,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='onedrive' PACKAGE_TARNAME='onedrive' -PACKAGE_VERSION='v2.4.13-dev' -PACKAGE_STRING='onedrive v2.4.13-dev' +PACKAGE_VERSION='v2.4.13' +PACKAGE_STRING='onedrive v2.4.13' PACKAGE_BUGREPORT='https://github.com/abraunegg/onedrive' PACKAGE_URL='' @@ -1219,7 +1219,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures onedrive v2.4.13-dev to adapt to many kinds of systems. +\`configure' configures onedrive v2.4.13 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1280,7 +1280,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of onedrive v2.4.13-dev:";; + short | recursive ) echo "Configuration of onedrive v2.4.13:";; esac cat <<\_ACEOF @@ -1393,7 +1393,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -onedrive configure v2.4.13-dev +onedrive configure v2.4.13 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1410,7 +1410,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by onedrive $as_me v2.4.13-dev, which was +It was created by onedrive $as_me v2.4.13, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2162,7 +2162,7 @@ fi -PACKAGE_DATE="May 2021" +PACKAGE_DATE="July 2021" @@ -3159,7 +3159,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by onedrive $as_me v2.4.13-dev, which was +This file was extended by onedrive $as_me v2.4.13, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -3212,7 +3212,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -onedrive config.status v2.4.13-dev +onedrive config.status v2.4.13 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index d02092c0..01370a9a 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ dnl - commit the changed files (configure.ac, configure) dnl - tag the release AC_PREREQ([2.69]) -AC_INIT([onedrive],[v2.4.13-dev], [https://github.com/abraunegg/onedrive], [onedrive]) +AC_INIT([onedrive],[v2.4.13], [https://github.com/abraunegg/onedrive], [onedrive]) AC_CONFIG_SRCDIR([src/main.d]) diff --git a/contrib/gentoo/onedrive-2.4.12.ebuild b/contrib/gentoo/onedrive-2.4.13.ebuild similarity index 100% rename from contrib/gentoo/onedrive-2.4.12.ebuild rename to contrib/gentoo/onedrive-2.4.13.ebuild diff --git a/contrib/spec/onedrive.spec.in b/contrib/spec/onedrive.spec.in index 9d0a8b2c..70d1df39 100644 --- a/contrib/spec/onedrive.spec.in +++ b/contrib/spec/onedrive.spec.in @@ -6,7 +6,7 @@ %endif Name: onedrive -Version: 2.4.12 +Version: 2.4.13 Release: 1%{?dist} Summary: Microsoft OneDrive Client Group: System Environment/Network