]>
Commit | Line | Data |
---|---|---|
85245f48 RD |
1 | #!/usr/bin/env python |
2 | """ | |
3 | This script will search for installed versions of wxPython on OSX and | |
4 | allow the user to choose one to uninstall. It then will use the | |
5 | metadata stored about the installed package to remove all the files | |
6 | associated with that install. | |
7 | ||
8 | Only the files installed by the main Installer Package will be | |
9 | removed. This includes the Python modules and the wxWidgets shared | |
10 | libraries. If you also installed the demo or docs by dragging them out | |
11 | of the disk image, then you will need to drag them to the Trash | |
12 | yourself. | |
13 | """ | |
14 | ||
15 | import sys, os, glob | |
16 | import cPickle, urllib | |
17 | ||
18 | RCPTDIR = "/Library/Receipts" | |
19 | RSRCDIR = "Contents/Resources" | |
20 | ||
21 | # only clean up dirs that have one of these as a prefix. We do this | |
22 | # because the file list returned from lsbom will include /, /usr, | |
23 | # /usr/local, etc. | |
24 | PREFIXES = [ '/Library/Python/2.3/', | |
25 | '/Library/Python/2.4/', | |
26 | '/usr/local/lib/', | |
27 | ] | |
28 | ||
29 | ||
30 | class AccessError(Exception): | |
31 | pass | |
32 | ||
33 | ||
34 | class InstalledReceipt(object): | |
35 | def __init__(self, rcptPath): | |
36 | self.rcptPath = rcptPath | |
37 | self.rsrcPath = os.path.join(rcptPath, RSRCDIR) | |
38 | self.bomFile = glob.glob(os.path.join(self.rsrcPath, "*.bom"))[0] | |
39 | self.findMetaData() | |
40 | ||
41 | ||
42 | def findMetaData(self): | |
43 | # TODO: Make this be able to also look at Info.plist files | |
44 | infoFile = glob.glob(os.path.join(self.rsrcPath, "*.info"))[0] | |
45 | self.mdata = {} | |
46 | for line in open(infoFile, "r").readlines(): | |
47 | line = line.strip() | |
48 | if line and line[0] != '#': | |
49 | ls = line.split() | |
50 | self.mdata[ls[0]] = line[len(ls[0])+1:] | |
51 | ||
52 | ||
53 | def getFileList(self): | |
54 | p = os.popen("lsbom -s %s" % self.bomFile, "r") | |
55 | data = p.read() | |
56 | data.strip() | |
57 | data = filter(lambda s: s!='' and s!='.', data.split('\n')) | |
58 | loc = self.mdata['DefaultLocation'] | |
59 | return [loc+item for item in data] | |
60 | ||
61 | ||
62 | def walkFiles(self, handleFile, handleDir): | |
63 | dirs = [] | |
64 | names = self.getFileList() | |
65 | ||
66 | # the plain files | |
67 | for name in names: | |
68 | name = os.path.abspath(name) | |
69 | if os.path.isdir(name): | |
70 | dirs.append(name) | |
71 | else: | |
72 | handleFile(name) | |
73 | ||
74 | # the directories | |
75 | dirs.reverse() | |
76 | for dir in dirs: | |
77 | for prefix in PREFIXES: | |
78 | if dir.startswith(prefix): | |
79 | handleDir(dir) | |
80 | break | |
81 | ||
82 | # Finally, remove the Receipts package, bottom-up | |
83 | for dirpath, dirname, filenames in os.walk(self.rcptPath, False): | |
84 | for name in filenames: | |
85 | name = os.path.join(dirpath, name) | |
86 | handleFile(name) | |
87 | handleDir(dirpath) | |
88 | ||
89 | ||
90 | ||
91 | def showFiles(self): | |
92 | def show(name): | |
93 | if os.path.exists(name): | |
94 | print "Will remove:", name | |
95 | self.walkFiles(show, show) | |
96 | ||
97 | ||
98 | def testUninstallAccess(self): | |
99 | def testFile(name): | |
100 | if os.path.exists(name): | |
101 | if not os.access(name, os.W_OK): | |
102 | raise AccessError(name) | |
103 | self.walkFiles(testFile, testFile) | |
104 | ||
105 | ||
106 | def doUninstall(self): | |
107 | def removeFile(name): | |
108 | if os.path.exists(name): | |
109 | print "Removing:", name | |
110 | os.unlink(name) | |
111 | def removeDir(name): | |
112 | print "Removing:", name | |
113 | if os.path.exists(name): | |
114 | hasFiles = os.listdir(name) | |
115 | if hasFiles: # perhaps some left over symlinks, or .pyc files | |
116 | for file in hasFiles: | |
117 | os.unlink(os.path.join(name, file)) | |
118 | os.rmdir(name) | |
119 | ||
120 | try: | |
121 | self.testUninstallAccess() | |
122 | except AccessError, e: | |
123 | print "UNABLE TO UNINSTALL!\nNo permission to remove: ", e.args[0] | |
124 | sys.exit() | |
125 | ||
126 | self.walkFiles(removeFile, removeDir) | |
127 | ||
128 | ||
129 | ||
130 | ||
131 | def findInstalled(): | |
132 | installed = [] | |
133 | for name in glob.glob(os.path.join(RCPTDIR, "wxPython*")): | |
134 | ir = InstalledReceipt(name) | |
135 | installed.append(ir) | |
136 | ||
137 | return installed | |
138 | ||
139 | ||
140 | # Just in case a Python < 2.3 is used to run this | |
141 | try: | |
142 | enumerate | |
143 | except NameError: | |
144 | def enumerate(sequence): | |
145 | return zip(range(len(sequence)), sequence) | |
146 | ||
147 | ||
148 | def main(): | |
149 | if len(sys.argv) > 1 and sys.argv[1] == "-doit": | |
150 | inst = cPickle.loads(urllib.unquote(sys.argv[2])) | |
151 | inst.doUninstall() | |
152 | sys.exit() | |
153 | ||
154 | print __doc__ | |
155 | installed = findInstalled() | |
156 | ||
157 | if not installed: | |
158 | print "*** No wxPython installations found! ***" | |
159 | raw_input("Press RETURN...") | |
160 | sys.exit() | |
161 | ||
162 | for i, inst in enumerate(installed): | |
163 | print " %d. %s \t%s" % (i+1, inst.mdata["Title"], inst.mdata["Version"]) | |
164 | ||
165 | ||
166 | ans = raw_input("Enter the number of the install to examine or 'Q' to quit: ") | |
167 | if ans in ['Q', 'q']: | |
168 | sys.exit() | |
169 | inst = installed[int(ans) - 1] | |
170 | ||
171 | while True: | |
172 | ||
173 | print """ | |
174 | Title: %(Title)s | |
175 | Version: %(Version)s | |
176 | Description: %(Description)s | |
177 | """ % inst.mdata | |
178 | ||
179 | ans = raw_input("(U)ninstall, (S)how what will be removed, or (Q)uit? [u,s,q] ") | |
180 | if ans in ['Q', 'q']: | |
181 | sys.exit() | |
182 | ||
183 | elif ans in ['S', 's']: | |
184 | inst.showFiles() | |
185 | ||
186 | elif ans in ['U', 'u']: | |
187 | ||
188 | print "Launching uninstaller with sudo, please enter your password if prompted:" | |
189 | os.system("sudo %s -doit %s" % | |
190 | (sys.argv[0], | |
191 | urllib.quote(cPickle.dumps(inst)))) | |
192 | sys.exit() | |
193 | ||
194 | ||
195 | if __name__ == '__main__': | |
196 | main() |