X-Git-Url: https://www.irasnyder.com/gitweb/?p=animesorter.git;a=blobdiff_plain;f=animesorter2.py;h=e85a2b35637433f5764d0dcd35a8b399a8e580c4;hp=c56ad2bca93a8a77c0564feb0e2881aa965c9716;hb=HEAD;hpb=b6c8767dcf37347a87d38ef5545e96815cd3f0b7 diff --git a/animesorter2.py b/animesorter2.py index c56ad2b..e85a2b3 100755 --- a/animesorter2.py +++ b/animesorter2.py @@ -35,82 +35,96 @@ import re import sys import errno import shutil -from optparse import OptionParser +import logging +import optparse + +PROGRAM_NAME = 'animesorter2' +PROGRAM_VERSION = '2.1.0' ### Default Configuration Variables ### DICT_FILE = os.path.join ('~','.config','animesorter2','animesorter.dict') WORK_DIR = os.path.join ('~','downloads','usenet') SORT_DIR = os.path.join ('/','data','Anime') -TYPES_REGEX = '.*(avi|ogm|mkv|mp4|\d\d\d)$' - class AnimeSorter2: def __init__(self, options): self.options = options + self.dict = None + + def __valid_dict_line (self, line): + if len(line) <= 0: + return False + + if '=' not in line: + return False + + # Comment lines are not really valid + if re.match ('^(\s*#.*|\s*)$', line): + return False + + # Make sure that there is a definition and it is valid + try: + (regex, directory) = line.split('=') + regex = regex.strip() + directory = directory.strip() + except: + return False + + # Make sure they have length + if len(regex) <= 0 or len(directory) <= 0: + return False + + # I guess that it's valid now + return True def parse_dict(self): """Parses a dictionary file containing the sort definitions in the form: - DIRECTORY = PATTERN + REGEX_PATTERN = DIRECTORY Returns a list of tuples of the form (compiled_regex, to_directory)""" try: f = open(self.options.dict_file, 'r', 0) try: - data = f.read() + raw_lines = f.readlines() finally: f.close() except IOError: - self.print_dict_fail (self.options.dict_file) - sys.exit() + logging.critical ('Opening dictionary: %s FAILED' % self.options.dict_file) + sys.exit(1) - ### Get a LIST containing each line in the file - lines = [l for l in data.split('\n') if len(l) > 0] - - ### Remove comments / blank lines (zero length lines already removed above) - regex = re.compile ('^\s*#.*$') - lines = [l for l in lines if not re.match (regex, l)] - regex = re.compile ('^\s*$') - lines = [l for l in lines if not re.match (regex, l)] - - ### Split each line into a tuple, and strip each element of spaces - result = self.split_lines(lines) - result = [(re.compile(r), d) for r, d in result] - - ### Give some information about the dictionary we are using - self.print_dict_suc (self.options.dict_file, len(result)) - - return tuple(result) - - def split_lines(self, lines): + ### Find all of the valid lines in the file + valid_lines = [l for l in raw_lines if self.__valid_dict_line (l)] + # Set up variable for result result = [] - for l in lines: - - try: - r, d = l.split('=') - r = r.strip() - d = d.strip() - except ValueError: - self.print_dict_bad_line (l) - continue + ### Split each line into a tuple, and strip each element of spaces + for l in valid_lines: + (regex, directory) = l.split('=') + regex = regex.strip() + directory = directory.strip() - result.append((r, d)) + # Fix up the directory if necessary + if directory[0] != '/': + directory = os.path.join (self.options.output_dir, directory) - return result + # Fix up the regex + if regex[0] != '^': + regex = '^' + regex - def get_matches(self, files, pattern): - """get_matches(files, pattern): + if regex[-1] != '$': + regex += '$' - files is type LIST - pattern is type sre.SRE_Pattern + # Store the result + result.append ( (re.compile (regex), directory) ) - Returns a list of the files matching the pattern as type sre.SRE_Match.""" + ### Give some information about the dictionary we are using + logging.info ('Successfully loaded %d records from %s\n' % \ + (len(result), self.options.dict_file)) - matches = [m for m in files if pattern.search(m)] - return matches + return tuple (result) def as_makedirs (self, dirname): """Call os.makedirs(dirname), but check first whether we are in pretend @@ -119,16 +133,16 @@ class AnimeSorter2: if not os.path.isdir (dirname): if self.options.pretend: - self.print_dir_create_pretend (dirname) + logging.info ('Will create directory %s' % dirname) return 0 if self.get_user_choice ('Make directory?: %s' % (dirname, )): try: os.makedirs (dirname) - self.print_dir_create_suc (dirname) + logging.info ('Created directory %s' % dirname) except: - self.print_dir_create_fail (dirname) + logging.critical ('Failed to create directory %s' % dirname) return errno.EIO return 0 @@ -141,15 +155,15 @@ class AnimeSorter2: dstname = os.path.join (todir, f) if self.options.pretend: - self.print_move_file_pretend (f, todir) + logging.info ('Will move %s to %s' % (f, todir)) return 0 # success if self.get_user_choice ('Move file?: %s --> %s' % (srcname, dstname)): try: shutil.move (srcname, dstname) - self.print_move_file_suc (f, todir) + logging.info ('Moved %s to %s' % (f, todir)) except: - self.print_move_file_fail (f, todir) + logging.critical ('FAILED to move %s to %s' % (f, todir)) return errno.EIO return 0 @@ -160,9 +174,9 @@ class AnimeSorter2: ret = 0 - ## Check for a non-default directory - if todir[0] != '/': - todir = os.path.join(self.options.output_dir, todir) + # Leave immediately if we have nothing to do + if len(files) <= 0: + return ret ## Create the directory if it doesn't exist ret = self.as_makedirs (todir) @@ -181,19 +195,11 @@ class AnimeSorter2: return ret - def __dir_walker(self, dict, root, dirs, files): - - ## Get all of the files in the directory that are of the correct types - types_re = re.compile(TYPES_REGEX, re.IGNORECASE) - raw_matches = [f for f in files if types_re.match(f)] - - ### Loop through the dictionary and try to move everything that matches - for regex, todir in dict: - matches = self.get_matches(raw_matches, regex) + def __dir_walker(self, rootdir, files): - ## Move the files if we've found some - if len(matches) > 0: - self.move_files(matches, root, todir) + for (r,d) in self.dict: + matches = [f for f in files if r.match(f)] + self.move_files (matches, rootdir, d) def get_user_choice(self, prompt): @@ -217,78 +223,32 @@ class AnimeSorter2: def main(self): ## Print the program's header - self.print_prog_header () + logging.info ('Regular Expression File Sorter (aka animesorter)') + logging.info ('=' * 80) + logging.info ('Copyright (c) 2005-2007, Ira W. Snyder (devel@irasnyder.com)') + logging.info ('This program is licensed under the GNU GPL v2') + logging.info ('') ## Parse the dictionary - dict = self.parse_dict() + self.dict = self.parse_dict() if self.options.recursive: ## Start walking through directories for root, dirs, files in os.walk(self.options.start_dir): - self.__dir_walker(dict, root, dirs, files) + self.__dir_walker(root, files) else: - self.__dir_walker(dict, self.options.start_dir, - [d for d in os.listdir(self.options.start_dir) if os.path.isdir(d)], - [f for f in os.listdir(self.options.start_dir) if os.path.isfile(f)]) - - ############################################################################ - ### Functions for the printing system - ############################################################################ - - def print_prog_header(self): - if self.options.quiet: - return - - print 'Regular Expression File Sorter (aka animesorter)' - print '================================================================================' - print 'Copyright (c) 2005,2006, Ira W. Snyder (devel@irasnyder.com)' - print 'All rights reserved.' - print 'This program is licensed under the GNU GPL v2' - print - - def print_move_file_suc(self, f, t): - if self.options.quiet: - return - - print 'Moved %s to %s' % (f, t) - - def print_move_file_fail(self, f, t): - print 'FAILED to move %s to %s' % (f, t) - - def print_move_file_pretend (self, f, t): - print 'Will move %s to %s' % (f, t) - - def print_dir_create_suc(self, d): - if self.options.quiet: - return - - print 'Created directory %s' % (d, ) - - def print_dir_create_fail(self, d): - print 'Failed to create directory %s' % (d, ) - - def print_dir_create_pretend (self, d): - print 'Will create directory %s' % (d, ) - - def print_dict_suc(self, dic, num): - if self.options.quiet: - return - - print 'Successfully loaded %d records from %s\n' % (num, dic) - - def print_dict_fail(self, dic): - print 'Opening dictionary: %s FAILED' % (dic, ) - - def print_dict_bad_line(self, dic): - print 'Bad line in dictionary: %s' % (dic, ) - + self.__dir_walker(self.options.start_dir, [f for f in + os.listdir(self.options.start_dir) if os.path.isfile(f)]) ### MAIN IS HERE ### def main(): + # Set up the logger + logging.basicConfig (level=logging.INFO, format='%(message)s') + ### Get the program options - parser = OptionParser() + parser = optparse.OptionParser() parser.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help="Don't print status messages to stdout") parser.add_option('-d', '--dict', dest='dict_file', default=DICT_FILE, @@ -303,6 +263,10 @@ def main(): help='Confirm each move', action='store_true') parser.add_option('-p', '--pretend', dest='pretend', default=False, help='Enable pretend mode', action='store_true') + parser.add_option('-e', '--editor', dest='run_editor', default=False, + help='Run editor on dictionary', action='store_true') + parser.add_option('-V', '--version', dest='version', default=False, + help='Show version and exit', action='store_true') ## Parse the options (options, args) = parser.parse_args() @@ -312,10 +276,36 @@ def main(): options.start_dir = os.path.abspath(os.path.expanduser(options.start_dir)) options.output_dir = os.path.abspath(os.path.expanduser(options.output_dir)) - as = AnimeSorter2(options) - as.main() + ## Show version if necessary + if options.version: + print '%s - %s' % (PROGRAM_NAME, PROGRAM_VERSION) + print + print 'Copyright (c) 2005-2007 Ira W. Snyder (devel@irasnyder.com)' + print 'This program comes with ABSOLUTELY NO WARRANTY.' + print 'This is free software, and you are welcome to redistribute it' + print 'under certain conditions. See the file COPYING for details.' + + sys.exit (0) + + ## Run editor if necessary + if options.run_editor: + editor = os.getenv ('EDITOR') + + if editor != None: + os.system ('%s %s' % (editor, options.dict_file)) + else: + logging.critical ('Default editor could not be found!') + sys.exit (1) + + sys.exit (0) # successful, but exit anyway + + # Change the loglevel if we're running in quiet mode + if options.quiet: + logging.getLogger().setLevel (logging.CRITICAL) + + sorter = AnimeSorter2(options) + sorter.main() if __name__ == '__main__': main () -# vim: set ts=4 sw=4 sts=4 expandtab: