Пример #1
0
        /// <summary>
        /// Force to synchronize that data with the cloud.
        /// </summary>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        internal async Task SynchronizeAsync()
        {
            if (IsSynchronizing)
            {
                return;
            }

            Logger.Instance.Information("Synchronization with the cloud started.");
            IsSynchronizing = true;

            if (CurrentCloudStorageProvider == null)
            {
                Logger.Instance.Warning("The user is not logged to any cloud storage provider. The synchronization stopped.");
                IsSynchronizing = false;
                return;
            }

            if (!CoreHelper.IsUnitTesting() && !SystemInfoHelper.CheckForInternetConnection())
            {
                Logger.Instance.Warning("There is no internet connection. The synchronization stopped.");
                IsSynchronizing = false;
                return;
            }

            SynchronizationStarted?.Invoke(this, new EventArgs());

            try
            {
                if (!await CurrentCloudStorageProvider.TryAuthenticateAsync())
                {
                    Logger.Instance.Warning("The user is not authenticated correctly. Consider unlink the app and connect again. The synchronization stopped.");
                    IsSynchronizing = false;
                    SynchronizationEnded?.Invoke(this, new EventArgs());
                    return;
                }

                var userId = SecurityHelper.ToSecureString(await CurrentCloudStorageProvider.GetUserIdAsync());

                if (string.IsNullOrWhiteSpace(SecurityHelper.ToUnsecureString(userId)))
                {
                    Logger.Instance.Warning("The user's id from the cloud storage provider has not been found. The synchronization stopped.");
                    IsSynchronizing = false;
                    SynchronizationEnded?.Invoke(this, new EventArgs());
                    SynchronizationFailed?.Invoke(this, new EventArgs());
                    return;
                }

                Logger.Instance.Information("Freezing the data before synchronize.");
                var dataService = ServiceLocator.GetService <DataService>();
                var cloudDataEntryFromServer = new List <CloudDataEntry>();
                var cloudAppFolder           = await CurrentCloudStorageProvider.GetAppFolderAsync();

                var cloudDataEntryFilePath = Path.Combine(cloudAppFolder.FullPath, Consts.DataEntryFileName);
                var cloudDataPassword      = SecurityHelper.ToSecureString(SecurityHelper.EncryptString(userId, SecurityHelper.ToSecureString(await CurrentCloudStorageProvider.GetUserNameAsync())));
                var localFrozenDataEntries = DataHelper.FromByteArray <AsyncObservableCollection <DataEntry> >(DataHelper.ToByteArray(dataService.DataEntries));
                var localFrozenCache       = DataHelper.FromByteArray <List <DataEntryCache> >(DataHelper.ToByteArray(dataService.Cache));

                // Download data from server.
                if (cloudAppFolder.Files.Any(file => file.FullPath == cloudDataEntryFilePath))
                {
                    Logger.Instance.Information("Downloading the data entry file from the server.");
                    try
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            await CurrentCloudStorageProvider.DownloadFileAsync(cloudDataEntryFilePath, memoryStream);

                            memoryStream.Position = 0;
                            using (var aesStream = new AesStream(memoryStream, cloudDataPassword, SecurityHelper.GetSaltKeys(cloudDataPassword).GetBytes(16)))
                            {
                                var data = new byte[aesStream.Length];
                                aesStream.Read(data, 0, data.Length);
                                cloudDataEntryFromServer = JsonConvert.DeserializeObject <List <CloudDataEntry> >(Encoding.UTF8.GetString(data));
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        Logger.Instance.Warning($"Unable to download or read the data file entry from the cloud for the following reason : {exception.Message}");
                        IsSynchronizing = false;
                        SynchronizationEnded?.Invoke(this, new EventArgs());
                        SynchronizationFailed?.Invoke(this, new EventArgs());
                        return;
                    }
                }
                else
                {
                    Logger.Instance.Information("There is no data entry file on the server yet.");
                }

                // Synchronize locally the data. The result must corresponds to what we will have locally and on the server at the end of the synchronization process.
                var cloudDataEntryToServer = dataService.DifferenceLocalAndCloudDataEntries(cloudDataEntryFromServer);

                // Download the needed data from the server to the local machine.
                Logger.Instance.Information("Downloading the needed data from the server to the local machine.");
                var dataToDownload = cloudDataEntryFromServer.Cast <DataEntryBase>().Except(localFrozenDataEntries, (item1, item2) => item1.Identifier == item2.Identifier).ToList();
                var taskList       = new List <Task>();
                foreach (var cloudDataEntry in dataToDownload)
                {
                    if (dataToDownload.Any(data => localFrozenCache.Any(item => data.Identifier == item.Identifier && item.Status == DataEntryStatus.Deleted)))
                    {
                        continue;
                    }

                    foreach (var dataEntryDataIdentifier in cloudDataEntry.DataIdentifiers)
                    {
                        var task = DownloadDataFileAsync(dataService.ClipboardDataPath, cloudAppFolder, cloudDataPassword, dataEntryDataIdentifier);
                        taskList.Add(task);
                    }
                }
                await Task.WhenAll(taskList);

                // Delete the needed data from the server
                Logger.Instance.Information("Deleting the needed data from the server.");
                taskList = new List <Task>();
                foreach (var dataServiceDataEntry in localFrozenDataEntries.Where(item => !item.CanSynchronize))
                {
                    foreach (var dataEntryDataIdentifier in dataServiceDataEntry.DataIdentifiers)
                    {
                        var task = DeleteFileAsync(cloudAppFolder, dataEntryDataIdentifier);
                        taskList.Add(task);
                    }
                }
                await Task.WhenAll(taskList);

                taskList = new List <Task>();
                foreach (var cacheEntry in localFrozenCache.Where(item => item.Status == DataEntryStatus.Deleted))
                {
                    var dataEntry = cloudDataEntryFromServer.SingleOrDefault(item => item.Identifier == cacheEntry.Identifier);
                    if (dataEntry != null)
                    {
                        foreach (var dataEntryDataIdentifier in dataEntry.DataIdentifiers)
                        {
                            var task = DeleteFileAsync(cloudAppFolder, dataEntryDataIdentifier);
                            taskList.Add(task);
                        }
                    }
                }
                await Task.WhenAll(taskList);

                await dataService.MakeCacheSynchronized(cloudDataEntryToServer, true, localFrozenCache);

                localFrozenDataEntries = DataHelper.FromByteArray <AsyncObservableCollection <DataEntry> >(DataHelper.ToByteArray(dataService.DataEntries));
                localFrozenCache       = DataHelper.FromByteArray <List <DataEntryCache> >(DataHelper.ToByteArray(dataService.Cache));

                // Upload the needed data from the server to the local machine
                Logger.Instance.Information("Uploading the needed data from the server to the local machine.");
                var dataToUpload = localFrozenDataEntries.Cast <DataEntryBase>().Except(cloudDataEntryFromServer, (item1, item2) => item1.Identifier == item2.Identifier);
                taskList = new List <Task>();
                foreach (var dataEntry in dataToUpload)
                {
                    var localDataEntry = localFrozenDataEntries.Single(item => item.Identifier == dataEntry.Identifier);
                    if (!localDataEntry.CanSynchronize || localDataEntry.Thumbnail.Type == ThumbnailDataType.Files)
                    {
                        continue;
                    }

                    foreach (var dataEntryDataIdentifier in dataEntry.DataIdentifiers)
                    {
                        var task = UploadDataFileAsync(dataService.ClipboardDataPath, cloudAppFolder, cloudDataPassword, dataEntryDataIdentifier);
                        taskList.Add(task);
                    }
                }
                await Task.WhenAll(taskList);

                // Upload the new data to the server.
                Logger.Instance.Information("Uploading the data entry file to the server.");

                using (var memoryStream = new MemoryStream())
                    using (var aesStream = new AesStream(memoryStream, cloudDataPassword, SecurityHelper.GetSaltKeys(cloudDataPassword).GetBytes(16)))
                    {
                        var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cloudDataEntryToServer));
                        aesStream.Write(data, 0, data.Length);
                        aesStream.Position = 0;
                        await CurrentCloudStorageProvider.UploadFileAsync(memoryStream, cloudDataEntryFilePath);
                    }

                await dataService.MakeCacheSynchronized(cloudDataEntryToServer, false, localFrozenCache);
            }
            catch (Exception exception)
            {
                Logger.Instance.Warning($"Unable to synchronize for the following reason : {exception.Message}. {exception.InnerException?.Message}");
                SynchronizationFailed?.Invoke(this, new EventArgs());
            }

            _synchronizeTimer.Stop();
            _synchronizeTimer.Start();

            IsSynchronizing = false;
            SynchronizationEnded?.Invoke(this, new EventArgs());
        }
Пример #2
0
        private async Task SynchronizeAsync(CancellationToken cancellationToken)
        {
            // The semaphore acts like queue.
            using (await _sempahore.WaitAsync(cancellationToken).ConfigureAwait(false))
            {
                var synchronizationStarted = false;
                var succeeded = false;
                var requiresReloadLocalData = false;

                try
                {
                    if (!_settingsProvider.GetSetting(SettingsDefinitions.SyncDataWithCloud))
                    {
                        return;
                    }

                    string targetterProviderName = _settingsProvider.GetSetting(SettingsDefinitions.RemoteStorageProviderName);
                    IRemoteStorageProvider remoteStorageProvider = _remoteStorageProviders.SingleOrDefault(m => string.Equals(m.Metadata.ProviderName, targetterProviderName, StringComparison.Ordinal))?.Value;

                    if (remoteStorageProvider == null)
                    {
                        return;
                    }

                    if (!CoreHelper.IsInternetAccess())
                    {
                        return;
                    }

                    SynchronizationStarted?.Invoke(this, EventArgs.Empty);
                    synchronizationStarted = true;

                    if (!await remoteStorageProvider.SignInAsync(interactive: false, cancellationToken).ConfigureAwait(false))
                    {
                        // If fails to authenticate, disables synchronization and sign out.
                        _settingsProvider.SetSetting(SettingsDefinitions.SyncDataWithCloud, false);
                        await remoteStorageProvider.SignOutAsync().ConfigureAwait(false);

                        // TODO: Add a log to notify to let the user know it signed out and he should re-authenticate.

                        // returning here will still trigger the Finally block.
                        return;
                    }

                    // Retrieve the list of online files.
                    IReadOnlyList <RemoteFileInfo> roamingFiles =
                        await remoteStorageProvider.GetFilesAsync(Constants.DataFileCountLimit, cancellationToken).ConfigureAwait(false);

                    RemoteFileInfo roamingUserDataBundleFile = roamingFiles.FirstOrDefault(file
                                                                                           => string.Equals(Path.GetFileName(file.FullPath), Constants.UserDataBundleFileName, StringComparison.Ordinal));

                    IEnumerable <RemoteFileInfo> allOtherRoamingFiles = roamingFiles.Where(file
                                                                                           => !string.Equals(Path.GetFileName(file.FullPath), Constants.UserDataBundleFileName, StringComparison.Ordinal));

                    // Retrieve the list of local files.
                    var localUserDataFolder = await CoreHelper.GetOrCreateUserDataStorageFolderAsync().ConfigureAwait(false);

                    StorageFile localUserDataBundleFile = await localUserDataFolder.TryGetItemAsync(Constants.UserDataBundleFileName) as StorageFile;

                    IEnumerable <StorageFile> allOtherLocalFiles = (await localUserDataFolder.GetFilesAsync())
                                                                   .Where(file => !string.Equals(file.Name, Constants.UserDataBundleFileName, StringComparison.Ordinal));

                    if (localUserDataBundleFile == null && roamingUserDataBundleFile == RemoteFileInfo.Empty)
                    {
                        // Nothing locally and remotely?

                        succeeded = true;
                        return;
                    }

                    if (localUserDataBundleFile == null ||
                        (roamingUserDataBundleFile != RemoteFileInfo.Empty &&
                         roamingUserDataBundleFile.CreatedDateTime.ToUniversalTime()
                         > (await localUserDataBundleFile.GetBasicPropertiesAsync()).DateModified.ToUniversalTime()))
                    {
                        // If there is no local user data file, or that the file on the server is more recent than the local one,
                        // then we want to merge by taking the version from the server.

                        await DownloadRoamingDataFromServerAsync(
                            remoteStorageProvider,
                            allOtherRoamingFiles,
                            allOtherLocalFiles,
                            cancellationToken).ConfigureAwait(false);

                        // The local file changed, since we downloaded the one from the server, so let's indicate
                        // that we want to reload (and merge) the local data.
                        requiresReloadLocalData = true;
                    }
                    else
                    {
                        // Else, then it means the local file is more recent than the one on the server,
                        // or that there is simply no file on the server,
                        // so we want to merge by taking the version from the local file.

                        await UploadLocalDataToServerAsync(
                            remoteStorageProvider,
                            localUserDataBundleFile,
                            allOtherRoamingFiles,
                            allOtherLocalFiles,
                            cancellationToken).ConfigureAwait(false);
                    }

                    succeeded = true;
                }
                catch (OperationCanceledException)
                {
                    _logger.LogEvent(SynchronizeCanceledEvent, "The synchronization with the cloud has been canceled.");
                }
                catch (Exception ex)
                {
                    _logger.LogFault(SynchronizeFaultEvent, "Failed to synchronize the data with the cloud.", ex);
                }
                finally
                {
                    if (synchronizationStarted)
                    {
                        RaiseSynchronizationCompleted(succeeded, requiresReloadLocalData);
                    }
                }
            }
        }