/* low level */
        private async Task <DriveItem> TransferDriveItem(IDriveProxy sourceDrive, IDriveProxy targetDrive, DriveItem driveItem)
        {
            (var itemName1, var itemName2) = this.GetItemNames(driveItem);
            var isLocal = sourceDrive == _localDrive;

            // item exists on target drive
            if (_syncMode == SyncMode.TwoWay && await targetDrive.ExistsAsync(driveItem))
            {
                // item is unchanged on target drive
                // actions: do nothing
                if (await targetDrive.GetLastWriteTimeUtcAsync(driveItem) == driveItem.LastModified())
                {
                    _logger.LogDebug($"{itemName1} already exists and is unchanged on target drive '{targetDrive.Name}'. Action(s): do nothing.");
                }

                // item was modified on target drive
                else
                {
                    if (!isLocal)
                    {
                        _logger.LogDebug($"{itemName1} already exists and was modified on target drive '{targetDrive.Name}'. Action(s): handle conflict.");
                        await this.EnsureLocalConflict(driveItem);
                    }
                }
            }

            // item does not exist on target drive
            // actions: transfer item
            else
            {
                _logger.LogInformation($"{itemName1} is not available on target drive '{targetDrive.Name}'. Action(s): transfer {itemName2}.");
                return(await this.InternalTransferDriveItem(sourceDrive, targetDrive, driveItem));
            }

            return(driveItem);
        }
        private async Task <bool> CheckConflict(Conflict conflict)
        {
            var remoteItem        = _context.RemoteStates.FirstOrDefault(current => current.GetItemPath() == conflict.OriginalFilePath);
            var originalDriveItem = conflict.OriginalFilePath.ToDriveItem(DriveItemType.File);

            // original file exists locally
            if (await _localDrive.ExistsAsync(originalDriveItem))
            {
                _logger.LogDebug($"Original file exists locally.");
                var conflictDriveItem = conflict.ConflictFilePath.ToDriveItem(DriveItemType.File);

                // conflict file exists locally
                // actions: do nothing - user must delete or rename conflict file manually
                if (await _localDrive.ExistsAsync(conflictDriveItem))
                {
                    _logger.LogDebug($"Conflict file exists locally. Action(s): do nothing.");
                }

                // conflict file does not exist locally, i.e. conflict is solved
                else
                {
                    _logger.LogDebug($"Conflict file does not exist locally.");

                    // remote file is tracked in database
                    if (remoteItem != null)
                    {
                        _logger.LogDebug($"Remote file is tracked in database.");

                        // hashes are equal
                        // actions: do nothing
                        if (await _localDrive.GetHashAsync(originalDriveItem) == remoteItem.QuickXorHash)
                        {
                            _logger.LogDebug($"File is unchanged. Action(s): do nothing.");
                        }

                        // actions: upload file and replace remote version
                        else
                        {
                            _logger.LogInformation($"File was modified. Action(s): upload and replace file.");

                            originalDriveItem = await _localDrive.ToFullDriveItem(originalDriveItem);

                            var driveItem = await _remoteDrive.CreateOrUpdateAsync(originalDriveItem);

                            _context.RemoteStates.Add(driveItem.ToRemoteState());
                        }
                    }

                    // remote file is not tracked in database (e.g. upload failed previously)
                    // actions: upload file
                    else
                    {
                        _logger.LogInformation($"Remote file is not tracked in database. Action(s): upload file.");

                        originalDriveItem = await _localDrive.ToFullDriveItem(originalDriveItem);

                        var driveItem = await _remoteDrive.CreateOrUpdateAsync(originalDriveItem);

                        _context.RemoteStates.Add(driveItem.ToRemoteState());
                    }

                    return(true);
                }
            }

            // original file does not exist locally
            // actions: do nothing - user must delete or rename conflict file manually
            else
            {
                _logger.LogDebug($"Original file does not exist locally. Action(s): do nothing.");
            }

            return(false);
        }
        private async Task <(DriveItem UpdateDriveItem, WatcherChangeTypes ChangeType)> SyncDriveItem(
            IDriveProxy sourceDrive,
            IDriveProxy targetDrive,
            DriveItem oldDriveItem,
            DriveItem newDriveItem)
        {
            DriveItem updatedDriveItem;

            (var itemName1, var itemName2) = this.GetItemNames(newDriveItem);
            var changeType = newDriveItem.GetChangeType(oldDriveItem);

            switch (changeType)
            {
            case WatcherChangeTypes.Changed:
            case WatcherChangeTypes.Created:

                // Item was created or modified on source drive
                // actions: create or modify on target drive
                _logger.LogInformation($"{itemName1} was created or modified on drive '{sourceDrive.Name}'. Action(s): Create or modify {itemName2} on drive '{targetDrive.Name}'.");
                updatedDriveItem = await this.TransferDriveItem(sourceDrive, targetDrive, newDriveItem);

                break;

            case WatcherChangeTypes.Deleted:

                // item was deleted on source drive
                // actions: delete item on target drive
                _logger.LogInformation($"{itemName1} was deleted on drive '{sourceDrive.Name}'. Action(s): Delete {itemName2} on drive '{targetDrive.Name}'.");

                if (await targetDrive.ExistsAsync(newDriveItem))
                {
                    updatedDriveItem = await targetDrive.DeleteAsync(newDriveItem);
                }
                else
                {
                    _logger.LogWarning($"Cannot delete {itemName2} because it does not exist on drive '{targetDrive.Name}'.");
                    throw new InvalidOperationException($"Cannot delete {itemName2} because it does not exist on drive '{targetDrive.Name}'.");
                }

                break;

            case WatcherChangeTypes.Renamed:

                // item was renamed / moved on source drive
                // actions: rename / move item on target drive
                _logger.LogInformation($"{itemName1} was renamed / moved on drive '{sourceDrive.Name}'. Action(s): Rename / move {itemName2} on drive '{targetDrive.Name}'.");

                if (await targetDrive.ExistsAsync(newDriveItem))
                {
                    _logger.LogWarning($"Cannot move {itemName2} because the target {itemName2} already exists on drive '{targetDrive.Name}'.");
                    throw new InvalidOperationException($"Cannot move {itemName2} because the target {itemName2} already exists on drive '{targetDrive.Name}'.");
                }
                else
                {
                    updatedDriveItem = await targetDrive.MoveAsync(oldDriveItem, newDriveItem);
                }

                break;

            default:
                updatedDriveItem = newDriveItem;
                break;
            }

            return(updatedDriveItem, changeType);
        }