使用OneDrive API同步文件的正确方法 [英] Correct way to use OneDrive API to sync files

查看:101
本文介绍了使用OneDrive API同步文件的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I can't find any documentation that outlines the correct way to use OneDrive to store and keep app files syncrhonised across devices in C#

我已阅读 OneDrive开发中心上的文档,但我不理解http代码. (仅限自学C#).

I have read the documentation at OneDrive Dev Center but I don't understand the http code. (self taught C# only).

我有点理解,我使用delta方法从OneDrive中获取更改的文件,然后将其保存到本地,但是我不知道具体如何,所以通过使用方法. 与可能在API中处理得更好的东西相比,我目前的实现(下面作为参考)似乎比较笨拙.

I kind of understand that I use the delta method to get changed files from OneDrive, to then save locally, but I can't figure out exactly how, so have gotten around it by checking local vs OneDrive manually using the GetAsync<> methods. My current implementation (below for reference) seems to me to be rather clunky compared to what is probably handled better in the API.

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

In addition, it doesn't appear that there is a reverse 'delta' function? That is, where I write a file to the app locally, then tell OneDrive to sync the change. Is that because I need to actually upload it using the PutAsync<> method? (Currently what I am doing)

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来帮助您,有些则需要您自己完成.

Synchronization requires a few different steps, some of which the OneDrive API will help you with, some of which you'll have to do yourself.

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

Change Detection
The first stage is obviously to detect whether anything has changed. The OneDrive API provides two mechanism to detect changes in the service:

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

  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();

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

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.

可以使用delta API来检测层次结构的更改:

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

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

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

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.

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

On the local side you'll need to enumerate all of the files of interest and compare the timestamps to determine if something has changed.

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

Obviously the previous steps require knowledge of the "last seen" state, and so your application will need to keep track of this in some form of database/data structure. I'd suggest tracking the following:

+------------------+---------------------------------------------------------------------------+
|     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值.这是与项目无关的,因此需要存储在某些全局字段中.

Additionally, if using the delta approach you'll need to store the token value from the delta response. This is item independent, so would need to be stored in some global field.

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

Conflict Resolution
If changes were detected on both sides your app will need to go through a conflict resolution process. An app that lacks an understanding of the files being synced would need to either prompt the user for manual conflict resolution, or do something like fork the file so there are now two copies. However, apps that are dealing with custom file formats should have enough knowledge to effectively merge the files without any form of user interaction. What that entails is obviously completely dependent on the file being synced.

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

Apply Changes
The final step is to push the merged state to wherever is required (e.g. if the change was local then push remote, if the change was remote then push local, otherwise if the change was in both places push both places). It's important to make sure this step occurs in such a way as to avoid replacing content that was written after the "Change Detection" step has taken place. Locally you'd probably accomplish this by locking the file during the process, however you cannot do that with the remote file. Instead you'll want to use the etag value to make sure the service only accepts the request if the state is still what you expect:

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

这篇关于使用OneDrive API同步文件的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆