Поделиться через


Обработка изменений представления

При открытии файлов и каталогов в корне виртуализации поставщик создает заполнители на диске, а по мере считывания файлов заполнители заполняются содержимым. Эти заполнители представляют состояние резервного хранилища на момент их создания. Эти кэшированные элементы в сочетании с элементами, проецируемыми поставщиком в перечислениях, составляют клиентское "представление" резервного хранилища. Время от времени поставщик может захотеть обновлять представление клиента, будь то из-за изменений в резервном хранилище или из-за явных действий, предпринятых пользователем для изменения своего представления.

Если поставщик обновляет представление заранее, так как изменяется резервное хранилище, рекомендуется, чтобы изменения представления были относительно редкими. Это связано с тем, что при изменении представления в элементе, который был открыт и, следовательно, был создан заполнитель на диске, требуется обновить или удалить элемент на диске, чтобы отразить изменение представления. Частые обновления могут привести к дополнительной активности диска, что может негативно повлиять на производительность системы.

Управление версиями элементов

Для поддержки поддержки нескольких представлений ProjFS предоставляет структуру PRJ_PLACEHOLDER_VERSION_INFO . Поставщик использует автономную структуру в вызовах PrjMarkDirectoryAsPlaceholder и внедряется в структуры PRJ_PLACEHOLDER_INFO и PRJ_CALLBACK_DATA . PRJ_PLACEHOLDER_VERSION_INFO. В поле ContentID поставщик хранит до 128 байт сведений о версии для файла или каталога. Поставщик может затем внутренне связать это значение с каким-то значением, которое идентифицирует конкретное представление.

Например, поставщик, который виртуализирует репозиторий системы управления версиями, может использовать хэш содержимого файла в качестве ContentID, а также метки времени для определения представлений репозитория в различные моменты времени. Метки времени могут быть временем выполнения фиксаций в репозитории.

В примере репозитория с индексированием меток времени типичным потоком использования ContentID элемента может быть следующее:

  1. Клиент устанавливает представление, указывая метку времени для представления содержимого репозитория. Это можно сделать с помощью некоторого интерфейса, реализованного поставщиком, а не через API ProjFS. Например, у поставщика может быть средство командной строки, которое можно использовать для того, чтобы сообщить поставщику, какое представление требуется пользователю.
  2. Когда клиент создает дескриптор для виртуализированного файла или каталога, поставщик создает для него заполнитель, используя метаданные файловой системы из резервного хранилища. Конкретный набор метаданных, которые он использует, определяется значением ContentID, связанным с меткой времени нужного представления. Когда поставщик вызывает PrjWritePlaceholderInfo для записи сведений о заполнителях, он предоставляет ContentID в элементе VersionInfo аргумента placeholderInfo . Затем поставщик должен зафиксировать, что в этом представлении был создан заполнитель для этого файла или каталога.
  3. Когда клиент считывает из заполнителя, вызывается обратный вызов PRJ_GET_FILE_DATA_CB поставщика. Элемент VersionInfo параметра callbackData содержит contentID поставщика, указанного в PrjWritePlaceholderInfo при создании заполнителя файла. Поставщик использует этот идентификатор ContentID для получения правильного содержимого файла из его резервного хранилища.
  4. Клиент запрашивает изменение представления, указывая новую метку времени. Для каждого заполнителя, созданного поставщиком в предыдущем представлении, если в новом представлении существует другая версия этого файла, поставщик может заменить заполнитель на диске обновленным, contentID которого связан с новой меткой времени, вызвав PrjUpdateFileIfNeeded. Кроме того, поставщик может удалить заполнитель, вызвав PrjDeleteFile , чтобы новое представление файла проецировался в перечислениях. Если файл не существует в новом представлении, поставщик должен удалить его, вызвав PrjDeleteFile.

Обратите внимание, что поставщик должен гарантировать, что при перечислении клиентом каталога поставщик будет предоставлять содержимое, соответствующее представлению клиента. Например, поставщик может использовать элемент VersionInfo параметра callbackDataPRJ_START_DIRECTORY_ENUMERATION_CB и PRJ_GET_DIRECTORY_ENUMERATION_CB обратные вызовы для получения содержимого каталога на лету. Кроме того, он может создать структуру каталогов для этого представления в его резервном хранилище в рамках создания представления, а затем просто перечислить эту структуру.

При каждом вызове обратного вызова поставщика для заполнителя или частичного файла сведения о версии, указанные поставщиком при создании элемента, предоставляются в элементе VersionInfo параметра callbackData обратного вызова.

Обновление представления

Ниже приведен пример функции, иллюстрирующая, как поставщик может обновить локальное представление, когда пользователь указывает новую метку времени. Функция получает список заполнителей, созданных поставщиком для представления, с которого только что изменился пользователь, и обновляет состояние локального кэша на основе состояния каждого заполнителя в новом представлении. Для ясности эта процедура исключает некоторые сложности работы с файловой системой. Например, в старом представлении может существовать определенный каталог с некоторыми файлами или каталогами, но в новом представлении этот каталог (и его содержимое) может больше не существовать. Чтобы избежать ошибок в такой ситуации, поставщик должен обновить состояние файла и каталогов под корнем виртуализации на глубину.

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