使用OneDrive API同步文件的正确方法

Lin*_*say 1 c# onedrive uwp

我找不到任何文档来概述使用OneDrive存储和保持应用程序文件在C#中跨设备同步的正确方法的任何文档

我已经在OneDrive开发中心阅读了文档,但是我不理解http代码。(仅限自学C#)。

我有点理解,我使用delta方法从OneDrive中获取更改的文件,然后将其保存到本地,但是我不知道确切的方式,因此通过使用GetAsync<>方法手动检查本地与OneDrive来解决它。与可能在API中处理得更好的东西相比,我目前的实现(下面作为参考)似乎比较笨拙。

此外,似乎没有反向“增量”功能吗?也就是说,我在本地将文件写入应用程序,然后告诉OneDrive同步更改。那是因为我需要使用该PutAsync<>方法实际上传它吗?(目前我在做什么)

public async Task<T> ReadFromXML<T>(string gamename, string filename)
    {
        string filepath = _appFolder + @"\" + gamename + @"\" + filename + ".xml";
        T objectFromXML = default(T);
        var srializer = new XmlSerializer(typeof(T));
        Item oneDItem = null;
        int casenum = 0;
        //_userDrive is the IOneDriveClient
        if (_userDrive != null && _userDrive.IsAuthenticated)
        {
            try
            {
                oneDItem = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Request().GetAsync();
                if (oneDItem != null) casenum += 1;
            }
            catch (OneDriveException)
            { }
        }
        StorageFile localfile = null;
        try
        {
            localfile = await ApplicationData.Current.LocalFolder.GetFileAsync(filepath);
            if (localfile != null) casenum += 2;
        }
        catch (FileNotFoundException)
        { }
        switch (casenum)
        {
            case 0:
                //neither exist. Throws exception to tbe caught by the calling method, which should then instantiate a new object of type <T>
                throw new FileNotFoundException();
            case 1:
                //OneDrive only - should copy the stream to a new local file then return the object
                StorageFile writefile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filepath, CreationCollisionOption.ReplaceExisting);
                using (var newlocalstream = await writefile.OpenStreamForWriteAsync())
                {
                    using (var oneDStream = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Content.Request().GetAsync())
                    {
                        oneDStream.CopyTo(newlocalstream);
                    }
                }
                using (var newreadstream = await writefile.OpenStreamForReadAsync())
                { objectFromXML = (T)srializer.Deserialize(newreadstream); }
                break;
            case 2:
                //Local only - returns the object
                using (var existinglocalstream = await localfile.OpenStreamForReadAsync())
                { objectFromXML = (T)srializer.Deserialize(existinglocalstream); }
                break;
            case 3:
                //Both - compares last modified. If OneDrive, replaces local data then returns the object
                var localinfo = await localfile.GetBasicPropertiesAsync();
                var localtime = localinfo.DateModified;
                var oneDtime = (DateTimeOffset)oneDItem.FileSystemInfo.LastModifiedDateTime;
                switch (oneDtime > localtime)
                {
                    case true:
                        using (var newlocalstream = await localfile.OpenStreamForWriteAsync())
                        {
                            using (var oneDStream = await _userDrive.Drive.Special.AppRoot.ItemWithPath(filepath).Content.Request().GetAsync())
                            { oneDStream.CopyTo(newlocalstream); }
                        }
                        using (var newreadstream = await localfile.OpenStreamForReadAsync())
                        { objectFromXML = (T)srializer.Deserialize(newreadstream); }
                        break;
                    case false:
                        using (var existinglocalstream = await localfile.OpenStreamForReadAsync())
                        { objectFromXML = (T)srializer.Deserialize(existinglocalstream); }
                        break;
                }
                break;
        }
        return objectFromXML;
    }
Run Code Online (Sandbox Code Playgroud)

Bra*_*rad 5

同步需要几个不同的步骤,其中一些可以通过OneDrive API来帮助您,有些则需要您自己完成。

更改检测
第一步显然是检测是否有任何更改。OneDrive API提供了两种机制来检测服务中的更改:

  1. 可以使用带有以下内容的标准请求来检测单个文件的更改If-None-Match

    await this.userDrive.Drive.Special.AppRoot.ItemWithPath(remotePath).Content.Request(new Option[] { new HeaderOption("If-None-Match", "etag") }).GetAsync();
    
    Run Code Online (Sandbox Code Playgroud)

    如果该文件根本不存在,则会返回一个404 Not Found。否则,如果文件未更改,则会返回一个304 Not Modified
    否则,您将获得文件的当前状态。

  2. 可以使用deltaAPI 检测层次结构的更改:

    await this.userDrive.Drive.Special.AppRoot.Delta(previousDeltaToken).Request().GetAsync();
    
    Run Code Online (Sandbox Code Playgroud)

    这将返回自上次调用以来已更改的所有项目的当前状态delta。如果这是第一次调用,previousDeltaToken则将为null,并且API将返回中所有项目的当前状态AppRoot。对于响应中的每个文件,您都需要再次往返于服务以获取内容。

在本地,您需要枚举所有感兴趣的文件,并比较时间戳以确定是否已更改。

显然,前面的步骤需要了解“最后看到”状态,因此您的应用程序将需要以某种形式的数据库/数据结构来跟踪此状态。我建议跟踪以下内容:

+------------------+---------------------------------------------------------------------------+
|     Property     |                                   Why?                                    |
+------------------+---------------------------------------------------------------------------+
| Local Path       | You'll need this so that you can map a local file to its service identity |
| Remote Path      | You'll need this if you plan to address the remote file by path              |
| Remote Id        | You'll need this if you plan to address the remote file by unique id         |
| Hash             | The hash representing the current state of the file                       |
| Local Timestamp  | Needed to detect local changes                                            |
| Remote Timestamp | Needed for conflict resolution                                            |
| Remote ETag      | Needed to detect remote changes                                           |
+------------------+---------------------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

此外,如果使用此delta方法,则需要存储响应中的tokendelta。这是与项目无关的,因此需要存储在某些全局字段中。

冲突解决
如果双方都检测到更改,则您的应用将需要经历冲突解决过程。如果一个应用程序不了解要同步的文件,则可能需要提示用户进行手动冲突解决,或者执行类似分叉文件的操作,因此现在有两个副本。但是,处理自定义文件格式的应用程序应具有足够的知识,可以有效地合并文件,而无需任何形式的用户交互。显然,这完全取决于要同步的文件。

应用更改
最后一步是将合并状态推到所需的任何位置(例如,如果更改是本地的,则推到远程,如果更改是远程的,则推到本地,否则,如果更改在两个地方都推到两个位置)。确保此步骤的发生方式很重要,以避免替换发生“更改检测”步骤之后写入的内容。在本地,您可能会在此过程中通过锁定文件来完成此操作,但是您不能使用远程文件来做到这一点。相反,您将希望使用etag值来确保服务仅在状态仍为您期望的状态时才接受请求:

await this.userDrive.Drive.Special.AppRoot.ItemWithPath(remotePath).Content.Request(new Option[] { new HeaderOption("If-Match", "etag") }).PutAsync(newContentStream);
Run Code Online (Sandbox Code Playgroud)