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

Correct way to use OneDrive API to sync files

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

我已阅读 OneDrive Dev Center 上的文档,但我不明白 http 代码。 (仅限自学 C#)。

我有点理解我使用增量方法从 OneDrive 获取更改的文件,然后保存在本地,但我不知道具体如何,所以通过使用手动检查本地与 OneDrive 来绕过它GetAsync<> 方法。 与 API 中可能处理得更好的相比,我当前的实现(下面供参考)在我看来相当笨拙。

另外,好像没有反向'delta'的功能?也就是说,我在本地将文件写入应用程序,然后告诉 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;
    }

同步需要几个不同的步骤,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();
    

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

  2. 可以使用 delta API:

    检测层次结构的更改
    await this.userDrive.Drive.Special.AppRoot.Delta(previousDeltaToken).Request().GetAsync();
    

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

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

显然,前面的步骤需要了解 "last seen" 状态,因此您的应用程序需要以某种形式的 database/data 结构来跟踪此状态。我建议跟踪以下内容:

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

此外,如果使用 delta 方法,您需要存储 delta 响应中的 token 值。这是独立于项目的,因此需要存储在某个全局字段中。

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

应用更改
最后一步是将合并后的状态推送到需要的地方(例如,如果更改是本地的,则推送远程,如果更改是远程的,则推送本地,否则如果两个地方都有更改,则推送两个地方)。重要的是要确保此步骤发生时避免替换 "Change Detection" 步骤发生后写入的内容。在本地,您可能会通过在此过程中锁定文件来完成此操作,但是您不能对远程文件执行此操作。相反,您需要使用 etag 值来确保服务仅在状态仍然符合您的预期时才接受请求:

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