beets: program abortion due to exception in fnmatch_all()

Sorry about not knowing much how to compose this kind of messages!

In the duplicates init.py, under def prune_dirs(), I’ve added a try-except-clause in order to avoid error and aborted run

It now looks like this:

for directory in ancestors:
        directory = syspath(directory)
        if not os.path.exists(directory):
            # Directory gone already.
            continue
        try:
            if fnmatch_all(os.listdir(directory), clutter):
                # Directory contains only clutter (or nothing).
                try:
                    shutil.rmtree(directory)
                except OSError:
                    break
                except BaseException as foo:
                    print(foo)
                    break
            else:
                break
        except BaseException as foo:
            print(foo)
            break

Problem

I haven’t really understood exactly what’s going wrong. (Maybe some day when I’ve more energy?)

The program aborts with a non-intuitive error message about too many levels of symlinks.

Setup

  • Linux Mint 64 bits, 17.3, xfce
  • Python 2.7
  • beets version: beets version 1.3.17 plugins: duplicates

My configuration is:

user configuration: /home/johan/.config/beets/config.yaml
data directory: /home/johan/.config/beets
plugin paths: 
Sending event: pluginload
library database: /tmp/tracks.db
library directory: /mnt/qnap-212
Sending event: library_opened
verbose: 1
library: /tmp/tracks.db
per_disc_numbering: yes

statefile: prints.pickle
chroma:
    auto: yes
original_date: yes
ftintitle:
    format: (feat. {0})
lastgenre:
    auto: no
    count: 5
    force: no
    source: track
    separator: '/ '
    whitelist: ~/.config/beets/whitelist.txt
    canonical: ~/.config/beets/canonical.txt
duplicates:
    keys: [acoustid_fingerprint]
    merge: yes
    tiebreak:
        items: [bitrate]
    count: no
    full: no
    format: ''
    move: ''
    tag: ''
    path: no
    copy: ''
    album: no
    strict: no
    checksum: ''
    delete: no

plugins: chroma duplicates
directory: /mnt/qnap-212/

import:
    write: yes
    copy: no
    move: no
    link: no
    delete: no
    resume: ask
    incremental: yes
    timid: no
    log: prints_importlog.txt
    autotag: no
    singletons: yes
    default_action: apply
    detail: no
    flat: no
    group_albums: no
    pretend: no
    languages:
    - en
    - de
    - es
    - sv
    - fr
    - pt
    - fi
    - it
log: printslog.txt

paths:
    singleton: '%if{$mb_trackid,mb_}Singles/%if{$artist,%lower{%asciify{$artist}},_}/%if{$album,%lower{%asciify{$album}},_}%if{$year, [$year]}/%if{$disc,$disc-}%if{$track,$track. }%if{$title,$title,_}%if{$album, [$album]}'
    comp: '%if{$mb_albumid,mb_}Collections/%lower{%asciify{$album}}_%aunique{}/%if{$disc,$disc-}%if{$track,$track. }%if{$title,$title,_}%if{$artist, [$artist]}'
    default: '%if{$mb_albumid,mb_}Albums/%if{$albumartist,%lower{%asciify{$albumartist}}_}/%lower{%asciify{$album}}_%aunique{}/%if{$disc,$disc-}%if{$track,$track. }%if{$title,$title,_}'
replace:
    '[\\/\xa0]': _
    '[`\x27]': "\u2019"
    '[\"]': "\u201D"
    \.\.\.: "\u2026"
    ^\-: _
    ^\.: _
    '[\x00-\x1f]': _
    '[<>:"\?\*\|]': _
    \.$: _
    \s+$: ''
    ^\s+: ''

match:
    distance_weights:
        artist: 2.0
        album: 2.5
        year: 1.0
        label: 0.5
        catalognum: 0.5
        albumdisambig: 0.5
        album_id: 2.0
        tracks: 2.0
        missing_tracks: 0.1
        unmatched_tracks: 5.0
        track_title: 2.0
        track_artist: 2.0
        track_index: 1.0
        track_length: 9.0
        track_id: 2.0
    preferred:
        countries: []
        media: []
        original_year: yes
    ignored: [track_length unmatched_tracks]
    track_length_grace: 3
    track_length_max: 15

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

The best way to do it IMO would just be to change this:

    # Traverse upward from path.
    ancestors.append(path)
    ancestors.reverse()
    for directory in ancestors:
        directory = syspath(directory)
        if not os.path.exists(directory):
            # Directory gone already.
            continue
        if fnmatch_all(os.listdir(directory), clutter):
            # Directory contains only clutter (or nothing).
            try:
                shutil.rmtree(directory)
            except OSError:
                break
        else:
            break

to this:

    # Traverse upward from path.
    ancestors.append(path)
    ancestors.reverse()
    for directory in ancestors:
        directory = syspath(directory)
        if not os.path.exists(directory):
            # Directory gone already.
            continue
        try:
            if fnmatch_all(os.listdir(directory), clutter):
                # Directory contains only clutter (or nothing).
                shutil.rmtree(directory)
            else:
                break
        except OSError:
            break

i.e. we move the except outside the if statement.

Nice; I hadn’t looked to see that we already have an exception handler there. If the current behavior is just to silently give up when the filesystem prevents further pruning, then this solution seems perfect.

OK, sounds like we need to catch OSError, either inside prune_dirs or outside of its calls. (It will take some thinking to decide which.)

Honestly it would be better if the exception was thrown, as we can then see the stack trace.

You can re-throw an exception by just doing throw foo.

Also what is the issue with printing to stderr? You can capture it to a file like so if you want:

my_command 2> stderr_file

Well, we really do need to know which exception was raised so we know what to handle. Thank you!