oxen-server 0.53.0

Oxen server is a fast data version control backend, supporting local disk and S3. Self host your repositories on your own storage, or use the hosted platform on Oxen.ai. Stores, syncs, and serves versioned datasets, model checkpoints, game assets, studio media, and any large data. Use the oxen CLI to push and pull from the oxen server.
use crate::errors::OxenHttpError;
use crate::helpers::{get_repo, get_repo_async};
use crate::params::{app_data, parse_resource, parse_resource_async, path_param};

use liboxen::core::repo_locks;
use liboxen::view::StatusMessage;
use liboxen::view::entries::EMetadataEntry;
use liboxen::view::entry_metadata::EMetadataEntryResponseView;
use liboxen::{current_function, repositories};

use actix_web::{HttpRequest, HttpResponse};
use utoipa;

/// Get file metadata
#[utoipa::path(
    get,
    path = "/api/repos/{namespace}/{repo_name}/metadata/{resource}",
    tag = "Metadata",
    description = "Get metadata for a file or directory entry, with optional workspace support.",
    params(
        ("namespace" = String, Path, description = "Namespace of the repository", example = "ox"),
        ("repo_name" = String, Path, description = "Name of the repository", example = "ImageNet-1k"),
        ("resource" = String, Path, description = "Path to the file/dir (including branch/commit info)", example = "main/images/train/dog_1.jpg"),
    ),
    responses(
        (status = 200, description = "Metadata for the entry found", body = EMetadataEntryResponseView),
        (status = 404, description = "Entry not found"),
        (status = 400, description = "Invalid resource path")
    )
)]
pub async fn file(req: HttpRequest) -> actix_web::Result<HttpResponse, OxenHttpError> {
    let app_data = app_data(&req)?;
    let namespace = path_param(&req, "namespace")?.to_string();
    let repo_name = path_param(&req, "repo_name")?.to_string();
    let repo = get_repo_async(app_data, &namespace, &repo_name).await?;
    let resource = parse_resource_async(&req, &repo).await?;
    let workspace_ref = resource.workspace.as_ref();

    let commit = if let Some(workspace) = workspace_ref {
        workspace.commit.clone()
    } else {
        resource.clone().commit.unwrap()
    };

    log::debug!(
        "{} resource {}/{}",
        current_function!(),
        repo_name,
        resource
    );

    log::debug!(
        "{} resolve commit {} -> '{}'",
        current_function!(),
        commit.id,
        commit.message
    );

    let meta = if let Some(workspace) = resource.workspace.as_ref() {
        match repositories::entries::get_meta_entry_async(&repo, &commit, &resource.path).await {
            Ok(entry) => {
                let mut entry = repositories::workspaces::populate_entry_with_workspace_data(
                    resource.path.as_ref(),
                    entry.clone(),
                    workspace,
                )?;
                entry.set_resource(Some(resource.clone().into()));
                EMetadataEntryResponseView {
                    status: StatusMessage::resource_found(),
                    entry,
                }
            }
            Err(_) => {
                let added_entry = repositories::workspaces::get_added_entry(
                    &repo,
                    &resource.path,
                    workspace,
                    &resource,
                )?;
                EMetadataEntryResponseView {
                    status: StatusMessage::resource_found(),
                    entry: added_entry,
                }
            }
        }
    } else {
        let mut entry =
            repositories::entries::get_meta_entry_async(&repo, &commit, &resource.path).await?;
        entry.resource = Some(resource.clone().into());
        EMetadataEntryResponseView {
            status: StatusMessage::resource_found(),
            entry: EMetadataEntry::MetadataEntry(entry),
        }
    };

    Ok(HttpResponse::Ok().json(meta))
}

/// Update file metadata
#[utoipa::path(
    put,
    path = "/api/repos/{namespace}/{repo_name}/metadata/{resource}",
    tag = "Metadata",
    description = "Update metadata for a file version in the version store.",
    params(
        ("namespace" = String, Path, description = "Namespace of the repository", example = "ox"),
        ("repo_name" = String, Path, description = "Name of the repository", example = "ImageNet-1k"),
        ("resource" = String, Path, description = "Path to the file (including version to update)", example = "versions/files/b4/158b417c800c7322d711f1816f5c00e1215b4d7c001c9b6892556d11/data"),
    ),
    responses(
        (status = 200, description = "Metadata updated", body = StatusMessage),
        (status = 400, description = "Missing version in resource path"),
        (status = 404, description = "Repository not found")
    )
)]
pub async fn update_metadata(req: HttpRequest) -> actix_web::Result<HttpResponse, OxenHttpError> {
    let app_data = app_data(&req)?;
    let namespace = path_param(&req, "namespace")?.to_string();
    let repo_name = path_param(&req, "repo_name")?.to_string();
    let repo = get_repo(app_data, &namespace, &repo_name)?;
    let _write = repo_locks::acquire_write(&repo)?;
    let resource = parse_resource(&req, &repo)?;

    let version_str = resource
        .version
        .to_str()
        .ok_or_else(|| OxenHttpError::BadRequest("Missing resource version".into()))?;

    repositories::entries::update_metadata(&repo, version_str)?;
    Ok(HttpResponse::Ok().json(StatusMessage::resource_updated()))
}