Repository navigation
plugins: drives built from a plugin's library source, copying only audio files in the folders granted - #94
Merged
Merged
Conversation
…ing only audio files in the folders the user grants
A plugin may now contribute library sources ([[contributes.sources]], feature
`sources`): `library/read {source, options, roots, cursor?}` answers pages of
tracks (an absolute audio path, tags, key, BPM, cues, a beat grid, a waveform,
My Tags) and playlists, a big page in a blob. It needs the new `fs:read`
capability, and is given only the folders the user grants for that build.
The host trusts none of the plugin's paths. A track is copied only when its
audio file, links resolved, is a regular file players play inside a granted
folder and not on the drive written to; the host reads its format and length
itself, hashes it before copying and checks the copy against that hash. What
isn't is left out with a warning, and so is a playlist entry for it, a cue in a
taken slot, a grid that isn't one. Cues, beat grids and waveforms go into new
analysis files made with the anlz writers and read back before they're kept.
collection_export takes a source-neutral library (`Collected`) that
rekordbox's collection and plugins both fill, so a source builds a drive
through the same library_write and convert::build_databases path, with the
drive remembering which source it was built from.
`collection export DRIVE --from PLUGIN:SOURCE --grant FOLDER [--option K=V]`,
the player's "build from ..." form and the sidecar's `plugins.source.plan` /
`.apply` run it. The test plugin's sources read the files in their folders.
# Conflicts: # apps/locallibrary/docs/plugins.md # apps/locallibrary/docs/rpc/locallibrary.d.ts # apps/locallibrary/docs/rpc/schema.json # apps/locallibrary/src/export_cmd.rs # apps/locallibrary/src/plugin_cmd/listing.rs # apps/locallibrary/src/plugins.rs # apps/locallibrary/src/rpc.rs # apps/locallibrary/src/rpc/tests.rs # apps/locallibrary/src/tui/app.rs # apps/locallibrary/src/tui/drives.rs # apps/locallibrary/src/tui/form.rs # apps/locallibrary/src/tui/keymap.rs # apps/locallibrary/src/tui/plugins.rs # apps/locallibrary/src/tui/plugins/tests.rs # apps/locallibrary/src/tui/ui.rs # apps/locallibrary/tests/it/plugins/props.rs # crates/locallibrary/src/bin/test-plugin/main.rs # crates/locallibrary/src/bin/test-plugin/plugin.rs # crates/locallibrary/src/plugin.rs # crates/locallibrary/tests/it/plugins.rs # crates/loclib-plugin/README.md # crates/loclib-plugin/src/features.rs # crates/loclib-plugin/src/hooks.rs # crates/loclib-plugin/src/lib.rs # crates/loclib-plugin/src/manifest.rs # crates/loclib-plugin/src/manifest/tests.rs # crates/loclib-plugin/src/protocol.rs # crates/loclib-plugin/src/sdk/tests.rs
Coverage Report for apps/web
File CoverageNo changed files found.SizeCompared with main. KiB are 1,024 bytes.
|
collection_export: a made file's copy on the target drive is read with fsutil::read, so a rename onto it elsewhere in this process can't fail the read on Windows.
… the system's separator The host names a file it finds in a granted folder with the system's separator, so on Windows the warning says stick\00.wav; the test built stick/00.wav and failed there.
Rust coverageFrom run 278. Both are held to the thresholds in .cargo/config.toml.
|
…amed for what they are, so CodeQL takes neither for an account's data or a salt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
This builds on the plugin system from #67, #68, #74, #79 and #82, and on the analysis file writers from the beat grid, phrase and waveform PR it branches from.
Until now a drive could be built from rekordbox's collection (
collection export) or from another drive, never from anything else. A plugin could read a drive's library and export it to another program, but not bring a library in. With this PR a plugin can read a library from somewhere on the computer (another program's collection, a folder of tagged files) and locallibrary builds a drive from it, through the same path rekordbox's collection takes. The plugin names audio files by path, so everything here is about never copying a file the user didn't grant.Protocol and SDK (the
sourcesfeature)library/read {source, options, roots, cursor?, context}answers{tracks, playlists, blob?, next?}, a page at a time; the host passes eachnextback ascursoruntil a page has none (sources.rs).rootsare the folders the user granted for this build.Folder/Namepath and its tracks' paths.[[contributes.sources]] name, title, optionsneeds the new capabilityfs:read(sources.rs, manifest.rs). The feature is optional, so the protocol stays at 0.2.WaveformBlobgets a JSON Schema, and loclib-plugin asks for loclib-model'sschemarsfeature itself, sinceSourceTrackholds a beat grid; before, it only had it through the workspace.What the host checks
Plugins::plan_source_exportchecks the source and its options against the manifest, reads every page, and refuses a cursor answered twice, so a plugin can't page forever (plugin/sources.rs).Category/Tag, keys that aren't keys, beat grids out of order, waveforms that aren't waveforms, cues in a slot already taken or past H, loops that end before they start, colours players don't have (plugin/sources.rs, plugin/sources.rs).Building the drive
collection_exportnow takes a source-neutralCollectedlibrary: rekordbox's collection fills it as before, and the plugin host fills it with the files it checked (collection_export.rs). Both go through the same layout (Contents/<artist>/<album>/),library_writeandconvert::build_databases, so a source gets what an export of the collection gets: only changed files copied, a backup of what's replaced, history lists saved first, My Tags in OneLibrary..DATand.EXTfiles made with the analysis writers, its cues read back and compared before the files are kept (analysis_edit.rs, collection_export.rs). One whose analysis can't be written that way goes without it, with a warning. The made files wait in a temporary folder until they're copied, and one the drive already holds byte for byte isn't copied again.collection export(api/collection.rs). The plan says which folders were read; it's named<plugin>.source.<source>and its JSON hassource.Command line
collection export DRIVE --from PLUGIN:SOURCE --grant FOLDER(--grantagain for more,--option KEY=VALUEfor the source's options,--dry-run,--replace) (collection_cmd.rs).--dband--keyare refused with--from. The plan prints the folders read and what was left out.plugin infolists a plugin's library sources,plugin listsays it adds them, andfs:readreads "read audio files in folders you grant it, to build a drive from".Sidecar
plugins.source.plan {plugin, source, options, grants, drive, playlists?, all?, replace?}answers a plan shaped likecollection.export.plan's, applied byplugins.source.apply(rpc/methods/plugins/sources.rs). Its plans are kept with the other plugin plans and taken only byplugins.source.apply(rpc.rs).plugins.listhassources.docs/rpc/is regenerated.Player
~/Musicto begin with, several split by commas), whether to replace a library the drive holds, and the source's options (tui/plugins/sources.rs). The plan is confirmed as "Build DRIVE from the library source PLUGIN:SOURCE?" (or "Replace the library on ...") (tui/edits.rs).Design choices
fs:read, soplugin listsays up front that it reads files.fs:read, which many tests match its capability list against; the tests that need a source add it to echo's manifest themselves.How it was tested
New and updated tests:
a_source_answers_its_library_a_page_at_a_time(the SDK) anda_source_is_granted_reading_folders. The Hegel propertiesa_drawn_manifest_is_valid_and_reads_back_as_it_was_writtenandeach_kind_of_mistake_is_refused_saying_wheredraw sources, with a new mistake (a source withoutfs:read);capabilities_read_back_from_their_names_and_nothing_elsecoversfs:read.crates/locallibrary/tests/it/plugins/sources.rs):a_source_builds_a_drive_from_the_audio_files_it_was_granted(a file outside the grant, a link out of it, text named as audio, a relative path, a missing playlist entry; tags, cues, a grid and a waveform read back from the drive; building again copies nothing),a_source_answers_in_pages_and_blobs,what_a_track_brings_that_cant_be_written_is_left_out,grants_are_checked_and_files_copied_as_they_were_checked(a file changed after planning is refused as it's copied), and the Hegel propertiesthe_drive_holds_exactly_the_granted_audio_files_and_their_cues,every_track_left_out_is_counted_with_why(each reason a track is left out, counted in one warning with the first three examples exactly),pages_and_blobs_add_up_to_the_library(any page size, pages in blobs, playlists with folders and missing entries, a source that pages forever or can't read its JSON),what_tracks_bring_is_written_or_counted_as_left_out(keys, beat grids, waveforms, My Tags, cues and colours, read back from the drive, with a count per kind left out),files_are_copied_only_as_they_were_checked(a file that grows while the plan hashes it refuses the plan; one changed after it refuses the copy) andonly_absolute_folders_off_the_drive_are_granted..jsonfile beside each says, and a folder'splaylists.json; options page, answer in blobs or never stop (test-plugin/plugin.rs).tests/it/plugins/sources.rs):a_drive_is_built_from_a_plugins_library_sourceandthe_sidecar_builds_a_drive_from_a_library_source; the plan cache's model testplans_are_kept_and_taken_as_modelledcoversplugins.source, and the Hegel propertyplugins_are_listed_and_described_in_text_as_in_jsondraws library sources. The Hegel propertycollection_export_from_a_source_is_checked_then_writtendraws--from,--db,--option,--grant,--replace, a dry run,--yesor neither, and--json, and checks each refusal and what's written.tui/plugins/tests/sources.rs):a_drive_is_built_from_a_library_source, and the Hegel propertya_drive_is_built_from_a_source_once_confirmed(from the palette or the drive's menu, with drawn folders, replacing or not, cancelled while planning, then confirmed or declined).Needs testing on players
export.pdbonly: tracks, playlists, tags and keys show as they do from a collection export.PWV4, which isn't written) show a track without one.Checklist
apps/locallibrary/CHANGELOG.mdhas a line under Unreleased, if people using it will noticedocs/and--helpstill say what the code does (apps/locallibrary/docs/rpc/too, for a sidecar change)