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

4

我找不到任何文件说明如何正确使用OneDrive在C#中存储和同步应用程序文件跨设备。

我已经阅读了OneDrive开发中心的文档,但我不懂http代码(仅自学C#)。

我有点理解使用增量方法从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;
    }

不确定您想要做什么。Windows 10有一个OneDrive文件夹,您可以设置为已经同步本地文件。 - Ken Tucker
据我所知,该文件夹仅可在台式机上使用,手机设备上不可用(可能平板电脑可以,但我没有验证过)。尽管如此,目标是将数据保存到本地设备,将文件同步到OneDrive,以便其他设备可以保持最新状态(并执行相同操作)。数据文件过大,无法使用“RoamingData”。 - Lindsay
2
如果您喜欢,可以使用C# SDK。https://github.com/onedrive/onedrive-sdk-csharp 请记住,OneDrive只能存储文件的最新版本。如果您需要进行任何同步操作(例如将本地和OneDrive版本的数据合并),则必须在代码中完成。 - Ken Tucker
1个回答

7

同步需要几个不同的步骤,其中一些由OneDrive API帮助您完成,而另一些则需要您自己完成。

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

  1. Changes for individual files can be detected using a standard request with an If-None-Match:

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

    If the file doesn't yet exist at all you'll get back a 404 Not Found. Else if the file has not changed you'll get back a 304 Not Modified.
    Else you'll get the current state of the file.

  2. Changes for a hierarchy can be detected using the delta API:

    await this.userDrive.Drive.Special.AppRoot.Delta(previousDeltaToken).Request().GetAsync();
    

    This will return the current state for all items that changed since the last invocation of delta. If this is the first invocation, previousDeltaToken will be null and the API will return the current state for ALL items within the AppRoot. For each file in the response you'll need to make another round-trip to the service to get the content.

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

显然,前面的步骤需要知道“上次查看”的状态,因此您的应用程序需要以某种形式跟踪它,可以使用数据库/数据结构来记录以下内容:

+------------------+---------------------------------------------------------------------------+
|     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                                           |
+------------------+---------------------------------------------------------------------------+

此外,如果使用“增量”方法,则需要存储来自“增量”响应的“令牌”值。这是独立于项目的,因此需要存储在某个全局字段中。
冲突解决: 如果双方都检测到了更改,则您的应用程序将需要经过冲突解决过程。缺乏对正在同步的文件的理解的应用程序需要提示用户进行手动冲突解决,或者执行类似分叉文件的操作,以便现在有两个副本。然而,处理自定义文件格式的应用程序应该具有足够的知识,可以有效地合并文件,而无需任何形式的用户交互。这需要完全取决于正在同步的文件。
应用更改: 最后一步是将合并状态推送到所需的位置(例如,如果更改是本地的,则推送远程,如果更改是远程的,则推送本地,否则如果更改在两个位置,则推送两个位置)。重要的是要确保以避免替换在“更改检测”步骤之后编写的内容的方式进行此步骤。在本地,您可能会通过在过程中锁定文件来实现此目的,但是您无法对远程文件执行此操作。相反,您将想要使用etag值,以确保服务仅在状态仍然符合预期时才接受请求。
await this.userDrive.Drive.Special.AppRoot.ItemWithPath(remotePath).Content.Request(new Option[] { new HeaderOption("If-Match", "etag") }).PutAsync(newContentStream);

感谢您提供详细的回复,看起来这正是我需要处理的信息。我会在本周晚些时候尝试并在成功后将其标记为答案。关于“PreviousDeltaToken”的澄清,这是否意味着应用程序需要在初始调用后本地存储该令牌,以便在调用之间进行比较?如果是这样,那么它与您提到的“DeltaLink”值有何不同? - Lindsay
1
感谢您指出我的 deltaLink 引用 - 当您处理 C# SDK 时,它不适用,因此我已将其更新为 token。它应该是从 delta 调用的响应中的一个属性。您需要将其存储,因为这是系统知道您上次调用的时间,以便只返回那些已更改的内容。 - Brad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接