Control de cambios de vista
A medida que se abren archivos y directorios en la raíz de virtualización, el proveedor crea marcadores de posición en el disco y, a medida que se leen los marcadores de posición, los marcadores de posición se hidratan con el contenido. Estos marcadores de posición representan el estado del almacén de respaldo en el momento en que se crearon. Estos elementos almacenados en caché, combinados con los elementos proyectados por el proveedor en enumeraciones, constituyen la "vista" del cliente del almacén de respaldo. De vez en cuando, el proveedor puede querer actualizar la vista del cliente, ya sea debido a cambios en la memoria auxiliar o debido a una acción explícita realizada por el usuario para cambiar su vista.
Si el proveedor actualiza la vista de forma proactiva, a medida que cambia el almacén de respaldo, se recomienda que los cambios de vista sean relativamente poco frecuentes. Esto se debe a que un cambio de vista en un elemento que se ha abierto y, por lo tanto, tenía un marcador de posición creado en el disco, requiere que el elemento en disco se actualice o elimine para reflejar el cambio de vista. Las actualizaciones frecuentes pueden dar lugar a una actividad de disco adicional que puede afectar negativamente al rendimiento del sistema.
Control de versiones de elementos
Para admitir el mantenimiento de varias vistas, ProjFS proporciona la estructura PRJ_PLACEHOLDER_VERSION_INFO . Un proveedor usa este struct independiente en llamadas a PrjMarkDirectoryAsPlaceholder, y se inserta en los PRJ_PLACEHOLDER_INFO y PRJ_CALLBACK_DATA structs. El PRJ_PLACEHOLDER_VERSION_INFO. El campo ContentID es donde el proveedor almacena hasta 128 bytes de información de versión para un archivo o directorio. A continuación, el proveedor puede asociar internamente este valor a algún valor que identifique una vista determinada.
Por ejemplo, un proveedor que virtualiza un repositorio de control de código fuente podría optar por usar un hash del contenido de un archivo para servir como ContentID y podría usar marcas de tiempo para identificar vistas del repositorio en varios momentos. Las marcas de tiempo pueden ser las veces que se realizaron confirmaciones en el repositorio.
Con el ejemplo de un repositorio indizado por marca de tiempo, un flujo típico para usar contentID de un elemento podría ser:
- El cliente establece una vista especificando la marca de tiempo en la que se va a presentar el contenido del repositorio. Esto se haría a través de alguna interfaz implementada por el proveedor, no a través de una API de ProjFS. Por ejemplo, el proveedor puede tener una herramienta de línea de comandos que se podría usar para indicar al proveedor qué vista desea el usuario.
- Cuando el cliente crea un identificador en un archivo o directorio virtualizado, el proveedor crea un marcador de posición para él, mediante metadatos del sistema de archivos desde el almacén de respaldo. El conjunto determinado de metadatos que usa se identifica mediante el valor contentID asociado a la marca de tiempo de la vista deseada. Cuando el proveedor llama a PrjWritePlaceholderInfo para escribir la información del marcador de posición, proporciona el ContentID en el miembro VersionInfo del argumento placeholderInfo . A continuación, el proveedor debe registrar que se creó un marcador de posición para ese archivo o directorio en esta vista.
- Cuando el cliente lee desde un marcador de posición, se invoca la devolución de llamada PRJ_GET_FILE_DATA_CB del proveedor. El miembro VersionInfo del parámetro callbackData contiene el ContentID especificado en PrjWritePlaceholderInfo cuando creó el marcador de posición de archivo. El proveedor usa ese ContentID para recuperar el contenido correcto del archivo desde su almacén de respaldo.
- El cliente solicita un cambio de vista especificando una nueva marca de tiempo. Para cada marcador de posición que el proveedor creó en la vista anterior, si existe una versión diferente de ese archivo en la nueva vista, el proveedor puede reemplazar el marcador de posición en el disco por uno actualizado cuyo ContentID está asociado a la nueva marca de tiempo llamando a PrjUpdateFileIfNeeded. Como alternativa, el proveedor puede eliminar el marcador de posición llamando a PrjDeleteFile para que la nueva vista del archivo se proyectará en enumeraciones. Si el archivo no existe en la nueva vista, el proveedor debe eliminarlo llamando a PrjDeleteFile.
Tenga en cuenta que el proveedor debe asegurarse de que cuando el cliente enumera un directorio, el proveedor proporcionará el contenido correspondiente a la vista del cliente. Por ejemplo, el proveedor podría usar el miembro VersionInfo del parámetro callbackData del PRJ_START_DIRECTORY_ENUMERATION_CB y PRJ_GET_DIRECTORY_ENUMERATION_CB devoluciones de llamada para recuperar el contenido del directorio sobre la marcha. Como alternativa, podría crear una estructura de directorios para esa vista en su almacén de respaldo como parte del establecimiento de la vista y, a continuación, simplemente enumerar esa estructura.
Cada vez que se invoca una devolución de llamada de proveedor para un archivo parcial o marcador de posición, la información de versión especificada al crear el elemento se proporciona en el miembro VersionInfo del parámetro callbackData de la devolución de llamada.
Actualización de la vista
A continuación se muestra una función de ejemplo que ilustra cómo un proveedor podría actualizar la vista local cuando el usuario especifica una nueva marca de tiempo. La función recupera una lista de los marcadores de posición del proveedor creado para la vista a la que el usuario acaba de cambiar y actualiza el estado de la caché local en función del estado de cada marcador de posición en la nueva vista. Para mayor claridad, esta rutina omite ciertas complejidades de tratar con el sistema de archivos. Por ejemplo, en la vista anterior, un directorio determinado puede haber existido con algunos archivos o directorios en él, pero en la nueva vista que ese directorio (y su contenido) ya no pueden existir. Para evitar encontrar errores en esta situación, el proveedor debe actualizar el estado del archivo y los directorios debajo de la raíz de virtualización de forma profunda.
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;
}