Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ CD-ROMs.

### Export And Author Media

- Export exact audio, rendered SFZ instruments (needs more work), individual
- Export selected Samples as mono or interleaved stereo WAV files, selected Wave
Data as mono WAV files, rendered SFZ instruments (needs more work), individual
object packages, and dependency-complete volume packages.
- Batch-export every volume in a partition as packages or as per-volume floppy
sets.
Expand Down
6 changes: 5 additions & 1 deletion apps/application/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ add_library(axk_application STATIC src/alteration_journal.cpp src/alteration_jou
src/image_session_access.cpp src/image_session_allocation_map.cpp src/image_session_audition.cpp
src/image_allocation_map.cpp
src/image_session_floppy_set.cpp src/image_session_inspection.cpp
src/image_session_contracts.cpp src/image_session_operations.cpp
src/image_session_program_generation.cpp
src/image_session_program_assignment_cleanup.cpp
src/image_session_system_program_contexts.cpp
src/image_session_object_reader.cpp src/image_session_open.cpp
src/image_session_support.cpp src/image_sessions.cpp
src/job_request_resources.cpp src/jobs.cpp src/midi_operations.cpp
src/jobs_support.cpp
src/operation_registry.cpp src/package_filename.cpp src/package_operations.cpp
src/operation_registry.cpp src/operation_registry_names.cpp
src/operation_registry_program_assignments.cpp
src/package_filename.cpp src/package_operations.cpp
src/package_operations_support.cpp src/session_package_import_plan.cpp
src/session_package_operations.cpp
src/package_plan_store.cpp src/path_reservations.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include <nlohmann/json.hpp>

#include "axklib/application/contracts.hpp"
#include "axklib/application/image_sessions.hpp"

namespace axk::app {

[[nodiscard]] Result<ImageSourceRef> image_source_ref_from_json(const nlohmann::json &reference);
[[nodiscard]] nlohmann::json image_source_ref_json(const ImageSourceRef &source);
[[nodiscard]] nlohmann::json image_session_summary_json(const ImageSessionSummary &summary);

} // namespace axk::app
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include "axklib/application/contracts.hpp"

namespace axk::app {

class ImageSessionManager;
class OperationRegistry;

[[nodiscard]] Result<void> bind_image_session_operations(OperationRegistry &registry, ImageSessionManager &images);

} // namespace axk::app
44 changes: 42 additions & 2 deletions apps/application/include/axklib/application/image_sessions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,38 @@ struct ImageProgramGenerationPlan {
axk::AlterationManifest manifest;
};

struct ImageProgramAssignmentCleanupCandidate {
std::string program_object_id;
std::uint8_t program_number{};
std::string program_name;
std::uint8_t assignment_ordinal{};
std::string assignment_name;
std::string target_object_type;
std::string receive_channel_display;
std::string reason;
std::size_t candidate_target_count{};
bool default_selected{true};
};

struct ImageProgramAssignmentCleanupInspection {
std::string image_id;
std::uint64_t revision{};
std::string content_scope_id;
std::size_t total_candidate_count{};
std::vector<ImageProgramAssignmentCleanupCandidate> candidates;
};

struct ImageProgramAssignmentCleanupSelection {
std::string program_object_id;
std::uint8_t assignment_ordinal{};
};

struct ImageProgramAssignmentCleanupPlan {
ImageProgramAssignmentCleanupInspection inspection;
std::vector<ImageProgramAssignmentCleanupSelection> selections;
axk::AlterationManifest manifest;
};

struct ImageValidationItem {
std::string code;
std::string severity;
Expand Down Expand Up @@ -430,7 +462,8 @@ class ImageSessionManager {
ImageSessionManager &operator=(const ImageSessionManager &) = delete;

[[nodiscard]] Result<ImageSessionSummary> open(const ImageSourceRef &source, std::string owner_id,
const CancellationToken &cancellation = {});
const CancellationToken &cancellation = {},
ProgressSink *progress = nullptr);
[[nodiscard]] Result<ImageSessionSummary> attach_companions(std::string_view image_id, std::string_view owner_id,
std::uint64_t expected_revision,
const CompanionSelection &selection,
Expand All @@ -451,6 +484,13 @@ class ImageSessionManager {
plan_program_generation(std::string_view image_id, std::string_view owner_id, std::uint64_t expected_revision,
std::string_view content_scope_id,
const std::vector<ImageProgramGenerationSelection> &selections);
[[nodiscard]] Result<ImageProgramAssignmentCleanupInspection>
inspect_program_assignment_cleanup(std::string_view image_id, std::string_view owner_id,
std::uint64_t expected_revision, std::string_view content_scope_id);
[[nodiscard]] Result<ImageProgramAssignmentCleanupPlan>
plan_program_assignment_cleanup(std::string_view image_id, std::string_view owner_id,
std::uint64_t expected_revision, std::string_view content_scope_id,
const std::vector<ImageProgramAssignmentCleanupSelection> &selections);
[[nodiscard]] Result<ImageSessionRead> begin_read(std::string_view image_id, std::string_view owner_id,
std::uint64_t expected_revision);
[[nodiscard]] Result<ImageSessionMutation> begin_mutation(std::string_view image_id, std::string_view owner_id,
Expand Down Expand Up @@ -502,7 +542,7 @@ class ImageSessionManager {
[[nodiscard]] Result<ImageSessionSummary>
open_with_companion_sources(const ImageSourceRef &source, std::string owner_id,
const std::vector<ImageSourceRef> &companion_sources,
const CancellationToken &cancellation);
const CancellationToken &cancellation, ProgressSink *progress);
struct Implementation;
std::unique_ptr<Implementation> implementation_;
};
Expand Down
3 changes: 3 additions & 0 deletions apps/application/src/application_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "axklib/application/directory_archive_operations.hpp"
#include "axklib/application/extraction_operations.hpp"
#include "axklib/application/file_operations.hpp"
#include "axklib/application/image_session_operations.hpp"
#include "axklib/application/midi_operations.hpp"
#include "axklib/application/package_operations.hpp"
#include "axklib/application/session_audio_export_operations.hpp"
Expand Down Expand Up @@ -218,6 +219,8 @@ axk::app::make_application_registry(const Sandbox &sandbox, UploadStore &uploads
axk::app::Result<void> axk::app::bind_session_application_operations(
OperationRegistry &registry, const Sandbox &sandbox, UploadStore &uploads, ImageSessionManager &images,
AlterationJournalStore &journals, DownloadArchiveStore &downloads, const axk::MediaBuildLimits &media_limits) {
if (auto bound = bind_image_session_operations(registry, images); !bound)
return bound;
if (auto bound = bind_directory_archive_operations(registry, sandbox, downloads); !bound)
return bound;
if (auto bound = bind_audition_operations(registry, images); !bound)
Expand Down
84 changes: 84 additions & 0 deletions apps/application/src/image_session_contracts.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "axklib/application/image_session_contracts.hpp"

#include <string>
#include <string_view>
#include <utility>

namespace {

std::string_view floppy_set_status_name(axk::app::ImageFloppySetStatus status) {
switch (status) {
case axk::app::ImageFloppySetStatus::single:
return "SINGLE";
case axk::app::ImageFloppySetStatus::incomplete:
return "INCOMPLETE";
case axk::app::ImageFloppySetStatus::complete:
return "COMPLETE";
case axk::app::ImageFloppySetStatus::recovery:
return "RECOVERY";
}
return "SINGLE";
}

} // namespace

axk::app::Result<axk::app::ImageSourceRef> axk::app::image_source_ref_from_json(const nlohmann::json &reference) {
try {
const auto kind = reference.at("kind").get<std::string>();
if (kind == "FILE") {
const auto &file = reference.at("file");
return ImageSourceRef{file.at("rootId").get<std::string>(), file.at("relativePath").get<std::string>(),
ImageSourceKind::file};
}
if (kind == "AXK_OBJECT_DIRECTORY") {
const auto &directory = reference.at("directory");
return ImageSourceRef{directory.at("rootId").get<std::string>(),
directory.at("relativePath").get<std::string>(),
ImageSourceKind::axk_object_directory};
}
} catch (const nlohmann::json::exception &) {
return std::unexpected(Error{"invalid_request", "source must be one FILE or AXK_OBJECT_DIRECTORY reference"});
}
return std::unexpected(Error{"invalid_request", "image source kind is unsupported"});
}

nlohmann::json axk::app::image_source_ref_json(const ImageSourceRef &source) {
if (source.kind == ImageSourceKind::file) {
return {{"kind", "FILE"}, {"file", {{"rootId", source.root_id}, {"relativePath", source.relative_path}}}};
}
return {{"kind", "AXK_OBJECT_DIRECTORY"},
{"directory", {{"rootId", source.root_id}, {"relativePath", source.relative_path}}}};
}

nlohmann::json axk::app::image_session_summary_json(const ImageSessionSummary &summary) {
nlohmann::json companion_sources = nlohmann::json::array();
for (const auto &source : summary.companion_sources)
companion_sources.push_back(image_source_ref_json(source));
nlohmann::json floppy_set;
if (summary.floppy_set) {
nlohmann::json members = nlohmann::json::array();
for (const auto &member : summary.floppy_set->members)
members.push_back({{"index", member.index}, {"label", member.label}, {"marker", member.marker}});
floppy_set = {{"status", floppy_set_status_name(summary.floppy_set->status)},
{"setLabel", summary.floppy_set->set_label},
{"members", std::move(members)},
{"nextRequiredIndex", summary.floppy_set->next_required_index
? nlohmann::json(*summary.floppy_set->next_required_index)
: nlohmann::json{}}};
}
return {{"imageId", summary.image_id},
{"revision", summary.revision},
{"source", image_source_ref_json(summary.source)},
{"companionSources", std::move(companion_sources)},
{"floppySet", std::move(floppy_set)},
{"format", summary.format},
{"availableOperations", summary.available_operations},
{"rootCount", summary.root_count},
{"objectCount", summary.object_count},
{"relationshipCount", summary.relationship_count},
{"validation",
{{"valid", summary.validation.valid()},
{"infoCount", summary.validation.info_count},
{"warningCount", summary.validation.warning_count},
{"errorCount", summary.validation.error_count}}}};
}
2 changes: 2 additions & 0 deletions apps/application/src/image_session_inspection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ axk::app::Result<axk::app::ImageSessionSummary> axk::app::ImageSessionManager::i
available_operations.emplace_back("images.deletion.orphans.inspect");
available_operations.emplace_back("images.programs.generate.inspect");
available_operations.emplace_back("images.programs.generate");
available_operations.emplace_back("images.program_assignments.cleanup.inspect");
available_operations.emplace_back("images.program_assignments.cleanup");
}
return ImageSessionSummary{.image_id = (*session)->image_id,
.revision = (*session)->revision,
Expand Down
70 changes: 62 additions & 8 deletions apps/application/src/image_session_open.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@

namespace {

constexpr std::uint64_t image_open_progress_total = 5U;

axk::app::Result<void> report_image_open_progress(axk::ProgressSink *progress,
const axk::CancellationToken &cancellation, axk::ProgressPhase phase,
std::uint64_t completed, std::string label) {
if (const auto active = cancellation.check(); !active)
return std::unexpected(axk::app::Error{"operation_cancelled", active.error().message});
if (progress != nullptr) {
progress->report({.phase = phase,
.completed = completed,
.total = image_open_progress_total,
.label = std::move(label),
.output_path = std::nullopt});
}
return {};
}

std::optional<std::uint64_t>
exact_dependency_size(const axk::ObjectSnapshot &root, const axk::RelationshipGraph &graph,
const std::map<std::string, const axk::ObjectSnapshot *, std::less<>> &objects,
Expand Down Expand Up @@ -66,16 +83,20 @@ exact_dependency_size(const axk::ObjectSnapshot &root, const axk::RelationshipGr

axk::app::Result<axk::app::ImageSessionSummary>
axk::app::ImageSessionManager::open(const ImageSourceRef &source, std::string owner_id,
const CancellationToken &cancellation) {
return open_with_companion_sources(source, std::move(owner_id), {}, cancellation);
const CancellationToken &cancellation, ProgressSink *progress) {
return open_with_companion_sources(source, std::move(owner_id), {}, cancellation, progress);
}

axk::app::Result<axk::app::ImageSessionSummary>
axk::app::ImageSessionManager::open_with_companion_sources(const ImageSourceRef &source, std::string owner_id,
const std::vector<ImageSourceRef> &companion_sources,
const CancellationToken &cancellation) {
axk::app::Result<axk::app::ImageSessionSummary> axk::app::ImageSessionManager::open_with_companion_sources(
const ImageSourceRef &source, std::string owner_id, const std::vector<ImageSourceRef> &companion_sources,
const CancellationToken &cancellation, ProgressSink *progress) {
if (owner_id.empty())
return std::unexpected(session_error("invalid_owner", "image session owner is required"));
if (auto reported =
report_image_open_progress(progress, cancellation, ProgressPhase::opening, 0U, "Opening image source");
!reported) {
return std::unexpected(reported.error());
}
auto admission = implementation_->reserve_session();
if (!admission)
return std::unexpected(admission.error());
Expand Down Expand Up @@ -212,12 +233,22 @@ axk::app::ImageSessionManager::open_with_companion_sources(const ImageSourceRef
.marker = "NONE"});
}
}
if (auto reported =
report_image_open_progress(progress, cancellation, ProgressPhase::reading, 1U, "Reading image metadata");
!reported) {
return std::unexpected(reported.error());
}
const auto inventory_mode = media->kind() == axk::MediaKind::fat12_floppy_set
? axk::MediaObjectReadMode::complete
: axk::MediaObjectReadMode::decoded_metadata;
auto inventory = axk::build_media_inventory(*media, inventory_mode, 64U * 1024U * 1024U, cancellation);
if (!inventory)
return std::unexpected(core_error(inventory.error(), source));
if (auto reported = report_image_open_progress(progress, cancellation, ProgressPhase::resolving, 2U,
"Resolving sampler objects");
!reported) {
return std::unexpected(reported.error());
}
auto graph = axk::build_relationship_graph(inventory->catalog);
auto tree = axk::build_content_tree(*media, inventory->catalog, graph);
std::unordered_map<std::uint8_t, std::string> partition_names;
Expand Down Expand Up @@ -472,6 +503,12 @@ axk::app::ImageSessionManager::open_with_companion_sources(const ImageSourceRef
return std::unexpected(appended.error());
}

if (auto reported = report_image_open_progress(progress, cancellation, ProgressPhase::validating, 3U,
"Validating image contents");
!reported) {
return std::unexpected(reported.error());
}

const auto append_validation = [&](std::string code, std::string severity, std::string message,
std::string sampler_path, std::optional<std::string> object_id) {
session->validation.push_back(
Expand Down Expand Up @@ -512,6 +549,12 @@ axk::app::ImageSessionManager::open_with_companion_sources(const ImageSourceRef
session_error("image_source_changed", "image source changed while the session was opened", true));
}

if (auto reported = report_image_open_progress(progress, cancellation, ProgressPhase::publishing, 4U,
"Publishing image session");
!reported) {
return std::unexpected(reported.error());
}

do {
auto image_id = random_identifier("image-");
if (!image_id)
Expand All @@ -520,7 +563,17 @@ axk::app::ImageSessionManager::open_with_companion_sources(const ImageSourceRef
if ((*admission)->promote(session))
break;
} while (true);
return inspect(session->image_id, session->owner_id);
auto summary = inspect(session->image_id, session->owner_id);
if (!summary)
return std::unexpected(summary.error());
if (progress != nullptr) {
progress->report({.phase = ProgressPhase::publishing,
.completed = image_open_progress_total,
.total = image_open_progress_total,
.label = "Image session ready",
.output_path = std::nullopt});
}
return summary;
}

axk::app::Result<axk::app::ImageSessionSummary>
Expand Down Expand Up @@ -606,7 +659,8 @@ axk::app::ImageSessionManager::attach_companions(std::string_view image_id, std:
implementation_->idle_retention,
implementation_->clock,
implementation_->path_reservations};
auto opened = refreshed.open_with_companion_sources(source, std::string{owner_id}, unique_candidates, cancellation);
auto opened =
refreshed.open_with_companion_sources(source, std::string{owner_id}, unique_candidates, cancellation, nullptr);
if (!opened)
return std::unexpected(opened.error());
if (opened->companion_sources.empty()) {
Expand Down
Loading
Loading