Gestion des modifications d’affichage

À mesure que les fichiers et les répertoires sous la racine de virtualisation sont ouverts, le fournisseur crée des espaces réservés sur le disque et, à mesure que les fichiers sont lus, les espaces réservés sont hydratés avec du contenu. Ces espaces réservés représentent l’état du magasin de stockage au moment de leur création. Ces éléments mis en cache, combinés avec les éléments projetés par le fournisseur dans les énumérations, constituent la « vue » du magasin de stockage du client. De temps à autre, le fournisseur peut souhaiter mettre à jour la vue du client, que ce soit en raison de modifications apportées au magasin de stockage ou en raison d’une action explicite de l’utilisateur pour modifier son affichage.

Si le fournisseur met à jour la vue de manière proactive, à mesure que le magasin de stockage change, il est recommandé que les modifications d’affichage soient relativement peu fréquentes. Cela est dû au fait qu’une modification de l’affichage d’un élément qui a été ouvert et qui a donc un espace réservé créé sur le disque nécessite que l’élément sur disque soit mis à jour ou supprimé pour refléter la modification de l’affichage. Les mises à jour fréquentes peuvent entraîner une activité de disque supplémentaire qui peut avoir un impact négatif sur les performances du système.

Contrôle de version d’élément

Pour prendre en charge la maintenance de plusieurs vues, ProjFS fournit le struct PRJ_PLACEHOLDER_VERSION_INFO . Un fournisseur utilise ce struct autonome dans les appels à PrjMarkDirectoryAsPlaceholder, et il est incorporé dans les structs PRJ_PLACEHOLDER_INFO et PRJ_CALLBACK_DATA . Le PRJ_PLACEHOLDER_VERSION_INFO. Le champ ContentID est l’emplacement où le fournisseur stocke jusqu’à 128 octets d’informations de version pour un fichier ou un répertoire. Le fournisseur peut ensuite associer en interne cette valeur à une valeur qui identifie une vue particulière.

Par exemple, un fournisseur qui virtualise un référentiel de contrôle de code source peut choisir d’utiliser un hachage du contenu d’un fichier pour servir de ContentID, et il peut utiliser des horodatages pour identifier les vues du dépôt à différents moments dans le temps. Les horodatages peuvent correspondre à l’heure à laquelle les validations dans le dépôt ont été effectuées.

À l’aide de l’exemple d’un dépôt indexé timestamp, un flux classique pour l’utilisation du ContentID d’un élément peut être :

  1. Le client établit une vue en spécifiant l’horodatage auquel présenter le contenu du dépôt. Cela se ferait via une interface implémentée par le fournisseur, et non via une API ProjFS. Par exemple, le fournisseur peut avoir un outil en ligne de commande qui peut être utilisé pour indiquer au fournisseur la vue souhaitée par l’utilisateur.
  2. Lorsque le client crée un handle dans un fichier ou répertoire virtualisé, le fournisseur crée un espace réservé pour celui-ci, en utilisant les métadonnées du système de fichiers du magasin de stockage. L’ensemble particulier de métadonnées qu’il utilise est identifié par la valeur ContentID associée à l’horodatage de l’affichage souhaité. Lorsque le fournisseur appelle PrjWritePlaceholderInfo pour écrire les informations d’espace réservé, il fournit le ContentID dans le membre VersionInfo de l’argument placeholderInfo . Le fournisseur doit ensuite enregistrer qu’un espace réservé pour ce fichier ou répertoire a été créé dans cette vue.
  3. Lorsque le client lit à partir d’un espace réservé, le rappel PRJ_GET_FILE_DATA_CB du fournisseur est appelé. Le membre VersionInfo du paramètre callbackData contient le ContentID spécifié dans PrjWritePlaceholderInfo lors de la création de l’espace réservé de fichier. Le fournisseur utilise ce ContentID pour récupérer le contenu correct du fichier à partir de son magasin de stockage.
  4. Le client demande une modification d’affichage en spécifiant un nouvel horodatage. Pour chaque espace réservé créé par le fournisseur dans la vue précédente, s’il existe une version différente de ce fichier dans la nouvelle vue, le fournisseur peut remplacer l’espace réservé sur le disque par un espace réservé mis à jour dont l’ID de contenu est associé au nouvel horodatage en appelant PrjUpdateFileIfNeeded. Le fournisseur peut également supprimer l’espace réservé en appelant PrjDeleteFile afin que la nouvelle vue du fichier soit projetée dans des énumérations. Si le fichier n’existe pas dans la nouvelle vue, le fournisseur doit le supprimer en appelant PrjDeleteFile.

Notez que le fournisseur doit s’assurer que lorsque le client énumère un répertoire, le fournisseur fournit le contenu correspondant à la vue du client. Par exemple, le fournisseur peut utiliser le membre VersionInfo du paramètre callbackData du PRJ_START_DIRECTORY_ENUMERATION_CB et PRJ_GET_DIRECTORY_ENUMERATION_CB rappels pour récupérer le contenu du répertoire à la volée. Il peut également créer une structure de répertoires pour cette vue dans son magasin de stockage dans le cadre de l’établissement de la vue, puis énumérer simplement cette structure.

Chaque fois qu’un rappel de fournisseur est appelé pour un espace réservé ou un fichier partiel, les informations de version spécifiées par le fournisseur lors de la création de l’élément sont fournies dans le membre VersionInfo du paramètre callbackData du rappel.

Mise à jour de l’affichage

Vous trouverez ci-dessous un exemple de fonction illustrant comment un fournisseur peut mettre à jour l’affichage local lorsque l’utilisateur spécifie un nouvel horodatage. La fonction récupère la liste des espaces réservés créés par le fournisseur pour l’affichage dont l’utilisateur vient de changer et met à jour l’état du cache local en fonction de l’état de chaque espace réservé dans la nouvelle vue. Pour plus de clarté, cette routine omet certaines complexités du traitement du système de fichiers. Par exemple, dans l’ancienne vue, un répertoire donné peut avoir existé avec certains fichiers ou répertoires, mais dans la nouvelle vue, ce répertoire (et son contenu) n’existe peut-être plus. Pour éviter de rencontrer des erreurs dans une telle situation, le fournisseur doit mettre à jour l’état du fichier et des répertoires sous la racine de virtualisation de manière approfondie.

typedef struct MY_ON_DISK_PLACEHOLDER MY_ON_DISK_PLACEHOLDER;

typedef struct MY_ON_DISK_PLACEHOLDER {
    MY_ON_DISK_PLACEHOLDER* Next;
    PWSTR RelativePath;
} MY_ON_DISK_PLACEHOLDER;

HRESULT
MyViewUpdater(
    _In_ PRJ_CALLBACK_DATA callbackData,
    _In_ time_t newViewTime
    )
{
    HRESULT hr = S_OK;

    // MyGetOnDiskPlaceholders is a routine the provider might implement to produce
    // a pointer to a list of the placeholders the provider has created in the view.
    MY_ON_DISK_PLACEHOLDER* placeholder = MyGetOnDiskPlaceholders();
    while (placeholder != NULL)
    {
        // MyGetProjectedFileInfo is a routine the provider might implement to
        // determine whether a given file exists in its backing store at a
        // particular timestamp.  If it does, the routine returns a pointer to
        // a PRJ_PLACEHOLDER_INFO struct populated with information about the
        // file.
        PRJ_PLACEHOLDER_INFO* newViewPlaceholderInfo = NULL;
        UINT32 newViewPlaceholderInfoSize = 0;

        newViewPlaceholderInfo = MyGetProjectedFileInfo(placeholder->RelativePath,
                                                 newViewTime,
                                                 &newViewPlaceholderInfoSize);
        if (newViewPlaceholderInfo == NULL)
        {
            // The file no longer exists in the new view.  We want to remove its
            // placeholder from the local cache.
            PRJ_UPDATE_FAILURE_CAUSES deleteFailureReason;
            hr = PrjDeleteFile(callbackData->NamespaceVirtualizationContext,
                               placeholder->RelativePath,
                               PRJ_UPDATE_ALLOW_DIRTY_METADATA
                               | PRJ_UPDATE_ALLOW_DIRTY_DATA
                               | PRJ_UPDATE_ALLOW_TOMBSTONE,
                               &deleteFailureReason);

            if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION))
            {
                wprintf(L"Could not delete [%ls].\n", placeholder->RelativePath);
                wprintf(L"Failure reason: 0x%x", deleteFailureReason);
                return hr;
            }
            else
            {
                wprintf(L"Error deleting [%ls]: 0x%08x",
                        placeholder->RelativePath,
                        hr);
                return hr;
            }

            // MyRemovePlaceholderData is a routine the provider might implement
            // to remove an item from its list of placeholders it has created in
            // the view.
            MyRemovePlaceholderData(placeholder);
        }
        else
        {
            // The file exists in the new view.  Its local cache state may need
            // to be updated, for example if its content is different in this view.
            // As an optimization, the provider may compare the file's ContentID
            // in the new view (stored in newViewPlaceholderInfo->ContentId) with
            // the ContentID it had in the old view.  If they are the same, calling
            // PrjUpdateFileIfNeeded would not be needed.
            PRJ_UPDATE_FAILURE_CAUSES updateFailureReason;
            hr = PrjUpdateFileIfNeeded(callbackData->NamespaceVirtualizationContext,
                                       placeholder->RelativePath,
                                       newViewPlaceholderInfo,
                                       newViewPlaceholderInfoSize,
                                       PRJ_UPDATE_ALLOW_DIRTY_METADATA
                                       | PRJ_UPDATE_ALLOW_DIRTY_DATA
                                       | PRJ_UPDATE_ALLOW_TOMBSTONE,
                                       &updateFailureReason);

            if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_VIRTUALIZATION_INVALID_OPERATION))
            {
                wprintf(L"Could not update [%ls].\n", placeholder->RelativePath);
                wprintf(L"Failure reason: 0x%x", updateFailureReason);
                return hr;
            }
            else
            {
                wprintf(L"Error updating [%ls]: 0x%08x",
                        placeholder->RelativePath,
                        hr);
                return hr;
            }

            // MyUpdatePlaceholderData is a routine the provider might implement
            // to update information about a placeholder it has created in the view.
            MyUpdatePlaceholderData(placeholder, newViewPlaceholderInfo);
        }

        placeholder = placeholder->Next;
    }

    return hr;
}