private FileSystemTaskResult ExecuteBackgroundOperation()

in GVFS/GVFS.Virtualization/FileSystemCallbacks.cs [670:946]


        private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate)
        {
            EventMetadata metadata = new EventMetadata();

            FileSystemTaskResult result;

            switch (gitUpdate.Operation)
            {
                case FileSystemTask.OperationType.OnFileCreated:
                case FileSystemTask.OperationType.OnFailedPlaceholderDelete:
                case FileSystemTask.OperationType.OnFileSymLinkCreated:
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    break;

                case FileSystemTask.OperationType.OnFileHardLinkCreated:
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                    result = FileSystemTaskResult.Success;
                    if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
                    }

                    if ((result == FileSystemTaskResult.Success) &&
                        !string.IsNullOrEmpty(gitUpdate.VirtualPath) && !IsPathInsideDotGit(gitUpdate.VirtualPath))
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    }

                    break;

                case FileSystemTask.OperationType.OnFileRenamed:
                    metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    result = FileSystemTaskResult.Success;
                    if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
                    {
                        if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
                        {
                            result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: false);
                        }
                        else
                        {
                            result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
                        }
                    }

                    if (result == FileSystemTaskResult.Success &&
                        !string.IsNullOrEmpty(gitUpdate.VirtualPath) &&
                        !IsPathInsideDotGit(gitUpdate.VirtualPath))
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    }

                    break;

                case FileSystemTask.OperationType.OnFilePreDelete:
                    // This code assumes that the current implementations of FileSystemVirtualizer will call either
                    // the PreDelete or the Delete not both so if a new implementation starts calling both
                    // this will need to be cleaned up to not duplicate the work that is being done.
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                    {
                        string fullPathToFile = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);

                        // Because this is a predelete message the file could still be on disk when we make this check
                        // so we retry for a limited time before deciding the delete didn't happen
                        bool fileDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.FileExists(fullPathToFile), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
                        if (fileDeleted)
                        {
                            result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
                        }
                        else
                        {
                            result = FileSystemTaskResult.Success;
                        }
                    }
                    else
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    }

                    break;

                case FileSystemTask.OperationType.OnFileDeleted:
                    // This code assumes that the current implementations of FileSystemVirtualizer will call either
                    // the PreDelete or the Delete not both so if a new implementation starts calling both
                    // this will need to be cleaned up to not duplicate the work that is being done.
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
                    }
                    else
                    {
                        result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    }

                    break;

                case FileSystemTask.OperationType.OnFileOverwritten:
                case FileSystemTask.OperationType.OnFileSuperseded:
                case FileSystemTask.OperationType.OnFileConvertedToFull:
                case FileSystemTask.OperationType.OnFailedPlaceholderUpdate:
                case FileSystemTask.OperationType.OnFailedFileHydration:
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
                    break;

                case FileSystemTask.OperationType.OnFolderCreated:
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);

                    break;

                case FileSystemTask.OperationType.OnFolderRenamed:
                    result = FileSystemTaskResult.Success;
                    metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);

                    if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) &&
                        this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: true);
                    }

                    // An empty destination path means the folder was renamed to somewhere outside of the repo
                    // Note that only full folders can be moved\renamed, and so there will already be a recursive
                    // sparse-checkout entry for the virtualPath of the folder being moved (meaning that no
                    // additional work is needed for any files\folders inside the folder being moved)
                    if (result == FileSystemTaskResult.Success && !string.IsNullOrEmpty(gitUpdate.VirtualPath))
                    {
                        this.AddToNewlyCreatedList(gitUpdate.VirtualPath, isFolder: true);
                        result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                        if (result == FileSystemTaskResult.Success)
                        {
                            Queue<string> relativeFolderPaths = new Queue<string>();
                            relativeFolderPaths.Enqueue(gitUpdate.VirtualPath);

                            // Remove old paths from modified paths if in the newly created list
                            while (relativeFolderPaths.Count > 0)
                            {
                                string folderPath = relativeFolderPaths.Dequeue();
                                if (result == FileSystemTaskResult.Success)
                                {
                                    try
                                    {
                                        foreach (DirectoryItemInfo itemInfo in this.context.FileSystem.ItemsInDirectory(Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, folderPath)))
                                        {
                                            string itemVirtualPath = Path.Combine(folderPath, itemInfo.Name);
                                            string oldItemVirtualPath = gitUpdate.OldVirtualPath + itemVirtualPath.Substring(gitUpdate.VirtualPath.Length);

                                            this.AddToNewlyCreatedList(itemVirtualPath, isFolder: itemInfo.IsDirectory);
                                            if (this.newlyCreatedFileAndFolderPaths.Contains(oldItemVirtualPath))
                                            {
                                                result = this.TryRemoveModifiedPath(oldItemVirtualPath, isFolder: itemInfo.IsDirectory);
                                            }

                                            if (itemInfo.IsDirectory)
                                            {
                                                relativeFolderPaths.Enqueue(itemVirtualPath);
                                            }
                                        }
                                    }
                                    catch (DirectoryNotFoundException)
                                    {
                                        // DirectoryNotFoundException can occur when the renamed folder (or one of its children) is
                                        // deleted prior to the background thread running
                                        EventMetadata exceptionMetadata = new EventMetadata();
                                        exceptionMetadata.Add("Area", "ExecuteBackgroundOperation");
                                        exceptionMetadata.Add("Operation", gitUpdate.Operation.ToString());
                                        exceptionMetadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
                                        exceptionMetadata.Add("virtualPath", gitUpdate.VirtualPath);
                                        exceptionMetadata.Add(TracingConstants.MessageKey.InfoMessage, "DirectoryNotFoundException while traversing folder path");
                                        exceptionMetadata.Add("folderPath", folderPath);
                                        this.context.Tracer.RelatedEvent(EventLevel.Informational, "DirectoryNotFoundWhileUpdatingModifiedPaths", exceptionMetadata);
                                    }
                                    catch (IOException e)
                                    {
                                        metadata.Add("Details", "IOException while traversing folder path");
                                        metadata.Add("folderPath", folderPath);
                                        metadata.Add("Exception", e.ToString());
                                        result = FileSystemTaskResult.RetryableError;
                                        break;
                                    }
                                    catch (UnauthorizedAccessException e)
                                    {
                                        metadata.Add("Details", "UnauthorizedAccessException while traversing folder path");
                                        metadata.Add("folderPath", folderPath);
                                        metadata.Add("Exception", e.ToString());
                                        result = FileSystemTaskResult.RetryableError;
                                        break;
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }

                    break;

                case FileSystemTask.OperationType.OnFolderPreDelete:
                    // This code assumes that the current implementations of FileSystemVirtualizer will call either
                    // the PreDelete or the Delete not both so if a new implementation starts calling both
                    // this will need to be cleaned up to not duplicate the work that is being done.
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                    {
                        string fullPathToFolder = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);

                        // Because this is a predelete message the file could still be on disk when we make this check
                        // so we retry for a limited time before deciding the delete didn't happen
                        bool folderDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.DirectoryExists(fullPathToFolder), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
                        if (folderDeleted)
                        {
                            result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                        }
                        else
                        {
                            result = FileSystemTaskResult.Success;
                        }
                    }
                    else
                    {
                        result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    }

                    break;

                case FileSystemTask.OperationType.OnFolderDeleted:
                    // This code assumes that the current implementations of FileSystemVirtualizer will call either
                    // the PreDelete or the Delete not both so if a new implementation starts calling both
                    // this will need to be cleaned up to not duplicate the work that is being done.
                    metadata.Add("virtualPath", gitUpdate.VirtualPath);
                    if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
                    {
                        result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    }
                    else
                    {
                        result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
                    }

                    break;

                case FileSystemTask.OperationType.OnFolderFirstWrite:
                    result = FileSystemTaskResult.Success;
                    break;

                case FileSystemTask.OperationType.OnIndexWriteRequiringModifiedPathsValidation:
                    result = this.GitIndexProjection.AddMissingModifiedFiles();
                    break;

                case FileSystemTask.OperationType.OnPlaceholderCreationsBlockedForGit:
                    this.GitIndexProjection.ClearNegativePathCacheIfPollutedByGit();
                    result = FileSystemTaskResult.Success;
                    break;

                default:
                    throw new InvalidOperationException("Invalid background operation");
            }

            if (result != FileSystemTaskResult.Success)
            {
                metadata.Add("Area", "ExecuteBackgroundOperation");
                metadata.Add("Operation", gitUpdate.Operation.ToString());
                metadata.Add(TracingConstants.MessageKey.WarningMessage, "Background operation failed");
                metadata.Add(nameof(result), result.ToString());
                this.context.Tracer.RelatedEvent(EventLevel.Warning, "FailedBackgroundOperation", metadata);
            }

            return result;
        }