Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
548 views
in Technique[技术] by (71.8m points)

c# - Correct way to use OneDrive API to sync files

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#

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

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.

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;
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.

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

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

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.

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...