Mercurial > p > roundup > code
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() |
