comparison scripts/spam-remover @ 4660:9f507a042c1b

Add a script to remove file-spam from a tracker. See scripts/spam-remover.
author Ralf Schlatterbeck <rsc@runtux.com>
date Thu, 06 Sep 2012 10:57:51 +0200
parents
children 73129d1a1bc3
comparison
equal deleted inserted replaced
4659:eabe86afc6ee 4660:9f507a042c1b
1 #! /usr/bin/env python
2 # Copyright (C) 2012 Dr. Ralf Schlatterbeck Open Source Consulting.
3 # Reichergasse 131, A-3411 Weidling.
4 # Web: http://www.runtux.com Email: rsc@runtux.com
5 # All rights reserved
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 # SOFTWARE.
24
25 _doc = '''
26 %prog [options]
27 Remove file attachment spam from a tracker:
28 - Edit the journal of the given issue(s) and remove the links to the
29 spam-files
30 - Set the contents of the spam-files involved to zero length
31 WARNING:
32 This is a dangerous operation as it will edit the history *and* remove
33 data that is not in the journal (the contents of files). Be careful with
34 the file pattern (start of filename) you specify!
35 '''
36
37 import sys
38 from optparse import OptionParser
39 from roundup import instance, hyperdb
40
41 def main():
42 cmd = OptionParser(usage=_doc)
43 cmd.add_option \
44 ( "-i", "--instance"
45 , help = "Instance home"
46 , default = "."
47 )
48 cmd.add_option \
49 ( "-d", "--designator"
50 , dest = "designators"
51 , help = "Item designator for issue(s), to remove files from,\n"
52 "e.g. issue4711"
53 , action = "append"
54 , default = []
55 )
56 cmd.add_option \
57 ( "-f", "--filename"
58 , dest = "filenames"
59 , help = "Exact spam-filename to remove from issue(s)"
60 , action = "append"
61 , default = []
62 )
63 cmd.add_option \
64 ( "-a", "--action", "--no-dry-run"
65 , dest = "doit"
66 , help = "Don't perform any action by default unless specified"
67 , action = "store_true"
68 )
69 cmd.add_option \
70 ( "-s", "--file-start-pattern"
71 , dest = "file_pattern"
72 , help = "Start of spam-filename to remove from issue(s)"
73 , action = "append"
74 , default = []
75 )
76 cmd.add_option \
77 ( "-u", "--spam-user"
78 , dest = "users"
79 , help = "Username that created the spam-files to remove"
80 , action = "append"
81 , default = []
82 )
83 cmd.add_option \
84 ( "-q", "--quiet"
85 , dest = "quiet"
86 , help = "Be quiet about what we're doing"
87 , action = "store_true"
88 )
89 opt, args = cmd.parse_args()
90 # open the instance
91 if len(args):
92 print >> sys.stderr, "This command doesn't take arguments"
93 cmd.show_help()
94 tracker = instance.open(opt.instance)
95 db = tracker.open('admin')
96 users = dict.fromkeys (db.user.lookup(u) for u in opt.users)
97 files_to_remove = {}
98 for fn in opt.filenames:
99 for fid in db.files.filter(None,name=fn):
100 if db.file.get(fid,'name') == fn:
101 files_to_remove[fid] = True
102 for fn in opt.file_pattern:
103 for fid in db.files.filter(None,name=fn):
104 if db.file.get(fid,'name').startswith(fn):
105 files_to_remove[fid] = True
106 files_found = {}
107 for d in opt.designators:
108 clsname, id = hyperdb.splitDesignator(d)
109 cls = db.getclass(clsname)
110 issuefiles = dict.fromkeys(cls.get (id, 'files'))
111 for fid in issuefiles.keys():
112 f = db.file.getnode(fid)
113 if fid in files_to_remove or f.creator in users:
114 files_to_remove[fid] = True
115 files_found[fid] = True
116 if not opt.quiet:
117 print "deleting file %s from issue" % f
118 del issuefiles[fid]
119 if opt.doit:
120 cls.set(id, files=issuefiles.keys())
121 journal = oldjournal = db.getjournal(clsname, id)
122 # do this twice, we may have file-removals *before* file
123 # additions for files to delete and may discover mid-journal
124 # that there are new files to remove
125 for x in xrange(2):
126 newjournal = []
127 for j in journal:
128 if j[3] == 'set' and 'files' in j[4]:
129 changes = dict(j[4]['files'])
130 # only consider file additions by this user
131 if j[2] in users and '+' in changes:
132 f = dict.fromkeys(changes['+'])
133 files_found.update(f)
134 files_to_remove.update(f)
135 del changes['+']
136 # change dict in-place, don't use iteritems
137 for k, v in changes.items():
138 new_f = []
139 for f in v:
140 if f in files_to_remove:
141 files_found[f] = True
142 else:
143 new_f.append(f)
144 if new_f :
145 changes[k] = new_f
146 else:
147 del changes[k]
148 msg = []
149 if not opt.quiet:
150 msg.append ("Old journal entry: %s" % str(j))
151 if changes:
152 j[4]['files'] = tuple(changes.iteritems())
153 else:
154 del j[4]['files']
155 if j[4]:
156 newjournal.append(j)
157 if not opt.quiet:
158 msg.append ("New journal entry: %s" % str(j))
159 elif not opt.quiet:
160 msg.append ("deleted")
161 if len(msg) == 2 and msg[0][4:] != msg[1][4:]:
162 for m in msg:
163 print m
164 else:
165 newjournal.append(j)
166 journal = newjournal
167 if newjournal != oldjournal and opt.doit:
168 db.setjournal(clsname, id, newjournal)
169 if opt.doit:
170 for f in files_found:
171 db.file.set(f, content='')
172 db.commit()
173 else:
174 print "Database not changed"
175
176
177 if __name__ == '__main__':
178 main()

Roundup Issue Tracker: http://roundup-tracker.org/