Search code examples
c#google-apigoogle-drive-apigoogle-api-dotnet-clientdelta

Google Drive Delta Changes report moved folder but not the files inside that folder


I have implemented Google Drive delta changes logic in my program in C#.

 var request = service.Changes.List(pageToken);
 request.Spaces = "drive";
 request.IncludeRemoved = false;
 request.PageSize = 100;
 request.Fields = "changes(file (id,name,mimeType,createdTime,modifiedTime,size,parents,trashed),removed,kind,type),nextPageToken,newStartPageToken";
 var changes = request.Execute();

I am getting all the files and folders as delta changes when I upload the folder (containing some files) in Google Drive.

However, when I bring that folder (containing files) to the main folder via a move-to operation within Google Drive, I get delta changes containing only that folder I moved to the main folder, the files within that folder I moved are not reported in delta changes.

Is that the way Google Drive delta changes are designed? If yes, how do I identify whether the folder that is reported in delta changes has been moved so that I can then write custom logic to consider files within that folder as delta changes as a result of the move?

Please suggest

Thanks!


Solution

  • To investigate, I set up a little "playground" on my Google Drive in order to inject some changes in my polling loop. And when I drag Folder-To-Move from Folder-A to Folder-B by hand, we can see it "move", right?

    move folder

    But in a sense this is an illusion brought about by the fact that Folder-To-Move just got a new parent.

    how do I identify whether the folder that is reported in delta changes has been moved?

    When we get the Change that a folder has been modified, the first thing I've found can be helpful is to iterate its parents to get a full path. What happens next is a bit tricky to answer because you're maintaining synchronization of a local file system according to rules and locations only you could know. But as a start, you can compare the path relative to the cloud root to the local folder relative to the local root and do some detection that way.

    debug readout of new full path

    switch (change.File.MimeType)
    {
        case "application/vnd.google-apps.folder":
            var parent = change.File.Parents.FirstOrDefault();
            var builder = new List<string> { change.File.Name };
            while (parent is string pid)
            {
                FilesResource.GetRequest @get = DriveService.Files.Get(pid);
                @get.Fields = "*";
                var file = @get.Execute();
                builder.Add(file.Name);
                parent = file.Parents?.FirstOrDefault();
            }
            builder.Reverse();
            var folderPath = string.Join($"{Path.DirectorySeparatorChar}", builder);
            Debug.WriteLine(folderPath);
            break;
        case "application/vnd.google-apps.document":
            break;
        default:
            break;
    }
    

    But what about Document in Moved Folder. Has it really "moved" in the sense of did it get a new parent? No, it has not, and for that reason there is no change notification. But it has moved in the sense of does it have a different relative path now? Yes, it does. And if you need that info, you can iterate it the same manner. In other words, using the change.FileID of the folder, you could (for example) recursively query for all its children (and their children...) and then from that collection iterate back up from the child elements to get their new relative paths.


    What I personally do is maintain an index in an SQLite database on my local drive that links the cloud id to the local path, and I find this makes it easier to reconcile the two filesystems.


    Hope that gives you a start.


    Full context

    I hope I didn't leave out any important detail, but here's the entire polling loop just in case.

    public CancellationTokenSource CancelPolling { get; private set; } = new CancellationTokenSource();
    private async Task CheckForChangesAtInterval()
    {
        TimeSpan INTERVAL = TimeSpan.FromSeconds(15); // Short, for debug purposes.
        var changes = DriveService.Changes;
        // DEBUG ONLY. Will only detect changes since the start of this app.
        var pageToken =
            changes
            .GetStartPageToken()  
            .Execute()
            .StartPageTokenValue;
        while (!CancelPolling.IsCancellationRequested)
        {
            pageToken = await getChanges(pageToken);
            await Task.Delay(INTERVAL);
        }
    }
    private async Task<string> getChanges(string pageToken)
    {
        ChangeList rsp = null;
        while (pageToken != null)
        {
            if (CancelPolling.IsCancellationRequested) break;
            var request = DriveService.Changes.List(pageToken);
            request.Spaces = "drive";
            request.Fields = "*";
            try
            {
                await Task.Run(() =>
                {
                    rsp = request.Execute();
                });
            }
            catch (Exception ex)
            {
                Debug.Fail(ex.Message);
                break;
            }
            foreach (var change in rsp.Changes)
            {
                if (change.Removed == true)
                {
                    Debug.Assert(
                        change.File == null,
                        "Expecting that a deleted id has no usable metadata.");
                    Debug.WriteLine(
                        $@"ADVISORY: Cloud file id {change.FileId} has been DELETED.");
                }
                else
                {
                    switch (change.File.MimeType)
                    {
                        case "application/vnd.google-apps.folder":
                            var parent = change.File.Parents.FirstOrDefault();
                            var builder = new List<string> { change.File.Name };
                            while (parent is string pid)
                            {
                                FilesResource.GetRequest @get = DriveService.Files.Get(pid);
                                @get.Fields = "*";
                                var file = @get.Execute();
                                builder.Add(file.Name);
                                parent = file.Parents?.FirstOrDefault();
                            }
                            builder.Reverse();
                            var folderPath = string.Join($"{Path.DirectorySeparatorChar}", builder);
                            Debug.WriteLine($"Folder `{change.FileId}` has changed.");
                            Debug.WriteLine($"{folderPath}\n");
                            break;
                        case "application/vnd.google-apps.document": { /*...*/ } break;
                        default: { /*...*/ } break;
                    }
    
                    if (change.File.Trashed == true) { /*...*/ }
                    else { /*...*/ }
                }
                if (CancelPolling.IsCancellationRequested) break;
            }
            if (rsp.NewStartPageToken != null)
            {
                // Last page, save this token for the next polling interval
                break;
            }
            pageToken = rsp.NextPageToken;
        }
        return string.IsNullOrWhiteSpace(rsp?.NewStartPageToken) ? pageToken : rsp?.NewStartPageToken;
    }