Merge branch 'master' into fix-issue-966

This commit is contained in:
abraunegg 2021-04-07 07:39:09 +10:00 committed by GitHub
commit c4807fd25e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 729 additions and 397 deletions

View file

@ -2,6 +2,30 @@
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.11 - 2021-4-07
### Fixed
* Fix support for '/*' regardless of location within sync_list file
* Fix 429 response handling correctly check for 'retry-after' response header and use set value
* Fix 'sync_list' path handling for sub item matching, so that items in parent are not implicitly matched when there is no wildcard present
* Fix --get-O365-drive-id to use 'nextLink' value if present when searching for specific SharePoint site names
* Fix OneDrive Business Shared Folder existing name conflict check
* Fix incorrect error message 'Item cannot be deleted from OneDrive because it was not found in the local database' when item is actually present
* Fix application crash when unable to rename folder structure due to unhandled file-system issue
* Fix uploading documents to Shared Business Folders when the shared folder exists on a SharePoint site due to Microsoft Sharepoint 'enrichment' of files
* Fix that a file record is kept in database when using --no-remote-delete & --remove-source-files
### Added
* Added support in --get-O365-drive-id to provide the 'drive_id' for multiple 'document libraries' within a single Shared Library Site
### Removed
* Removed the depreciated config option 'force_http_11' which was flagged as depreciated by PR #549 in v2.3.6 (June 2019)
### Updated
* Updated error output of --get-O365-drive-id to provide more details why an error occurred if a SharePoint site lacks the details we need to perform the match
* Updated Docker build files for Raspberry Pi to dedicated armhf & aarch64 Dockerfiles
* Updated logging output when in --monitor mode, avoid outputting misleading logging when the new or modified item is a file, not a directory
* Updated documentation (various)
## 2.4.10 - 2021-2-19
### Fixed
* Catch database assertion when item path cannot be calculated

View file

@ -33,7 +33,10 @@ This client is a 'fork' of the [skilion](https://github.com/skilion/onedrive) cl
## Frequently Asked Questions
Refer to [Frequently Asked Questions](https://github.com/abraunegg/onedrive/wiki/Frequently-Asked-Questions)
## Reporting issues
## Have a question
If you have a question or need something clarified, please raise a new disscussion post [here](https://github.com/abraunegg/onedrive/discussions)
## Reporting an Issue or Bug
If you encounter any bugs you can report them here on Github. Before filing an issue be sure to:
1. Check the version of the application you are using `onedrive --version` and ensure that you are running either the latest [release](https://github.com/abraunegg/onedrive/releases) or built from master.

1
config
View file

@ -18,7 +18,6 @@
# disable_notifications = "false"
# disable_upload_validation = "false"
# enable_logging = "false"
# force_http_11 = "false"
# force_http_2 = "false"
# local_first = "false"
# no_remote_delete = "false"

20
configure vendored
View file

@ -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.11-dev.
# Generated by GNU Autoconf 2.69 for onedrive v2.4.11.
#
# Report bugs to <https://github.com/abraunegg/onedrive>.
#
@ -579,8 +579,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='onedrive'
PACKAGE_TARNAME='onedrive'
PACKAGE_VERSION='v2.4.11-dev'
PACKAGE_STRING='onedrive v2.4.11-dev'
PACKAGE_VERSION='v2.4.11'
PACKAGE_STRING='onedrive v2.4.11'
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.11-dev to adapt to many kinds of systems.
\`configure' configures onedrive v2.4.11 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.11-dev:";;
short | recursive ) echo "Configuration of onedrive v2.4.11:";;
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.11-dev
onedrive configure v2.4.11
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.11-dev, which was
It was created by onedrive $as_me v2.4.11, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@ -2162,7 +2162,7 @@ fi
PACKAGE_DATE="February 2021"
PACKAGE_DATE="April 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.11-dev, which was
This file was extended by onedrive $as_me v2.4.11, 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.11-dev
onedrive config.status v2.4.11
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"

View file

@ -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.11-dev], [https://github.com/abraunegg/onedrive], [onedrive])
AC_INIT([onedrive],[v2.4.11], [https://github.com/abraunegg/onedrive], [onedrive])
AC_CONFIG_SRCDIR([src/main.d])

View file

@ -0,0 +1,21 @@
# -*-Dockerfile-*-
FROM debian:stretch
RUN apt update && \
apt install -y build-essential curl libcurl4-openssl-dev libsqlite3-dev pkg-config wget git
RUN wget https://github.com/ldc-developers/ldc/releases/download/v1.16.0/ldc2-1.16.0-linux-aarch64.tar.xz && \
tar -xvf ldc2-1.16.0-linux-aarch64.tar.xz
COPY . /usr/src/onedrive
RUN cd /usr/src/onedrive/ && \
./configure DC=/ldc2-1.16.0-linux-aarch64/bin/ldmd2 && \
make clean && \
make && \
make install
FROM debian:stretch-slim
ENTRYPOINT ["/entrypoint.sh"]
RUN apt update && \
apt install -y gosu libcurl3 libsqlite3-0 && \
rm -rf /var/*/apt && \
mkdir -p /onedrive/conf /onedrive/data
COPY contrib/docker/entrypoint.sh /
COPY --from=0 /usr/local/bin/onedrive /usr/local/bin/

View file

@ -1,13 +1,12 @@
# -*-Dockerfile-*-
FROM debian:stretch
ARG ARCH armhf # or aarch64
RUN apt update && \
apt install -y build-essential curl libcurl4-openssl-dev libsqlite3-dev pkg-config wget git
RUN wget https://github.com/ldc-developers/ldc/releases/download/v1.16.0/ldc2-1.16.0-linux-${ARCH}.tar.xz && \
tar -xvf ldc2-1.16.0-linux-${ARCH}.tar.xz
RUN wget https://github.com/ldc-developers/ldc/releases/download/v1.16.0/ldc2-1.16.0-linux-armhf.tar.xz && \
tar -xvf ldc2-1.16.0-linux-armhf.tar.xz
COPY . /usr/src/onedrive
RUN cd /usr/src/onedrive/ && \
./configure DC=/ldc2-1.16.0-linux-${ARCH}/bin/ldmd2 && \
./configure DC=/ldc2-1.16.0-linux-armhf/bin/ldmd2 && \
make clean && \
make && \
make install

View file

@ -6,7 +6,7 @@
%endif
Name: onedrive
Version: 2.4.10
Version: 2.4.11
Release: 1%{?dist}
Summary: Microsoft OneDrive Client
Group: System Environment/Network

View file

@ -1,56 +1,89 @@
# onedrive docker image
# Run the OneDrive Client for Linux under Docker
This client can be run as a Docker container, with 3 available options for you to choose from:
1. Container based on CentOS 7 - Docker Tag: latest
2. Container based on Debian Stretch - Docker Tag: stretch
3. Container based on Alpine Linux - Docker Tag: alpine
Thats right folks onedrive is now dockerized ;)
These containers offer a simple monitoring-mode service for the OneDrive Client for Linux.
The instructions below have been validated on:
* Red Hat Enterprise Linux 8.x
* Ubuntu Server 20.04
The instructions below will utilise the 'latest' tag, however this can be substituted for 'stretch' or 'alpine' if desired.
This container offers simple monitoring-mode service for 'Free Client for OneDrive on Linux'.
## Basic Setup
### 0. Install docker using your distribution platform's instructions
1. Ensure that SELinux has been disabled on your system. A reboot may be required to ensure that this is correctly disabled.
2. Install Docker as per requried for your platform
3. Obtain your normal, non-root user UID and GID by using the `id` command
4. As your normal, non-root user, ensure that you can run `docker run hello-world` *without* using `sudo`
### 0. Install docker under your own platform's instructions
Once the above 4 steps are complete and you can successfully run `docker run hello-world` without sudo, only then proceed to 'Pulling and Running the Docker Image'
## Pulling and Running the Docker Image
### 1. Pull the image
```bash
docker pull driveone/onedrive:latest
```
**NOTE:** SELinux context needs to be configured or disabled for Docker, to be able to write to OneDrive host directory.
**NOTE:** SELinux context needs to be configured or disabled for Docker to be able to write to OneDrive host directory.
### 2. Prepare config volume
The Docker container requries 2 Docker volumes:
* Config Volume
* Data Volume
Onedrive needs two volumes. One of them is the config volume. Create it with:
Create the config volume with the following command:
```bash
docker volume create onedrive_conf
```
This will create a docker volume labeled `onedrive_conf`, where all configuration of your onedrive account will be stored. You can add a custom config file and other things later.
The second docker volume is for your data folder and is created in the next step. It needs the path to a folder on your filesystem that you want to keep in sync with OneDrive. Keep in mind that:
The second docker volume is for your data folder and is created in the next step. This volume needs to be a path to a directory on your local filesystem, and this is where your data will be stored from OneDrive. Keep in mind that:
- The owner of your specified folder must not be root
- The owner of your specified folder must have permissions for its parent directory
### 3. First run
Onedrive needs to be authorized with your Microsoft account. This is achieved by running docker in interactive mode. Run the docker image with the two commands below and **make sure to change `onedriveDir` to the onedrive data directory on your filesystem (e.g. `"/home/abraunegg/OneDrive"`)**.
Additionally, the user id and group id should be added to remove any potential user conflicts, denoted by the environment variables `${ONEDRIVE_UID}` and `${ONEDRIVE_GID}`.
* The owner of this specified folder must not be root
* The owner of this specified folder must have permissions for its parent directory
**NOTE:** Issues occur when this target folder is a mounted folder of an external system (NAS, SMB mount, USB Drive etc) as the 'mount' itself is owed by 'root'. If this is your use case, you *must* ensure your normal user can mount your desired target without having the target mounted by 'root'. If you do not fix this, your Docker container will fail to start with the following error message:
```bash
onedriveDir="${HOME}/OneDrive"
docker run -it --name onedrive -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" -e "ONEDRIVE_UID:${ONEDRIVE_UID}" -e "ONEDRIVE_GID:${ONEDRIVE_GID}" driveone/onedrive:latest
ROOT level privileges prohibited!
```
- You will be asked to open a specific link using your web browser
- Login to your Microsoft Account and give the application the permission
- After giving the permission, you will be redirected to a blank page.
- Copy the URI of the blank page into the application.
### 3. First run
The 'onedrive' client within the Docker container needs to be authorized with your Microsoft account. This is achieved by initially running docker in interactive mode.
The onedrive monitor is configured to start with your host system. If your onedrive is working as expected, you can detach from the container with Ctrl+p, Ctrl+q.
Run the docker image with the commands below and make sure to change `ONEDRIVE_DATA_DIR` to the actual onedrive data directory on your filesystem that you wish to use (e.g. `"/home/abraunegg/OneDrive"`).
```bash
export ONEDRIVE_DATA_DIR="${HOME}/OneDrive"
mkdir -p ${ONEDRIVE_DATA_DIR}
docker run -it --name onedrive -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" -e "ONEDRIVE_UID:${ONEDRIVE_UID}" -e "ONEDRIVE_GID:${ONEDRIVE_GID}" driveone/onedrive:latest
```
**NOTE:** It is also highly advisable for you to replace `${ONEDRIVE_UID}` and `${ONEDRIVE_GID}` with your actual UID and GID as specified by your `id` command output to avoid any any potential user or group conflicts.
### 4. Status, stop, and restart
**Important:** The 'target' folder of `ONEDRIVE_DATA_DIR` must exist before running the Docker container, otherwise, Docker will create the target folder, and the folder will be given 'root' permissions, which then causes the Docker container to fail upon startup with the following error message:
```bash
ROOT level privileges prohibited!
```
**Example:**
```
docker run -it --name onedrive -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" -e "ONEDRIVE_UID:1000" -e "ONEDRIVE_GID:1000" driveone/onedrive:latest
```
When the Docker container successfully starts:
* You will be asked to open a specific link using your web browser
* Login to your Microsoft Account and give the application the permission
* After giving the permission, you will be redirected to a blank page
* Copy the URI of the blank page into the application prompt to authorise the application
Once the 'onedrive' application is authorised, the client will automatically start monitoring your `ONEDRIVE_DATA_DIR` for data changes to be uploaded to OneDrive. Files stored on OneDrive will be downloaded to this location.
If the client is working as expected, you can detach from the container with Ctrl+p, Ctrl+q.
### 4. Docker Container Status, stop, and restart
Check if the monitor service is running
```bash
@ -75,7 +108,7 @@ Resume monitor
docker start onedrive
```
Remove onedrive monitor
Remove onedrive Docker container
```bash
docker rm -f onedrive
@ -83,9 +116,8 @@ docker rm -f onedrive
## Advanced Setup
### 5. Docker-compose
Also supports docker-compose schemas > 3.
In the following example it is assumed you have a `onedriveDir` environment variable and a `onedrive_conf` volume.
In the following example it is assumed you have a `ONEDRIVE_DATA_DIR` environment variable and a `onedrive_conf` volume.
However, you can also use bind mounts for the configuration folder, e.g. `export ONEDRIVE_CONF="${HOME}/OneDriveConfig"`.
```
@ -99,14 +131,13 @@ services:
- ONEDRIVE_GID=${PGID}
volumes:
- onedrive_conf:/onedrive/conf
- ${onedriveDir}:/onedrive/data
- ${ONEDRIVE_DATA_DIR}:/onedrive/data
```
Note that you still have to perform step 3: First Run.
### 6. Edit the config
Onedrive should run in default configuration, however you can change your configuration by placing a custom config file in the `onedrive_conf` docker volume. First download the default config from [here](https://raw.githubusercontent.com/abraunegg/onedrive/master/config)
The 'onedrive' client should run in default configuration, however you can change this default configuration by placing a custom config file in the `onedrive_conf` docker volume. First download the default config from [here](https://raw.githubusercontent.com/abraunegg/onedrive/master/config)
Then put it into your onedrive_conf volume path, which can be found with:
```bash
@ -118,34 +149,31 @@ Or you can map your own config folder to the config volume. Make sure to copy al
The detailed document for the config can be found here: [Configuration](https://github.com/abraunegg/onedrive/blob/master/docs/USAGE.md#configuration)
### 7. Sync multiple accounts
There are many ways to do this, the easiest is probably to
1. Create a second docker config volume (replace `Work` with your desired name): `docker volume create onedrive_conf_Work`
2. And start a second docker monitor container (again replace `Work` with your desired name):
```
onedriveDirWork="/home/abraunegg/OneDriveWork"
docker run -it --restart unless-stopped --name onedrive_Work -v onedrive_conf_Work:/onedrive/conf -v "${onedriveDirWork}:/onedrive/data" driveone/onedrive:latest
export ONEDRIVE_DATA_DIR_WORK="/home/abraunegg/OneDriveWork"
mkdir -p ${ONEDRIVE_DATA_DIR_WORK}
docker run -it --restart unless-stopped --name onedrive_Work -v onedrive_conf_Work:/onedrive/conf -v "${ONEDRIVE_DATA_DIR_WORK}:/onedrive/data" driveone/onedrive:latest
```
## Run or update with one script
If you are experienced with docker and onedrive, you can use the following script:
```bash
# Update onedriveDir with correct existing OneDrive directory path
onedriveDir="${HOME}/OneDrive"
# Update ONEDRIVE_DATA_DIR with correct existing OneDrive directory path
ONEDRIVE_DATA_DIR="${HOME}/OneDrive"
firstRun='-d'
docker pull driveone/onedrive:latest
docker inspect onedrive_conf > /dev/null || { docker volume create onedrive_conf; firstRun='-it'; }
docker inspect onedrive > /dev/null && docker rm -f onedrive
docker run $firstRun --restart unless-stopped --name onedrive -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker run $firstRun --restart unless-stopped --name onedrive -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
## Environment Variables
| Variable | Purpose | Sample Value |
| ---------------- | --------------------------------------------------- |:-------------:|
| <B>ONEDRIVE_UID</B> | UserID (UID) to run as | 1000 |
@ -160,24 +188,24 @@ docker run $firstRun --restart unless-stopped --name onedrive -v onedrive_conf:/
### Usage Examples
**Verbose Output:**
```bash
docker container run -e ONEDRIVE_VERBOSE=1 -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker container run -e ONEDRIVE_VERBOSE=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
**Debug Output:**
```bash
docker container run -e ONEDRIVE_DEBUG=1 -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker container run -e ONEDRIVE_DEBUG=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
**Perform a --resync:**
```bash
docker container run -e ONEDRIVE_RESYNC=1 -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker container run -e ONEDRIVE_RESYNC=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
**Perform a --resync and --verbose:**
```bash
docker container run -e ONEDRIVE_RESYNC=1 -e ONEDRIVE_VERBOSE=1 -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker container run -e ONEDRIVE_RESYNC=1 -e ONEDRIVE_VERBOSE=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
**Perform a --logout and re-authenticate:**
```bash
docker container run -e ONEDRIVE_LOGOUT=1 -v onedrive_conf:/onedrive/conf -v "${onedriveDir}:/onedrive/data" driveone/onedrive:latest
docker container run -e ONEDRIVE_LOGOUT=1 -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" driveone/onedrive:latest
```
## Build instructions
@ -185,7 +213,7 @@ docker container run -e ONEDRIVE_LOGOUT=1 -v onedrive_conf:/onedrive/conf -v "${
* Build environment must have at least 1GB of memory & 2GB swap space
There are 2 ways to validate this requirement:
* Modify the file `/etc/dphys-swapfile` and edit the `CONF_SWAPSIZE`, for example: `CONF_SWAPSIZE=2024`. A reboot is required to make this change effective.
* Modify the file `/etc/dphys-swapfile` and edit the `CONF_SWAPSIZE`, for example: `CONF_SWAPSIZE=2048`. A reboot is required to make this change effective.
* Dynamically allocate a swapfile for building:
```bash
cd /var
@ -201,7 +229,7 @@ swapon -s
free -h
```
### Building the Docker image
### Building a custom Docker image
You can also build your own image instead of pulling the one from [hub.docker.com](https://hub.docker.com/r/driveone/onedrive):
```bash
git clone https://github.com/abraunegg/onedrive
@ -214,18 +242,32 @@ Dockerfile-stretch or Dockerfile-alpine. These [multi-stage builder
pattern](https://docs.docker.com/develop/develop-images/multistage-build/)
Dockerfiles require Docker version at least 17.05.
#### How to build a Docker image based on Debian Stretch
#### How to build and run a custom Docker image based on Debian Stretch
``` bash
docker build . -t local-ondrive-stretch -f contrib/docker/Dockerfile-stretch
docker container run -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" local-ondrive-stretch:latest
```
#### How to build a Docker image based on Alpine Linux
#### How to build and run a custom Docker image based on Alpine Linux
``` bash
docker build . -t local-ondrive-alpine -f contrib/docker/Dockerfile-alpine
docker container run -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" local-ondrive-alpine:latest
```
#### How to build a Docker image for ARMHF (Raspberry Pi)
#### How to build and run a custom Docker image for ARMHF (Raspberry Pi)
Compatible with:
* Raspberry Pi
* Raspberry Pi 2
* Raspberry Pi Zero
* Raspberry Pi 3
* Raspberry Pi 4
``` bash
docker build . -t local-onedrive-rpi -f contrib/docker/Dockerfile-rpi
docker container run -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" local-ondrive-rpi:latest
```
#### How to build and run a custom Docker image for AARCH64 Platforms
``` bash
docker build . -t local-onedrive-aarch64 -f contrib/docker/Dockerfile-aarch64
docker container run -v onedrive_conf:/onedrive/conf -v "${ONEDRIVE_DATA_DIR}:/onedrive/data" local-onedrive-aarch64:latest
```

View file

@ -5,6 +5,7 @@ This project has been packaged for the following Linux distributions:
| Distribution | Package Name & Package Link | &nbsp;i686&nbsp; | x86_64 | ARMHF | AARCH64 | Extra Details |
|---------------------------------|------------------------------------------------------------------------------|:----:|:------:|:-----:|:-------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Alpine Linux | [onedrive](https://pkgs.alpinelinux.org/packages?name=onedrive&branch=edge) |<img src="./images/cross.gif" alt="not_supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/cross.gif" alt="not_supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|
| Arch Linux<br><br>Manjaro Linux | [onedrive-abraunegg](https://aur.archlinux.org/packages/onedrive-abraunegg/) |<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>| Install via: `pamac build onedrive-abraunegg` from the Arch Linux User Repository (AUR)<br><br>**Note:** If asked regarding a provider for 'd-runtime' and 'd-compiler', select 'liblphobos' and 'ldc'<br><br>**Note:** System must have at least 1GB of memory & 1GB swap space
| Debian | [onedrive](https://packages.debian.org/search?keywords=onedrive) |<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>| |
| Fedora | [onedrive](https://koji.fedoraproject.org/koji/packageinfo?packageID=26044) |<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>|<img src="./images/tick.gif" alt="supported" width="39" height="39"/>| |
@ -236,28 +237,42 @@ sudo pacman -S libnotify
```
### Dependencies: Raspbian (ARMHF)
Validated using:
* `Linux raspberrypi 5.4.79-v7+ #1373 SMP Mon Nov 23 13:22:33 GMT 2020 armv7l GNU/Linux` (2020-12-02-raspios-buster-armhf) using Raspberry Pi 2 Model B
* `Linux raspberrypi 5.4.83-v8+ #1379 SMP PREEMPT Mon Dec 14 13:15:14 GMT 2020 aarch64` (2021-01-11-raspios-buster-armhf) using Raspberry Pi 3 Model B+
**Note:** Build environment must have at least 1GB of memory & 1GB swap space. Check with `swapon`.
```text
sudo apt-get install libcurl4-openssl-dev
sudo apt-get install libsqlite3-dev
sudo apt-get install libxml2
sudo apt-get install pkg-config
wget https://github.com/ldc-developers/ldc/releases/download/v1.16.0/ldc2-1.16.0-linux-armhf.tar.xz
tar -xvf ldc2-1.16.0-linux-armhf.tar.xz
sudo apt install build-essential
sudo apt install libcurl4-openssl-dev
sudo apt install libsqlite3-dev
sudo apt install pkg-config
sudo apt install git
sudo apt install curl
wget https://github.com/ldc-developers/ldc/releases/download/v1.17.0/ldc2-1.17.0-linux-armhf.tar.xz
tar -xvf ldc2-1.17.0-linux-armhf.tar.xz
```
For notifications the following is also necessary:
```text
sudo apt install libnotify-dev
```
### Dependencies: Debian (ARM64)
### Dependencies: Ubuntu 20.x / Debian 10 (ARM64)
Validated using:
* `Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-1028-raspi aarch64)` (ubuntu-20.04.2-preinstalled-server-arm64+raspi) using Raspberry Pi 3 Model B+
**Note:** Build environment must have at least 1GB of memory & 1GB swap space. Check with `swapon`.
```text
sudo apt-get install libcurl4-openssl-dev
sudo apt-get install libsqlite3-dev
sudo apt-get install libxml2
sudo apt-get install pkg-config
wget https://github.com/ldc-developers/ldc/releases/download/v1.16.0/ldc2-1.16.0-linux-aarch64.tar.xz
tar -xvf ldc2-1.16.0-linux-aarch64.tar.xz
sudo apt install build-essential
sudo apt install libcurl4-openssl-dev
sudo apt install libsqlite3-dev
sudo apt install pkg-config
sudo apt install git
sudo apt install curl
wget https://github.com/ldc-developers/ldc/releases/download/v1.25.1/ldc2-1.25.1-linux-aarch64.tar.xz
tar -xvf ldc2-1.25.1-linux-aarch64.tar.xz
```
For notifications the following is also necessary:
```text
@ -337,7 +352,7 @@ as far as possible automatically, but can be overridden by passing
```text
git clone https://github.com/abraunegg/onedrive.git
cd onedrive
./configure DC=~/ldc2-1.16.0-linux-armhf/bin/ldmd2
./configure DC=~/ldc2-1.17.0-linux-armhf/bin/ldmd2
make clean; make
sudo make install
```
@ -347,7 +362,7 @@ sudo make install
```text
git clone https://github.com/abraunegg/onedrive.git
cd onedrive
./configure DC=~/ldc2-1.16.0-linux-aarch64/bin/ldmd2
./configure DC=~/ldc2-1.25.1-linux-aarch64/bin/ldmd2
make clean; make
sudo make install
```

View file

@ -14,18 +14,38 @@ Syncing a OneDrive SharePoint library requires additional configuration for your
## Query that shared library name using the client to obtain the required configuration details
2. Run the following command using the 'onedrive' client
```text
onedrive --get-O365-drive-id '<your library name>'
onedrive --get-O365-drive-id '<your site name to search>'
```
This will return something similar to the following:
```text
Configuration file successfully loaded
Configuring Global Azure AD Endpoints
Initializing the Synchronization Engine ...
Office 365 Library Name Query: <your library name>
SiteName: <your library name>
drive_id: b!6H_y8B...xU5
URL: <your site URL>
Office 365 Library Name Query: <your site name to search>
-----------------------------------------------
Site Name: <your site name>
Library Name: <your library name>
drive_id: b!6H_y8B...xU5
Library URL: <your library URL>
-----------------------------------------------
```
If there are no matches to the site you are attempting to search, the following will be displayed:
```text
Configuration file successfully loaded
Configuring Global Azure AD Endpoints
Initializing the Synchronization Engine ...
Office 365 Library Name Query: blah
ERROR: The requested SharePoint site could not be found. Please check it's name and your permissions to access the site.
The following SharePoint site names were returned:
* <site name 1>
* <site name 2>
...
* <site name X>
```
This list of site names can be used as a basis to search for the correct site for which you are searching
## Configure the client's config file with the required 'drive_id' & 'sync_dir' options
3. Create a new local folder to store the SharePoint Library data in
@ -54,6 +74,5 @@ The OneDrive client will now be configured to sync this SharePoint shared librar
## Sync the SharePoint Library as required
6. Sync the SharePoint Library to your system with either `--synchronize` or `--monitor` operations
# How to configure multiple OneDrive SharePoint Shared Library sync
Refer to [./advanced-usage.md](advanced-usage.md) for configuration assistance.

View file

@ -326,7 +326,6 @@ See the [config](https://raw.githubusercontent.com/abraunegg/onedrive/master/con
# disable_notifications = "false"
# disable_upload_validation = "false"
# enable_logging = "false"
# force_http_11 = "false"
# force_http_2 = "false"
# local_first = "false"
# no_remote_delete = "false"
@ -698,11 +697,35 @@ systemctl --user start onedrive
**Note:** This will run the 'onedrive' process with a UID/GID of '0', thus, any files or folders that are created will be owned by 'root'
To see the logs run:
To see the systemd application logs run:
```text
journalctl --user-unit=onedrive -f
```
**Note:** It is a 'systemd' requirement that the XDG environment variables exist for correct enablement and operation of systemd services. If you receive this error when enabling the systemd service:
```
Failed to connect to bus: No such file or directory
```
The most likely cause is that the XDG environment variables are missing. To fix this, you must add the following to `.bashrc` or any other file which is run on user login:
```
export XDG_RUNTIME_DIR="/run/user/$UID"
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
```
To make this change effective, you must logout of all user accounts where this change has been made.
**Note:** On some systems (for example - Raspbian / Ubuntu / Debian on Raspberry Pi) the above XDG fix may not be reliable after system reboots. The potential alternative to start the client via systemd as root, is to perform the following:
1. Create a symbolic link from `/home/root/.config/onedrive` pointing to `/root/.config/onedrive/`
2. Create a systemd service using the '@' service file: `systemctl enable onedrive@root.service`
3. Start the root@service: `systemctl start onedrive@root.service`
This will ensure that the service will correctly restart on system reboot.
To see the systemd application logs run:
```text
journalctl --unit=onedrive@<username> -f
```
### OneDrive service running as root user via systemd (Red Hat Enterprise Linux, CentOS Linux)
```text
systemctl enable onedrive
@ -710,7 +733,7 @@ systemctl start onedrive
```
**Note:** This will run the 'onedrive' process with a UID/GID of '0', thus, any files or folders that are created will be owned by 'root'
To see the logs run:
To see the systemd application logs run:
```text
journalctl --unit=onedrive -f
```
@ -732,7 +755,7 @@ systemctl start onedrive@<username>.service
systemctl status onedrive@<username>.service
```
To see the logs run:
To see the systemd application logs run:
```text
journalctl --unit=onedrive@<username> -f
```
@ -752,7 +775,7 @@ systemctl --user enable onedrive
systemctl --user start onedrive
```
To see the logs run:
To see the systemd application logs run:
```text
journalctl --user-unit=onedrive -f
```

View file

@ -61,7 +61,7 @@ Test the configuration using '--display-config' and '--dry-run'. By doing so, th
#### Display the configuration
```text
onedrive --confdir="~/.config/my-new-config --display-config"
onedrive --confdir="~/.config/my-new-config" --display-config
```
#### Test the configuration by performing a dry-run

View file

@ -89,11 +89,6 @@ Configuration file key: \fBenable_logging\fP (default: \fBfalse\fP)
\fB\-\-force\fP
Force the deletion of data when a 'big delete' is detected
.TP
\fB\-\-force\-http\-1.1\fP
Force the use of HTTP 1.1 for all operations (DEPRECIATED)
.br
Configuration file key: \fBforce_http_11\fP (default: \fBfalse\fP)
.TP
\fB\-\-force\-http\-2\fP
Force the use of HTTP/2 for all operations where applicable
.br

View file

@ -61,7 +61,6 @@ final class Config
boolValues["disable_notifications"] = false;
boolValues["disable_upload_validation"] = false;
boolValues["enable_logging"] = false;
boolValues["force_http_11"] = false;
boolValues["force_http_2"] = false;
boolValues["local_first"] = false;
boolValues["no_remote_delete"] = false;
@ -343,9 +342,6 @@ final class Config
"enable-logging",
"Enable client activity to a separate log file",
&boolValues["enable_logging"],
"force-http-1.1",
"Force the use of HTTP/1.1 for all operations (DEPRECIATED)",
&boolValues["force_http_11"],
"force-http-2",
"Force the use of HTTP/2 for all operations where applicable",
&boolValues["force_http_2"],

View file

@ -580,11 +580,6 @@ int main(string[] args)
return EXIT_SUCCESS;
}
// If the user is still using --force-http-1.1 advise its no longer required
if (cfg.getValueBool("force_http_11")) {
log.log("NOTE: The use of --force-http-1.1 is depreciated");
}
// Test if OneDrive service can be reached, exit if it cant be reached
log.vdebug("Testing network to ensure network connectivity to Microsoft OneDrive Service");
online = testNetwork();
@ -1048,7 +1043,7 @@ int main(string[] args)
log.vlog("Offline, cannot delete item!");
} catch(SyncException e) {
if (e.msg == "The item to delete is not in the local database") {
log.vlog("Item cannot be deleted from OneDrive because not found in the local database");
log.vlog("Item cannot be deleted from OneDrive because it was not found in the local database");
} else {
log.logAndNotify("Cannot delete remote item: ", e.msg);
}

View file

@ -852,10 +852,15 @@ final class OneDriveApi
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/site_search?view=odsp-graph-online
JSONValue o365SiteSearch(){
JSONValue o365SiteSearch(const(char)[] nextLink){
checkAccessTokenExpired();
const(char)[] url;
url = siteSearchUrl ~ "=*";
// configure URL to query
if (nextLink.empty) {
url = siteSearchUrl ~ "=*";
} else {
url = nextLink;
}
return get(url);
}

View file

@ -661,9 +661,12 @@ final class SyncEngine
Item databaseItem;
foreach (searchDriveId; driveIDsArray) {
log.vdebug("searching database for: ", searchDriveId, " ", sharedFolderName);
if (itemdb.selectByPath(sharedFolderName, searchDriveId, databaseItem)) {
if (itemdb.idInLocalDatabase(searchDriveId, searchResult["remoteItem"]["id"].str)){
// Shared folder is present
log.vdebug("Found shared folder name in database");
itemInDatabase = true;
// Query the DB for the details of this item
itemdb.selectByPath(sharedFolderName, searchDriveId, databaseItem);
log.vdebug("databaseItem: ", databaseItem);
// Does the databaseItem.driveId == defaultDriveId?
if (databaseItem.driveId == defaultDriveId) {
@ -958,7 +961,17 @@ final class SyncEngine
}
Item item;
if (!itemdb.selectByPath(path, defaultDriveId, item)) {
// Need to check all driveid's we know about, not just the defaultDriveId
bool itemInDB = false;
foreach (searchDriveId; driveIDsArray) {
if (itemdb.selectByPath(path, searchDriveId, item)) {
// item was found in the DB
itemInDB = true;
break;
}
}
// Was the item found in the DB
if (!itemInDB) {
// this is odd .. this directory is not in the local database - just go delete it
log.vlog("The requested directory to delete was not found in the local database - pushing delete request direct to OneDrive");
uploadDeleteItem(item, path);
@ -2538,7 +2551,14 @@ final class SyncEngine
safeRename(newPath);
}
}
rename(oldPath, newPath);
// try and rename path, catch exception
try {
log.vdebug("Calling rename(oldPath, newPath)");
rename(oldPath, newPath);
} catch (FileException e) {
// display the error message
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
}
}
// handle changed content and mtime
// HACK: use mtime+hash instead of cTag because of https://github.com/OneDrive/onedrive-api-docs/issues/765
@ -2923,7 +2943,12 @@ final class SyncEngine
}
// scan for changes in the path provided
log.log("Uploading differences of ", logPath);
if (isDir(path)) {
// if this path is a directory, output this message.
// if a file, potentially leads to confusion as to what the client is actually doing
log.log("Uploading differences of ", logPath);
}
Item item;
// For each unique OneDrive driveID we know about
foreach (driveId; driveIDsArray) {
@ -2943,7 +2968,13 @@ final class SyncEngine
}
}
log.log("Uploading new items of ", logPath);
// scan for changes in the path provided
if (isDir(path)) {
// if this path is a directory, output this message.
// if a file, potentially leads to confusion as to what the client is actually doing
log.log("Uploading new items of ", logPath);
}
// Filesystem walk to find new files not uploaded
uploadNewItems(path);
// clean up idsToDelete only if --dry-run is set
@ -3505,10 +3536,18 @@ final class SyncEngine
itemdb.deleteById(item.driveId, item.id);
return;
} else {
// For logging consistency
writeln("");
// normal session upload
response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag);
if ((!syncBusinessFolders) || (item.driveId == defaultDriveId)) {
// For logging consistency
writeln("");
// If we are not syncing Shared Business Folders, or this change is going to the 'users' default drive, handle normally
// Perform a normal session upload
response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag);
} else {
// If we are uploading to a shared business folder, there are a couple of corner cases here:
// 1. Shared Folder is a 'users' folder
// 2. Shared Folder is a 'SharePoint Library' folder, meaning we get hit by this stupidity: https://github.com/OneDrive/onedrive-api-docs/issues/935
response = handleSharePointMetadataAdditionBug(item, path);
}
}
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
@ -3542,12 +3581,17 @@ final class SyncEngine
uploadFailed = true;
return;
}
// upload done without error
writeln("done.");
// As the session.upload includes the last modified time, save the response
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
// Did the upload fail?
if (!uploadFailed){
// upload done without error or failure
writeln("done.");
// As the session.upload includes the last modified time, save the response
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
} else {
// uploadFailed, return
return;
}
}
// OneDrive documentLibrary
@ -3565,59 +3609,19 @@ final class SyncEngine
itemdb.deleteById(item.driveId, item.id);
return;
} else {
// Handle certain file types differently
if ((extension(path) == ".txt") || (extension(path) == ".csv")) {
// .txt and .csv are unaffected by https://github.com/OneDrive/onedrive-api-docs/issues/935
// For logging consistency
writeln("");
try {
response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag);
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return;
}
// Resolve https://github.com/abraunegg/onedrive/issues/36
if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) {
// The file is currently checked out or locked for editing by another user
// We cant upload this file at this time
writeln("skipped.");
log.fileOnly("Uploading modified file ", path, " ... skipped.");
writeln("", path, " is currently checked out or locked for editing by another user.");
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
uploadFailed = true;
return;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
// upload done without error
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
// This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ......
response = handleSharePointMetadataAdditionBug(item, path);
// Did the upload fail?
if (!uploadFailed){
// upload done without error or failure
writeln("done.");
// As the session.upload includes the last modified time, save the response
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
} else {
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
// This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ......
writeln("skipped.");
log.fileOnly("Uploading modified file ", path, " ... skipped.");
log.vlog("Skip Reason: Microsoft Sharepoint 'enrichment' after upload issue");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
// Delete record from the local database - file will be uploaded as a new file
itemdb.deleteById(item.driveId, item.id);
} else {
// uploadFailed, return
return;
}
}
@ -3732,6 +3736,68 @@ final class SyncEngine
}
}
}
private JSONValue handleSharePointMetadataAdditionBug(const ref Item item, const(string) path)
{
// Explicit function for handling https://github.com/OneDrive/onedrive-api-docs/issues/935
JSONValue response;
// Handle certain file types differently
if ((extension(path) == ".txt") || (extension(path) == ".csv")) {
// .txt and .csv are unaffected by https://github.com/OneDrive/onedrive-api-docs/issues/935
// For logging consistency
writeln("");
try {
response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag);
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return response;
}
// Resolve https://github.com/abraunegg/onedrive/issues/36
if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) {
// The file is currently checked out or locked for editing by another user
// We cant upload this file at this time
writeln("skipped.");
log.fileOnly("Uploading modified file ", path, " ... skipped.");
writeln("", path, " is currently checked out or locked for editing by another user.");
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
uploadFailed = true;
return response;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
// upload done without error
writeln("done.");
} else {
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
// This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ......
writeln("skipped.");
log.fileOnly("Uploading modified file ", path, " ... skipped.");
log.vlog("Skip Reason: Microsoft Sharepoint 'enrichment' after upload issue");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
// Delete record from the local database - file will be uploaded as a new file
itemdb.deleteById(item.driveId, item.id);
uploadFailed = true;
return response;
}
// return a JSON response so that it can be used and saved
return response;
}
// upload new items to OneDrive
private void uploadNewItems(const(string) path)
@ -3973,18 +4039,15 @@ final class SyncEngine
if (!uploadFailed) {
// Upload did not fail
// Issue #763 - Delete local files after sync handling
// are we in an --upload-only scenario?
if (uploadOnly) {
// are we in a delete local file after upload?
if (localDeleteAfterUpload) {
// Log that we are deleting a local item
log.log("Removing local file as --upload-only & --remove-source-files configured");
// are we in a --dry-run scenario?
if (!dryRun) {
// No --dry-run ... process local file delete
log.vdebug("Removing local file: ", path);
safeRemove(path);
}
// are we in an --upload-only & --remove-source-files scenario?
if ((uploadOnly) && (localDeleteAfterUpload)) {
// Log that we are deleting a local item
log.log("Removing local file as --upload-only & --remove-source-files configured");
// are we in a --dry-run scenario?
if (!dryRun) {
// No --dry-run ... process local file delete
log.vdebug("Removing local file: ", path);
safeRemove(path);
}
}
}
@ -4551,9 +4614,20 @@ final class SyncEngine
// This has been seen with PNG / JPG files mainly, which then contributes to generating a 412 error when we attempt to update the metadata
// Validate here that the file uploaded, at least in size, matches in the response to what the size is on disk
if (thisFileSize != uploadFileSize){
if(disableUploadValidation){
// Print a warning message
// Upload size did not match local size
// There are 2 scenarios where this happens:
// 1. Failed Transfer
// 2. Upload file is going to a SharePoint Site, where Microsoft enriches the file with additional metadata with no way to disable
// For this client:
// - If a SharePoint Library, disableUploadValidation gets flagged as True
// - If we are syncing a business shared folder, this folder could reside on a Users Path (there should be no upload issue) or SharePoint (upload issue)
if ((disableUploadValidation)|| (syncBusinessFolders && (parent.driveId != defaultDriveId))){
// Print a warning message - should only be triggered if:
// - disableUploadValidation gets flagged (documentLibrary account type)
// - syncBusinessFolders is being used & parent.driveId != defaultDriveId
log.log("WARNING: Uploaded file size does not match local file - skipping upload validation");
log.vlog("WARNING: Due to Microsoft Sharepoint 'enrichment' of files, this file is now technically different to your local copy");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
} else {
// OK .. the uploaded file does not match and we did not disable this validation
log.log("Uploaded file size does not match local file - upload failure - retrying");
@ -4820,53 +4894,71 @@ final class SyncEngine
} else {
// OneDrive Business account modified file upload handling
if (accountType == "business"){
// OneDrive Business Account - always use a session to upload
writeln("");
try {
response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive["eTag"].str);
} catch (OneDriveException e) {
log.vdebug("response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive['eTag'].str); generated a OneDriveException");
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
// OneDrive Business Account
if ((!syncBusinessFolders) || (parent.driveId == defaultDriveId)) {
// If we are not syncing Shared Business Folders, or this change is going to the 'users' default drive, handle normally
// For logging consistency
writeln("");
try {
response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive["eTag"].str);
} catch (OneDriveException e) {
log.vdebug("response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive['eTag'].str); generated a OneDriveException");
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
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 uploadNewFile(path);");
uploadNewFile(path);
// return back to original call
return;
}
if (e.httpStatusCode == 504) {
// HTTP request returned status code 504 (Gateway Timeout)
log.log("OneDrive returned a 'HTTP 504 - Gateway Timeout' - retrying upload request");
// Retry original request by calling function again to avoid replicating any further error handling
uploadNewFile(path);
// return back to original call
return;
} else {
// error uploading file
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
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 uploadNewFile(path);");
uploadNewFile(path);
// return back to original call
return;
}
if (e.httpStatusCode == 504) {
// HTTP request returned status code 504 (Gateway Timeout)
log.log("OneDrive returned a 'HTTP 504 - Gateway Timeout' - retrying upload request");
// Retry original request by calling function again to avoid replicating any further error handling
uploadNewFile(path);
// return back to original call
return;
// upload complete
writeln("done.");
saveItem(response);
} else {
// If we are uploading to a shared business folder, there are a couple of corner cases here:
// 1. Shared Folder is a 'users' folder
// 2. Shared Folder is a 'SharePoint Library' folder, meaning we get hit by this stupidity: https://github.com/OneDrive/onedrive-api-docs/issues/935
// Need try{} & catch (OneDriveException e) { & catch (FileException e) { handler for this query
response = handleSharePointMetadataAdditionBugReplaceFile(fileDetailsFromOneDrive, parent, path);
if (!uploadFailed){
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
} else {
// error uploading file
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
// uploadFailed, return
return;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
// upload complete
writeln("done.");
saveItem(response);
}
// OneDrive SharePoint account modified file upload handling
@ -4875,93 +4967,15 @@ final class SyncEngine
// as if too large, the following error will be generated by OneDrive:
// HTTP request returned status code 413 (Request Entity Too Large)
// We also cant use a session to upload the file, we have to use simpleUploadReplace
// Calculate existing hash for this file
string existingFileHash = computeQuickXorHash(path);
if (getSize(path) <= thresholdFileSize) {
// Upload file via simpleUploadReplace as below threshold size
try {
response = onedrive.simpleUploadReplace(path, fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str);
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
// Need try{} & catch (OneDriveException e) { & catch (FileException e) { handler for this query
response = handleSharePointMetadataAdditionBugReplaceFile(fileDetailsFromOneDrive, parent, path);
if (!uploadFailed){
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
} else {
// Have to upload via a session, however we have to delete the file first otherwise this will generate a 404 error post session upload
// Remove the existing file
onedrive.deleteById(fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str);
// Upload as a session, as a new file
writeln("");
try {
response = session.upload(path, parent.driveId, parent.id, baseName(path));
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return;
}
}
writeln(" done.");
// Is the response a valid JSON object - validation checking done in saveItem
saveItem(response);
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
// So - now the 'local' and 'remote' file is technically DIFFERENT ... thanks Microsoft .. NO way to disable this stupidity
string uploadNewFileHash;
if (hasQuickXorHash(response)) {
// use the response json hash detail to compare
uploadNewFileHash = response["file"]["hashes"]["quickXorHash"].str;
}
if (existingFileHash != uploadNewFileHash) {
// file was modified by Microsoft post upload to SharePoint site
log.vdebug("Existing Local File Hash: ", existingFileHash);
log.vdebug("New Remote File Hash: ", uploadNewFileHash);
if(!uploadOnly){
// Download the Microsoft 'modified' file so 'local' is now in sync
log.vlog("Due to Microsoft Sharepoint 'enrichment' of files, downloading 'enriched' file to ensure local file is in-sync");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
auto fileSize = response["size"].integer;
onedrive.downloadById(response["parentReference"]["driveId"].str, response["id"].str, path, fileSize);
} else {
// we are not downloading a file, warn that file differences will exist
log.vlog("WARNING: Due to Microsoft Sharepoint 'enrichment' of files, this file is now technically different to your local copy");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
}
// uploadFailed, return
return;
}
}
}
@ -5038,6 +5052,106 @@ final class SyncEngine
}
}
private JSONValue handleSharePointMetadataAdditionBugReplaceFile(JSONValue fileDetailsFromOneDrive, const ref Item parent, const(string) path)
{
// Explicit function for handling https://github.com/OneDrive/onedrive-api-docs/issues/935
// Replace existing file
JSONValue response;
// Depending on the file size, this will depend on how best to handle the modified local file
// as if too large, the following error will be generated by OneDrive:
// HTTP request returned status code 413 (Request Entity Too Large)
// We also cant use a session to upload the file, we have to use simpleUploadReplace
// Calculate existing hash for this file
string existingFileHash = computeQuickXorHash(path);
if (getSize(path) <= thresholdFileSize) {
// Upload file via simpleUploadReplace as below threshold size
try {
response = onedrive.simpleUploadReplace(path, fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str);
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return response;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
} else {
// Have to upload via a session, however we have to delete the file first otherwise this will generate a 404 error post session upload
// Remove the existing file
onedrive.deleteById(fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str);
// Upload as a session, as a new file
writeln("");
try {
response = session.upload(path, parent.driveId, parent.id, baseName(path));
} catch (OneDriveException e) {
if (e.httpStatusCode == 401) {
// OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded
writeln("skipped.");
log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error");
uploadFailed = true;
return response;
} else {
// display what the error is
writeln("skipped.");
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
} catch (FileException e) {
// display the error message
writeln("skipped.");
displayFileSystemErrorMessage(e.msg, getFunctionName!({}));
uploadFailed = true;
return response;
}
}
writeln("done.");
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
// So - now the 'local' and 'remote' file is technically DIFFERENT ... thanks Microsoft .. NO way to disable this stupidity
string uploadNewFileHash;
if (hasQuickXorHash(response)) {
// use the response json hash detail to compare
uploadNewFileHash = response["file"]["hashes"]["quickXorHash"].str;
}
if (existingFileHash != uploadNewFileHash) {
// file was modified by Microsoft post upload to SharePoint site
log.vdebug("Existing Local File Hash: ", existingFileHash);
log.vdebug("New Remote File Hash: ", uploadNewFileHash);
if(!uploadOnly){
// Download the Microsoft 'modified' file so 'local' is now in sync
log.vlog("Due to Microsoft Sharepoint 'enrichment' of files, downloading 'enriched' file to ensure local file is in-sync");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
auto fileSize = response["size"].integer;
onedrive.downloadById(response["parentReference"]["driveId"].str, response["id"].str, path, fileSize);
} else {
// we are not downloading a file, warn that file differences will exist
log.vlog("WARNING: Due to Microsoft Sharepoint 'enrichment' of files, this file is now technically different to your local copy");
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
}
}
// return a JSON response so that it can be used and saved
return response;
}
// delete an item on OneDrive
private void uploadDeleteItem(Item item, const(string) path)
{
@ -5212,11 +5326,18 @@ final class SyncEngine
if (jsonItem.type() == JSONType.object){
// Check if the response JSON has an 'id', otherwise makeItem() fails with 'Key not found: id'
if (hasId(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
log.vdebug("Adding to database: ", item);
itemdb.upsert(item);
// Are we in a --upload-only & --remove-source-files scenario?
// We do not want to add the item to the database in this situation as there is no local reference to the file post file deletion
if ((uploadOnly) && (localDeleteAfterUpload)) {
// Log that we are deleting a local item
log.vdebug("Skipping adding to database as --upload-only & --remove-source-files configured");
} else {
// 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
log.vdebug("Adding to database: ", item);
itemdb.upsert(item);
}
} else {
// log error
log.error("ERROR: OneDrive response missing required 'id' element");
@ -5356,9 +5477,19 @@ final class SyncEngine
void deleteByPath(const(string) path)
{
Item item;
if (!itemdb.selectByPath(path, defaultDriveId, item)) {
// Need to check all driveid's we know about, not just the defaultDriveId
bool itemInDB = false;
foreach (searchDriveId; driveIDsArray) {
if (itemdb.selectByPath(path, searchDriveId, item)) {
// item was found in the DB
itemInDB = true;
break;
}
}
if (!itemInDB) {
throw new SyncException("The item to delete is not in the local database");
}
if (item.parentId == null) {
// the item is a remote folder, need to do the operation on the parent
enforce(itemdb.selectByPathWithoutRemote(path, defaultDriveId, item));
@ -5418,93 +5549,158 @@ final class SyncEngine
string site_id;
string drive_id;
string webUrl;
bool found = false;
JSONValue siteQuery;
JSONValue siteQuery;
string nextLink;
string[] siteSearchResults;
log.log("Office 365 Library Name Query: ", o365SharedLibraryName);
try {
siteQuery = onedrive.o365SiteSearch();
} catch (OneDriveException e) {
log.error("ERROR: Query of OneDrive for Office 365 Library Name failed");
if (e.httpStatusCode == 403) {
// Forbidden - most likely authentication scope needs to be updated
log.error("ERROR: Authentication scope needs to be updated. Use --logout and re-authenticate client.");
return;
} else {
// display what the error is
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
return;
}
}
// is siteQuery a valid JSON object & contain data we can use?
if ((siteQuery.type() == JSONType.object) && ("value" in siteQuery)) {
// valid JSON object
log.vdebug("O365 Query Response: ", siteQuery);
foreach (searchResult; siteQuery["value"].array) {
// Need an 'exclusive' match here with o365SharedLibraryName as entered
log.vdebug("Found O365 Site: ", searchResult);
// 'displayName', 'id' and 'webUrl' have to be present in the search result record
if (("displayName" in searchResult) && ("id" in searchResult) && ("webUrl" in searchResult)) {
if (o365SharedLibraryName == searchResult["displayName"].str){
// 'displayName' matches search request
site_id = searchResult["id"].str;
webUrl = searchResult["webUrl"].str;
JSONValue siteDriveQuery;
try {
siteDriveQuery = onedrive.o365SiteDrives(site_id);
} catch (OneDriveException e) {
log.error("ERROR: Query of OneDrive for Office Site ID failed");
// display what the error is
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
return;
}
// is siteDriveQuery a valid JSON object & contain data we can use?
if ((siteDriveQuery.type() == JSONType.object) && ("value" in siteDriveQuery)) {
// valid JSON object
foreach (driveResult; siteDriveQuery["value"].array) {
// Display results
found = true;
writeln("SiteName: ", searchResult["displayName"].str);
writeln("drive_id: ", driveResult["id"].str);
writeln("URL: ", webUrl);
}
} else {
// not a valid JSON object
log.error("ERROR: There was an error performing this operation on OneDrive");
log.error("ERROR: Increase logging verbosity to assist determining why.");
return;
}
for (;;) {
try {
siteQuery = onedrive.o365SiteSearch(nextLink);
} catch (OneDriveException e) {
log.error("ERROR: Query of OneDrive for Office 365 Library Name failed");
if (e.httpStatusCode == 403) {
// Forbidden - most likely authentication scope needs to be updated
log.error("ERROR: Authentication scope needs to be updated. Use --logout and re-authenticate client.");
return;
}
// HTTP request returned status code 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();
log.vdebug("Retrying original request that generated the OneDrive HTTP 429 Response Code (Too Many Requests) - attempting to query OneDrive drive children");
}
// HTTP request returned status code 504 (Gateway Timeout) or 429 retry
if ((e.httpStatusCode == 429) || (e.httpStatusCode == 504)) {
// re-try the specific changes queries
if (e.httpStatusCode == 504) {
log.log("OneDrive returned a 'HTTP 504 - Gateway Timeout' when attempting to query Sharepoint Sites - retrying applicable request");
log.vdebug("siteQuery = onedrive.o365SiteSearch(nextLink) previously threw an error - retrying");
// The server, while acting as a proxy, did not receive a timely response from the upstream server it needed to access in attempting to complete the request.
log.vdebug("Thread sleeping for 30 seconds as the server did not receive a timely response from the upstream server it needed to access in attempting to complete the request");
Thread.sleep(dur!"seconds"(30));
}
// re-try original request - retried for 429 and 504
try {
log.vdebug("Retrying Query: siteQuery = onedrive.o365SiteSearch(nextLink)");
siteQuery = onedrive.o365SiteSearch(nextLink);
log.vdebug("Query 'siteQuery = onedrive.o365SiteSearch(nextLink)' performed successfully on re-try");
} catch (OneDriveException e) {
// display what the error is
log.vdebug("Query Error: siteQuery = onedrive.o365SiteSearch(nextLink) on re-try after delay");
// error was not a 504 this time
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
return;
}
} else {
// 'displayName' not present in JSON results
log.error("ERROR: The results returned from OneDrive API do not contain the required items to match. Please check your permissions with your site administrator.");
log.error("ERROR: Your site security settings is preventing the following details from being accessed: 'displayName', 'id' and 'webUrl'");
log.error("ERROR: To debug this further, please use --verbose --verbose to provide insight as to what details are actually returned.");
// display what the error is
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
return;
}
}
if(!found) {
log.error("ERROR: The requested SharePoint site could not be found. Please check it's name and your permissions to access the site.");
// List all sites returned to assist user
log.log("\nThe following SharePoint site names were returned:");
// is siteQuery a valid JSON object & contain data we can use?
if ((siteQuery.type() == JSONType.object) && ("value" in siteQuery)) {
// valid JSON object
log.vdebug("O365 Query Response: ", siteQuery);
foreach (searchResult; siteQuery["value"].array) {
// list the display name that we use to match against the user query
log.log(" * ", searchResult["displayName"].str);
// Need an 'exclusive' match here with o365SharedLibraryName as entered
log.vdebug("Found O365 Site: ", searchResult);
// 'displayName' and 'id' have to be present in the search result record in order to query the site
if (("displayName" in searchResult) && ("id" in searchResult)) {
if (o365SharedLibraryName == searchResult["displayName"].str){
// 'displayName' matches search request
site_id = searchResult["id"].str;
JSONValue siteDriveQuery;
try {
siteDriveQuery = onedrive.o365SiteDrives(site_id);
} catch (OneDriveException e) {
log.error("ERROR: Query of OneDrive for Office Site ID failed");
// display what the error is
displayOneDriveErrorMessage(e.msg, getFunctionName!({}));
return;
}
// is siteDriveQuery a valid JSON object & contain data we can use?
if ((siteDriveQuery.type() == JSONType.object) && ("value" in siteDriveQuery)) {
// valid JSON object
foreach (driveResult; siteDriveQuery["value"].array) {
// Display results
writeln("-----------------------------------------------");
log.vdebug("Site Details: ", driveResult);
found = true;
writeln("Site Name: ", searchResult["displayName"].str);
writeln("Library Name: ", driveResult["name"].str);
writeln("drive_id: ", driveResult["id"].str);
writeln("Library URL: ", driveResult["webUrl"].str);
}
// closeout
writeln("-----------------------------------------------");
} else {
// not a valid JSON object
log.error("ERROR: There was an error performing this operation on OneDrive");
log.error("ERROR: Increase logging verbosity to assist determining why.");
return;
}
}
} else {
// 'displayName', 'id' or ''webUrl' not present in JSON results for a specific site
string siteNameAvailable = "Site 'name' was restricted by OneDrive API permissions";
bool displayNameAvailable = false;
bool idAvailable = false;
if ("name" in searchResult) siteNameAvailable = searchResult["name"].str;
if ("displayName" in searchResult) displayNameAvailable = true;
if ("id" in searchResult) idAvailable = true;
// Display error details for this site data
log.error("\nERROR: SharePoint Site details not provided for: ", siteNameAvailable);
log.error("ERROR: The SharePoint Site results returned from OneDrive API do not contain the required items to match. Please check your permissions with your site administrator.");
log.error("ERROR: Your site security settings is preventing the following details from being accessed: 'displayName' or 'id'");
log.vlog(" - Is 'displayName' available = ", displayNameAvailable);
log.vlog(" - Is 'id' available = ", idAvailable);
log.error("ERROR: To debug this further, please increase verbosity (--verbose or --verbose --verbose) to provide further insight as to what details are actually being returned.");
}
}
if(!found) {
// The SharePoint site we are searching for was not found in this bundle set
// Add to siteSearchResults so we can display what we did find
string siteSearchResultsEntry;
foreach (searchResult; siteQuery["value"].array) {
siteSearchResultsEntry = " * " ~ searchResult["displayName"].str;
siteSearchResults ~= siteSearchResultsEntry;
}
}
} else {
// not a valid JSON object
log.error("ERROR: There was an error performing this operation on OneDrive");
log.error("ERROR: Increase logging verbosity to assist determining why.");
return;
}
// If a collection exceeds the default page size (200 items), the @odata.nextLink property is returned in the response
// to indicate more items are available and provide the request URL for the next page of items.
if ("@odata.nextLink" in siteQuery) {
// Update nextLink to next set of SharePoint library names
nextLink = siteQuery["@odata.nextLink"].str;
log.vdebug("Setting nextLink to (@odata.nextLink): ", nextLink);
} else break;
}
// Was the intended target found?
if(!found) {
log.error("\nERROR: The requested SharePoint site could not be found. Please check it's name and your permissions to access the site.");
// List all sites returned to assist user
log.log("\nThe following SharePoint site names were returned:");
foreach (searchResultEntry; siteSearchResults) {
// list the display name that we use to match against the user query
log.log(searchResultEntry);
}
} else {
// not a valid JSON object
log.error("ERROR: There was an error performing this operation on OneDrive");
log.error("ERROR: Increase logging verbosity to assist determining why.");
return;
}
}
@ -6209,8 +6405,8 @@ final class SyncEngine
// to indicate more items are available and provide the request URL for the next page of items.
if ("@odata.nextLink" in thisLevelChildren) {
// Update nextLink to next changeSet bundle
log.vdebug("Setting nextLink to (@odata.nextLink): ", nextLink);
nextLink = thisLevelChildren["@odata.nextLink"].str;
log.vdebug("Setting nextLink to (@odata.nextLink): ", nextLink);
} else break;
}