Skip to content

Add .lrrignore support for filtering archives - #1672

Open
sitiyou wants to merge 1 commit into
Difegue:devfrom
sitiyou:feat-lrrignore
Open

Add .lrrignore support for filtering archives#1672
sitiyou wants to merge 1 commit into
Difegue:devfrom
sitiyou:feat-lrrignore

Conversation

@sitiyou

@sitiyou sitiyou commented Jul 10, 2026

Copy link
Copy Markdown

This PR add .lrrignore support with plexignore based syntax, triggered during Shinobu scanning and when new files are added.

See #1120

syntax

Based on the Plex .plexignore spec, extended with some useful features from gitignore:

Syntax Description
* glob wildcard, matches any character except /
# comment (line start only; \# escapes to literal #)
! negation, overrides parent directory rules (\! escapes to literal !)
Leading / anchors the pattern to the .lrrignore file's directory
Trailing / matches all contents of the directory
\* literal *
\ for any other \X, the backslash is silently dropped (\XX)

Rules are resolved relative to the .lrrignore file's own directory, with nested rules overriding parent ones.

interface

Ignore.pm exports four methods:

  • is_ignored($file, $rules): walks parent directories via dirname and matches against compiled regex rules
  • build_ignore_rules($content_dir): scans the directory tree for .lrrignore files and builds a rule set
  • initialize(): called at server startup, builds rules and stores them in Redis as LRR_IGNORE_RULES
  • load_ignore_rules(): loads the frozen rule set from Redis with state $cached

windows compatibility

The project's existing cross-platform filesystem APIs don't quite cover some of the path operations this feature needs. I'm not familiar with Windows, so I can't say for sure it'll work there. The tests pass under MSYS2. And that is all I can guaranteen.

unit test

Added tests covering the various ignore syntax rules.

@sitiyou
sitiyou marked this pull request as draft July 10, 2026 11:30
@sitiyou

sitiyou commented Jul 10, 2026

Copy link
Copy Markdown
Author

I forgot to handle escaped special characters in ignore rules.
Maybe I need to rethink the .lrrignore syntax design, since implementing full gitignore compatibility syntax is turning out to be more complex than I expected.
It also means characters like [] would require escaping to work properly, but they are extremely common in manga titles.

Maybe I should only implement a subset of gitignore instead.

@sitiyou
sitiyou marked this pull request as ready for review July 10, 2026 18:02
@sitiyou

sitiyou commented Jul 10, 2026

Copy link
Copy Markdown
Author

@Difegue this PR is ready for review. The syntax has been simplified and I've tested some common use cases in docker.

@sitiyou
sitiyou force-pushed the feat-lrrignore branch 2 times, most recently from 7a862fb to c9c6463 Compare July 10, 2026 18:48
Comment thread lib/Shinobu.pm Outdated
$_ = create_path($_);
return if -d $_; #Directories are excluded on the spot

update_ignore_rules( dirname($_), $ignore_rules );

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-updating the rules for every single incoming file feels overkill.

I think we could get by only updating the rules when Shinobu gets started.

@sitiyou sitiyou Jul 12, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading all .lrrignore files at startup would mean walking the filesystem twice: once to gather ignore rules, once to update filemaps.

Since find_path visits files in the same directory sequentially, update_ignore_rules can be simplified to a single string comparison for most calls:

return if $rules->{current_dir} && $rules->{current_dir} eq $dst_dir;

BTW, File::Find supports pruning to skip ignored directories, but Win32::LongPath::Find lacks this functionality so I did not implement pruning.

And Win32::LongPath::Find does not return directories, meaning we can't only update rules when entering a directory.

Comment thread lib/Shinobu.pm Outdated
unless ( -d $name ) {

my $dirname = LANraragi::Model::Config->get_userdir;
my $ignore_rules = build_ignore_rules( $dirname, $name );

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be optimized by caching all ignore rules after the filemap scan. So any modifications made by the user will not take effect until Shinobu is restarted.
And newly added files can simply use the cached rules, no extra filesystem api calls are needed.

Comment thread lib/Shinobu.pm Outdated

@psilabs-dev psilabs-dev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple questions/issues from me at first look

I think ignore reads are cheap compared to ID compute, shinobu can probably do a read once before every scan. Upload API on the other hand is less forgiving (maybe cache the rules in redis? but ehh)

Otherwise, users should not expect to update rules during LRR uptime, bc the results will be inconsistent per worker (unless you're reading per call).

Comment thread lib/Shinobu.pm Outdated
@@ -154,14 +166,22 @@ sub update_filemap {
my @deletedfiles = grep { !$fshash{$_} } @filemapfiles;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the ignored files are treated as deleted? What happens to metadata during a cleanup?

Comment thread lib/Shinobu.pm Outdated
if ( -e $deletedfile ) {
my $id = $redis->hget( "LRR_FILEMAP", $deletedfile );
if ($id) {
$redis_arc->hset( $id, "file", "" );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related (since "file" is false now):

unless ( -e $file ) {

@sitiyou sitiyou Jul 19, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems ok in my test. delete_archive will not delete the actual file since the file field was wiped, only metadata will be deleted.

# lib/LANraragi/Model/Archive.pm +426
    if ( -e $filename ) {
        my $status = unlink_path($filename);

Comment thread lib/LANraragi/Utils/Ignore.pm
Comment thread lib/Shinobu.pm Outdated
@sitiyou

sitiyou commented Jul 14, 2026

Copy link
Copy Markdown
Author

OK, let me rethink the design:

  • The ignore rules should be global and immutable, to make them consistent across all workers (upload, shinobu, and any other plugins).
  • An extra scan of the filesystem to construct ignore rules is actually acceptable, considering the filesystem cache. (30ms vs 10ms, in my test with 700 files and 15 directories on HDD using find . > /dev/null)

So I should construct ignore rules only at startup, and cache them internally in ignore.pm. (but is it ok to make ignore.pm stateful? or simply put them in redis?)

@sitiyou

sitiyou commented Jul 19, 2026

Copy link
Copy Markdown
Author

UPDATE:

  • Ignore rules are now built at startup and stored in redis for global access.
  • upload.pm now rejects incoming files if they match any ignore rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants