Commit graph

60 commits

Author SHA1 Message Date
lrnselfreliance
9bbef50ca1
Remove a file's Tags Directory links when WROLPi deletes it (#861)
* Remove a file's Tags Directory links when WROLPi deletes it

When WROLPi deleted a tagged file (a forced delete, duplicate Video cleanup, or a poster conversion), the
source was unlinked but its Tags Directory hardlink was not.  The link became the only copy, so the tags sync
refused to delete it and warned on every sync.  The links of a file are now removed before WROLPi removes it,
and only if the link is the same file as its source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove Tags Directory links on path deletes; keep a valid poster's link

The file browser deletes by path (`lib.delete`), which unlinks the sources before any FileGroup is
deleted, so its tagged files still left orphaned links.  It now removes the links of the files it is about
to delete, or of every FileGroup in a deleted directory.

A poster is no longer unlinked from the Tags Directory unless it is actually removed: a valid JPEG that is
not named .jpg is kept with its link, and a converted poster's links are removed only once the conversion
succeeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:40:57 -06:00
lrnselfreliance
bed25b429d
db: request-path library functions use the request's connection (#774)
* db: request-path library functions use the request's connection

Library functions an API request reaches now take the request's Session (first argument) instead of
opening their own session or cursor, so a request needs one SQLite connection.  Raw SQL runs through the
new `wrolpi.db.session_curs(session)`, a cursor on the Session's connection; `RequestDB.curs()` uses it.

- Search: files, videos, archives, flasher, and docs search share the request connection for the total,
  the page of ids, and the page's models (3 connections -> 1).
- Zim search, including tag-filtered search and estimates (2 -> 1).
- Channel detail (`Channel.get_statistics` reads on the Channel's own Session) and the channel list.
- Video detail, edit, description, captions, comments and transcode.  `get_video_for_app` and
  `update_video` no longer commit; their handlers use `request.ctx.db.write()`, so editing a Video and
  reading it back is one transaction.
- Tags (list, overlaps, recent), file statistics and estimates, other estimates, bulk tag preview, the
  download list, retry, kill and the status summary, and docs/videos/archive statistics.

Not converted: file operations that refresh files inside the request (delete, rename, upload, channel
and collection tag/move) commit page by page in code shared with the file worker; the cached zim entry
lookup; config import.

The connection-count tests now assert one connection for every endpoint they cover (no xfails left), and
cover the videos and archive statistics endpoints, which had no API test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* downloads: startup retries in a write session of its own

start_single_tasks still called retry_downloads() without a Session after it began requiring one, so any
start after a completed refresh raised TypeError there: incomplete downloads were not reset and the
download_on_startup enable after it never ran.  Startup is not a request, so
DownloadManager.retry_downloads_on_startup() opens and commits its own short write transaction.

Tested through the method, not start_single_tasks: importing main registers its startup listeners on the
shared app, and every later test in that worker then runs them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:05:20 -06:00
lrnselfreliance
0b61d3ab08
videos: codec download preferences with transcode and strict-fail options (#701)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
* videos: codec download preferences with transcode and strict-fail options

Add video_codecs/audio_codecs ordered preference lists to the video,
channel, and RSS downloaders, alongside the existing resolution
selector.  Codec preference is codec-major: a preferred codec at a
lower resolution beats another codec at a higher resolution.

When 'transcode' is enabled, a downloaded file whose streams do not
match the preferences is re-encoded with ffmpeg after download (only
the mismatching stream; targets are extensible maps, currently h264
and aac/opus/mp3).  When 'strict_codecs' is enabled and transcode is
not, a download with no matching format fails permanently instead of
falling back.

Empty codec lists produce a byte-identical yt-dlp command to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: honor strict_codecs when transcode cannot produce the preference

Review fixes:
- strict_codecs is now suppressed only when transcoding can produce a
  preferred codec for every non-empty preference list (new
  transcode_can_satisfy_codecs); av1-only preferences with transcode
  enabled fail under strict instead of silently keeping the file.
- No partial transcode: an unfixable video mismatch blocks an
  audio-only conversion (and vice versa); the mismatch falls through
  to strict/keep.
- fsync the transcode output before rename, and the directory after,
  so power loss on a Pi cannot leave a truncated final file.
- mp3 yt-dlp pattern matches mp4a.6B as well as mp4a.6b.
- ffmpeg maps use 0:V:0 (excludes attached pictures) and 0🅰️0?
  (tolerates missing audio).
- Strict toggle is no longer disabled by transcode in the UI; popup
  wording corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: serialize transcodes machine-wide via shared_ctx lock

Sanic runs multiple worker processes, so an asyncio primitive cannot
serialize transcodes across download workers.  A multiprocessing.Lock
in shared_ctx now guarantees only one ffmpeg transcode runs at a time
on the machine; waiting workers poll the lock asynchronously so the
event loop stays responsive while a transcode holds it for hours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: transcode-lock review fixes

- Document that a crashed lock holder requires an API restart; this is
  accepted rather than adding owner-death recovery.
- Move the 'Transcoding' user notification to after the lock is
  acquired, so it fires when work begins rather than before a
  potentially hours-long wait.
- Soften the lock test docstring to what it measures (in-process
  wait/release); trim the acquire docstring to the polling rationale.
- Give transcode_lock its own section header in contexts.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 09:12:00 -06:00
lrnselfreliance
340aacb633
videos: parse the published date out of audio file names too (#676)
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run
NAME_PARSER's suffix whitelist had no audio suffixes (and no mkv/m4v),
so a correctly-named audio file got no published date and sorted to the
very end of its Channel -- which reads as missing. The whitelist now
carries the media suffixes a Video FileGroup can lead with.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 12:14:07 -06:00
lrnselfreliance
57d842671f
test: do not leave videos_destination as an invalid template (#656)
* test: do not leave videos_destination as an invalid template

test_format_videos_destination deliberately sets %(channel)s to prove
unknown placeholders fail, then never restored the global WROLPi config.
On xdist workers that later ran channel_factory without async_client
re-init, the polluted template caused KeyError/FileNotFoundError in
test_format_video_filename_uses_channel_name_over_info_json.

* test: stop search_suggestions from flaking empty domains on CI

A different failure than videos_destination pollution: under xdist load,
search_suggestions sometimes returned no domains for a valid query while
channels still matched. Harden the path by:

- running channel/domain/author/subject searches sequentially on the shared
  Session (asyncio.gather is not safe with one SQLAlchemy session)
- giving archive_factory a URL when only a domain is set so validate() does
  not clear collection_id via apply_domain()
- rewriting test_search_suggestions without hardcoded Collection ids and with
  unique names that will not collide with other tests
2026-08-11 18:51:06 -06:00
Roland
cdd28d0a39
Videos: a Channel's directory beats source_id when assigning a Video.
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
A user reported videos placed in a Channel's directory did not appear on
that Channel's page. Their info json named another existing Channel, and
get_channel preferred the source_id match over the directory, so the
videos were assigned to the other Channel.

Reorder get_channel so the directory match (with its parent-directory
walk) runs before the source_id/url matches. A Video's files must live
in their Channel's directory, so the directory reflects the user's
intent and beats yt-dlp metadata. This also aligns validation with
VideoDownloader._get_channel, which already prefers the destination
directory, and stops validation from clobbering that decision.

channel_id still wins over all other params; source_id/url still apply
when the file is not in any Channel's directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 18:33:15 -06:00
Roland
66a891c759
Refactoring videos and archives UI paths for consistent scheme. 2026-03-20 15:38:32 -06:00
Roland
0ffbc05e91
Unifying video download settings so that global/channel/rss/video
download settings have inheritance.  Allowing more granular control of
potentially failed downloads.
2026-03-05 17:40:17 -07:00
Roland
0c059865e2
Updating download process to prevent rate limiting.
* Added encrypted cookies feature for use with yt-dlp. Decrypted cookies never touch the disk.
* Adding sleep between all downloads to prevent rapid requests.
* Channels are fully refreshed before downloading.
* Removing superseded video browser profile.
2026-02-21 14:08:00 -07:00
Roland
929468821b
Using gnomekeyring for yt-dlp brave cookie parsing. 2026-02-19 17:44:57 -07:00
Roland
d8c51db5a3
Fetching video comments uses video downloader browser profile. 2026-02-19 16:25:35 -07:00
Roland
ae17152e79
Expanding video browser profile selection to match archive feature. Adding toggle to disable downloading missing video comments. 2026-02-19 16:15:18 -07:00
Roland
3f5470d974
Channels are linked to downloads by directory or download source id when downloading, or when importing config. 2026-02-08 14:27:59 -07:00
Roland
387ef6dfe8
Saving video ffprobe json when video file is uploaded. 2026-02-08 12:29:25 -07:00
Roland
8a4f9201f6
Using Channel name when reorganizing channel's files, fallback to info json uploader name (which may be outdated).
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
2026-02-05 11:48:25 -07:00
Roland
74cb5171da
FileGroups in Collections can be reorganized in the Collection's directory.
* Batch reorganization is supported for both Domain and Channel Collections.
* Batch reorganization happens when any Collection does not match the respective file format. For example, a Channel Collection file format that does not match the `yt_dlp_options.file_name_format` will show `needs_reorganization`.
* Individual Collections can always be reorganized. The reorganization is a little more aggressive. Useful for when some files may have been missed in the batch reorganization.
2026-01-31 18:08:41 -07:00
Roland
173284699d
FileWorker: Async queue-based file synchronization system
Introduces FileWorker for background file operations with dual-queue
architecture (public/private). Handles refresh, move, and rename tasks
with progress tracking, job completion monitoring, and batch processing.
Compares DB FileGroups to filesystem state using FileGroupDiff to detect
new/modified/deleted files. Designed for memory efficiency on Raspberry Pi.
2026-01-23 15:29:15 -07:00
Roland
344fe9035f
Removing optional_session, passing session down from request middleware. Moving session param to the front of functions.
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
2025-12-11 16:03:18 -07:00
Roland
a1e0fb4e92 Creating a new Collection model for use in organizing FileGroups.
Replacing Channels and Domains with Collection.

Moving downloading relationships from Channel to Collection.

Created domains.yaml config to manage tagged domain collections.

Adding turn-key upgrade solution in the UI.
2025-12-06 15:11:37 -07:00
Roland
c635267eda
Fixing bug where Archive screenshots would not be tracked when a user uploads a screenshot.
Some checks failed
CodeQL / Analyze (push) Has been cancelled
2025-10-12 17:19:54 -06:00
Roland
76a454ea34
Linking to uploaded file on download after a user uploads a file with the same URL.
Some checks failed
CodeQL / Analyze (push) Has been cancelled
2025-08-11 16:54:10 -06:00
Roland
00c86e1a1a
Searching wrolpi user's home directory for browser profiles. 2025-05-02 17:24:51 -06:00
Roland
15f6e7ed5c
Handling new issue with youtube caption file names. Adding QR Code calculator. 2025-02-07 11:44:17 -07:00
Roland
376850ceeb
Moving config api endpoints to their own file.. 2024-12-03 22:33:19 -07:00
Roland
bf29bdcff7
Displaying error when video file name format is invalid. 2024-11-29 17:01:16 -07:00
Roland
b7d163e7ff
Config files are initialized on startup, if they are missing. 2024-11-28 18:13:43 -07:00
Roland
5621d47309
Video downloads have many more options. Downloads are migrated.
Storing video settings in session storage to help user start downloads consistently.

Applying channel tag name to new channels when download videos or channels.

Initializing map tiles when importing and resetting map.
2024-11-28 09:48:57 -07:00
Roland
04d92dca09
Not deleting video poster when converting to jpg. 2024-10-16 18:57:22 -06:00
Roland
d3e5b56520
Bumping python requirements versions. 2024-10-16 18:56:41 -06:00
Roland
d54735c4bb
Configs can be saved in background using switches. 2024-10-16 18:56:38 -06:00
Roland
45167e4f32
A Channel can be tagged with a single Tag. The Channel can be moved into a Tag Directory according to the config. 2024-10-16 18:56:19 -06:00
Roland
3906223844
Moving Channel attachment logic to recurring_download method because only recurring downloads can have Channels. 2024-10-16 18:56:13 -06:00
Roland
a98aebfdda
A video that is uploaded is attached to its Channel. Saving Channels config with both download url and frequency in case downloads config is corrupted. 2024-10-16 18:56:11 -06:00
Roland
2ad363281f
Delaying map startup because it prevents the API from starting. 2024-10-16 18:56:00 -06:00
Roland
7933d9625c
Downloads without a frequency cannot be a Channel Download. 2024-10-16 18:55:57 -06:00
Roland
63738bd70f
Removing ChannelDownload in favor of Download.channel_id. 2024-10-16 18:55:54 -06:00
Roland
37ff1ab2cd
Changing title_match to title_include for clarity. 2024-10-16 18:55:51 -06:00
Roland
287beabb9c
Creating missing ChannelDownload when importing channels config. 2024-10-16 18:55:49 -06:00
Roland
ce0ae66a9e
Storing video download_datetime. 2024-03-12 11:06:30 -06:00
Roland
15687e534a
Moving data from models to FileGroup.
* Ebook.creator -> FileGroup.author
* Video.viewed -> FileGroup.viewed
* Video.duration -> FileGroup.length
* Video.url -> FileGroup.url
* Archive.url -> FileGroup.url
2023-10-29 17:31:54 -06:00
Roland
ae9d188391
Adding FileGroup.published_date/author/etc. Creating script which converts a Singlefile to an Archive FileGroup files.
Parsing SingleFile datetime using dates.strpdate.

Adding timezone when no timezone info is available.

Installing python-dateutil to assist in handling diverse datetime strings.

Reworking refresh progress to use idempotency to display more accurate progress.  Only logging that files have been invalidated if they used to be indexed.

Clearing archives to force re-index.

Ignoring 'Unknown' author from PDF metadata. Using file_title when pdf metadata title is empty.
2023-10-23 18:09:08 -06:00
Roland
e7582944b9
Channels not in the channels config are deleted on import. 2023-09-12 20:24:02 -06:00
Roland
b481334e1a
Fixing bug with parsing video upload dates. 2023-03-29 08:53:01 -06:00
Roland
5615ce542d
Making example label a tag.
Tags can be created and deleted in UI.

Adding tag_file using id to avoid session error.

Using UTC in test.

Matching tag_names style in test.

Tag icon is displayed on file rows.

Archives, Videos and Files can be filtered by their tags.

Tags can be added and removed from UI.

Tags are displayed in VideoPlayer.

The tags of a file are returned in it's JSON.

Video favorites are converted to Favorite tag on channel config import.

Updating tests to match frontend usage.

PDF creator and modification time are displayed.

Reworking file search to use Video.__json__ for complex video relations.

Archives can be viewed.

Removing ebook.title.

Reworking alembic migration to be an upgrade rather than a replace.

Skipping test on circleci.

Clearing out video caption index only if extract_subtitles if off.

Channel tests pass.

Some video tests pass.

Missing videos can be found.  A video can be downloaded.

Orphaned files can be found.

Videos can be tagged.

Videos can be searched.

Videos can be modeled.

Converting some tests to use FileGroup.primary_path.

Adding archive and domains to alembic migration.

Testing cascading Archive titles.

Archives now uses FileGroup.

archive.lib tests pass.

Archive is partially converted to FileGroup.

Calculating FileGroup.primary file on refresh, rather than on select.

wrolpi tests pass.

Using FilesType to convert between str/pathlib.Path for FileGroup.files.

Using list of files in FileGroup.files instead of dictionary.

Storing files in dedicated FileGroup.files to avoid overwriting them.

Adding comment explaining CASE.

Not overwriting FileGroup.data on refresh if no change has been detected.

Removing File model in favor of FileGroup.data.

Adding typing to Files methods.

Files are indexed using the primary mimetype of the FileGroup.

files test_lib tests pass.

Removing file_group_file in favor of simple one-to-many relationship.

PDF files are indexed during refresh.

Adding Tag and TagFile to tag files with specific tags.

Only iterating when full_stem_ids has contents.

Testing that FileGroups are related to their Files.

Files and file_groups are created and linked.

Bump webpack from 5.75.0 to 5.76.1 in /app (#160)

Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-25 17:54:41 -06:00
Roland
3751ed7999
Reformatting files. 2022-12-14 19:11:11 -07:00
Roland
9ffe6ee3f6
Info JSON epoch is time of download, not time of upload. 2022-12-08 13:45:46 -07:00
Roland
d0d69519cd
Bumping version for new feature.
Skipping video epoch test on circleci.

Created script which finds and cleans up (renames/deletes) orphaned video meta files.

Reworking orphaned video query, some video files were leaking through.

Modifying File table to allow finding of orphaned video files.

Extracting epoch from video info_json for more accurate upload_date.

Extracting epoch from video info_json for more accurate upload_date.
2022-12-07 18:15:28 -07:00
Roland
83bd19d91f
Returned Video.censored when fetching video. 2022-11-10 08:21:54 -07:00
Roland
b81e00ff3d
Perserving ts_rank when searching. 2022-10-24 15:34:13 -06:00
Roland
6fa6be50fe
Stopping renderd when reseting map db.
Skipping channel import during test unless explicitly requested.

Deleting gis database during upgrade, if user agrees.  Increasing API verbosity.

Using status context to prevent fetching status in useHotspot.

Disabling wifi toggle button when pending.

Toggling hotspot when clicking nav hotspot icon.

Displaying Hotspot status icon in nav menu.

Importing channels/favorites after refreshing.

Refresh tasks cancel when server shuts down.

Bumping version for release speed.

Refreshing files is 3x faster.

Creating useRecurringTimeout hook which repeatedly calls a callback after a timeout.

Changing intervals to timeout so fetches don't stack up while server is loaded.

Checking that video file exists before fetching.

Creating release candidate branch.  Not indexing model associated files.
2022-10-21 15:21:57 -06:00