116 |
ira |
1 |
#!/usr/bin/python
|
|
|
2 |
|
121 |
ira |
3 |
import os, re, shutil, sys
|
|
|
4 |
from optparse import OptionParser
|
116 |
ira |
5 |
|
121 |
ira |
6 |
### DEFAULT CONFIGURATION VARIABLES ###
|
175 |
ira |
7 |
DICT_FILE = os.path.expanduser('~/.config/animesorter/animesorter.dict')
|
121 |
ira |
8 |
WORK_DIR = os.path.expanduser('~/downloads/usenet')
|
|
|
9 |
SORT_DIR = os.path.expanduser('/data/Anime')
|
116 |
ira |
10 |
|
121 |
ira |
11 |
### Globals
|
|
|
12 |
options = {}
|
|
|
13 |
move_status = []
|
|
|
14 |
|
|
|
15 |
|
|
|
16 |
def parse_dict():
|
116 |
ira |
17 |
"""Parses a dictionary file containing the sort definitions in the form:
|
|
|
18 |
DIRECTORY = PATTERN
|
|
|
19 |
|
121 |
ira |
20 |
Returns a list of tuples of the form (compiled_regex, to_directory)"""
|
116 |
ira |
21 |
try:
|
121 |
ira |
22 |
f = open(options.dict_file, 'r', 0)
|
116 |
ira |
23 |
try:
|
|
|
24 |
data = f.read()
|
|
|
25 |
finally:
|
|
|
26 |
f.close()
|
|
|
27 |
except IOError:
|
121 |
ira |
28 |
print 'Error opening %s ... exiting!' % options.dict_file
|
116 |
ira |
29 |
sys.exit()
|
|
|
30 |
|
|
|
31 |
### Get a LIST containing each line in the file
|
|
|
32 |
lines = [l for l in data.split('\n') if len(l) > 0]
|
|
|
33 |
|
|
|
34 |
### Split each line into a tuple, and strip each element of spaces
|
121 |
ira |
35 |
result = [(r, d) for r, d in [l.split('=') for l in lines]]
|
|
|
36 |
result = [(r.strip() ,d.strip()) for r, d in result]
|
|
|
37 |
result = [(re.compile(r), d) for r, d in result]
|
116 |
ira |
38 |
|
121 |
ira |
39 |
return tuple(result)
|
116 |
ira |
40 |
|
121 |
ira |
41 |
def get_types_re():
|
|
|
42 |
"""Returns a compiled regular expression which will find objects of the
|
|
|
43 |
types specified in this function's definition."""
|
129 |
ira |
44 |
types = ('avi', 'ogm', 'mkv', 'mp4')
|
121 |
ira |
45 |
types_regex = ''
|
116 |
ira |
46 |
|
121 |
ira |
47 |
for i in types:
|
|
|
48 |
types_regex += '%s|' % i
|
116 |
ira |
49 |
|
121 |
ira |
50 |
types_regex = '.*(%s)$' % types_regex[:-1]
|
116 |
ira |
51 |
|
121 |
ira |
52 |
return re.compile(types_regex, re.IGNORECASE)
|
116 |
ira |
53 |
|
121 |
ira |
54 |
def get_matches(files, pattern):
|
|
|
55 |
"""get_matches(files, pattern):
|
116 |
ira |
56 |
|
|
|
57 |
files is type LIST
|
|
|
58 |
pattern is type sre.SRE_Pattern
|
|
|
59 |
|
|
|
60 |
Returns a list of the files matching the pattern as type sre.SRE_Match."""
|
|
|
61 |
matches = [m for m in files if pattern.search(m)]
|
|
|
62 |
return matches
|
|
|
63 |
|
121 |
ira |
64 |
def move_files(files, fromdir, todir):
|
|
|
65 |
"""move_files(files, fromdir, todir):
|
|
|
66 |
Move the files represented by the list FILES from FROMDIR to TODIR"""
|
|
|
67 |
## Check for a non-default directory
|
|
|
68 |
if todir[0] != '/':
|
|
|
69 |
todir = os.path.join(options.output_dir, todir)
|
116 |
ira |
70 |
|
121 |
ira |
71 |
## Create the directory if it doesn't exist
|
|
|
72 |
if not os.path.isdir(todir):
|
|
|
73 |
try:
|
|
|
74 |
os.makedirs(todir)
|
|
|
75 |
except:
|
|
|
76 |
move_status.append('FAILED to create directory: %s' % todir)
|
116 |
ira |
77 |
|
121 |
ira |
78 |
## Try to move every file, one at a time
|
|
|
79 |
for f in files:
|
|
|
80 |
srcname = os.path.join(fromdir, f)
|
|
|
81 |
dstname = os.path.join(todir, f)
|
116 |
ira |
82 |
|
121 |
ira |
83 |
try:
|
|
|
84 |
shutil.move(srcname, dstname)
|
|
|
85 |
except:
|
|
|
86 |
move_status.append('FAILED to move %s to %s' % (f, todir))
|
|
|
87 |
return
|
116 |
ira |
88 |
|
121 |
ira |
89 |
move_status.append('Moved %s to %s' % (f, todir))
|
116 |
ira |
90 |
|
121 |
ira |
91 |
def print_prog_header():
|
|
|
92 |
if options.verbose:
|
|
|
93 |
print 'Regular Expression File Sorter (aka animesorter)'
|
|
|
94 |
print '================================================================================'
|
|
|
95 |
print 'Copyright (c) 2005, Ira W. Snyder (devel@irasnyder.com)'
|
|
|
96 |
print 'All rights reserved.'
|
|
|
97 |
print 'This program is licensed under the GNU GPL v2'
|
|
|
98 |
print
|
116 |
ira |
99 |
|
121 |
ira |
100 |
def print_dir_header(dir):
|
|
|
101 |
if options.verbose:
|
|
|
102 |
print 'Working in directory: %s' % dir
|
116 |
ira |
103 |
print '================================================================================'
|
|
|
104 |
|
121 |
ira |
105 |
def print_move_status():
|
|
|
106 |
if options.verbose:
|
|
|
107 |
for i in move_status:
|
|
|
108 |
print i
|
116 |
ira |
109 |
|
|
|
110 |
print
|
|
|
111 |
|
121 |
ira |
112 |
def get_parsed_options():
|
|
|
113 |
parser = OptionParser()
|
|
|
114 |
parser.add_option('-q', '--quiet', action='store_false', dest='verbose',
|
|
|
115 |
default=True, help='don\'t print status messages to stdout')
|
|
|
116 |
parser.add_option('-d', '--dict', dest='dict_file', default=DICT_FILE,
|
|
|
117 |
help='read dictionary from FILE', metavar='FILE')
|
|
|
118 |
#parser.add_option('-n', '--not-recursive', action='store_false', dest='recursive',
|
|
|
119 |
# default=True, help='don\'t run recursively')
|
|
|
120 |
parser.add_option('-s', '--start-dir', dest='start_dir', default=WORK_DIR,
|
|
|
121 |
help='start running at directory DIR', metavar='DIR')
|
|
|
122 |
parser.add_option('-o', '--output-dir', dest='output_dir', default=SORT_DIR,
|
|
|
123 |
help='sort files into DIR', metavar='DIR')
|
116 |
ira |
124 |
|
121 |
ira |
125 |
## Parse the options
|
|
|
126 |
(options, args) = parser.parse_args()
|
116 |
ira |
127 |
|
121 |
ira |
128 |
## Correct directories
|
|
|
129 |
options.dict_file = os.path.abspath(options.dict_file)
|
|
|
130 |
options.start_dir = os.path.abspath(options.start_dir)
|
|
|
131 |
options.output_dir = os.path.abspath(options.output_dir)
|
116 |
ira |
132 |
|
121 |
ira |
133 |
return options
|
116 |
ira |
134 |
|
175 |
ira |
135 |
def main():
|
116 |
ira |
136 |
|
121 |
ira |
137 |
## Get Options
|
|
|
138 |
options = get_parsed_options()
|
116 |
ira |
139 |
|
121 |
ira |
140 |
## Print the program's header
|
|
|
141 |
print_prog_header()
|
116 |
ira |
142 |
|
121 |
ira |
143 |
## Parse the dictionary
|
|
|
144 |
dict = parse_dict()
|
116 |
ira |
145 |
|
121 |
ira |
146 |
## Start walking through directories
|
|
|
147 |
for root, dirs, files in os.walk(options.start_dir):
|
116 |
ira |
148 |
|
121 |
ira |
149 |
## Blank the status variable
|
|
|
150 |
move_status = []
|
116 |
ira |
151 |
|
121 |
ira |
152 |
## Get all of the files in the directory that are of the correct types
|
|
|
153 |
types_re = get_types_re()
|
|
|
154 |
raw_matches = [f for f in files if types_re.match(f)]
|
116 |
ira |
155 |
|
121 |
ira |
156 |
### Loop through the dictionary and try to move everything that matches
|
|
|
157 |
for regex, todir in dict:
|
|
|
158 |
matches = get_matches(raw_matches, regex)
|
116 |
ira |
159 |
|
121 |
ira |
160 |
## Move the files if we've found some
|
|
|
161 |
if len(matches) > 0:
|
|
|
162 |
move_files(matches, root, todir)
|
116 |
ira |
163 |
|
121 |
ira |
164 |
if len(move_status) > 0:
|
|
|
165 |
## print header
|
|
|
166 |
print_dir_header(root)
|
|
|
167 |
|
|
|
168 |
## print status
|
|
|
169 |
print_move_status()
|
175 |
ira |
170 |
|
|
|
171 |
### MAIN IS HERE ###
|
|
|
172 |
if __name__ == '__main__':
|
|
|
173 |
main()
|
|
|
174 |
|