Split tagfarm.utils off from tagfarm.main.
Chris Pressey
5 years ago
0 | 0 | # encoding: UTF-8 |
1 | 1 | |
2 | 2 | from argparse import ArgumentParser |
3 | import errno | |
4 | 3 | import os |
5 | 4 | import sys |
6 | from subprocess import Popen, STDOUT, PIPE | |
7 | 5 | |
8 | ||
9 | # - - - - | |
10 | ||
11 | ||
12 | def mkdir_p(path): | |
13 | try: | |
14 | os.makedirs(path) | |
15 | except OSError as exc: | |
16 | if exc.errno == errno.EEXIST and os.path.isdir(path): | |
17 | pass | |
18 | else: | |
19 | raise | |
20 | ||
21 | ||
22 | def find_media_root(path): | |
23 | while path != '/': | |
24 | if os.path.isdir(os.path.join(path, 'by-tag')): | |
25 | return path | |
26 | path = os.path.abspath(os.path.join(path, os.pardir)) | |
27 | raise ValueError("could not locate `by-tag` directory in curent directory or any parent directory thereof") | |
28 | ||
29 | ||
30 | def index_files(media_root): | |
31 | index = {} | |
32 | for root, dirs, files in os.walk(media_root): | |
33 | if os.path.normpath(root) == os.path.normpath(os.path.join(media_root, 'by-tag')): | |
34 | dirs[:] = [] | |
35 | continue | |
36 | ||
37 | for basename in files: | |
38 | filename = os.path.normpath(os.path.join(root, basename)) | |
39 | if os.path.islink(filename): | |
40 | continue | |
41 | ||
42 | index.setdefault(basename, set()).add(filename) | |
43 | return index | |
44 | ||
45 | ||
46 | def is_broken_link(path): | |
47 | return os.path.lexists(path) and not os.path.exists(path) | |
48 | ||
49 | ||
50 | def is_absolute_link(path): | |
51 | return os.path.lexists(path) and os.readlink(path).startswith('/') | |
52 | ||
53 | ||
54 | def readlink_or_broken(path): | |
55 | return '*BROKEN*' if is_broken_link(path) else os.readlink(path) | |
56 | ||
57 | ||
58 | def relativize_target(media_root, path): | |
59 | # Prepends `../..` because the link always resides in `by-tag/<tagname>` | |
60 | return os.path.join('..', '..', os.path.relpath(path, media_root)) | |
61 | ||
62 | ||
63 | def tag_file(media_root, filename, tag): | |
64 | mkdir_p(os.path.join(media_root, 'by-tag', tag)) | |
65 | linkname = os.path.join(media_root, 'by-tag', tag, os.path.basename(filename)) | |
66 | if not os.path.lexists(linkname): | |
67 | srcname = relativize_target(media_root, filename) | |
68 | os.symlink(srcname, linkname) | |
69 | ||
70 | ||
71 | def perform_repair(media_root, verbose=False, force_relink=False, prune=False): | |
72 | index = index_files(media_root) | |
73 | ||
74 | by_tags_dir = os.path.join(media_root, 'by-tag') | |
75 | for tag in os.listdir(by_tags_dir): | |
76 | tagdir = os.path.join(by_tags_dir, tag) | |
77 | if not os.path.isdir(tagdir): | |
78 | continue | |
79 | repairs_made = [] | |
80 | for basename in os.listdir(tagdir): | |
81 | ||
82 | linkname = os.path.join(tagdir, basename) | |
83 | ||
84 | if basename.startswith('Link to '): | |
85 | new_basename = basename[8:] | |
86 | new_linkname = os.path.join(tagdir, new_basename) | |
87 | if os.path.lexists(new_linkname): | |
88 | repairs_made.append( | |
89 | "WARNING: not renaming '{}' (-> '{}') because '{}' (-> '{}') already exists".format( | |
90 | linkname, readlink_or_broken(linkname), new_linkname, readlink_or_broken(new_linkname) | |
91 | ) | |
92 | ) | |
93 | else: | |
94 | repairs_made.append("RENAMING {} -> {}".format(linkname, new_linkname)) | |
95 | os.rename(linkname, new_linkname) | |
96 | linkname = new_linkname | |
97 | ||
98 | if not force_relink: | |
99 | if not os.path.islink(linkname): | |
100 | repairs_made.append("WARNING: skipping {} (is regular file, but --force-relink not given)".format(linkname)) | |
101 | continue | |
102 | if not (is_broken_link(linkname) or is_absolute_link(linkname)): | |
103 | if verbose: | |
104 | repairs_made.append('kept {} -> {}'.format(linkname, os.readlink(linkname))) | |
105 | continue | |
106 | ||
107 | candidates = index.get(basename, set()) | |
108 | if len(candidates) == 0: | |
109 | if prune: | |
110 | repairs_made.append("NOTICE: no candidates for {}, DELETING".format(basename)) | |
111 | os.remove(linkname) | |
112 | else: | |
113 | repairs_made.append("WARNING: no candidates for {}".format(basename)) | |
114 | elif len(candidates) > 1: | |
115 | repairs_made.append("WARNING: multiple candidates for {}: {}".format(basename, candidates)) | |
116 | else: | |
117 | os.remove(linkname) | |
118 | filename = list(candidates)[0] | |
119 | srcname = relativize_target(media_root, filename) | |
120 | os.symlink(srcname, linkname) | |
121 | repairs_made.append('FIXED {} -> {}'.format(linkname, srcname)) | |
122 | ||
123 | if repairs_made: | |
124 | print('*** {}'.format(tag)) | |
125 | for repair_made in repairs_made: | |
126 | print(repair_made) | |
127 | ||
128 | ||
129 | # - - - - commands - - - - | |
6 | from tagfarm.utils import ( | |
7 | mkdir_p, find_media_root, tag_file, perform_repair | |
8 | ) | |
130 | 9 | |
131 | 10 | |
132 | 11 | def tag(media_root, args, verbose=False): |
0 | # encoding: UTF-8 | |
1 | ||
2 | import errno | |
3 | import os | |
4 | ||
5 | ||
6 | def mkdir_p(path): | |
7 | try: | |
8 | os.makedirs(path) | |
9 | except OSError as exc: | |
10 | if exc.errno == errno.EEXIST and os.path.isdir(path): | |
11 | pass | |
12 | else: | |
13 | raise | |
14 | ||
15 | ||
16 | def find_media_root(path): | |
17 | while path != '/': | |
18 | if os.path.isdir(os.path.join(path, 'by-tag')): | |
19 | return path | |
20 | path = os.path.abspath(os.path.join(path, os.pardir)) | |
21 | raise ValueError("could not locate `by-tag` directory in curent directory or any parent directory thereof") | |
22 | ||
23 | ||
24 | def index_files(media_root): | |
25 | index = {} | |
26 | for root, dirs, files in os.walk(media_root): | |
27 | if os.path.normpath(root) == os.path.normpath(os.path.join(media_root, 'by-tag')): | |
28 | dirs[:] = [] | |
29 | continue | |
30 | ||
31 | for basename in files: | |
32 | filename = os.path.normpath(os.path.join(root, basename)) | |
33 | if os.path.islink(filename): | |
34 | continue | |
35 | ||
36 | index.setdefault(basename, set()).add(filename) | |
37 | return index | |
38 | ||
39 | ||
40 | def is_broken_link(path): | |
41 | return os.path.lexists(path) and not os.path.exists(path) | |
42 | ||
43 | ||
44 | def is_absolute_link(path): | |
45 | return os.path.lexists(path) and os.readlink(path).startswith('/') | |
46 | ||
47 | ||
48 | def readlink_or_broken(path): | |
49 | return '*BROKEN*' if is_broken_link(path) else os.readlink(path) | |
50 | ||
51 | ||
52 | def relativize_target(media_root, path): | |
53 | # Prepends `../..` because the link always resides in `by-tag/<tagname>` | |
54 | return os.path.join('..', '..', os.path.relpath(path, media_root)) | |
55 | ||
56 | ||
57 | def tag_file(media_root, filename, tag): | |
58 | mkdir_p(os.path.join(media_root, 'by-tag', tag)) | |
59 | linkname = os.path.join(media_root, 'by-tag', tag, os.path.basename(filename)) | |
60 | if not os.path.lexists(linkname): | |
61 | srcname = relativize_target(media_root, filename) | |
62 | os.symlink(srcname, linkname) | |
63 | ||
64 | ||
65 | def perform_repair(media_root, verbose=False, force_relink=False, prune=False): | |
66 | index = index_files(media_root) | |
67 | ||
68 | by_tags_dir = os.path.join(media_root, 'by-tag') | |
69 | for tag in os.listdir(by_tags_dir): | |
70 | tagdir = os.path.join(by_tags_dir, tag) | |
71 | if not os.path.isdir(tagdir): | |
72 | continue | |
73 | repairs_made = [] | |
74 | for basename in os.listdir(tagdir): | |
75 | ||
76 | linkname = os.path.join(tagdir, basename) | |
77 | ||
78 | if basename.startswith('Link to '): | |
79 | new_basename = basename[8:] | |
80 | new_linkname = os.path.join(tagdir, new_basename) | |
81 | if os.path.lexists(new_linkname): | |
82 | repairs_made.append( | |
83 | "WARNING: not renaming '{}' (-> '{}') because '{}' (-> '{}') already exists".format( | |
84 | linkname, readlink_or_broken(linkname), new_linkname, readlink_or_broken(new_linkname) | |
85 | ) | |
86 | ) | |
87 | else: | |
88 | repairs_made.append("RENAMING {} -> {}".format(linkname, new_linkname)) | |
89 | os.rename(linkname, new_linkname) | |
90 | linkname = new_linkname | |
91 | ||
92 | if not force_relink: | |
93 | if not os.path.islink(linkname): | |
94 | repairs_made.append("WARNING: skipping {} (is regular file, but --force-relink not given)".format(linkname)) | |
95 | continue | |
96 | if not (is_broken_link(linkname) or is_absolute_link(linkname)): | |
97 | if verbose: | |
98 | repairs_made.append('kept {} -> {}'.format(linkname, os.readlink(linkname))) | |
99 | continue | |
100 | ||
101 | candidates = index.get(basename, set()) | |
102 | if len(candidates) == 0: | |
103 | if prune: | |
104 | repairs_made.append("NOTICE: no candidates for {}, DELETING".format(basename)) | |
105 | os.remove(linkname) | |
106 | else: | |
107 | repairs_made.append("WARNING: no candidates for {}".format(basename)) | |
108 | elif len(candidates) > 1: | |
109 | repairs_made.append("WARNING: multiple candidates for {}: {}".format(basename, candidates)) | |
110 | else: | |
111 | os.remove(linkname) | |
112 | filename = list(candidates)[0] | |
113 | srcname = relativize_target(media_root, filename) | |
114 | os.symlink(srcname, linkname) | |
115 | repairs_made.append('FIXED {} -> {}'.format(linkname, srcname)) | |
116 | ||
117 | if repairs_made: | |
118 | print('*** {}'.format(tag)) | |
119 | for repair_made in repairs_made: | |
120 | print(repair_made) |