module

ArrTop::ImportWatch

Reads live import (copy) progress off disk. The *arr API reports an import as importing with sizeleft: 0 and no copy progress, so the only way to show how far the copy has gotten is to watch the destination file grow on disk.

This works only when arrtop runs *on the arr host (or on a mount of its library). Off-host — the folder does not exist or cannot be read — it degrades to "unknown" (nil) and never raises.

Constants

Log = ::Log.for("arrtop.import")
VIDEO_EXTENSIONS = Set {".mkv", ".mp4", ".avi", ".m4v", ".ts", ".m2ts", ".mov", ".wmv", ".mpg", ".mpeg", ".webm", ".flv"}

Video-file extensions (lower-case, with dot) that count as an import's destination file. Anything else in the folder (.nfo, .srt, artwork, ...) is ignored.

Class methods

episode_pattern(season : Int32, episode : Int32) : Regex

A case-insensitive regex matching a filename's SxxEyy token for the given season/episode, tolerant of zero-padding (S2E3 == S02E03). The leading \b anchors the season, the optional (E\d+)*? run lets an earlier episode in a multi-episode file (S02E03E04) be skipped so BOTH episode 3 and episode 4 match their own token, and the trailing (\b|E) accepts a boundary or the next E in that run.

Public so TorrentSizes matches a torrent's cached file names to an episode with the exact same rule the disk-watch uses on on-disk file names.

Source
episode_progress(dest_folder : String | Nil, target : Int64, season : Int32, episode : Int32) : Tuple(ImportProgress, Bool) | Nil

Episode-aware watch for a Sonarr season pack. Finds this episode's file (by its SxxEyy token) under dest_folder and reports both its live copy progress and whether that file is the folder's newest-mtime video — the one Sonarr is actively copying right now.

Returns nil (nothing to show) under the same conditions as .progress (off-host / blank folder / non-positive target / no matching file yet). Otherwise returns {ImportProgress, active} where active == true means this episode's file is the folder-wide newest video: a season pack is copied one file at a time, so already-copied episodes have older mtimes (active == false, i.e. done) and only the file being written is newest.

Two walks: one filtered to this episode's token (its bytes/path), one folder-wide (the newest video's path). All the walk's off-host / vanished- file / unreadable guarantees are inherited from newest_video_file/walk.

Source
progress(dest_folder : String | Nil, target : Int64, season : Int32 | Nil = nil, episode : Int32 | Nil = nil) : ImportProgress | Nil

Live copy progress for an Importing row, or nil when there is nothing to show.

Returns nil (unwatchable / nothing yet) when: dest_folder is nil or blank; target is <= 0; the folder does not exist or cannot be read; or no candidate video file exists yet (the import may not have created it).

Otherwise it walks dest_folder recursively, picks the most recently modified video file (the one actively being written — see below), reads its size, and returns an ImportProgress.

The destination is a folder: Radarr's movie folder, or Sonarr's series folder (where the file lands in a Season NN/ subfolder), so the walk is recursive. Selection is by most-recent mtime, not largest size: during an upgrade a full old file sits beside the new partial one, and the largest-size heuristic would wrongly pick the old file — the freshly written file is the one with the newest mtime.

When season and episode are both given (Sonarr episode rows), only the video file whose name carries that episode's SxxEyy token is considered — a season pack drops N episodes into ONE series folder, so without this filter every episode row would watch the same single newest file. Movies (nil season/episode) keep the folder-wide "newest video file" behaviour.

Source