Skip to content

Commit ea7c3ab

Browse files
authored
Fix Bug #3355: Fix that long running big upload (250GB+) fails because of an expired access token (#3361)
* Revert back to v2.5.5 performSessionFileUpload() and apply minimal change for upload session offset handling to prevent desynchronisation on large files * Add specific 403 handler for when the upload session URL itself expires * Add 'file_fragment_size' * Clean up debug logging output * Add 'tempauth' to spelling words * Update documentation URL's * Ensure that on each fragment upload, whilst the application is using the 'tempauth' for session upload, the global OAuth2 token needs to be checked for validity and refreshed if required * Add limit check for 'file_fragment_size' option * Add to default 'config' file * Update documentation for 'file_fragment_size' * Add 'file_fragment_size' to --display-config output * Add --file-fragment-size option to enable use via Docker option * Add to manpage * Update Docker entrypoint * Update Docker | Podman documentation * Update logging output to include connection method to URL * Update Upload Session URL expiry update to include UTC and LocalTime values * Update comment which was dropped / missed * Clarify that this is the OAuth2 Access Token * Clarify that the expiry timestamp is localTime * Update PR with dynamic use of fragment size if fileSize > 100MiB * Enforce multiple 320KiB for fragment size to align to Microsoft documentation * Fix Docker entrypoint and confirm working for ONEDRIVE_FILE_FRAGMENT_SIZE * Change 'defaultMaxFileFragmentSize' to 60 * Revise fragmentSize calculation to be as close to 60 MiB as possible without breaching Microsoft documented threshold
1 parent 5ff8c01 commit ea7c3ab

File tree

11 files changed

+281
-85
lines changed

11 files changed

+281
-85
lines changed

.github/actions/spelling/allow.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ systemdsystemunitdir
445445
systemduserunitdir
446446
tbh
447447
tdcockers
448+
tempauth
448449
templ
449450
testbuild
450451
Thh

config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
## This setting controls the application logging all actions to a separate file.
7777
#enable_logging = "false"
7878

79+
## This setting controls the file fragment size when uploading large files to Microsoft OneDrive.
80+
#file_fragment_size = "10"
81+
7982
## This setting controls the application HTTP protocol version, downgrading to HTTP/1.1 when enabled.
8083
#force_http_11 = "false"
8184

contrib/docker/entrypoint.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ if [ "${ONEDRIVE_SYNC_SHARED_FILES:=0}" == "1" ]; then
174174
ARGS=(--sync-shared-files ${ARGS[@]})
175175
fi
176176

177+
# Tell client to use a different value for file fragment size for large file uploads
178+
if [ -n "${ONEDRIVE_FILE_FRAGMENT_SIZE:=""}" ]; then
179+
echo "# We are specifying the file fragment size for large file uploads (in MB)"
180+
echo "# Adding --file-fragment-size ARG"
181+
ARGS=(--file-fragment-size ${ONEDRIVE_FILE_FRAGMENT_SIZE} ${ARGS[@]})
182+
fi
183+
177184
if [ ${#} -gt 0 ]; then
178185
ARGS=("${@}")
179186
fi

docs/application-config-options.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Before reading this document, please ensure you are running application version
2929
- [drive_id](#drive_id)
3030
- [dry_run](#dry_run)
3131
- [enable_logging](#enable_logging)
32+
- [file_fragment_size](#file_fragment_size)
3233
- [force_http_11](#force_http_11)
3334
- [force_session_upload](#force_session_upload)
3435
- [inotify_delay](#inotify_delay)
@@ -412,6 +413,20 @@ _**CLI Option Use:**_ `--enable-logging`
412413
> [!IMPORTANT]
413414
> Additional configuration is potentially required to configure the default log directory. Refer to the [Enabling the Client Activity Log](./usage.md#enabling-the-client-activity-log) section in usage.md for details
414415
416+
### file_fragment_size
417+
_**Description:**_ This option controls the fragment size when uploading large files to Microsoft OneDrive. The value specified is in MB.
418+
419+
_**Value Type:**_ Integer
420+
421+
_**Default Value:**_ 10
422+
423+
_**Maximum Value:**_ 60
424+
425+
_**Config Example:**_ `file_fragment_size = "25"`
426+
427+
_**CLI Option Use:**_ `--file-fragment-size = '25'`
428+
429+
415430
### force_http_11
416431
_**Description:**_ This setting controls the application HTTP protocol version. By default, the application will use libcurl defaults for which HTTP protocol version will be used to interact with Microsoft OneDrive. Use this setting to downgrade libcurl to only use HTTP/1.1.
417432

@@ -871,6 +886,10 @@ _**Config Example:**_ `skip_size = "50"`
871886

872887
_**CLI Option Use:**_ `--skip-size '50'`
873888

889+
> [!NOTE]
890+
> This option is considered a 'Client Side Filtering Rule' and if configured, is utilised for all sync operations. After changing this option, you will be required to perform a resync.
891+
892+
874893
### skip_symlinks
875894
_**Description:**_ This configuration option controls whether the application will skip all symbolic links when performing sync operations. Microsoft OneDrive has no concept or understanding of symbolic links, and attempting to upload a symbolic link to Microsoft OneDrive generates a platform API error. All data (files and folders) that are uploaded to OneDrive must be whole files or actual directories.
876895

docs/docker.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ docker run $firstRun --restart unless-stopped --name onedrive -v onedrive_conf:/
290290
| <B>ONEDRIVE_SYNC_SHARED_FILES</B> | Controls "--sync-shared-files" option. Default is 0 | 1 |
291291
| <B>ONEDRIVE_RUNAS_ROOT</B> | Controls if the Docker container should be run as the 'root' user instead of 'onedrive' user. Default is 0 | 1 |
292292
| <B>ONEDRIVE_SYNC_ONCE</B> | Controls if the Docker container should be run in Standalone Mode. It will use Monitor Mode otherwise. Default is 0 | 1 |
293+
| <B>ONEDRIVE_FILE_FRAGMENT_SIZE</B> | Controls the fragment size when uploading large files to Microsoft OneDrive. The value specified is in MB. Default is 10, Limit is 60 | 25 |
293294

294295
### Environment Variables Usage Examples
295296
**Verbose Output:**

docs/podman.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ podman run -it --name onedrive_work --user "${ONEDRIVE_UID}:${ONEDRIVE_GID}" \
305305
| <B>ONEDRIVE_SYNC_SHARED_FILES</B> | Controls "--sync-shared-files" option. Default is 0 | 1 |
306306
| <B>ONEDRIVE_RUNAS_ROOT</B> | Controls if the Docker container should be run as the 'root' user instead of 'onedrive' user. Default is 0 | 1 |
307307
| <B>ONEDRIVE_SYNC_ONCE</B> | Controls if the Docker container should be run in Standalone Mode. It will use Monitor Mode otherwise. Default is 0 | 1 |
308+
| <B>ONEDRIVE_FILE_FRAGMENT_SIZE</B> | Controls the fragment size when uploading large files to Microsoft OneDrive. The value specified is in MB. Default is 10, Limit is 60 | 25 |
308309

309310
### Environment Variables Usage Examples
310311
**Verbose Output:**

onedrive.1.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ Perform a trial sync with no changes made.
204204
\fB\-\-enable-logging\fR
205205
Enable client activity to a separate log file.
206206

207+
.TP
208+
\fB\-\-file-fragment-size\fR
209+
Specify the file fragment size for large file uploads (in MB).
210+
207211
.TP
208212
\fB\-\-force\fR
209213
Force the deletion of data when a 'big delete' is detected.

src/config.d

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class ApplicationConfig {
4646
immutable string defaultConfigDirName = "~/.config/onedrive";
4747
// - Default 'OneDrive Business Shared Files' Folder Name
4848
immutable string defaultBusinessSharedFilesDirectoryName = "Files Shared With Me";
49+
// - Default file fragment size for uploads
50+
immutable long defaultFileFragmentSize = 10;
51+
immutable long defaultMaxFileFragmentSize = 60;
4952

5053
// Microsoft Requirements
5154
// - Default Application ID (abraunegg)
@@ -288,6 +291,8 @@ class ApplicationConfig {
288291
longValues["rate_limit"] = 0;
289292
// - To ensure we do not fill up the load disk, how much disk space should be reserved by default
290293
longValues["space_reservation"] = 50 * 2^^20; // 50 MB as Bytes
294+
// - How large should our file fragments be when uploading as an 'upload session' ?
295+
longValues["file_fragment_size"] = defaultFileFragmentSize; // whole number, treated as MB, will be converted to bytes within performSessionFileUpload(). Default is 10.
291296

292297
// HTTPS & CURL Operation Settings
293298
// - Maximum time an operation is allowed to take
@@ -1040,6 +1045,20 @@ class ApplicationConfig {
10401045
tempValue = 0;
10411046
}
10421047
setValueLong("skip_size", tempValue);
1048+
} else if (key == "file_fragment_size") {
1049+
ulong tempValue = thisConfigValue;
1050+
// If set, this must be greater than the default, but also aligning to Microsoft upper limit of 60 MiB
1051+
// Enforce lower bound (must be greater than default)
1052+
if (tempValue < defaultFileFragmentSize) {
1053+
addLogEntry("Invalid value for key in config file (too low) - using default value: " ~ key);
1054+
tempValue = defaultFileFragmentSize;
1055+
}
1056+
// Enforce upper bound (safe maximum)
1057+
else if (tempValue > defaultMaxFileFragmentSize) {
1058+
addLogEntry("Invalid value for key in config file (too high) - using maximum safe value: " ~ key);
1059+
tempValue = defaultMaxFileFragmentSize;
1060+
}
1061+
setValueLong("file_fragment_size", tempValue);
10431062
}
10441063
} else {
10451064
addLogEntry("Unknown key in config file: " ~ key);
@@ -1138,25 +1157,25 @@ class ApplicationConfig {
11381157
std.getopt.config.bundling,
11391158
std.getopt.config.caseSensitive,
11401159
"auth-files",
1141-
"Perform authentication not via interactive dialog but via files read/writes to these files.",
1160+
"Perform authentication not via interactive dialog but via files read/writes to these files",
11421161
&stringValues["auth_files"],
11431162
"auth-response",
1144-
"Perform authentication not via interactive dialog but via providing the response url directly.",
1163+
"Perform authentication not via interactive dialog but via providing the response url directly",
11451164
&stringValues["auth_response"],
11461165
"check-for-nomount",
1147-
"Check for the presence of .nosync in the syncdir root. If found, do not perform sync.",
1166+
"Check for the presence of .nosync in the syncdir root. If found, do not perform sync",
11481167
&boolValues["check_nomount"],
11491168
"check-for-nosync",
1150-
"Check for the presence of .nosync in each directory. If found, skip directory from sync.",
1169+
"Check for the presence of .nosync in each directory. If found, skip directory from sync",
11511170
&boolValues["check_nosync"],
11521171
"classify-as-big-delete",
11531172
"Number of children in a path that is locally removed which will be classified as a 'big data delete'",
11541173
&longValues["classify_as_big_delete"],
11551174
"cleanup-local-files",
1156-
"Cleanup additional local files when using --download-only. This will remove local data.",
1175+
"Cleanup additional local files when using --download-only. This will remove local data",
11571176
&boolValues["cleanup_local_files"],
11581177
"create-directory",
1159-
"Create a directory on OneDrive - no sync will be performed.",
1178+
"Create a directory on OneDrive - no sync will be performed",
11601179
&stringValues["create_directory"],
11611180
"create-share-link",
11621181
"Create a shareable link for an existing file on OneDrive",
@@ -1165,10 +1184,10 @@ class ApplicationConfig {
11651184
"Debug OneDrive HTTPS communication.",
11661185
&boolValues["debug_https"],
11671186
"destination-directory",
1168-
"Destination directory for renamed or move on OneDrive - no sync will be performed.",
1187+
"Destination directory for renamed or move on OneDrive - no sync will be performed",
11691188
&stringValues["destination_directory"],
11701189
"disable-notifications",
1171-
"Do not use desktop notifications in monitor mode.",
1190+
"Do not use desktop notifications in monitor mode",
11721191
&boolValues["disable_notifications"],
11731192
"disable-download-validation",
11741193
"Disable download validation when downloading from OneDrive",
@@ -1177,26 +1196,29 @@ class ApplicationConfig {
11771196
"Disable upload validation when uploading to OneDrive",
11781197
&boolValues["disable_upload_validation"],
11791198
"display-config",
1180-
"Display what options the client will use as currently configured - no sync will be performed.",
1199+
"Display what options the client will use as currently configured - no sync will be performed",
11811200
&boolValues["display_config"],
11821201
"display-running-config",
1183-
"Display what options the client has been configured to use on application startup.",
1202+
"Display what options the client has been configured to use on application startup",
11841203
&boolValues["display_running_config"],
11851204
"display-sync-status",
1186-
"Display the sync status of the client - no sync will be performed.",
1205+
"Display the sync status of the client - no sync will be performed",
11871206
&boolValues["display_sync_status"],
11881207
"display-quota",
1189-
"Display the quota status of the client - no sync will be performed.",
1208+
"Display the quota status of the client - no sync will be performed",
11901209
&boolValues["display_quota"],
11911210
"download-only",
1192-
"Replicate the OneDrive online state locally, by only downloading changes from OneDrive. Do not upload local changes to OneDrive.",
1211+
"Replicate the OneDrive online state locally, by only downloading changes from OneDrive. Do not upload local changes to OneDrive",
11931212
&boolValues["download_only"],
11941213
"dry-run",
11951214
"Perform a trial sync with no changes made",
11961215
&boolValues["dry_run"],
11971216
"enable-logging",
11981217
"Enable client activity to a separate log file",
11991218
&boolValues["enable_logging"],
1219+
"file-fragment-size",
1220+
"Specify the file fragment size for large file uploads (in MB)",
1221+
&longValues["file_fragment_size"],
12001222
"force-http-11",
12011223
"Force the use of HTTP 1.1 for all operations",
12021224
&boolValues["force_http_11"],
@@ -1222,10 +1244,10 @@ class ApplicationConfig {
12221244
"Sync OneDrive Business Shared Files to the local filesystem",
12231245
&boolValues["sync_business_shared_files"],
12241246
"local-first",
1225-
"Synchronize from the local directory source first, before downloading changes from OneDrive.",
1247+
"Synchronize from the local directory source first, before downloading changes from OneDrive",
12261248
&boolValues["local_first"],
12271249
"log-dir",
1228-
"Directory where logging output is saved to, needs to end with a slash.",
1250+
"Directory where logging output is saved to, needs to end with a slash",
12291251
&stringValues["log_dir"],
12301252
"logout",
12311253
"Logout the current user",
@@ -1237,7 +1259,7 @@ class ApplicationConfig {
12371259
"Keep monitoring for local and remote changes",
12381260
&boolValues["monitor"],
12391261
"monitor-interval",
1240-
"Number of seconds by which each sync operation is undertaken when idle under monitor mode.",
1262+
"Number of seconds by which each sync operation is undertaken when idle under monitor mode",
12411263
&longValues["monitor_interval"],
12421264
"monitor-fullscan-frequency",
12431265
"Number of sync runs before performing a full local scan of the synced directory",
@@ -1261,13 +1283,13 @@ class ApplicationConfig {
12611283
"Approve the use of performing a --resync action",
12621284
&boolValues["resync_auth"],
12631285
"remove-directory",
1264-
"Remove a directory on OneDrive - no sync will be performed.",
1286+
"Remove a directory on OneDrive - no sync will be performed",
12651287
&stringValues["remove_directory"],
12661288
"remove-source-files",
12671289
"Remove source file after successful transfer to OneDrive when using --upload-only",
12681290
&boolValues["remove_source_files"],
12691291
"single-directory",
1270-
"Specify a single local directory within the OneDrive root to sync.",
1292+
"Specify a single local directory within the OneDrive root to sync",
12711293
&stringValues["single_directory"],
12721294
"skip-dot-files",
12731295
"Skip dot files and folders from syncing",
@@ -1288,7 +1310,7 @@ class ApplicationConfig {
12881310
"Skip syncing of symlinks",
12891311
&boolValues["skip_symlinks"],
12901312
"source-directory",
1291-
"Source directory to rename or move on OneDrive - no sync will be performed.",
1313+
"Source directory to rename or move on OneDrive - no sync will be performed",
12921314
&stringValues["source_directory"],
12931315
"space-reservation",
12941316
"The amount of disk space to reserve (in MB) to avoid 100% disk space utilisation",
@@ -1306,10 +1328,10 @@ class ApplicationConfig {
13061328
"Perform a synchronisation with Microsoft OneDrive (DEPRECATED)",
13071329
&boolValues["synchronize"],
13081330
"sync-root-files",
1309-
"Sync all files in sync_dir root when using sync_list.",
1331+
"Sync all files in sync_dir root when using sync_list",
13101332
&boolValues["sync_root_files"],
13111333
"upload-only",
1312-
"Replicate the locally configured sync_dir state to OneDrive, by only uploading local changes to OneDrive. Do not download changes from OneDrive.",
1334+
"Replicate the locally configured sync_dir state to OneDrive, by only uploading local changes to OneDrive. Do not download changes from OneDrive",
13131335
&boolValues["upload_only"],
13141336
"confdir",
13151337
"Set the directory used to store the configuration files",
@@ -1325,7 +1347,7 @@ class ApplicationConfig {
13251347
&boolValues["with_editing_perms"]
13261348
);
13271349

1328-
// Was --syncdir used?
1350+
// Was --syncdir specified?
13291351
if (!getValueString("sync_dir_cli").empty) {
13301352
// Build the line we need to update and/or write out
13311353
string newConfigOptionSyncDirLine = "sync_dir = \"" ~ getValueString("sync_dir_cli") ~ "\"";
@@ -1411,12 +1433,24 @@ class ApplicationConfig {
14111433
setValueString("sync_dir", getValueString("sync_dir_cli"));
14121434
}
14131435

1414-
// was --monitor-interval used and now set to a value below minimum requirement?
1436+
// Was --monitor-interval specified and now set to a value below minimum requirement?
14151437
if (getValueLong("monitor_interval") < 300 ) {
14161438
addLogEntry("Invalid value for --monitor-interval - using default value: 300");
14171439
setValueLong("monitor_interval", 300);
14181440
}
14191441

1442+
// Was --file-fragment-size specified and now set to a value below or above maximum?
1443+
// Enforce lower bound (must be greater than default) for 'file_fragment_size'
1444+
if (getValueLong("file_fragment_size") < defaultFileFragmentSize) {
1445+
addLogEntry("Invalid value for --file-fragment-size (too low) - using default value: " ~ to!string(defaultFileFragmentSize));
1446+
setValueLong("file_fragment_size", defaultFileFragmentSize);
1447+
}
1448+
// Enforce upper bound (safe maximum) for 'file_fragment_size'
1449+
if (getValueLong("file_fragment_size") > defaultMaxFileFragmentSize) {
1450+
addLogEntry("Invalid value for --file-fragment-size (too high) - using maximum safe value: " ~ to!string(defaultMaxFileFragmentSize));
1451+
setValueLong("file_fragment_size", defaultMaxFileFragmentSize);
1452+
}
1453+
14201454
// Was --auth-files used?
14211455
if (!getValueString("auth_files").empty) {
14221456
// --auth-files used, need to validate that '~' was not used as a path identifier, and if yes, perform the correct expansion
@@ -1597,6 +1631,7 @@ class ApplicationConfig {
15971631
addLogEntry("Config option 'inotify_delay' = " ~ to!string(getValueLong("inotify_delay")));
15981632
addLogEntry("Config option 'display_transfer_metrics' = " ~ to!string(getValueBool("display_transfer_metrics")));
15991633
addLogEntry("Config option 'force_session_upload' = " ~ to!string(getValueBool("force_session_upload")));
1634+
addLogEntry("Config option 'file_fragment_size' = " ~ to!string(getValueLong("file_fragment_size")));
16001635

16011636
// data integrity
16021637
addLogEntry("Config option 'classify_as_big_delete' = " ~ to!string(getValueLong("classify_as_big_delete")));

0 commit comments

Comments
 (0)