Merge lp:~jolo/computer-janitor/bug-726616 into lp:computer-janitor

Proposed by Jon Lowag
Status: Rejected
Rejected by: Barry Warsaw
Proposed branch: lp:~jolo/computer-janitor/bug-726616
Merge into: lp:computer-janitor
Diff against target: 10575 lines (+10103/-0) (has conflicts)
83 files modified
COPYING (+676/-0)
CoverageTestRunner.py (+203/-0)
Makefile (+52/-0)
NEWS (+262/-0)
README (+31/-0)
TODO (+30/-0)
computer-janitor (+41/-0)
computer-janitor-gtk (+68/-0)
computer-janitor-gtk.8 (+36/-0)
computer-janitor.8 (+222/-0)
computerjanitorapp/__init__.py (+44/-0)
computerjanitorapp/cli/main.py (+312/-0)
computerjanitorapp/gtk/dialogs.py (+125/-0)
computerjanitorapp/gtk/main.py (+32/-0)
computerjanitorapp/gtk/store.py (+162/-0)
computerjanitorapp/gtk/ui.py (+594/-0)
computerjanitorapp/terminalsize.py (+63/-0)
computerjanitorapp/tests/test_all.py (+29/-0)
computerjanitorapp/tests/test_terminalsize.py (+54/-0)
computerjanitorapp/tests/test_utilities.py (+70/-0)
computerjanitorapp/utilities.py (+51/-0)
computerjanitord/application.py (+90/-0)
computerjanitord/authenticator.py (+85/-0)
computerjanitord/collector.py (+168/-0)
computerjanitord/data/com.ubuntu.ComputerJanitor.conf (+15/-0)
computerjanitord/data/com.ubuntu.ComputerJanitor.service (+4/-0)
computerjanitord/data/com.ubuntu.computerjanitor.policy (+19/-0)
computerjanitord/errors.py (+95/-0)
computerjanitord/main.py (+120/-0)
computerjanitord/service.py (+288/-0)
computerjanitord/state.py (+92/-0)
computerjanitord/tests/data/etc/apt/sources.list (+1/-0)
computerjanitord/tests/data/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_intrepid_restricted_binary-i386_Packages (+54/-0)
computerjanitord/tests/data/var/lib/dpkg/status (+28/-0)
computerjanitord/tests/test_all.py (+41/-0)
computerjanitord/tests/test_application.py (+105/-0)
computerjanitord/tests/test_authenticator.py (+97/-0)
computerjanitord/tests/test_collector.py (+225/-0)
computerjanitord/tests/test_state.py (+141/-0)
computerjanitord/tests/test_whitelist.py (+119/-0)
computerjanitord/whitelist.py (+86/-0)
data/ComputerJanitor.ui (+552/-0)
data/computer-janitor-gtk.desktop.in (+11/-0)
debian/changelog (+561/-0)
debian/compat (+1/-0)
debian/computer-janitor-gtk.install (+6/-0)
debian/computer-janitor-gtk.lintian-overrides (+2/-0)
debian/computer-janitor.docs (+1/-0)
debian/computer-janitor.install (+8/-0)
debian/computer-janitor.postinst (+28/-0)
debian/computer-janitor.postrm (+18/-0)
debian/control (+56/-0)
debian/copyright (+35/-0)
debian/default.whitelist (+4/-0)
debian/pycompat (+1/-0)
debian/rules (+41/-0)
debian/watch (+3/-0)
janitord (+27/-0)
license-check (+72/-0)
license-exceptions (+24/-0)
plugins/add_nfs_common_plugin.py (+58/-0)
plugins/autoremoval_plugin.py (+40/-0)
plugins/autoremoval_plugin_tests.py (+54/-0)
plugins/check_admin_group_plugin.py (+85/-0)
plugins/dpkg_dotfile_plugin.py (+87/-0)
plugins/dpkg_dotfile_plugin_tests.py (+107/-0)
plugins/fstab_plugin.py (+124/-0)
plugins/fstab_plugin_tests.py (+123/-0)
plugins/landscape_stub_plugin.py (+59/-0)
plugins/landscape_stub_plugin_tests.py (+87/-0)
plugins/unsupported_plugin.py (+131/-0)
plugins/unsupported_plugin_tests.py (+103/-0)
po/POTFILES.in (+29/-0)
po/computerjanitor.pot (+289/-0)
po/es.po (+427/-0)
po/fi.po (+427/-0)
po/fr.po (+426/-0)
po/ja.po (+422/-0)
po/pl.po (+401/-0)
run_from_checkout.sh (+10/-0)
setup.cfg (+5/-0)
setup.py (+97/-0)
test-env (+31/-0)
Conflict adding file COPYING.  Moved existing file to COPYING.moved.
Conflict adding file CoverageTestRunner.py.  Moved existing file to CoverageTestRunner.py.moved.
Conflict adding file Makefile.  Moved existing file to Makefile.moved.
Conflict adding file NEWS.  Moved existing file to NEWS.moved.
Conflict adding file README.  Moved existing file to README.moved.
Conflict adding file TODO.  Moved existing file to TODO.moved.
Conflict adding file computer-janitor-gtk.8.  Moved existing file to computer-janitor-gtk.8.moved.
Conflict adding file computer-janitor-gtk.  Moved existing file to computer-janitor-gtk.moved.
Conflict adding file computer-janitor.8.  Moved existing file to computer-janitor.8.moved.
Conflict adding file computer-janitor.  Moved existing file to computer-janitor.moved.
Conflict adding file computerjanitorapp.  Moved existing file to computerjanitorapp.moved.
Conflict adding file computerjanitord.  Moved existing file to computerjanitord.moved.
Conflict adding file data.  Moved existing file to data.moved.
Conflict adding file debian.  Moved existing file to debian.moved.
Conflict adding file janitord.  Moved existing file to janitord.moved.
Conflict adding file license-check.  Moved existing file to license-check.moved.
Conflict adding file license-exceptions.  Moved existing file to license-exceptions.moved.
Conflict adding file plugins.  Moved existing file to plugins.moved.
Conflict adding file po.  Moved existing file to po.moved.
Conflict adding file run_from_checkout.sh.  Moved existing file to run_from_checkout.sh.moved.
Conflict adding file setup.cfg.  Moved existing file to setup.cfg.moved.
Conflict adding file setup.py.  Moved existing file to setup.py.moved.
Conflict adding file test-env.  Moved existing file to test-env.moved.
To merge this branch: bzr merge lp:~jolo/computer-janitor/bug-726616
Reviewer Review Type Date Requested Status
Barry Warsaw Disapprove
Review via email: mp+51604@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :

Thanks for following along in the UDD class on irc. It was my fault but your original branch should have been against ubuntu:computer-janitor instead of lp:computer-janitor.

review: Disapprove

Unmerged revisions

26. By Jon Lowag

NEWS: fixed a typo. (LP: #726616)

25. By Barry Warsaw

[ Loïc Minier ]
* debian/control: computer-janitor-gtk depends gir1.2-pango-1.0 not
  gir1.2-pango-2.0. (LP: #720529)

[ Barry Warsaw ]
* setup.py, computer-janitor-gtk.install: Install 24x24.png into
  /usr/share/computer-janitor which is where gtk expects it to be.
  (LP: #720743)
* Fix various apt_cache deprecation warnings.
* debian/rules:
  - Switch to dh_python2.
  - Run the unit tests at build time.
  - Add --keep to dh_installchangelogs.
  - Remove override_dh_pysupport.
* debian/control:
  - Add Build-Depends on python-dbus and update-manager-core so that the
    unit tests can be run at build time. Remove B-D on python-support.
    Change B-D to python-all for tests under all available Python versions.
  - Fix typo in descriptions.
  - Remove XB-Python-Version and add X-Python-Version for dh_python2.
* Note: 2.1.0-0ubuntu2 was a quick fix upload to the archive containing
  the above debian/control file fix.

24. By Loïc Minier

* control: computer-janitor-gtk depends gir1.2-pango-1.0 not
  gir1.2-pango-2.0; LP: #720529.
* Temporarily comment out Vcs-Bzr as I can't commit to it.

23. By Barry Warsaw

[ Martin Pitt ]
* data/ComputerJanitor.ui: Drop obsolete has_separator properties.
* run_from_checkout.sh: Drop setting of $PYTHONPATH. Current directory is
  there by default anyway, and this breaks setting it from the environment.
* computerjanitorapp/gtk/*: Port from pygtk2 to pygi. Works fully with GTK3
  now, with GTK2 we need to disable the right-click popup menu
  (popup_for_device() is introspection safe, but only exists in GTK3.
  popup() isn't introspectable).
* computerjanitorapp/gtk/ui.py: Force GTK2 for now, as we do not yet have a
  GTK3 theme in Natty, and don't carry the GTK3 stack in the default
  install.
* debian/control: Update dependencies for the pygtk → pygi switch.
* debian/control: Drop obsolete system-cleaner conflicts and gksu
  dependency.

[ Barry Warsaw ]
* Add Edit menu items for selecting and deselecting cruft.
* Bump version number.
* Update copyright years.
* Python style, whitespace, and import cleanups.
* Print error messages via logger instead of stderr.

22. By Barry Warsaw

* python-dbus cannot type-convert a set, so use a tuple for the package
  set when --all is given to the computer-janitor cli. (LP: #601585)
* Fix exceptoin when the user de-selects all cruft in the ui, then clicks
  on the 'Do' button. Instead, a dialog pops up that says there's nothing
  to do. (LP: #591433)
* Clean up some unused imports.

21. By Barry Warsaw

* debian/control: Add explicit dependency on dbus since depending on
  python-dbus does not yield an implicit dependency on dbus. (LP: #665740)
* computerjanitord/authenticator.py: Explicitly convert start-time to a
  UInt64(0). Under Natty, implicit conversion uses a UInt32 which does
  not match the CheckAuthorization signature. (LP: #676488)

20. By Matthias Klose

Rebuild with python 2.7 as the python default.

19. By Barry Warsaw

Fix icon; given by Pavol Klačanský. (LP: #434431)

18. By Barry Warsaw

Remove dependency on python-fstab since the fstab_plugin is disabled
now and no package provides python-fstab any more.

17. By Barry Warsaw

* Fixes for translation support.
  - Turn {foo} string substitutions back into %(foo)s strings since
    gettext does not yet support validation of {foo} or $foo
    substitutions. (LP: #621723)
  - Ensure that POTFILES.in has the proper type information for the
    ComputerJanitor.ui file. (LP: #612493)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'COPYING'
--- COPYING 1970-01-01 00:00:00 +0000
+++ COPYING 2011-02-28 19:00:59 +0000
@@ -0,0 +1,676 @@
1
2 GNU GENERAL PUBLIC LICENSE
3 Version 3, 29 June 2007
4
5 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
8
9 Preamble
10
11 The GNU General Public License is a free, copyleft license for
12software and other kinds of works.
13
14 The licenses for most software and other practical works are designed
15to take away your freedom to share and change the works. By contrast,
16the GNU General Public License is intended to guarantee your freedom to
17share and change all versions of a program--to make sure it remains free
18software for all its users. We, the Free Software Foundation, use the
19GNU General Public License for most of our software; it applies also to
20any other work released this way by its authors. You can apply it to
21your programs, too.
22
23 When we speak of free software, we are referring to freedom, not
24price. Our General Public Licenses are designed to make sure that you
25have the freedom to distribute copies of free software (and charge for
26them if you wish), that you receive source code or can get it if you
27want it, that you can change the software or use pieces of it in new
28free programs, and that you know you can do these things.
29
30 To protect your rights, we need to prevent others from denying you
31these rights or asking you to surrender the rights. Therefore, you have
32certain responsibilities if you distribute copies of the software, or if
33you modify it: responsibilities to respect the freedom of others.
34
35 For example, if you distribute copies of such a program, whether
36gratis or for a fee, you must pass on to the recipients the same
37freedoms that you received. You must make sure that they, too, receive
38or can get the source code. And you must show them these terms so they
39know their rights.
40
41 Developers that use the GNU GPL protect your rights with two steps:
42(1) assert copyright on the software, and (2) offer you this License
43giving you legal permission to copy, distribute and/or modify it.
44
45 For the developers' and authors' protection, the GPL clearly explains
46that there is no warranty for this free software. For both users' and
47authors' sake, the GPL requires that modified versions be marked as
48changed, so that their problems will not be attributed erroneously to
49authors of previous versions.
50
51 Some devices are designed to deny users access to install or run
52modified versions of the software inside them, although the manufacturer
53can do so. This is fundamentally incompatible with the aim of
54protecting users' freedom to change the software. The systematic
55pattern of such abuse occurs in the area of products for individuals to
56use, which is precisely where it is most unacceptable. Therefore, we
57have designed this version of the GPL to prohibit the practice for those
58products. If such problems arise substantially in other domains, we
59stand ready to extend this provision to those domains in future versions
60of the GPL, as needed to protect the freedom of users.
61
62 Finally, every program is threatened constantly by software patents.
63States should not allow patents to restrict development and use of
64software on general-purpose computers, but in those that do, we wish to
65avoid the special danger that patents applied to a free program could
66make it effectively proprietary. To prevent this, the GPL assures that
67patents cannot be used to render the program non-free.
68
69 The precise terms and conditions for copying, distribution and
70modification follow.
71
72 TERMS AND CONDITIONS
73
74 0. Definitions.
75
76 "This License" refers to version 3 of the GNU General Public License.
77
78 "Copyright" also means copyright-like laws that apply to other kinds of
79works, such as semiconductor masks.
80
81 "The Program" refers to any copyrightable work licensed under this
82License. Each licensee is addressed as "you". "Licensees" and
83"recipients" may be individuals or organizations.
84
85 To "modify" a work means to copy from or adapt all or part of the work
86in a fashion requiring copyright permission, other than the making of an
87exact copy. The resulting work is called a "modified version" of the
88earlier work or a work "based on" the earlier work.
89
90 A "covered work" means either the unmodified Program or a work based
91on the Program.
92
93 To "propagate" a work means to do anything with it that, without
94permission, would make you directly or secondarily liable for
95infringement under applicable copyright law, except executing it on a
96computer or modifying a private copy. Propagation includes copying,
97distribution (with or without modification), making available to the
98public, and in some countries other activities as well.
99
100 To "convey" a work means any kind of propagation that enables other
101parties to make or receive copies. Mere interaction with a user through
102a computer network, with no transfer of a copy, is not conveying.
103
104 An interactive user interface displays "Appropriate Legal Notices"
105to the extent that it includes a convenient and prominently visible
106feature that (1) displays an appropriate copyright notice, and (2)
107tells the user that there is no warranty for the work (except to the
108extent that warranties are provided), that licensees may convey the
109work under this License, and how to view a copy of this License. If
110the interface presents a list of user commands or options, such as a
111menu, a prominent item in the list meets this criterion.
112
113 1. Source Code.
114
115 The "source code" for a work means the preferred form of the work
116for making modifications to it. "Object code" means any non-source
117form of a work.
118
119 A "Standard Interface" means an interface that either is an official
120standard defined by a recognized standards body, or, in the case of
121interfaces specified for a particular programming language, one that
122is widely used among developers working in that language.
123
124 The "System Libraries" of an executable work include anything, other
125than the work as a whole, that (a) is included in the normal form of
126packaging a Major Component, but which is not part of that Major
127Component, and (b) serves only to enable use of the work with that
128Major Component, or to implement a Standard Interface for which an
129implementation is available to the public in source code form. A
130"Major Component", in this context, means a major essential component
131(kernel, window system, and so on) of the specific operating system
132(if any) on which the executable work runs, or a compiler used to
133produce the work, or an object code interpreter used to run it.
134
135 The "Corresponding Source" for a work in object code form means all
136the source code needed to generate, install, and (for an executable
137work) run the object code and to modify the work, including scripts to
138control those activities. However, it does not include the work's
139System Libraries, or general-purpose tools or generally available free
140programs which are used unmodified in performing those activities but
141which are not part of the work. For example, Corresponding Source
142includes interface definition files associated with source files for
143the work, and the source code for shared libraries and dynamically
144linked subprograms that the work is specifically designed to require,
145such as by intimate data communication or control flow between those
146subprograms and other parts of the work.
147
148 The Corresponding Source need not include anything that users
149can regenerate automatically from other parts of the Corresponding
150Source.
151
152 The Corresponding Source for a work in source code form is that
153same work.
154
155 2. Basic Permissions.
156
157 All rights granted under this License are granted for the term of
158copyright on the Program, and are irrevocable provided the stated
159conditions are met. This License explicitly affirms your unlimited
160permission to run the unmodified Program. The output from running a
161covered work is covered by this License only if the output, given its
162content, constitutes a covered work. This License acknowledges your
163rights of fair use or other equivalent, as provided by copyright law.
164
165 You may make, run and propagate covered works that you do not
166convey, without conditions so long as your license otherwise remains
167in force. You may convey covered works to others for the sole purpose
168of having them make modifications exclusively for you, or provide you
169with facilities for running those works, provided that you comply with
170the terms of this License in conveying all material for which you do
171not control copyright. Those thus making or running the covered works
172for you must do so exclusively on your behalf, under your direction
173and control, on terms that prohibit them from making any copies of
174your copyrighted material outside their relationship with you.
175
176 Conveying under any other circumstances is permitted solely under
177the conditions stated below. Sublicensing is not allowed; section 10
178makes it unnecessary.
179
180 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
181
182 No covered work shall be deemed part of an effective technological
183measure under any applicable law fulfilling obligations under article
18411 of the WIPO copyright treaty adopted on 20 December 1996, or
185similar laws prohibiting or restricting circumvention of such
186measures.
187
188 When you convey a covered work, you waive any legal power to forbid
189circumvention of technological measures to the extent such circumvention
190is effected by exercising rights under this License with respect to
191the covered work, and you disclaim any intention to limit operation or
192modification of the work as a means of enforcing, against the work's
193users, your or third parties' legal rights to forbid circumvention of
194technological measures.
195
196 4. Conveying Verbatim Copies.
197
198 You may convey verbatim copies of the Program's source code as you
199receive it, in any medium, provided that you conspicuously and
200appropriately publish on each copy an appropriate copyright notice;
201keep intact all notices stating that this License and any
202non-permissive terms added in accord with section 7 apply to the code;
203keep intact all notices of the absence of any warranty; and give all
204recipients a copy of this License along with the Program.
205
206 You may charge any price or no price for each copy that you convey,
207and you may offer support or warranty protection for a fee.
208
209 5. Conveying Modified Source Versions.
210
211 You may convey a work based on the Program, or the modifications to
212produce it from the Program, in the form of source code under the
213terms of section 4, provided that you also meet all of these conditions:
214
215 a) The work must carry prominent notices stating that you modified
216 it, and giving a relevant date.
217
218 b) The work must carry prominent notices stating that it is
219 released under this License and any conditions added under section
220 7. This requirement modifies the requirement in section 4 to
221 "keep intact all notices".
222
223 c) You must license the entire work, as a whole, under this
224 License to anyone who comes into possession of a copy. This
225 License will therefore apply, along with any applicable section 7
226 additional terms, to the whole of the work, and all its parts,
227 regardless of how they are packaged. This License gives no
228 permission to license the work in any other way, but it does not
229 invalidate such permission if you have separately received it.
230
231 d) If the work has interactive user interfaces, each must display
232 Appropriate Legal Notices; however, if the Program has interactive
233 interfaces that do not display Appropriate Legal Notices, your
234 work need not make them do so.
235
236 A compilation of a covered work with other separate and independent
237works, which are not by their nature extensions of the covered work,
238and which are not combined with it such as to form a larger program,
239in or on a volume of a storage or distribution medium, is called an
240"aggregate" if the compilation and its resulting copyright are not
241used to limit the access or legal rights of the compilation's users
242beyond what the individual works permit. Inclusion of a covered work
243in an aggregate does not cause this License to apply to the other
244parts of the aggregate.
245
246 6. Conveying Non-Source Forms.
247
248 You may convey a covered work in object code form under the terms
249of sections 4 and 5, provided that you also convey the
250machine-readable Corresponding Source under the terms of this License,
251in one of these ways:
252
253 a) Convey the object code in, or embodied in, a physical product
254 (including a physical distribution medium), accompanied by the
255 Corresponding Source fixed on a durable physical medium
256 customarily used for software interchange.
257
258 b) Convey the object code in, or embodied in, a physical product
259 (including a physical distribution medium), accompanied by a
260 written offer, valid for at least three years and valid for as
261 long as you offer spare parts or customer support for that product
262 model, to give anyone who possesses the object code either (1) a
263 copy of the Corresponding Source for all the software in the
264 product that is covered by this License, on a durable physical
265 medium customarily used for software interchange, for a price no
266 more than your reasonable cost of physically performing this
267 conveying of source, or (2) access to copy the
268 Corresponding Source from a network server at no charge.
269
270 c) Convey individual copies of the object code with a copy of the
271 written offer to provide the Corresponding Source. This
272 alternative is allowed only occasionally and noncommercially, and
273 only if you received the object code with such an offer, in accord
274 with subsection 6b.
275
276 d) Convey the object code by offering access from a designated
277 place (gratis or for a charge), and offer equivalent access to the
278 Corresponding Source in the same way through the same place at no
279 further charge. You need not require recipients to copy the
280 Corresponding Source along with the object code. If the place to
281 copy the object code is a network server, the Corresponding Source
282 may be on a different server (operated by you or a third party)
283 that supports equivalent copying facilities, provided you maintain
284 clear directions next to the object code saying where to find the
285 Corresponding Source. Regardless of what server hosts the
286 Corresponding Source, you remain obligated to ensure that it is
287 available for as long as needed to satisfy these requirements.
288
289 e) Convey the object code using peer-to-peer transmission, provided
290 you inform other peers where the object code and Corresponding
291 Source of the work are being offered to the general public at no
292 charge under subsection 6d.
293
294 A separable portion of the object code, whose source code is excluded
295from the Corresponding Source as a System Library, need not be
296included in conveying the object code work.
297
298 A "User Product" is either (1) a "consumer product", which means any
299tangible personal property which is normally used for personal, family,
300or household purposes, or (2) anything designed or sold for incorporation
301into a dwelling. In determining whether a product is a consumer product,
302doubtful cases shall be resolved in favor of coverage. For a particular
303product received by a particular user, "normally used" refers to a
304typical or common use of that class of product, regardless of the status
305of the particular user or of the way in which the particular user
306actually uses, or expects or is expected to use, the product. A product
307is a consumer product regardless of whether the product has substantial
308commercial, industrial or non-consumer uses, unless such uses represent
309the only significant mode of use of the product.
310
311 "Installation Information" for a User Product means any methods,
312procedures, authorization keys, or other information required to install
313and execute modified versions of a covered work in that User Product from
314a modified version of its Corresponding Source. The information must
315suffice to ensure that the continued functioning of the modified object
316code is in no case prevented or interfered with solely because
317modification has been made.
318
319 If you convey an object code work under this section in, or with, or
320specifically for use in, a User Product, and the conveying occurs as
321part of a transaction in which the right of possession and use of the
322User Product is transferred to the recipient in perpetuity or for a
323fixed term (regardless of how the transaction is characterized), the
324Corresponding Source conveyed under this section must be accompanied
325by the Installation Information. But this requirement does not apply
326if neither you nor any third party retains the ability to install
327modified object code on the User Product (for example, the work has
328been installed in ROM).
329
330 The requirement to provide Installation Information does not include a
331requirement to continue to provide support service, warranty, or updates
332for a work that has been modified or installed by the recipient, or for
333the User Product in which it has been modified or installed. Access to a
334network may be denied when the modification itself materially and
335adversely affects the operation of the network or violates the rules and
336protocols for communication across the network.
337
338 Corresponding Source conveyed, and Installation Information provided,
339in accord with this section must be in a format that is publicly
340documented (and with an implementation available to the public in
341source code form), and must require no special password or key for
342unpacking, reading or copying.
343
344 7. Additional Terms.
345
346 "Additional permissions" are terms that supplement the terms of this
347License by making exceptions from one or more of its conditions.
348Additional permissions that are applicable to the entire Program shall
349be treated as though they were included in this License, to the extent
350that they are valid under applicable law. If additional permissions
351apply only to part of the Program, that part may be used separately
352under those permissions, but the entire Program remains governed by
353this License without regard to the additional permissions.
354
355 When you convey a copy of a covered work, you may at your option
356remove any additional permissions from that copy, or from any part of
357it. (Additional permissions may be written to require their own
358removal in certain cases when you modify the work.) You may place
359additional permissions on material, added by you to a covered work,
360for which you have or can give appropriate copyright permission.
361
362 Notwithstanding any other provision of this License, for material you
363add to a covered work, you may (if authorized by the copyright holders of
364that material) supplement the terms of this License with terms:
365
366 a) Disclaiming warranty or limiting liability differently from the
367 terms of sections 15 and 16 of this License; or
368
369 b) Requiring preservation of specified reasonable legal notices or
370 author attributions in that material or in the Appropriate Legal
371 Notices displayed by works containing it; or
372
373 c) Prohibiting misrepresentation of the origin of that material, or
374 requiring that modified versions of such material be marked in
375 reasonable ways as different from the original version; or
376
377 d) Limiting the use for publicity purposes of names of licensors or
378 authors of the material; or
379
380 e) Declining to grant rights under trademark law for use of some
381 trade names, trademarks, or service marks; or
382
383 f) Requiring indemnification of licensors and authors of that
384 material by anyone who conveys the material (or modified versions of
385 it) with contractual assumptions of liability to the recipient, for
386 any liability that these contractual assumptions directly impose on
387 those licensors and authors.
388
389 All other non-permissive additional terms are considered "further
390restrictions" within the meaning of section 10. If the Program as you
391received it, or any part of it, contains a notice stating that it is
392governed by this License along with a term that is a further
393restriction, you may remove that term. If a license document contains
394a further restriction but permits relicensing or conveying under this
395License, you may add to a covered work material governed by the terms
396of that license document, provided that the further restriction does
397not survive such relicensing or conveying.
398
399 If you add terms to a covered work in accord with this section, you
400must place, in the relevant source files, a statement of the
401additional terms that apply to those files, or a notice indicating
402where to find the applicable terms.
403
404 Additional terms, permissive or non-permissive, may be stated in the
405form of a separately written license, or stated as exceptions;
406the above requirements apply either way.
407
408 8. Termination.
409
410 You may not propagate or modify a covered work except as expressly
411provided under this License. Any attempt otherwise to propagate or
412modify it is void, and will automatically terminate your rights under
413this License (including any patent licenses granted under the third
414paragraph of section 11).
415
416 However, if you cease all violation of this License, then your
417license from a particular copyright holder is reinstated (a)
418provisionally, unless and until the copyright holder explicitly and
419finally terminates your license, and (b) permanently, if the copyright
420holder fails to notify you of the violation by some reasonable means
421prior to 60 days after the cessation.
422
423 Moreover, your license from a particular copyright holder is
424reinstated permanently if the copyright holder notifies you of the
425violation by some reasonable means, this is the first time you have
426received notice of violation of this License (for any work) from that
427copyright holder, and you cure the violation prior to 30 days after
428your receipt of the notice.
429
430 Termination of your rights under this section does not terminate the
431licenses of parties who have received copies or rights from you under
432this License. If your rights have been terminated and not permanently
433reinstated, you do not qualify to receive new licenses for the same
434material under section 10.
435
436 9. Acceptance Not Required for Having Copies.
437
438 You are not required to accept this License in order to receive or
439run a copy of the Program. Ancillary propagation of a covered work
440occurring solely as a consequence of using peer-to-peer transmission
441to receive a copy likewise does not require acceptance. However,
442nothing other than this License grants you permission to propagate or
443modify any covered work. These actions infringe copyright if you do
444not accept this License. Therefore, by modifying or propagating a
445covered work, you indicate your acceptance of this License to do so.
446
447 10. Automatic Licensing of Downstream Recipients.
448
449 Each time you convey a covered work, the recipient automatically
450receives a license from the original licensors, to run, modify and
451propagate that work, subject to this License. You are not responsible
452for enforcing compliance by third parties with this License.
453
454 An "entity transaction" is a transaction transferring control of an
455organization, or substantially all assets of one, or subdividing an
456organization, or merging organizations. If propagation of a covered
457work results from an entity transaction, each party to that
458transaction who receives a copy of the work also receives whatever
459licenses to the work the party's predecessor in interest had or could
460give under the previous paragraph, plus a right to possession of the
461Corresponding Source of the work from the predecessor in interest, if
462the predecessor has it or can get it with reasonable efforts.
463
464 You may not impose any further restrictions on the exercise of the
465rights granted or affirmed under this License. For example, you may
466not impose a license fee, royalty, or other charge for exercise of
467rights granted under this License, and you may not initiate litigation
468(including a cross-claim or counterclaim in a lawsuit) alleging that
469any patent claim is infringed by making, using, selling, offering for
470sale, or importing the Program or any portion of it.
471
472 11. Patents.
473
474 A "contributor" is a copyright holder who authorizes use under this
475License of the Program or a work on which the Program is based. The
476work thus licensed is called the contributor's "contributor version".
477
478 A contributor's "essential patent claims" are all patent claims
479owned or controlled by the contributor, whether already acquired or
480hereafter acquired, that would be infringed by some manner, permitted
481by this License, of making, using, or selling its contributor version,
482but do not include claims that would be infringed only as a
483consequence of further modification of the contributor version. For
484purposes of this definition, "control" includes the right to grant
485patent sublicenses in a manner consistent with the requirements of
486this License.
487
488 Each contributor grants you a non-exclusive, worldwide, royalty-free
489patent license under the contributor's essential patent claims, to
490make, use, sell, offer for sale, import and otherwise run, modify and
491propagate the contents of its contributor version.
492
493 In the following three paragraphs, a "patent license" is any express
494agreement or commitment, however denominated, not to enforce a patent
495(such as an express permission to practice a patent or covenant not to
496sue for patent infringement). To "grant" such a patent license to a
497party means to make such an agreement or commitment not to enforce a
498patent against the party.
499
500 If you convey a covered work, knowingly relying on a patent license,
501and the Corresponding Source of the work is not available for anyone
502to copy, free of charge and under the terms of this License, through a
503publicly available network server or other readily accessible means,
504then you must either (1) cause the Corresponding Source to be so
505available, or (2) arrange to deprive yourself of the benefit of the
506patent license for this particular work, or (3) arrange, in a manner
507consistent with the requirements of this License, to extend the patent
508license to downstream recipients. "Knowingly relying" means you have
509actual knowledge that, but for the patent license, your conveying the
510covered work in a country, or your recipient's use of the covered work
511in a country, would infringe one or more identifiable patents in that
512country that you have reason to believe are valid.
513
514 If, pursuant to or in connection with a single transaction or
515arrangement, you convey, or propagate by procuring conveyance of, a
516covered work, and grant a patent license to some of the parties
517receiving the covered work authorizing them to use, propagate, modify
518or convey a specific copy of the covered work, then the patent license
519you grant is automatically extended to all recipients of the covered
520work and works based on it.
521
522 A patent license is "discriminatory" if it does not include within
523the scope of its coverage, prohibits the exercise of, or is
524conditioned on the non-exercise of one or more of the rights that are
525specifically granted under this License. You may not convey a covered
526work if you are a party to an arrangement with a third party that is
527in the business of distributing software, under which you make payment
528to the third party based on the extent of your activity of conveying
529the work, and under which the third party grants, to any of the
530parties who would receive the covered work from you, a discriminatory
531patent license (a) in connection with copies of the covered work
532conveyed by you (or copies made from those copies), or (b) primarily
533for and in connection with specific products or compilations that
534contain the covered work, unless you entered into that arrangement,
535or that patent license was granted, prior to 28 March 2007.
536
537 Nothing in this License shall be construed as excluding or limiting
538any implied license or other defenses to infringement that may
539otherwise be available to you under applicable patent law.
540
541 12. No Surrender of Others' Freedom.
542
543 If conditions are imposed on you (whether by court order, agreement or
544otherwise) that contradict the conditions of this License, they do not
545excuse you from the conditions of this License. If you cannot convey a
546covered work so as to satisfy simultaneously your obligations under this
547License and any other pertinent obligations, then as a consequence you may
548not convey it at all. For example, if you agree to terms that obligate you
549to collect a royalty for further conveying from those to whom you convey
550the Program, the only way you could satisfy both those terms and this
551License would be to refrain entirely from conveying the Program.
552
553 13. Use with the GNU Affero General Public License.
554
555 Notwithstanding any other provision of this License, you have
556permission to link or combine any covered work with a work licensed
557under version 3 of the GNU Affero General Public License into a single
558combined work, and to convey the resulting work. The terms of this
559License will continue to apply to the part which is the covered work,
560but the special requirements of the GNU Affero General Public License,
561section 13, concerning interaction through a network will apply to the
562combination as such.
563
564 14. Revised Versions of this License.
565
566 The Free Software Foundation may publish revised and/or new versions of
567the GNU General Public License from time to time. Such new versions will
568be similar in spirit to the present version, but may differ in detail to
569address new problems or concerns.
570
571 Each version is given a distinguishing version number. If the
572Program specifies that a certain numbered version of the GNU General
573Public License "or any later version" applies to it, you have the
574option of following the terms and conditions either of that numbered
575version or of any later version published by the Free Software
576Foundation. If the Program does not specify a version number of the
577GNU General Public License, you may choose any version ever published
578by the Free Software Foundation.
579
580 If the Program specifies that a proxy can decide which future
581versions of the GNU General Public License can be used, that proxy's
582public statement of acceptance of a version permanently authorizes you
583to choose that version for the Program.
584
585 Later license versions may give you additional or different
586permissions. However, no additional obligations are imposed on any
587author or copyright holder as a result of your choosing to follow a
588later version.
589
590 15. Disclaimer of Warranty.
591
592 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
593APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
594HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
595OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
596THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
597PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
598IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
599ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
600
601 16. Limitation of Liability.
602
603 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
604WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
605THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
606GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
607USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
608DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
609PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
610EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
611SUCH DAMAGES.
612
613 17. Interpretation of Sections 15 and 16.
614
615 If the disclaimer of warranty and limitation of liability provided
616above cannot be given local legal effect according to their terms,
617reviewing courts shall apply local law that most closely approximates
618an absolute waiver of all civil liability in connection with the
619Program, unless a warranty or assumption of liability accompanies a
620copy of the Program in return for a fee.
621
622 END OF TERMS AND CONDITIONS
623
624 How to Apply These Terms to Your New Programs
625
626 If you develop a new program, and you want it to be of the greatest
627possible use to the public, the best way to achieve this is to make it
628free software which everyone can redistribute and change under these terms.
629
630 To do so, attach the following notices to the program. It is safest
631to attach them to the start of each source file to most effectively
632state the exclusion of warranty; and each file should have at least
633the "copyright" line and a pointer to where the full notice is found.
634
635 <one line to give the program's name and a brief idea of what it does.>
636 Copyright (C) <year> <name of author>
637
638 This program is free software: you can redistribute it and/or modify
639 it under the terms of the GNU General Public License as published by
640 the Free Software Foundation, either version 3 of the License, or
641 (at your option) any later version.
642
643 This program is distributed in the hope that it will be useful,
644 but WITHOUT ANY WARRANTY; without even the implied warranty of
645 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
646 GNU General Public License for more details.
647
648 You should have received a copy of the GNU General Public License
649 along with this program. If not, see <http://www.gnu.org/licenses/>.
650
651Also add information on how to contact you by electronic and paper mail.
652
653 If the program does terminal interaction, make it output a short
654notice like this when it starts in an interactive mode:
655
656 <program> Copyright (C) <year> <name of author>
657 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
658 This is free software, and you are welcome to redistribute it
659 under certain conditions; type `show c' for details.
660
661The hypothetical commands `show w' and `show c' should show the appropriate
662parts of the General Public License. Of course, your program's commands
663might be different; for a GUI interface, you would use an "about box".
664
665 You should also get your employer (if you work as a programmer) or school,
666if any, to sign a "copyright disclaimer" for the program, if necessary.
667For more information on this, and how to apply and follow the GNU GPL, see
668<http://www.gnu.org/licenses/>.
669
670 The GNU General Public License does not permit incorporating your program
671into proprietary programs. If your program is a subroutine library, you
672may consider it more useful to permit linking proprietary applications with
673the library. If this is what you want to do, use the GNU Lesser General
674Public License instead of this License. But first, please read
675<http://www.gnu.org/philosophy/why-not-lgpl.html>.
676
0677
=== renamed file 'COPYING' => 'COPYING.moved'
=== added file 'CoverageTestRunner.py'
--- CoverageTestRunner.py 1970-01-01 00:00:00 +0000
+++ CoverageTestRunner.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,203 @@
1# Copyright (C) 2007 Lars Wirzenius <liw@iki.fi>
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17
18import coverage
19import unittest
20import optparse
21import os
22import imp
23import sys
24import time
25
26
27class CoverageTestResult(unittest.TestResult):
28
29 def __init__(self, output, total):
30 unittest.TestResult.__init__(self)
31 self.output = output
32 self.total = total
33 self.lastmsg = ""
34 self.coverage_missed = []
35 self.timings = []
36
37 def addCoverageMissed(self, filename, statements, missed_statements,
38 missed_description):
39 self.coverage_missed.append((filename, statements, missed_statements,
40 missed_description))
41
42 def wasSuccessful(self, ignore_coverage=False):
43 return (unittest.TestResult.wasSuccessful(self) and
44 (ignore_coverage or not self.coverage_missed))
45
46 def clearmsg(self):
47 self.output.write("\b \b" * len(self.lastmsg))
48 self.lastmsg = ""
49
50 def write(self, test):
51 self.clearmsg()
52 self.lastmsg = "Running test %d/%d: %s" % (self.testsRun,
53 self.total,
54 str(test)[:50])
55 self.output.write(self.lastmsg)
56 self.output.flush()
57
58 def startTest(self, test):
59 unittest.TestResult.startTest(self, test)
60 self.write(test)
61 self.start_time = time.time()
62
63 def stopTest(self, test):
64 end_time = time.time()
65 unittest.TestResult.stopTest(self, test)
66 self.timings.append((end_time - self.start_time, test))
67
68
69class CoverageTestRunner:
70
71 """A test runner class that insists modules' tests cover them fully."""
72
73 def __init__(self):
74 self._module_pairs = []
75
76 def add_pair(self, module_pathname, test_module_pathname):
77 """Add a module and its test module to list of tests."""
78 self._module_pairs.append((module_pathname, test_module_pathname))
79
80 def find_pairs(self, dirname):
81 """Find all module/test module pairs in directory tree.
82
83 This method relies on a naming convention: it scans a directory
84 tree and assumes that for any file foo.py, if there exists a
85 file foo_tests.py or fooTests.py, they form a pair.
86
87 """
88
89 suffixes = ["_tests.py", "Tests.py"]
90
91
92 for dirname, dirnames, filenames in os.walk(dirname):
93 tests = []
94 for filename in filenames:
95 for suffix in suffixes:
96 if filename.endswith(suffix):
97 module = filename[:-len(suffix)] + ".py"
98 if module in filenames:
99 module = os.path.join(dirname, module)
100 filename = os.path.join(dirname, filename)
101 self.add_pair(module, filename)
102
103 def _load_module_from_pathname(self, pathname):
104 for tuple in imp.get_suffixes():
105 suffix, mode, type = tuple
106 if pathname.endswith(suffix):
107 name = os.path.basename(pathname[:-len(suffix)])
108 f = file(pathname, mode)
109 return imp.load_module(name, f, pathname, tuple)
110 raise Exception("Unknown module: %s" % pathname)
111
112 def _load_pairs(self):
113 module_pairs = []
114 loader = unittest.defaultTestLoader
115 for pathname, test_pathname in self._module_pairs:
116 module = self._load_module_from_pathname(pathname)
117 test_module = self._load_module_from_pathname(test_pathname)
118 suite = loader.loadTestsFromModule(test_module)
119 module_pairs.append((module, test_module, suite))
120 return module_pairs
121
122 def printErrorList(self, flavor, errors):
123 for test, error in errors:
124 print "%s: %s" % (flavor, str(test))
125 print str(error)
126
127 def run(self):
128 start_time = time.time()
129
130 module_pairs = self._load_pairs()
131 total_tests = sum(suite.countTestCases()
132 for x, y, suite in module_pairs)
133 result = CoverageTestResult(sys.stdout, total_tests)
134
135 for module, test_module, suite in module_pairs:
136 coverage.erase()
137 coverage.exclude("#\s*pragma: no cover")
138 coverage.start()
139 sys.path.insert(0, os.path.dirname(module.__file__))
140 reload(module)
141 del sys.path[0]
142 suite.run(result)
143 coverage.stop()
144 filename, stmts, missed, missed_desc = coverage.analysis(module)
145 if missed:
146 result.addCoverageMissed(filename, stmts, missed, missed_desc)
147
148 end_time = time.time()
149
150 sys.stdout.write("\n\n")
151
152 if result.wasSuccessful():
153 print "OK"
154 else:
155 print "FAILED"
156 print
157 if result.errors:
158 self.printErrorList("ERROR", result.errors)
159 if result.failures:
160 self.printErrorList("FAILURE", result.failures)
161 if result.coverage_missed:
162 print
163 print "Statements missed by per-module tests:"
164 width = max(len(x[0]) for x in result.coverage_missed)
165 fmt = " %-*s %s"
166 print fmt % (width, "Module", "Missed statements")
167 for filename, _, _, desc in sorted(result.coverage_missed):
168 print fmt % (width, filename, desc)
169 print
170
171 print "%d failures, %d errors" % (len(result.failures),
172 len(result.errors))
173
174 if end_time - start_time > 10:
175 print
176 print "Slowest tests:"
177 for secs, test in sorted(result.timings)[-10:]:
178 print " %5.1f s %s" % (secs, str(test)[:70])
179
180 print "Time: %.1f s" % (end_time - start_time)
181
182 return result
183
184
185def run(dirname="."):
186 """Use CoverageTestRunner on the desired directory."""
187
188 parser = optparse.OptionParser()
189 parser.add_option("--ignore-coverage", action="store_true",
190 help="Don't fail tests even if coverage is "
191 "incomplete.")
192
193 opts, args = parser.parse_args()
194
195 runner = CoverageTestRunner()
196 runner.find_pairs(dirname)
197 result = runner.run()
198 if not result.wasSuccessful(ignore_coverage=opts.ignore_coverage):
199 sys.exit(1)
200
201
202if __name__ == "__main__":
203 run()
0204
=== renamed file 'CoverageTestRunner.py' => 'CoverageTestRunner.py.moved'
=== added file 'Makefile'
--- Makefile 1970-01-01 00:00:00 +0000
+++ Makefile 2011-02-28 19:00:59 +0000
@@ -0,0 +1,52 @@
1# Makefile for Computer Janitor
2#
3# Copyright (C) 2008-2011 Canonical, Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17all: data/computer-janitor-gtk.desktop
18
19DESKTOP=data/computer-janitor-gtk.desktop
20$(DESKTOP): $(DESKTOP).in
21 intltool-merge -d po $(DESKTOP).in $(DESKTOP)
22
23check: check-unittests check-has-unit-tests check-licenses
24
25check-unittests:
26 env COMPUTER_JANITOR_UNITTEST=yes LC_ALL=C python -m CoverageTestRunner
27 rm -f .coverage
28
29check-has-unit-tests:
30 find computerjanitorapp -name '*.py' ! -name '*_tests.py' | \
31 grep -Exv '^computerjanitorapp/(__init__|ui_gtk).py' | \
32 sed 's/\.py$$//' | \
33 while read file; do \
34 [ -e $${file}_tests.py ] || \
35 (echo "Missing unit test: $$file.py"; exit 1); done
36
37check-licenses:
38 ./license-check
39
40clean:
41 rm -f computerjanitorapp/*.py[co] .coverage
42 rm -f po/computerjanitor.pot
43 rm -rf build $(DESKTOP)
44
45update-po:
46 echo '[encoding: UTF-8]' > po/POTFILES.in; \
47 find computerjanitorapp -name \*.py >> po/POTFILES.in; \
48 for f in data/*.ui; do \
49 echo '[type: gettext/glade]'$$f >> po/POTFILES.in; \
50 done; \
51 ls data/*.in plugins/*.py >> po/POTFILES.in; \
52 python setup.py build_i18n --merge-po --po-dir po
053
=== renamed file 'Makefile' => 'Makefile.moved'
=== added file 'NEWS'
--- NEWS 1970-01-01 00:00:00 +0000
+++ NEWS 2011-02-28 19:00:59 +0000
@@ -0,0 +1,262 @@
1NEWS for Computer Janitor
2=========================
3
4This file summarizes mainly user-visible changes. For detailed
5changes, please see the bzr commit log messages.
6
7Version 2.1.1, released 20XX-XX-XX
8
9 * 24x24 icon location is fixed. (LP: #720743)
10
11Version 2.1.0, released 2011-02-16
12
13 Martin Pitt
14 * Port from pygtk to pygi, giving identical functionality under GTK3,
15 however main-panel popup menus are non-functional under GTK2.
16 Barry Warsaw
17 * Add Edit menu items for selecting and deselecting cruft.
18 * Bump version number.
19 * Update copyright years.
20 * Python style, whitespace, and import cleanups.
21 * Print error messages via logger instead of stderr.
22
23Version 2.0.5, released 2011-02-15
24
25 * python-dbus cannot type-convert a set, so use a tuple for the package
26 set when --all is given to the computer-janitor cli. (LP: #601585)
27 * Fix exception when the user de-selects all cruft in the ui, then clicks
28 on the 'Do' button. Instead, a dialog pops up that says there's nothing
29 to do. (LP: #591433)
30 * Clean up some unused imports.
31
32Version 2.0.4, released 2010-09-20
33
34 * Update translation strings (LP: #621723) (LP: #612493)
35 * computer-janitor-gtk --help and --version switches added
36 * Fixed SystemError crash in open() (LP: #545306)
37 * Do not depend on python-fstab any more.
38 * Fixed icon (LP: #434431) - Pavol Klačanský
39
40Version 2.0, released 2010-03-15
41
42 * Refactored the system modifying parts of Computer Janitor into a
43 separate dbus daemon. This allows you to run the cli or gtk versions
44 without being root. You will be prompted for the administrative
45 password when necessary.
46
47 * The user interface has been cleaned up to remove previously unused gui
48 elements.
49
50Version 1.13.3, released 2009-09-09
51
52 * Fixed URL in About dialog.
53
54 * The "Do cleanups" button is now set to sensitive when there is
55 something to clean.
56
57 * The user is now told if nothing was found to clean up, so they
58 don't wonder why the window is empty.
59
60 * The 'unsupported package' plugin is re-enabled.
61
62Version 1.13.2, released 2009-08-21
63
64 * Versions 1.13.0 and 1.13.1 were not released publicly. This
65 section documents all changes since 1.12.1.
66
67 * The GTK user interface has been completely rewritten from scratch.
68 Thanks to Martin Albisetti for the general design. All bugs are belong
69 to Lars Wirzenius anyway.
70
71 * Adapted to new python-apt API. This means the code only works on
72 Ubuntu karmic, not earlier releases.
73
74 * The 'unsupported package' plugin is now not installed by default.
75 This should make it less likely people remove third-party packages
76 by default.
77
78 * The fstab plugin is not installed by default. It is no longer needed
79 in karmic, since the Linux kernel now uses relatime by default.
80
81 BUG FIXES:
82
83 * I18n changes to account for the fact that Computer Janitor's code
84 is in two places (itself, and Update Manager), and thus two po
85 files.
86
87 * I18n change to include plugin code as well. Thanks to Gabor Kelemen.
88
89 * Other i18n changes, in both the GTK and command line user interfaces.
90
91 * All instances of the word 'cruft' have been removed from the user
92 interface. It is not a proper English word.
93
94 * Typo fix in manual page. Thanks to Charlie_Smotherman.
95
96Version 1.12.1, released 2009-03-04
97
98 * Change the description text at the top of the main window to be
99 clearer.
100
101 * Sort cruft in the GTK user interface in groups. This should
102 some day get user-visible headings, so it's clearer to the user.
103
104 BUG FIXES:
105
106 * In the GTK user interface, the button "Cleanup" now uses "u" as the
107 accelerator key, rather than "c", which confliced with the
108 accelerator key for the "Close" button. Fixes LP: #311557, reported
109 by hackel.
110
111 * Changed the label for the yes/no column in the GTK UI to be
112 "Remove/fix?" rather than just "Remove?", since not all things
113 are removals. Fixes LP: #300354, reported by Hernando Torque.
114
115Version 1.12, released 2009-02-19
116
117 * New plugin: remove files left on the filesystem by the dpkg
118 conffile handling (*.dpkg-old and *.dpkg-new).
119
120 * Code base has been split so that the core parts (plugin management,
121 some of the plugins) now reside in the update-manager source tree.
122 This was done so that update-manager can use the computer-janitor
123 plugins, since there was no point in both programs having code to do
124 the same thing.
125
126 * When handling exceptions within the computer-janitor code, the
127 actual exception is now logged (at DEBUG level), to aid in
128 debugging the cause.
129
130 * Plugins can now declare that they should only be used under specific
131 conditions, rather than always. This is useful for plugins that are
132 used by update-manager.
133
134Version 1.11, released 2009-02-03
135
136 * Renamed to Computer Janitor. The old name, Cruft Remover, was using
137 computer jargon (cruft) and gave the wrong impression (the program
138 does not just remove stuff).
139
140 * New plugin to compact the dpkg status file, from Michael Vogt.
141
142Version 1.10.5, released 2009-01-16
143
144 * Upon startup, we now verify that the package lists look sane by
145 checking for the dash and gzip packages. If they're not available,
146 we assume something is badly wrong and we won't continue. This
147 makes is very unlikely that people will accidentally remove all
148 their packages because apt didn't have any package lists available.
149
150 * A further safeguard: the running kernel and related packages
151 won't ever be considered cruft anymore.
152
153 * It is now no longer enough to click on the "Cleanup" button to
154 initiate changes (pacxkage removals etc). Instead, a dialog window
155 will pop up and the user has to click on "OK" to continue.
156
157 * A .deb package's short description is now shown in the cruft
158 description. This will help users have an idea what it's all
159 about. (It's insufficient, but was all that there was time for
160 for the Ubuntu intrepid release schedule.)
161
162 * Columns in the cruft list now have titles, to make it easier
163 to understand what ticks mean (remove cruft? don't remove
164 cruft?).
165
166 * The program now reads whitelists in /etc/cruft-remover.d. Cruft
167 named in files in that directory will _not_ be removed.
168
169 * The plugin to remove packages apt considers autoremovable is now
170 enabled. It has been written ages ago, but not been enabled by
171 default until now.
172
173 * The application now uses the icon it has.
174
175 * Added Polish translation from Piotr Makowski.
176
177 * Added Finnish translation from Lars Wirzenius and Timo Jyrinki.
178
179Version 1.10.4, released 2008-10-27
180
181 * Quick bug fixes for the Ubuntu 8.10 release. A proper release
182 of Cruft Remover will be made later.
183
184Version 1.10.3, released 2008-10-14
185
186 * Bug fixes to how messages are logged, primarily to how character
187 sets are encoded. Found by James Westby.
188
189Version 1.10.2, released 2008-10-14
190
191 IMPORTANT CHANGES:
192
193 * If setting up logging to syslog fails, don't crash. Thanks,
194 Ricardo Wurmus. (LP:#274970)
195
196 * The program now supports translations via gettext. A Finnish
197 translation was added. (LP:#279580)
198
199 * Handles errors by giving nice(r) error messages also in the
200 command line version. The GTK+ version already did that.
201 Thanks, Marco Rodrigues. (LP:#281657)
202
203 MINOR CHANGES:
204
205 * Wording in manual page fixed. Thanks, James Westby.
206
207 * Non-functional help button hidden. Thanks, Loïc Minier. (LP:#279577)
208
209 * Cruft description in the GTK+ user interface is cleared after cruft has
210 been cleaned up.
211
212Version 1.10.1, released 2008-10-06
213
214 * Bugfix: Cruft in /etc/fstab now goes away from the GTK user interface
215 after a cleanup. Found by James Westby, thanks.
216
217 * cruft-remover-gtk now has a manual page.
218
219 * Bugfix: The GTK user interface now no longer imports the gtk and
220 gobject Python modules globally, but only when the code actually
221 runs, to prevent them from being loaded when running unit tests,
222 etc. (The GTK UI code is not unit tested.)
223
224Version 1.10, released 2008-09-30
225
226 * Important fixes to how GTK+ and threads are used: now all GTK+
227 stuff happens only in the main thread. This turns out to be
228 easier to get right than using gtk.gdk.threads_enter and _leave
229 correctly in all situations, and also allows things like
230 gtk.Dialog.run to be used.
231
232 * Missing test module for the deb_plugin module written.
233
234 * SystemError exceptions (which are raised by python-apt when
235 there is a dpkg error) are now handled, and an error displayed
236 to the user. This fixes LP: #275034.
237
238 * The GTK user interface now displays the version number at startup.
239 The command line interface shows it with the new --version option.
240
241Version 1.9, released 2008-09-26
242
243 * Bug fix: cruft-remover-gtk now correctly refreshes the list of
244 cruft after a bug has been fixed. (LP:274715)
245
246 * Bug fix: cruft-remover-gtk disables the "Cleanup" button if there
247 is nothing to clean up. (LP:274717)
248
249Version 1.8, released 2008-09-23
250
251 * The GTK user interface now lets you select a row without toggling
252 the status of the cruft. This is useful, because the description
253 of each cruft is shown for the selected row only.
254
255 * Both the GTK and command line user interfaces now attempt to
256 be more helpful in describing what each piece of cruft is.
257
258Version 1.7, released 2008-09-12
259
260 * First real public release, despite the version number. (If you have
261 seen any earlier versions, they were not real. Not real, I tell
262 you! Not real! They didn't exist! Really!)
0263
=== renamed file 'NEWS' => 'NEWS.moved'
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2011-02-28 19:00:59 +0000
@@ -0,0 +1,31 @@
1README for Computer Janitor
2===========================
3
4
5Computer Janitor is a tool to clean up a system so that it is more like
6a freshly installed one. It was written for Ubuntu, but should be
7possible to port to other Unix-like operating systems.
8
9
10Copyright license
11-----------------
12
13Computer Janitor - clean up a computer system
14Copyright (C) 2008-2011 Canonical, Ltd.
15
16The icon (in data/cruftremover-256x256.png and data/cruftremover-24x25.png):
17Copyright (C) Marco Rodrigues
18
19The following license applies to all files (including the icons):
20
21This program is free software: you can redistribute it and/or modify
22it under the terms of the GNU General Public License as published by
23the Free Software Foundation, version 3 of the License.
24
25This program is distributed in the hope that it will be useful,
26but WITHOUT ANY WARRANTY; without even the implied warranty of
27MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28GNU General Public License for more details.
29
30You should have received a copy of the GNU General Public License
31along with this program. If not, see <http://www.gnu.org/licenses/>.
032
=== renamed file 'README' => 'README.moved'
=== added file 'TODO'
--- TODO 1970-01-01 00:00:00 +0000
+++ TODO 2011-02-28 19:00:59 +0000
@@ -0,0 +1,30 @@
1Computer Janitor ideas
2
3 - policykit
4 - aptdaemon backend
5 - nx is disabled?
6 - cronjob to run daily to report cruft
7 - vrms plugin
8 - plugin to suggest removing -generic kernel if more specific one
9 is installed and running (-server, e.g.)
10 - configuration mechanism for plugins
11 - plugin to check for sources.list inconsistencies: $released-updates
12 enabled for foo, if $released is enabled for foo (where foo is
13 main, universe, etc)
14 - UI improvement: group items so that previously ignored stuff is in a
15 separate section of the list, and new stuff is at the top.
16 - UI improvement: more information about a .deb: size, full
17 description, when was it installed, which release did it come
18 from
19 - UI improvement: show sections based on type of cruft. e.g., .deb, fstab
20 - add policykit support
21 - cj: setup.py shouldn't hardcode version string, but should not
22 import computerjanitorapp either, since that requires access
23 to the computerjanitor package.
24 - import computerjanitor.version?
25 - write plugin: unpurged packages
26 - kde UI
27 - write plugin (early karmic): apt-get autoclean
28 - relatime unnecessary in karmic? at least obey noatime, strictatime
29 http://mjg59.livejournal.com/109286.html
30
031
=== renamed file 'TODO' => 'TODO.moved'
=== added file 'computer-janitor'
--- computer-janitor 1970-01-01 00:00:00 +0000
+++ computer-janitor 2011-02-28 19:00:59 +0000
@@ -0,0 +1,41 @@
1#!/usr/bin/python
2#
3# Copyright (C) 2008-2011 Canonical, Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Start the command-line computer janitor."""
18
19from __future__ import absolute_import, unicode_literals
20
21__metaclass__ = type
22__all__ = [
23 ]
24
25
26import sys
27
28from computerjanitorapp import setup_gettext
29from computerjanitorapp.cli.main import main
30from dbus.exceptions import DBusException
31_ = setup_gettext()
32
33
34try:
35 main()
36except KeyboardInterrupt:
37 pass
38except DBusException as error:
39 print >> sys.stderr, _(
40 'Cannot contact Computer Janitor dbus service; try again.')
41 sys.exit(1)
042
=== added file 'computer-janitor-gtk'
--- computer-janitor-gtk 1970-01-01 00:00:00 +0000
+++ computer-janitor-gtk 2011-02-28 19:00:59 +0000
@@ -0,0 +1,68 @@
1#!/usr/bin/python
2#
3# computer-janitor-gtk - clean up a Unix-like operating system (GTK version)
4#
5# Copyright (C) 2008-2011 Canonical, Ltd.
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, version 3 of the License.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Start the gtk computer janitor."""
20
21from __future__ import absolute_import, unicode_literals
22
23__metaclass__ = type
24__all__ = [
25 ]
26
27
28import os
29import sys
30import logging
31import argparse
32import traceback
33import computerjanitor
34
35from computerjanitorapp import __version__, setup_gettext
36from computerjanitorapp.gtk.main import main
37from dbus.exceptions import DBusException
38
39_ = setup_gettext()
40
41
42# Parse some simple command line arguments.
43parser = argparse.ArgumentParser(
44 description=_("""\
45 Find and remove cruft from your system.
46
47 Cruft is anything that shouldn't be on your system, but is.
48 Stretching the definition, cruft is also things that should be on
49 your system but aren't."""))
50parser.add_argument(
51 '--version', action='version',
52 version=_('Computer Janitor {version}').format(
53 version=__version__))
54parser.parse_args()
55# If --version or --help is given, .parse_args() will not return.
56
57try:
58 logging.basicConfig()
59 log = logging.getLogger('computerjanitor')
60 main()
61except KeyboardInterrupt:
62 pass
63except DBusException as error:
64 log.exception('Cannot contact Computer Janitor dbus service; try again.')
65 sys.exit(1)
66except:
67 log.exception('computer-janitor-gtk uncaught exception')
68 sys.exit(1)
069
=== added file 'computer-janitor-gtk.8'
--- computer-janitor-gtk.8 1970-01-01 00:00:00 +0000
+++ computer-janitor-gtk.8 2011-02-28 19:00:59 +0000
@@ -0,0 +1,36 @@
1.\" Copyright (C) 2008-2011 Canonical, Ltd.
2.\"
3.\" This program is free software: you can redistribute it and/or modify
4.\" it under the terms of the GNU General Public License as published by
5.\" the Free Software Foundation, version 3 of the License.
6.\"
7.\" This program is distributed in the hope that it will be useful,
8.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
9.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10.\" GNU General Public License for more details.
11.\"
12.\" You should have received a copy of the GNU General Public License
13.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
14.TH COMPUTER-JANITOR-GTK 8 2011-02-16 Ubuntu "Ubuntu system administration"
15.SH NAME
16computer-janitor-gtk \- remove cruft from system (GUI version)
17.SH SYNOPSIS
18.B computer-janitor-gtk
19.SH DESCRIPTION
20.B computer-janitor-gtk
21is a version of
22.BR computer-janitor (8)
23with a graphical user interface.
24See the latter's manual page for more information.
25.SH ENVIRONMENT
26In addition to the environment variables understood by
27.BR computer-janitor (8),
28the following one is understood by
29.B computer-janitor-gtk
30as well.
31.TP
32.B COMPUTER_JANITOR_GLADE
33The location of the Glade data file to specify the user interface.
34You should not need to set this, unless you really know what you are doing.
35.SH "SEE ALSO"
36.BR computer-janitor (8).
037
=== renamed file 'computer-janitor-gtk.8' => 'computer-janitor-gtk.8.moved'
=== renamed file 'computer-janitor-gtk' => 'computer-janitor-gtk.moved'
=== added file 'computer-janitor.8'
--- computer-janitor.8 1970-01-01 00:00:00 +0000
+++ computer-janitor.8 2011-02-28 19:00:59 +0000
@@ -0,0 +1,222 @@
1.\" Copyright (C) 2008-2011 Canonical, Ltd.
2.\"
3.\" This program is free software: you can redistribute it and/or modify
4.\" it under the terms of the GNU General Public License as published by
5.\" the Free Software Foundation, version 3 of the License.
6.\"
7.\" This program is distributed in the hope that it will be useful,
8.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
9.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10.\" GNU General Public License for more details.
11.\"
12.\" You should have received a copy of the GNU General Public License
13.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
14.TH COMPUTER-JANITOR 8 2011-02-16 Ubuntu "Ubuntu system administration"
15.SH NAME
16computer-janitor \- clean up a system installation
17.SH SYNOPSIS
18.B "computer-janitor"
19.RB [ --version|-h ]
20.br
21.B "computer-janitor find"
22.RB [ -i|-r|-s|-v|-h ]
23.RI
24.br
25.B "computer-janitor clean"
26.RB [ -a|-v|-h ]
27.RI [ CRUFT ]...
28.br
29.B "computer-janitor ignore"
30.RB [ -h ]
31.RI CRUFT
32.br
33.B "computer-janitor unignore"
34.RB [ -h ]
35.RI CRUFT
36.br
37.B computer-janitor-gtk
38.SH DESCRIPTION
39.B computer-janitor
40and
41.B computer-janitor-gtk
42find and remove
43.I cruft
44from your system.
45The former is a command line program,
46the latter has a graphical user interface.
47.PP
48Cruft is anything that shouldn't be on the system, but is.
49Stretching the definition, it is also things that should be on the system,
50but aren't.
51Examples:
52.IP \(bu
53Packages that were originally installed because something else depended on
54them, but the depending package has since been removed.
55Typically this includes large numbers of libraries.
56.IP \(bu
57Packages that are no longer supported by the current release of the
58operating system.
59For example, this can be applications whose development have stopped
60and that no longer have support, including for security issues.
61Keeping such applications installed can be dangerous.
62.IP \(bu
63Configuration tweaks that are missing from the system,
64but which would be there if the system was installed from scratch.
65For example, mount options for filesystems such as
66the
67.B relatime
68option.
69.PP
70.B computer-janitor
71has four modes of operation, invoked by the first non-option
72word in the argument list.
73.IP \(bu
74.B find
75searches for cruft and prints out a list of them.
76Each piece of cruft is also tagged with its state:
77.I ignored
78or
79.IR removable .
80.IP \(bu
81.B clean
82actually removes the cruft.
83It will remove those pieces of cruft you name on the command line.
84If you want to remove everything identified by
85.B find
86that is marked
87.IR removable ,
88use the
89.B --all
90option.
91.IP \(bu
92.B ignore
93and
94.B unignore
95mark cruft as
96.I ignored
97or
98.IR removable ,
99respectively.
100.SH OPTIONS
101Each subcommand listed above has its own set of options. If
102.B computer-janitor
103is invoked with no subcommand, the following options are available:
104.TP
105.B --version
106Print the version number and exit.
107.TP
108.BR --help | -h
109Print some global help and exit.
110.PP
111The
112.B find
113subcommand supports the following options:
114.TP
115.BR --ignored | -i
116Find and display only the system's ignored cruft.
117.TP
118.BR --removable | -r
119Find and display only the system's removable cruft.
120.TP
121.BR --short | -s
122Display only the cruft names; do not use with
123.BR --verbose .
124.TP
125.BR --verbose | -v
126Display a detailed explanation for each piece of cruft found.
127.TP
128.BR --help | -h
129Print detailed help for the
130.B find
131subcommand and exit.
132.PP
133The
134.B clean
135subcommand requires either a cruft name or the
136.B --all
137option to specify which cruft to remove. It supports the following options:
138.TP
139.B --all | -a
140Remove all system cruft that are not ignored.
141.TP
142.BR --verbose | -v
143Provide more details about the cruft being cleaned up.
144.TP
145.BR --help | -h
146Print detailed help for the
147.B clean
148subcommand and exit.
149.PP
150The
151.B ignore
152and
153.B unignore
154commands both take the name of a cruft to mark ignored or removable,
155respectively. They both also accept these options:
156.TP
157.BR --help | -h
158Print detailed help for the
159.B ignore
160or
161.B unignore
162subcommands and exit.
163.SH "EXIT STATUS"
164.B computer-janitor
165will return an exit code of 0 for successful operation (no errors).
166It will return a non-zero exit code if there are any errors.
167It is not an error to find cruft, or to not find cruft.
168.SH FILES
169.TP
170.B /var/lib/computer-janitor/state.dat
171This file stores the
172.I ignored
173or
174.I removable
175state of system cruft. Any cruft not listed in this file is by default
176.IR removable .
177.TP
178.B /etc/computer-janitor.d
179This directory contains whitelist files, which specify
180things that are never considered cruft.
181A whitelist file has a name that ends with
182.BR .whitelist ,
183and contains one (potential) cruft name per line.
184(Empty lines and lines beginning with # are ignored.)
185.SH EXAMPLE
186To find all cruft on the system:
187.sp 1
188.RS
189computer-janitor find
190.RE
191.PP
192To remove a specific piece of cruft:
193.sp 1
194.RS
195computer-janitor clean hello
196.RE
197.PP
198To mark a piece of cruft as
199.IR ignored ,
200so that it isn't removed by
201.B clean
202.BR --all :
203.sp 1
204.RS
205computer-janitor ignore hello
206.RE
207.PP
208To mark a piece of cruft as
209.I removable
210again:
211.sp 1
212.RS
213computer-janitor unignore hello
214.RE
215.PP
216To remove all cruft that isn't ignored:
217.sp 1
218.RS
219computer-janitor clean --all
220.RE
221.SH "SEE ALSO"
222.BR computer-janitor-gtk (8).
0223
=== renamed file 'computer-janitor.8' => 'computer-janitor.8.moved'
=== renamed file 'computer-janitor' => 'computer-janitor.moved'
=== added directory 'computerjanitorapp'
=== renamed directory 'computerjanitorapp' => 'computerjanitorapp.moved'
=== added file 'computerjanitorapp/__init__.py'
--- computerjanitorapp/__init__.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/__init__.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,44 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""computerjanitorapp module."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 '__version__',
22 'setup_gettext',
23 ]
24
25
26__version__ = '2.1.0'
27
28
29# Set up gettext.
30def setup_gettext():
31 """Set up gettext for a module.
32
33 Return a method to be used for looking up translations. Usage:
34
35 >>> import computerjanitorapp
36 >>> _ = computerjanitorapp.setup_gettext()
37 """
38 import gettext
39 import os
40
41 domain = 'computerjanitor'
42 localedir = os.environ.get('LOCPATH', None)
43 t = gettext.translation(domain, localedir=localedir, fallback=True)
44 return t.ugettext
045
=== added directory 'computerjanitorapp/cli'
=== added file 'computerjanitorapp/cli/__init__.py'
=== added file 'computerjanitorapp/cli/main.py'
--- computerjanitorapp/cli/main.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/cli/main.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,312 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Command line user interface."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'main',
22 ]
23
24
25import sys
26import dbus
27import gobject
28import argparse
29import textwrap
30
31from dbus.mainloop.glib import DBusGMainLoop
32from operator import mod
33
34from computerjanitorapp import __version__, setup_gettext
35from computerjanitorapp.terminalsize import get_terminal_size
36from computerjanitorapp.utilities import format_size
37from computerjanitord.service import DBUS_INTERFACE_NAME
38_ = setup_gettext()
39
40
41class Options:
42 """Command line option parser."""
43 def __init__(self, runner):
44 """Parse the command line options.
45
46 :param runner: The class implementing the sub-commands.
47 :type runner: `Runner`
48 """
49 self.runner = runner
50 # Parse the arguments.
51 self.parser = argparse.ArgumentParser(
52 description=_("""\
53 Find and remove cruft from your system.
54
55 Cruft is anything that shouldn't be on your system, but is.
56 Stretching the definition, cruft is also things that should be on
57 your system but aren't."""))
58 self.parser.add_argument(
59 '--version', action='version',
60 # XXX barry 2010-08-23: LP: #622720
61 version=mod(_('Computer Janitor %(version)s'),
62 dict(version=__version__)))
63 subparser = self.parser.add_subparsers(title='Commands')
64 # The 'find' subcommand.
65 command = subparser.add_parser(
66 'find', help=_('Find and display all cruft found on your system.'))
67 command.add_argument(
68 '-v', '--verbose', action='store_true',
69 help=_("""\
70 Display a detailed explanation for each piece of cruft found."""))
71 command.add_argument(
72 '-i', '--ignored', action='store_true',
73 help=_('Find and display only the ignored cruft.'))
74 command.add_argument(
75 '-r', '--removable', action='store_true',
76 help=_('Find and display only the removable cruft.'))
77 command.add_argument(
78 '-s', '--short', action='store_true',
79 help=_('Display only the package names. Do not use with -v.'))
80 command.set_defaults(func=self.runner.find)
81 # The 'ignore' subcommand.
82 command = subparser.add_parser(
83 'ignore', help=_("""\
84 Ignore a piece of cruft so that it is not cleaned up."""))
85 command.add_argument(
86 'cruft', nargs=1,
87 help=_('The name of the cruft to ignore.'))
88 command.set_defaults(func=self.runner.ignore)
89 # The 'unignore' subcommand.
90 command = subparser.add_parser(
91 'unignore', help=_("""\
92 Unignore a piece of cruft so that it will be cleaned up."""))
93 command.add_argument(
94 'cruft', nargs=1,
95 help=_('The name of the cruft to unignore.'))
96 command.set_defaults(func=self.runner.unignore)
97 # The 'clean' subcommand.
98 command = subparser.add_parser(
99 'clean',
100 help=_('Remove the selected cruft from the system.'))
101 command.add_argument(
102 '-a', '--all', action='store_true',
103 help=_('Clean up all unignored cruft.'))
104 command.add_argument(
105 '-v', '--verbose', action='store_true',
106 help=_("Provide more details on what's being cleaned."))
107 command.add_argument(
108 'cruft', nargs='?',
109 help=_("""\
110 The name of the cruft to clean up. Do not use if specifying
111 --all."""))
112 command.set_defaults(func=self.runner.clean)
113 # Parse the arguments and execute the subcommand.
114 self.arguments = self.parser.parse_args()
115
116
117class Runner:
118 """Implementations of subcommands."""
119
120 def __init__(self):
121 # Connect to the dbus service.
122 system_bus = dbus.SystemBus()
123 proxy = system_bus.get_object(DBUS_INTERFACE_NAME, '/')
124 self.janitord = dbus.Interface(
125 proxy, dbus_interface=DBUS_INTERFACE_NAME)
126 # Connect to the signal the server will emit when cleaning up.
127 self.janitord.connect_to_signal('cleanup_status', self._clean_working)
128 # This will get backpatched by __main__. We need it to produce error
129 # messages from the argparser.
130 self.options = None
131 # The main loop for asynchronous calls and signal reception.
132 self.loop = gobject.MainLoop()
133
134 def _error(self, message):
135 """Generate a parser error and exit.
136
137 :param message: The error message.
138 :type message: string
139 """
140 self.options.parser.error(message)
141 # No return.
142
143 def find(self, arguments):
144 """Find and display all cruft.
145
146 :param arguments: Command line options.
147 """
148 # Cruft will be prefixed by 'removable' if it is not being ignored.
149 ignored = set(self.janitord.ignored())
150 cruft_names = set(self.janitord.find())
151 # Filter names based on option flags.
152 if arguments.ignored:
153 cruft = sorted(cruft_names & ignored)
154 elif arguments.removable:
155 cruft = sorted(cruft_names - ignored)
156 else:
157 cruft = sorted(cruft_names)
158 # The prefix will either be 'ignored' or 'removable' however this
159 # string will be translated, so calculate the prefix size in the
160 # native language, then add two columns of separator, followed by the
161 # cruft name.
162 prefixi = _('ignored')
163 prefixr = _('removable')
164 prefix_width = max(len(prefixi), len(prefixr))
165 # Long, short, shorter display.
166 if arguments.verbose and arguments.short:
167 self._error('Use either -s or -v but not both.')
168 if arguments.short:
169 # This is the shorter output.
170 for name in cruft:
171 print name
172 elif not arguments.verbose:
173 # This is the short output.
174 for name in cruft:
175 prefix = (prefixi if name in ignored else prefixr)
176 # 10 spaces for the prefix
177 print '{0:{1}} {2}'.format(prefix, prefix_width, name)
178 else:
179 # This is the verbose output. Start by getting the terminal's
180 # size, though all we care about is the width.
181 rows, columns = get_terminal_size()
182 width = ((80 if columns in (None, 0) else columns)
183 - prefix_width # space for prefix
184 - 2 # separator
185 - 1 # avoid last column, some terminals wrap
186 )
187 margin = ' ' * (prefix_width + 2)
188 for name in cruft:
189 prefix = (prefixi if name in ignored else prefixr)
190 # 10 spaces for the prefix
191 print '{0:{1}} {2}'.format(prefix, prefix_width, name)
192 # Print some details about the cruft.
193 cruft_type, disk_usage = self.janitord.get_details(name)
194 print '{0}{1} of type {2}'.format(
195 margin, format_size(disk_usage), cruft_type)
196 # Get the description, wrap it to the available columns, and
197 # display it line-by-line with the proper amount of leading
198 # spaces (prefix_width + 2).
199 description = self.janitord.get_description(name)
200 if len(description) == 0:
201 print
202 continue
203 paragraphs = description.split('\n\n')
204 for paragraph in paragraphs:
205 for line in textwrap.wrap(paragraph, width):
206 # 2010-02-09 barry: The original code forced the
207 # output to utf-8, claiming that was necessary to
208 # "write out stuff even when it's going to somewhere
209 # like a pipe [...] ignor[ing] the user's desired
210 # charset, which is bad, bad, bad...". This makes me
211 # pretty uncomfortable, so I'd like to see a bug
212 # report before I copy that from the previous
213 # implementation.
214 print '{0}{1}'.format(margin, line)
215 # Paragraph separator.
216 print
217
218 def ignore(self, arguments):
219 """Ignore some cruft.
220
221 :param arguments: Command line options.
222 """
223 assert len(arguments.cruft) == 1, 'Unexpected arguments'
224 self.janitord.ignore(arguments.cruft[0])
225 self.janitord.save()
226
227 def unignore(self, arguments):
228 """Unignore some cruft.
229
230 :param arguments: Command line options.
231 """
232 assert len(arguments.cruft) == 1, 'Unexpected arguments'
233 self.janitord.unignore(arguments.cruft[0])
234 self.janitord.save()
235
236 def _clean_reply(self):
237 """The 'clean' operation has completed successfully."""
238 self.loop.quit()
239 print 'done.'
240
241 def _clean_error(self, exception):
242 """The 'clean' operation has failed."""
243 self.loop.quit()
244 print 'dbus service error:', exception
245
246 def _clean_working(self, cruft):
247 """The 'clean' operation is in progress.
248
249 :param cruft: The cruft that is being cleaned up.
250 :type cruft: string
251 """
252 verbose = self.options.arguments.verbose
253 if cruft == '':
254 # We're done.
255 if not verbose:
256 sys.stdout.write(' ')
257 sys.stdout.flush()
258 else:
259 if verbose:
260 print 'Working on', cruft
261 else:
262 sys.stdout.write('.')
263 sys.stdout.flush()
264
265 def clean(self, arguments):
266 """Clean up the cruft.
267
268 :param arguments: Command line options.
269 """
270 if arguments.all:
271 # You can't specify both --all and a cruft name.
272 if arguments.cruft is not None:
273 self._error('Specify a cruft name or --all, but not both')
274 # No return.
275 all_cruft = set(self.janitord.find())
276 ignored = set(self.janitord.ignored())
277 cleanable_cruft = tuple(all_cruft - ignored)
278 else:
279 if arguments.cruft is None:
280 self._error('You must specify a cruft name, or use --all')
281 # No return.
282 cleanable_cruft = (arguments.cruft,)
283 # Make the asynchronous call because this can take a long time. We'll
284 # get status updates periodically. Note however that even though this
285 # is asynchronous, dbus still expects a response within a certain
286 # amount of time. We have no idea how long it will take to clean up
287 # the cruft though, so just crank the timeout up to some insanely huge
288 # number (of seconds).
289 self.janitord.clean(cleanable_cruft,
290 reply_handler=self._clean_reply,
291 error_handler=self._clean_error,
292 # If it takes longer than an hour, we're screwed.
293 timeout=3600)
294 # Start the main loop. This will exit when the remote operation is
295 # complete.
296 if not arguments.verbose:
297 sys.stdout.write('processing')
298 sys.stdout.flush()
299 self.loop.run()
300
301
302def main():
303 # We'll need a main loop to receive status signals from the dbus service.
304 # Don't start the main loop yet though, since we only need it for 'clean'
305 # commands.
306 DBusGMainLoop(set_as_default=True)
307 runner = Runner()
308 options = Options(runner)
309 # Backpatch runner because of circular references.
310 runner.options = options
311 # Execute the subcommand.
312 options.arguments.func(options.arguments)
0313
=== added directory 'computerjanitorapp/gtk'
=== added file 'computerjanitorapp/gtk/__init__.py'
=== added file 'computerjanitorapp/gtk/dialogs.py'
--- computerjanitorapp/gtk/dialogs.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/gtk/dialogs.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,125 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Confirm some actions."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'AreYouSure',
22 'CleanupProblem',
23 ]
24
25
26from computerjanitorapp import setup_gettext
27from gi.repository import Gtk
28from operator import mod as interpolate
29
30
31_ = setup_gettext()
32NL = '\n'
33
34
35class DialogBase:
36 """Base class for dialogs."""
37
38 def __init__(self, ui):
39 self._ui = ui
40
41
42class AreYouSure(DialogBase):
43 """Confirmation of destructive actions."""
44
45 def verify(self):
46 """Confirm package removal."""
47 # Start by getting all the active, non-ignored cruft. These are the
48 # candidates for removal.
49 cleanable_cruft = self._ui.get_cleanable_cruft()
50 package_cruft_count = sum(1 for cruft_name, is_pkg in cleanable_cruft
51 if is_pkg)
52 other_cruft_count = len(cleanable_cruft) - package_cruft_count
53 messages = []
54 ok_button = None
55 # XXX vv barry 2010-08-23: LP: #622720
56 if package_cruft_count > 0:
57 messages = [
58 _('<b>Software packages to remove: %(packages)s</b>.'),
59 _('\nRemoving packages that are still in use can '
60 'cause errors.'),
61 ]
62 ok_button = _('Remove packages')
63 if other_cruft_count > 0:
64 messages.insert(1, _('Non-package items to remove: %(others)s.'))
65 ok_button = _('Clean up')
66 secondary_message = interpolate(NL.join(messages),
67 dict(packages=package_cruft_count,
68 others=other_cruft_count))
69 # XXX ^^ barry 2010-08-23: LP: #622720
70 if ok_button is None:
71 # The user de-selected all cruft from the ui, so there's actually
72 # nothing to clean up.
73 dialog = Gtk.MessageDialog(
74 parent=self._ui.widgets['window'],
75 type=Gtk.MessageType.WARNING,
76 buttons=Gtk.ButtonsType.NONE,
77 message_format=_('There is nothing to clean up'),
78 )
79 dialog.set_title(_('Clean up'))
80 dialog.add_button(_('Ok'), Gtk.ResponseType.YES)
81 dialog.show_all()
82 dialog.run()
83 dialog.hide()
84 return False
85 # It would be nice if we could produce better messages than these,
86 # but that would require a richer interface to the dbus service,
87 # and probably to the cruft plugin architecture underneath that.
88 message = _('Are you sure you want to clean your system?')
89 dialog = Gtk.MessageDialog(
90 parent=self._ui.widgets['window'],
91 type=Gtk.MessageType.WARNING,
92 buttons=Gtk.ButtonsType.NONE,
93 message_format=message)
94 dialog.set_title(_('Clean up'))
95 dialog.format_secondary_markup(secondary_message)
96 dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CLOSE)
97 dialog.add_button(ok_button, Gtk.ResponseType.YES)
98 # Show the dialog and get the user's response.
99 dialog.show_all()
100 response = dialog.run()
101 dialog.hide()
102 return response == Gtk.ResponseType.YES
103
104
105class CleanupProblem(DialogBase):
106 """Some problem occurred during cleanup.
107
108 This is almost definitely caused by another package manager running at the
109 same time.
110 """
111
112 def run(self):
113 """Show the dialog and wait for confirmation."""
114 message = _('System clean up could not complete. '
115 'Be sure no other package manager such as Synaptic or '
116 'Update Manager is running.')
117 dialog = Gtk.MessageDialog(
118 parent=self._ui.widgets['window'],
119 type=Gtk.MessageType.ERROR,
120 buttons=Gtk.ButtonsType.OK,
121 message_format=message)
122 dialog.set_title(_('System clean up failure'))
123 dialog.show_all()
124 dialog.run()
125 dialog.hide()
0126
=== added file 'computerjanitorapp/gtk/main.py'
--- computerjanitorapp/gtk/main.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/gtk/main.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,32 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Command line user interface."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'main',
22 ]
23
24
25from computerjanitorapp.gtk.ui import UserInterface
26from dbus.mainloop.glib import DBusGMainLoop
27
28
29def main():
30 DBusGMainLoop(set_as_default=True)
31 ui = UserInterface()
32 ui.run()
033
=== added file 'computerjanitorapp/gtk/store.py'
--- computerjanitorapp/gtk/store.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/gtk/store.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,162 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Gtk ListStore model for backing the TreeViews of cruft."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'Store',
22 'optimize',
23 'unused',
24 ]
25
26
27import gobject
28
29from gi.repository import Gtk
30
31
32# XXX 2010-03-04 barry: Use a munepy enum.
33class ListStoreColumns:
34 # The cruft name as known by the dbus service.
35 name = 0
36 # The short name of the cruft.
37 short_name = 1
38 # The text displayed in the cell.
39 text = 2
40 # Should the cruft be shown?
41 show = 3
42 # Is the cruft active locally (i.e. toggle button is set)?
43 active = 4
44 # Is the cruft being ignored in the dbus service?
45 server_ignored = 5
46 # Is the cruft's description expanded?
47 expanded = 6
48 # Is this cruft package cruft or some other kind of cruft?
49 is_package_cruft = 7
50
51
52class Store:
53 """The higher level wrapper around the Gtk.ListStore."""
54
55 def __init__(self, janitord):
56 """Create the Store.
57
58 :param janitord: The `dbus.Interface` for talking to the Computer
59 Janitor dbus service.
60 """
61 self.janitord = janitord
62 # This ListStore is the backing data structure for the TreeViews in
63 # the main window. It holds information about the individual pieces
64 # of cruft. See ListStoreColumns for details.
65 #
66 # XXX 2010-03-04 barry: pygtk does not like 'unicode'.
67 self.store = Gtk.ListStore(str, str, str, bool, bool, bool, bool, bool)
68
69 def find_cruft(self):
70 """Find cruft and populate the backing store.
71
72 This actually just asks the backend dbus service to find all the cruft
73 asynchronously. When the dbus service completes the task, it sends a
74 `find_finished` signal with the new list of cruft. Upon receipt of
75 that signal, the ListStore is updated.
76 """
77 self.janitord.connect_to_signal('find_finished', self.on_find_finished)
78 self.janitord.find_async()
79
80 def on_find_finished(self, all_cruft_names):
81 """dbus signal handler.
82
83 This actually updates the store with the new cruft results.
84 """
85 ignored_cruft = set(self.janitord.ignored())
86 all_cruft_names = set(all_cruft_names);
87 pkg_cruft_names = set(
88 cruft_name for cruft_name in all_cruft_names
89 if self.janitord.get_details(cruft_name)[0].lower()
90 == 'packagecruft')
91 self.store.clear()
92 # Cruft always starts out in the active, not-expanded state. Package
93 # cruft goes in the 'unused' column while non-package cruft goes in
94 # the 'optimize' column.
95 for cruft_name in all_cruft_names:
96 cruft_shortname = self.janitord.get_shortname(cruft_name)
97 self.store.append((
98 cruft_name, cruft_shortname,
99 # What is being displayed in the cell.
100 gobject.markup_escape_text(cruft_shortname),
101 # Show the cruft be shown?
102 cruft_name not in ignored_cruft,
103 # Is the cruft active locally?
104 cruft_name not in ignored_cruft,
105 # Is the cruft ignored in the dbus service?
106 cruft_name in ignored_cruft,
107 # Is the cruft's description expanded?
108 False,
109 # Is this package cruft?
110 cruft_name in pkg_cruft_names,
111 ))
112
113 # Forwards to the underlying store, for convenience. Really, these should
114 # use generic delegates, or clients should just use self.store.store.
115
116 def get_value(self, *args, **kws):
117 return self.store.get_value(*args, **kws)
118
119 def set_value(self, *args, **kws):
120 return self.store.set_value(*args, **kws)
121
122 def filter_new(self, *args, **kws):
123 return self.store.filter_new(*args, **kws)
124
125 def foreach(self, *args, **kws):
126 return self.store.foreach(*args, **kws)
127
128 def reorder(self, *args, **kws):
129 return self.store.reorder(*args, **kws)
130
131 def get_iter_first(self, *args, **kws):
132 return self.store.get_iter_first(*args, **kws)
133
134 def iter_next(self, *args, **kws):
135 return self.store.iter_next(*args, **kws)
136
137 def clear(self, *args, **kws):
138 return self.store.clear(*args, **kws)
139
140
141# Filter functions for use with TreeView column display.
142
143def unused(store, iter, data):
144 """True if the cruft is not being ignored and is package cruft.
145
146 :param store: The ListStore instance.
147 :param iter: The TreeIter instance.
148 """
149 show = store.get_value(iter, ListStoreColumns.show)
150 is_package_cruft = store.get_value(iter, ListStoreColumns.is_package_cruft)
151 return show and is_package_cruft
152
153
154def optimize(store, iter, data):
155 """True if the cruft is not package cruft.
156
157 :param store: The ListStore instance.
158 :param iter: The TreeIter instance.
159 """
160 show = store.get_value(iter, ListStoreColumns.show)
161 is_package_cruft = store.get_value(iter, ListStoreColumns.is_package_cruft)
162 return show and not is_package_cruft
0163
=== added file 'computerjanitorapp/gtk/ui.py'
--- computerjanitorapp/gtk/ui.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/gtk/ui.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,594 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Gtk user interface for computer janitor."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'UserInterface',
22 ]
23
24
25import os
26import dbus
27import glib
28import gobject
29import logging
30
31from operator import mod
32
33from gi.repository import Gtk, Pango
34
35from computerjanitorapp import __version__, setup_gettext
36from computerjanitorapp.gtk.dialogs import AreYouSure, CleanupProblem
37from computerjanitorapp.gtk.store import (
38 ListStoreColumns, Store, optimize, unused)
39from computerjanitorapp.utilities import format_size
40from computerjanitord.service import DBUS_INTERFACE_NAME
41
42
43Gtk.require_version('2.0')
44log = logging.getLogger('computerjanitor')
45_ = setup_gettext()
46
47
48GLADE = '/usr/share/computer-janitor/ComputerJanitor.ui'
49ROOT_WIDTH = 900
50ROOT_HEIGHT = 500
51NL = '\n'
52
53# Keys are lower-cased cruft types, i.e. class name of cruft instances.
54ACTIONS = dict(
55 packagecruft=_('Package will be <b>removed</b>.'),
56 filecruft=_('Package will be <b>installed</b>.'),
57 missingpackagecruft=_('File will be <b>removed</b>.'),
58 )
59
60SENSITIVE_WIDGETS = (
61 'do_button',
62 'optimize_treeview',
63 'quit_menuitem',
64 'show_previously_ignored',
65 'sort_by_name',
66 'sort_by_size',
67 'unused_treeview',
68 )
69
70
71class UserInterface:
72 """Implementation of the Gtk user interface."""
73
74 def __init__(self):
75 # Connect to the dbus service.
76 system_bus = dbus.SystemBus()
77 proxy = system_bus.get_object(DBUS_INTERFACE_NAME, '/')
78 self.janitord = dbus.Interface(
79 proxy, dbus_interface=DBUS_INTERFACE_NAME)
80 # Create the model on which the TreeViews will be based.
81 self.store = Store(self.janitord)
82 self.popup_menus = {}
83 self.cruft_name_columns = set()
84 # Sort by name by default.
85 self._sort_key = self._by_name
86 # Work is happening asynchronously on the dbus service.
87 self.working = False
88 # Connect to the signal the server will emit when cleaning up.
89 self.janitord.connect_to_signal('cleanup_status', self._clean_working)
90
91 def run(self):
92 """Set up the widgets and run the main loop."""
93 builder = Gtk.Builder()
94 builder.set_translation_domain('computerjanitor')
95 # Load the glade ui file, which can be overridden from the environment
96 # for testing purposes.
97 glade_file = os.environ.get('COMPUTER_JANITOR_GLADE', GLADE)
98 builder.add_from_file(glade_file)
99 # Bind widgets to callbacks. Initialize the Do... button to
100 # insensitive, but possibly toggle it back to sensitive if we find
101 # cruft.
102 self.find_and_bind_widgets(builder)
103 self.widgets['do_button'].set_sensitive(False)
104 self.janitord.connect_to_signal('find_finished', self._find_finished)
105 # Do the initial search for cruft and set up the TreeView model.
106 self.store.find_cruft()
107 self.sort_cruft()
108 # Now hook the TreeViews up to there view of the model.
109 self.create_column('unused_treeview', unused)
110 self.create_column('optimize_treeview', optimize)
111 # Set the dimensions of the root window.
112 root = self.widgets['window']
113 root.set_default_size(ROOT_WIDTH, ROOT_HEIGHT)
114 # Map the root window and go!
115 root.show()
116 Gtk.main()
117
118 def find_and_bind_widgets(self, builder):
119 """Bind widgets and callbacks."""
120 # Start by extracting all the bindable widgets from the ui builder,
121 # keeping track of them as mapped to their name.
122 self.widgets = {}
123 for ui_object in builder.get_objects():
124 if issubclass(type(ui_object), Gtk.Buildable):
125 widget_name = Gtk.Buildable.get_name(ui_object)
126 self.widgets[widget_name] = ui_object
127 # Search through the attributes of this instance looking for
128 # callbacks for this widget. We use the naming convention
129 # 'on_<widget>_<signal>' for such methods. Both the widget
130 # name and signal (or event) can contain underscores. Connect
131 # the widget to the callback.
132 prefix = 'on_{0}_'.format(widget_name)
133 for method_name in dir(self):
134 if method_name.startswith(prefix):
135 signal_name = method_name[len(prefix):]
136 ui_object.connect(
137 signal_name, getattr(self, method_name))
138
139 def create_column(self, widget_name, filter_func):
140 """Set up a column in the named TreeView."""
141 treeview = self.widgets[widget_name]
142 # XXX 2010-03-04 barry: This is a bit of an abuse because it's
143 # supposed to specify whether users are to read across the rows. As a
144 # side effect it renders the columns with alternating row colors, but
145 # that's not it's primary function.
146 treeview.set_rules_hint(True)
147 # Each TreeView contains two columns. The leftmost one is a toggle
148 # that when select tells c-j to act on that cruft. Deselecting the
149 # toggle ignores the package for next time.
150 toggle_cr = Gtk.CellRendererToggle()
151 toggle_cr.connect('toggled', self._toggled, treeview)
152 toggle_cr.set_property('yalign', 0)
153 toggle_col = Gtk.TreeViewColumn()
154 toggle_col.pack_start(toggle_cr, True)
155 toggle_col.add_attribute(toggle_cr, 'active', ListStoreColumns.active)
156 treeview.append_column(toggle_col)
157 # The rightmost column contains the details of the cruft. It will
158 # always contain the cruft name and can be expanded to display cruft
159 # details. Tell the column to get its toggle's active state from the
160 # model.
161 name_cr = Gtk.CellRendererText()
162 name_cr.set_property('yalign', 0)
163 name_cr.set_property('wrap-mode', Pango.WrapMode.WORD)
164 name_col = Gtk.TreeViewColumn()
165 name_col.pack_start(name_cr, True)
166 name_col.add_attribute(name_cr, 'markup', ListStoreColumns.text)
167 treeview.append_column(name_col)
168 self.cruft_name_columns.add(name_col)
169 # The individual crufts may or may not be visible in this TreeView.
170 # It's the filter function that controls this, so set that now.
171 filter_store = self.store.filter_new(None)
172 filter_store.set_visible_func(filter_func, None)
173 treeview.set_model(filter_store)
174 # Each TreeView has a popup menu for select or deselecting all visible
175 # cruft.
176 self.create_popup_menu_for_treeview(treeview)
177
178 def create_popup_menu_for_treeview(self, treeview):
179 """The tree views have a popup menu to select/deselect everything.
180
181 :param treeview: The `TreeView` to attach the menu to.
182 """
183 select_all = Gtk.MenuItem(label='Select all')
184 select_all.connect('activate', self.popup_menu_select_all, treeview)
185 unselect_all = Gtk.MenuItem(label='Unselect all')
186 unselect_all.connect('activate',
187 self.popup_menu_unselect_all, treeview)
188 menu = Gtk.Menu()
189 menu.append(select_all)
190 menu.append(unselect_all)
191 menu.show_all()
192 self.popup_menus[treeview] = menu
193
194 def _by_name(self, cruft_name):
195 """Sort by cruft name."""
196 return cruft_name
197
198 def _by_size(self, cruft_name):
199 """Sort by cruft size, from largest to smallest."""
200 # Return negative size to sort from largest to smallest.
201 return -self.janitord.get_details(cruft_name)[1]
202
203 def sort_cruft(self):
204 """Sort the cruft displays, either by name or size."""
205 # The way reordering (not technically 'sorting') works in gtk is that
206 # you give a list of integer indexes to the the ListStore. These
207 # indexes are in sorted order, and refer to the pre-sort indexes of
208 # the items in the store. IOW, the ListStore knows that if the first
209 # integer in the list is 7, it will move the 7th item to the top.
210 #
211 # Start by getting the indexes and names of the currenly sorted cruft.
212 # We'll fill this list with 2-tuples of the format:
213 # (sort-key, current-index).
214 cruft_data = []
215 def get(model, path, iter, crufts):
216 cruft_name = model.get_value(iter, ListStoreColumns.name)
217 crufts.append((self._sort_key(cruft_name), len(crufts)))
218 # Continue iterating.
219 return False
220 self.store.foreach(get, cruft_data)
221 cruft_data.sort()
222 cruft_indexes = [index for key, index in cruft_data]
223 # No need to do anything if there is no cruft.
224 if len(cruft_indexes) > 0:
225 self.store.reorder(cruft_indexes)
226
227 def get_cleanable_cruft(self):
228 """Return the list of cleanable cruft candidates.
229
230 :return: List of cleanable cruft.
231 :rtype: list of 2-tuples of (cruft_name, is_package_cruft)
232 """
233 cleanable_cruft = []
234 def collect(model, path, iter, crufts):
235 # Only clean up active cruft, i.e. those that are specifically
236 # checked as ready for cleaning.
237 cruft_active = model.get_value(iter, ListStoreColumns.active)
238 if cruft_active:
239 cruft_name = model.get_value(iter, ListStoreColumns.name)
240 cruft_is_package_cruft = model.get_value(
241 iter, ListStoreColumns.is_package_cruft)
242 crufts.append((cruft_name, cruft_is_package_cruft))
243 # Continue iterating.
244 return False
245 self.store.foreach(collect, cleanable_cruft)
246 return cleanable_cruft
247
248 def toggle_long_description(self, treeview):
249 """Toggle the currently selected cruft's long description.
250
251 :param treeview: The TreeView
252 """
253 selection = treeview.get_selection()
254 filter_model, selected = selection.get_selected()
255 if not selected:
256 return
257 model = filter_model.get_model()
258 iter = filter_model.convert_iter_to_child_iter(selected)
259 cruft_name = model.get_value(iter, ListStoreColumns.name)
260 expanded = model.get_value(iter, ListStoreColumns.expanded)
261 shortname = model.get_value(iter, ListStoreColumns.short_name)
262 if expanded:
263 # Collapse it.
264 value = gobject.markup_escape_text(shortname)
265 else:
266 cruft_type, size = self.janitord.get_details(cruft_name)
267 lines = [gobject.markup_escape_text(shortname)]
268 action = ACTIONS.get(cruft_type.lower())
269 if action is not None:
270 lines.append(action)
271 # XXX barry 2010-08-23: LP: #622720
272 lines.append(mod(_('Size: %(bytes)s'),
273 dict(bytes=format_size(size))))
274 lines.append('')
275 description = self.janitord.get_description(cruft_name)
276 lines.append(gobject.markup_escape_text(description))
277 value = NL.join(lines)
278 model.set_value(iter, ListStoreColumns.text, value)
279 model.set_value(iter, ListStoreColumns.expanded, not expanded)
280
281 def desensitize(self):
282 """Make certain ui elements insensitive during work."""
283 for widget in SENSITIVE_WIDGETS:
284 self.widgets[widget].set_sensitive(False)
285
286 def sensitize(self):
287 """Make certain ui elements sensitive after work."""
288 for widget in SENSITIVE_WIDGETS:
289 self.widgets[widget].set_sensitive(True)
290
291 def _find_finished(self, all_cruft_names):
292 """dbus signal handler."""
293 self.widgets['do_button'].set_sensitive(len(all_cruft_names) > 0)
294
295 # Popup menu support.
296
297 def _treeview_foreach_set_set_state(self, treeview, enabled):
298 """Set the state of the cruft 'active' flag for all cruft.
299
300 :param treeview: The `TreeView` to set cruft state on.
301 :param enabled: The new state flag for all cruft. True means enabled.
302 :type enabled: bool
303 """
304 def set_state(model, path, iter, user_data):
305 # Set the state on an individual piece of cruft. Start by
306 # changing the state of the cruft on the dbus service.
307 child_iter = model.convert_iter_to_child_iter(iter)
308 cruft_name = self.store.get_value(
309 child_iter, ListStoreColumns.name)
310 if enabled:
311 self.janitord.unignore(cruft_name)
312 else:
313 self.janitord.ignore(cruft_name)
314 # Now set the active state in the model.
315 self.store.set_value(child_iter, ListStoreColumns.active, enabled)
316 treeview.get_model().foreach(set_state, None)
317 # Save the updated state on the dbus service.
318 self.janitord.save()
319
320 def popup_menu_select_all(self, menuitem, treeview):
321 self._treeview_foreach_set_set_state(treeview, True)
322
323 def popup_menu_unselect_all(self, menuitem, treeview):
324 self._treeview_foreach_set_set_state(treeview, False)
325
326 # Progress bar
327
328 def pulse(self):
329 """Progress bar callback, showing that something is happening."""
330 progress = self.widgets['progressbar_status']
331 if self.working:
332 progress.show()
333 progress.pulse()
334 return True
335 else:
336 # All done. Hide the progress bar, make the ui elements sensitive
337 # again, update the store, and kill the timer.
338 progress.hide()
339 self.store.clear()
340 self.store.find_cruft()
341 self.sensitize()
342 return False
343
344 def _clean_working(self, cruft):
345 """dbus signal handler; the 'clean' operation is in progress.
346
347 :param done: The cruft that is being cleaned up.
348 :type done: string
349 """
350 # Just mark the status here. The progress bar pulsar will handle
351 # doing the actual work.
352 self.working = (cruft != '')
353 if self.working:
354 self.widgets['progressbar_status'].set_text(
355 # XXX barry 2010-08-23: LP: #622720
356 mod(_('Processing %(cruft)s'), dict(cruft=cruft)))
357
358 # Callbacks
359
360 def _toggled(self, widget, path, treeview):
361 """Handle the toggle button in a TreeView cell.
362
363 :param widget: The CellRendererToggle
364 :param path: The cell's path.
365 :param treeview: The TreeView
366 """
367 # Find out which cruft's toggle was clicked.
368 model = treeview.get_model()
369 filter_iter = model.get_iter(path)
370 child_iter = model.convert_iter_to_child_iter(filter_iter)
371 cruft_name = self.store.get_value(child_iter, ListStoreColumns.name)
372 state = self.store.get_value(child_iter, ListStoreColumns.active)
373 # Toggle the current state.
374 new_state = not state
375 if new_state:
376 self.janitord.unignore(cruft_name)
377 else:
378 self.janitord.ignore(cruft_name)
379 self.store.set_value(child_iter, ListStoreColumns.active, new_state)
380 self.store.set_value(
381 child_iter, ListStoreColumns.server_ignored, not new_state)
382 # Save the new ignored state on the dbus service.
383 self.janitord.save()
384
385 # Signal and event handlers.
386
387 def on_quit_menuitem_activate(self, *args):
388 """Signal and event handlers for quitting.
389
390 Since we just want things to go away, we don't really care about the
391 arguments. Just tell the main loop to exit.
392 """
393 # Don't quit while we're working.
394 if self.working:
395 return True
396 Gtk.main_quit()
397
398 on_window_delete_event = on_quit_menuitem_activate
399
400 def treeview_button_press_event(self, treeview, event):
401 """Handle mouse button press events on the TreeView ourselves.
402
403 We handle mouse button presses ourselves so that we can either
404 toggle the long description (button 1, typically left) or
405 pop up a menu (button 3, typically right).
406 """
407 # Original comment: This is slightly tricky and probably a source of
408 # bugs. Oh well.
409 if event.button.button == 1:
410 # Left button event. Select the row being clicked on. If the
411 # click is on the cruft name, show or hide its long description.
412 # If the click the click is elsewhere do not handle it. This
413 # allows the toggle button event to be handled separately.
414 x = int(event.x)
415 y = int(event.y)
416 time = event.time
417 pathinfo = treeview.get_path_at_pos(x, y)
418 if pathinfo is None:
419 # The click was not in a cell, but we've handled it anyway.
420 return True
421 path, column, cell_x, cell_y = pathinfo
422 if column in self.cruft_name_columns:
423 treeview.set_cursor(path, column, False)
424 self.toggle_long_description(treeview)
425 return True
426 else:
427 # We are not handling this event so that the toggle button
428 # handling can occur.
429 return False
430 elif event.button.button == 3:
431 # Right button event. Pop up the select/deselect all menu.
432 treeview.grab_focus()
433 x = int(event.x)
434 y = int(event.y)
435 time = event.time
436 pathinfo = treeview.get_path_at_pos(x, y)
437 if pathinfo is not None:
438 path, column, cell_x, cell_y = pathinfo
439 treeview.set_cursor(path, column, False)
440 menu = self.popup_menus[treeview]
441 try:
442 menu.popup_for_device(None, None, None, None, None,
443 event.button.button, time)
444 except AttributeError:
445 # popup_for_device() is introspection safe, but only exists in
446 # GTK3. popup() isn't introspectable, so in GTK 2 we need to
447 # disable the popup menu functionality
448 log.warning('popup menu not supported when using GTK2')
449 return True
450 else:
451 # No other events are handled by us.
452 return False
453
454 # The actual event handler is totally generic. Alias it to names
455 # recognized by the automatic event binding scheme.
456 on_unused_treeview_button_press_event = treeview_button_press_event
457 on_optimize_treeview_button_press_event = treeview_button_press_event
458
459 def treeview_size_allocate(self, treeview, *args):
460 """Allocate space for the tree view and set wrap width.
461
462 :param treeview: The TreeView
463 :param args: Additional ignored positional arguments
464 """
465 # Get the rightmost of the two columns in the TreeView, i.e. the one
466 # containing the text.
467 column = treeview.get_column(1)
468 name_cr = column.get_cells()[0]
469 # Wrap to the entire width of the column.
470 width = column.get_width()
471 name_cr.set_property('wrap-width', width)
472
473 on_unused_treeview_size_allocate = treeview_size_allocate
474 on_optimize_treeview_size_allocate = treeview_size_allocate
475
476 def on_sort_by_name_toggled(self, menuitem):
477 """Reorder the crufts to be sorted by name or size."""
478 if menuitem.get_active():
479 self._sort_key = self._by_name
480 else:
481 self._sort_key = self._by_size
482 self.sort_cruft()
483
484 def on_about_menuitem_activate(self, *args):
485 dialog = self.widgets['about_dialog']
486 dialog.set_name(_('Computer Janitor'))
487 dialog.set_version(__version__)
488 dialog.show()
489 dialog.run()
490 dialog.hide()
491
492 def on_show_previously_ignored_toggled(self, menuitem):
493 """Show all cruft, even those being ignored.
494
495 Normally, we only show cruft that wasn't explicitly ignored. By
496 toggling this menu item, the janitor can also display cruft that is
497 marked as ignored on the dbus service.
498 """
499 show_ignored_cruft = menuitem.get_active()
500 iter = self.store.get_iter_first()
501 while iter:
502 server_ignored = self.store.get_value(
503 iter, ListStoreColumns.server_ignored)
504 show = (show_ignored_cruft or not server_ignored)
505 self.store.set_value(iter, ListStoreColumns.show, show)
506 iter = self.store.iter_next(iter)
507
508 def _edit_menuitem_set_select_state(self, enabled, *treeviews):
509 """Set the specified state on the selected treeviews.
510
511 :param enabled: The new state flag for all cruft. True means enabled.
512 :type enabled: bool
513 :param treeviews: Sequence of `TreeView` object to set cruft state on.
514 """
515 for treeview in treeviews:
516 self._treeview_foreach_set_set_state(treeview, enabled)
517
518 def on_select_all_cruft_activate(self, menuitem):
519 """Select all cruft, both package and other."""
520 self._edit_menuitem_set_select_state(
521 True,
522 self.widgets['unused_treeview'],
523 self.widgets['optimize_treeview'])
524
525 def on_select_all_packages_activate(self, menuitem):
526 """Select all cruft, both package and other."""
527 self._edit_menuitem_set_select_state(
528 True, self.widgets['unused_treeview'])
529
530 def on_select_all_other_activate(self, menuitem):
531 """Select all cruft, both package and other."""
532 self._edit_menuitem_set_select_state(
533 True, self.widgets['optimize_treeview'])
534
535 def on_deselect_all_cruft_activate(self, menuitem):
536 """Select all cruft, both package and other."""
537 self._edit_menuitem_set_select_state(
538 False,
539 self.widgets['unused_treeview'],
540 self.widgets['optimize_treeview'])
541
542 def on_deselect_all_packages_activate(self, menuitem):
543 """Select all cruft, both package and other."""
544 self._edit_menuitem_set_select_state(
545 False, self.widgets['unused_treeview'])
546
547 def on_deselect_all_other_activate(self, menuitem):
548 """Select all cruft, both package and other."""
549 self._edit_menuitem_set_select_state(
550 False, self.widgets['optimize_treeview'])
551
552 def on_do_button_clicked(self, *args):
553 """JFDI, well almost."""
554 self.count = 0
555 response = AreYouSure(self).verify()
556 if not response:
557 return
558 # This can take a long time. Make an asynchronous call to the dbus
559 # service and arrange for it to occasionally provide us with status.
560 # This isn't great ui, but OTOH, the package cruft cleaners themselves
561 # don't provide much granularity, so there's little we can do anyway
562 # without a major rewrite of the plugin architecture.
563 self.working = True
564 glib.timeout_add(150, self.pulse)
565 # Make various ui elements insensitive.
566 self.desensitize()
567 cleanable = [cruft for cruft, ispkg in self.get_cleanable_cruft()]
568 def error(exception):
569 # XXX 2010-05-19 barry: This will (almost?) always be caused by
570 # some other package manager already running, so if we get that
571 # exception, display a (hopefully) useful dialog. I'm not sure
572 # it's very helpful to display the low-level apt exception in a
573 # dialog. `exception` will always be a DBusException; we won't
574 # get the more useful PackageCleanupError.
575 #
576 # We use log.error() instead of log.exception() because we're not
577 # in an exception handler.
578 log.error('%s', exception)
579 CleanupProblem(self).run()
580 self.working = False
581 def reply():
582 pass
583 # Make the asynchronous call because this can take a long time. We'll
584 # get status updates periodically. Note however that even though this
585 # is asynchronous, dbus still expects a response within a certain
586 # amount of time. We have no idea how long it will take to clean up
587 # the cruft though, so just crank the timeout up to some insanely huge
588 # number (of seconds).
589 self.widgets['progressbar_status'].set_text('Authenticating...')
590 self.janitord.clean(cleanable,
591 reply_handler=reply,
592 error_handler=error,
593 # If it takes longer than an hour, we're screwed.
594 timeout=3600)
0595
=== added file 'computerjanitorapp/terminalsize.py'
--- computerjanitorapp/terminalsize.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/terminalsize.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,63 @@
1# terminalsize.py - find size of terminal
2#
3# Copyright (C) 2008-2011 Canonical, Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, version 3 of the License.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17# Inspired by code by Chuck Blake, at
18# http://pdos.csail.mit.edu/~cblake/cls/cls.py, but rewritten in an
19# attempt to clarify things. This code is slightly tricky, so I thought
20# the extra clarity would be worth it.
21
22import fcntl
23import struct
24import termios
25
26
27def get_terminal_size(fd=1):
28 """Return size of terminal attached to the standard output.
29
30 Use ioctl(2) to query a terminal for its size, given a file descriptor
31 attached to the terminal.
32
33 :param fd: Use the given file descriptor.
34 :type fd: int
35 :return: The columns and rows representing the size of the terminal. If
36 this cannot be determined, None is returned for both values.
37 :rtype: 2-tuple
38 """
39 try:
40 # Do the ioctl call. termios.TIOCGWINSZ is the code to query terminal
41 # size (see tty_ioctl(4), at least on Linux). We need to give it a
42 # string of suitable size to use as the input buffer for ioctl.
43 # ioctl() modifies the buffer and returns the modified buffer as its
44 # return value.
45 #
46 # The manual page specifies a struct winsize to be used, which
47 # consists of four unsigned shorts. We use struct.calcsize() to
48 # compute the size of that.
49 #
50 # Note that Blake's original code assumes only the first two shorts in
51 # the struct are used, and that two shorts fit into four bytes, which
52 # is probably true for all the relevant platforms, but is cramped
53 # enough that it makes me feel icky. Thus, I assume less. This will
54 # still break if the contents of the struct change, but since that
55 # would change the system call API, that's unlikely.
56 buflen = struct.calcsize('hhhh')
57 buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * buflen)
58 # ioctl returns a binary buffer that represents the struct
59 # at the C level. We unpack it with struct.unpack.
60 return tuple(struct.unpack('hhhh', buf)[:2])
61 except Exception:
62 # If anything went wrong, we give up and claim we don't know.
63 return None, None
064
=== added directory 'computerjanitorapp/tests'
=== added file 'computerjanitorapp/tests/__init__.py'
=== added file 'computerjanitorapp/tests/test_all.py'
--- computerjanitorapp/tests/test_all.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/tests/test_all.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,29 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test suite for Computer Janitor."""
16
17import unittest
18
19from computerjanitorapp.tests import test_terminalsize
20from computerjanitorapp.tests import test_utilities
21from computerjanitord.tests.test_all import test_suite as cjd_suite
22
23
24def test_suite():
25 suite = unittest.TestSuite()
26 suite.addTests(test_terminalsize.test_suite())
27 suite.addTests(test_utilities.test_suite())
28 suite.addTests(cjd_suite())
29 return suite
030
=== added file 'computerjanitorapp/tests/test_terminalsize.py'
--- computerjanitorapp/tests/test_terminalsize.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/tests/test_terminalsize.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,54 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test calculation of terminal sizes."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'test_suite',
22 ]
23
24
25import os
26import unittest
27
28from computerjanitorapp import terminalsize
29
30
31class TestTerminalSize(unittest.TestCase):
32 """Test calculation of terminal sizes."""
33
34 def test_returns_unknown_when_querying_dev_null(self):
35 fd = os.open("/dev/null", os.O_RDONLY)
36 rows, cols = terminalsize.get_terminal_size(fd)
37 os.close(fd)
38 self.assertEqual(rows, None)
39 self.assertEqual(cols, None)
40
41 def test_returns_two_integers_when_stdout_is_a_terminal(self):
42 # We only run this check if stdout is a terminal.
43 # Unfortunately, there is no sensible way of checking the values.
44 # But that's OK, they're lumberjacks.
45 if os.isatty(1):
46 rows, cols = terminalsize.get_terminal_size(1)
47 self.assertEqual(type(rows), int)
48 self.assertEqual(type(cols), int)
49
50
51def test_suite():
52 suite = unittest.TestSuite()
53 suite.addTests(unittest.makeSuite(TestTerminalSize))
54 return suite
055
=== added file 'computerjanitorapp/tests/test_utilities.py'
--- computerjanitorapp/tests/test_utilities.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/tests/test_utilities.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,70 @@
1# Copyright (C) 2010-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test common utilities."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'test_suite',
22 ]
23
24
25import unittest
26
27from computerjanitorapp.utilities import format_size
28
29
30class TestUtilities(unittest.TestCase):
31 """Test common utilities."""
32
33 def test_format_negative(self):
34 self.assertRaises(AssertionError, format_size, -1)
35
36 def test_format_zero(self):
37 self.assertEqual(format_size(0), '0B')
38
39 def test_format_small(self):
40 self.assertEqual(format_size(500), '500B')
41
42 def test_format_1k(self):
43 self.assertEqual(format_size(1000), '1kB')
44
45 def test_format_smallish(self):
46 self.assertEqual(format_size(500000), '500kB')
47
48 def test_format_1M(self):
49 self.assertEqual(format_size(1000000), '1MB')
50
51 def test_format_mediumish(self):
52 self.assertEqual(format_size(500000000), '500MB')
53
54 def test_format_1G(self):
55 self.assertEqual(format_size(1000000000), '1GB')
56
57 def test_format_bigish(self):
58 self.assertEqual(format_size(500000000000), '500GB')
59
60 def test_format_1T(self):
61 self.assertEqual(format_size(1000000000000), '1TB')
62
63 def test_format_hugish(self):
64 self.assertEqual(format_size(500000000000000), '>1TB')
65
66
67def test_suite():
68 suite = unittest.TestSuite()
69 suite.addTests(unittest.makeSuite(TestUtilities))
70 return suite
071
=== added file 'computerjanitorapp/utilities.py'
--- computerjanitorapp/utilities.py 1970-01-01 00:00:00 +0000
+++ computerjanitorapp/utilities.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,51 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Common utilities."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'format_size',
22 ]
23
24
25from math import log10
26
27
28TABLE = (
29 'B',
30 'kB',
31 'MB',
32 'GB',
33 'TB',
34 )
35
36
37def format_size(bytes):
38 """Format size in bytes.
39
40 :param bytes: Integer size in bytes.
41 :type bytes: integer
42 :return: Formatted size
43 :rtype: string
44 """
45 assert bytes >= 0, 'Cannot have negative sizes'
46 if bytes == 0:
47 return '0B'
48 if bytes > 10**12:
49 return '>1TB'
50 key = divmod(int(log10(bytes)), 3)[0]
51 return '{0}{1}'.format(bytes // 10**(key * 3), TABLE[key])
052
=== added directory 'computerjanitord'
=== renamed directory 'computerjanitord' => 'computerjanitord.moved'
=== added file 'computerjanitord/__init__.py'
=== added file 'computerjanitord/application.py'
--- computerjanitord/application.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/application.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,90 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Application interface for use by plugins.
16
17This primarily makes certain apt functionality available to the plugin.
18"""
19
20from __future__ import absolute_import, unicode_literals
21
22__metaclass__ = type
23__all__ = [
24 'Application',
25 ]
26
27
28import apt
29
30import computerjanitorapp
31
32from computerjanitord.errors import (
33 MissingLandmarkError, NonDownloadableError)
34
35
36_ = computerjanitorapp.setup_gettext()
37
38SYNTAPTIC_PREFERENCES_FILE = '/var/lib/synaptic/preferences'
39# There really isn't anything special about these packages. These really just
40# represent landmarks in the package namespace that we look for to try to
41# judge the sanity of the apt cache. If these are missing, things are really
42# messed up and we can't actually figure out how to continue.
43LANDMARK_PACKAGES = [
44 'dash',
45 'gzip',
46 ]
47
48
49class Application:
50 """Interface for plugins requesting apt actions."""
51
52 def __init__(self, apt_cache=None):
53 """Create the application interface.
54
55 :param apt_cache: Alternative apt cache for testing purposes. When
56 `None` use the default apt cache.
57 """
58 if apt_cache is None:
59 # Use the real apt cache.
60 self.apt_cache = apt.Cache()
61 else:
62 self.apt_cache = apt_cache
63 self.refresh_apt_cache()
64
65 def refresh_apt_cache(self):
66 """Refresh the apt cache.
67
68 This API is used by plugins.
69 """
70 self.apt_cache.open()
71 # For historical purposes, Synaptic has a different way of pinning
72 # packages than apt, so we have to load its preferences file in order
73 # to know what it's pinning.
74 self.apt_cache._depcache.read_pinfile(SYNTAPTIC_PREFERENCES_FILE)
75
76 def verify_apt_cache(self):
77 """Verify that essential packages are available in the apt cache.
78
79 This API is used by plugins.
80
81 :raises MissingLandmarkError: When an essential package is not
82 available.
83 :raises NonDownloadableError: When an essential package cannot be
84 downloaded.
85 """
86 for name in LANDMARK_PACKAGES:
87 if name not in self.apt_cache:
88 raise MissingLandmarkError(name)
89 if not any(v.downloadable for v in self.apt_cache[name].versions):
90 raise NonDownloadableError(name)
091
=== added file 'computerjanitord/authenticator.py'
--- computerjanitord/authenticator.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/authenticator.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,85 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Authentication for Computer Janitor backend services."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'Authenticator',
22 ]
23
24
25import dbus
26
27
28PK_AUTHORITY_BUS_NAME = 'org.freedesktop.PolicyKit1'
29PK_AUTHORITY_OBJECT_PATH = '/org/freedesktop/PolicyKit1/Authority'
30PK_AUTHORITY_INTERFACE = 'org.freedesktop.PolicyKit1.Authority'
31# From the PolicyKit API.
32# http://hal.freedesktop.org/docs/polkit/
33# eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html
34AllowUserInteraction = 0x00000001
35
36
37class Authenticator:
38 """PolicyKit authenticator."""
39
40 def authenticate(self, sender, connection, privilege):
41 """Authenticate with PolicyKit.
42
43 :param sender: The initiator of the action.
44 :param connection: The dbus connection that initiated the action.
45 :param privilege: The privilege being requested.
46 :return: Whether the subject is authorized or not.
47 :rtype: bool
48 """
49 policykit = self._get_policykit_proxy()
50 sender_pid = self._get_sender_pid(connection, sender)
51 # This is the CheckAuthorization() 'subject' structure.
52 subject = (
53 'unix-process', {
54 'pid': sender_pid,
55 'start-time': dbus.UInt64(0),
56 })
57 # No details or cancellation_id needed.
58 details = {'': ''}
59 cancellation_id = ''
60 flags = AllowUserInteraction
61 # CheckAuthorization returns an AuthorizationResult structure, modeled
62 # as a 3-tuple. The only thing we care about though is the boolean
63 # describing whether we got authorized or not.
64 is_authorized, is_challenge, details = policykit.CheckAuthorization(
65 subject, privilege, details, flags, cancellation_id)
66 return is_authorized
67
68 def _get_policykit_proxy(self):
69 """Contact the system bus to get a PolicyKit proxy."""
70 system_bus = dbus.SystemBus()
71 pk_proxy = system_bus.get_object(
72 PK_AUTHORITY_BUS_NAME, PK_AUTHORITY_OBJECT_PATH)
73 return dbus.Interface(pk_proxy, PK_AUTHORITY_INTERFACE)
74
75 def _get_sender_pid(self, connection, sender):
76 """Contact the system bus to get the sender connection PID."""
77 # Since we're going to authorize a Unix process, we need to get the
78 # sender's process id. This is available on the dbus. The
79 # CheckAuthorization() method also requires us to have a start-time,
80 # but it's not clear what the semantics are for that, so we'll just
81 # put a zero there.
82 db_proxy = connection.get_object(
83 dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH, introspect=False)
84 info = dbus.Interface(db_proxy, dbus.BUS_DAEMON_IFACE)
85 return info.GetConnectionUnixProcessID(sender)
086
=== added file 'computerjanitord/collector.py'
--- computerjanitord/collector.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/collector.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,168 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""A cruft collector."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'Collector',
22 ]
23
24
25import os
26import time
27import logging
28
29from computerjanitor import PluginManager
30from computerjanitord.errors import (
31 DuplicateCruftError, NoSuchCruftError, PackageCleanupError)
32from computerjanitord.whitelist import Whitelist
33
34
35log = logging.getLogger('computerjanitor')
36MISSING = object()
37DEFAULT_PLUGINS_DIRS = "/usr/share/computerjanitor/plugins"
38
39# For testing purposes.
40SLEEPY_TIME = float(os.environ.get('COMPUTER_JANITOR_SLEEPY_TIME', '1.0'))
41
42
43class Collector:
44 """A cruft collector."""
45
46 def __init__(self, application, plugin_manager_class=None,
47 whitelist_dirs=None, service=None):
48 """Create a cruft collector.
49
50 :param application: The `Application` class.
51 :type application: This object is used by plugins to access the apt
52 database. It must have an attribute named `apt_cache` and a
53 method named `refresh_apt_cache()`.
54 :param plugin_manager_class: The plugin manager class. If None (the
55 default), then `computerjanitor.PluginManager` is used.
56 :type plugin_manager_class: callable accepting a single argument,
57 which is a sequence of plugin directories.
58 :param whitelist_dirs: Sequence of directories to search for
59 '.whitelist' files. Passed directly to
60 `computerjanitord.whitelist.Whitelist`.
61 :param service: The dbus service; when doing plugin post-cleanup, this
62 will be used to emit a progress signal.
63 """
64 self.application = application
65 self.service = service
66 self.whitelist = Whitelist(whitelist_dirs)
67 # Keep track of cruft and map between the cruft's name and its Cruft
68 # instance. We'll use the latter when cruft cleanup is requested
69 # through the dbus API.
70 self.cruft = None
71 self.cruft_by_name = None
72 # Set up the plugin manager.
73 plugin_path = os.environ.get('COMPUTER_JANITOR_PLUGINS',
74 DEFAULT_PLUGINS_DIRS)
75 plugin_dirs = plugin_path.split(':')
76 if plugin_manager_class is None:
77 plugin_manager_class = PluginManager
78 self.plugin_manager = plugin_manager_class(application, plugin_dirs)
79 self.load()
80
81 def load(self):
82 """Reload all cruft."""
83 self.cruft = []
84 self.cruft_by_name = {}
85 # Ask all the plugins to find their cruft, filtering out whitelisted
86 # cruft.
87 for plugin in self.plugin_manager.get_plugins():
88 for cruft in plugin.get_cruft():
89 if not self.whitelist.is_whitelisted(cruft):
90 # Different plugins can give us duplicate cruft names,
91 # however the Cruft class better be the same, otherwise we
92 # won't actually know how to map the name back to a cruft
93 # instance for proper cleanup.
94 if cruft.get_name() in self.cruft_by_name:
95 my_cruft = self.cruft_by_name[cruft.get_name()]
96 if cruft.__class__ is my_cruft.__class__:
97 # We only need one instance of this cruft.
98 continue
99 else:
100 raise DuplicateCruftError(cruft.get_name())
101 #print ' ', cruft.get_name()
102 self.cruft.append(cruft)
103 self.cruft_by_name[cruft.get_name()] = cruft
104
105 def clean(self, names, dry_run=False):
106 """Clean up the named cruft.
107
108 :param names: The names of the cruft to clean up.
109 :type names: list of strings
110 :param dry_run: Flag indicating whether to do permanent changes.
111 :type dry_run: bool
112 """
113 for name in names:
114 # Ensure that all named cruft is known.
115 cruft = self.cruft_by_name.get(name, MISSING)
116 if cruft is MISSING:
117 log.error('No such cruft: {0}'.format(name))
118 raise NoSuchCruftError(name)
119 # Ensure that the cruft is not being ignored.
120 if cruft in self.service.state.ignore:
121 log.error('Skipping ignored cruft: {0}'.format(name))
122 continue
123 log.info('cleaning cruft{1}: {0}'.format(
124 cruft.get_name(), ('[X]' if dry_run else '[Y]')))
125 if not dry_run:
126 try:
127 cruft.cleanup()
128 except:
129 log.exception('cruft.cleanup(): {0}'.format(name))
130 raise
131 # Do plugin-specific post-cleanup.
132 for plugin in self.plugin_manager.get_plugins():
133 log.info('post-cleanup: {0}'.format(plugin))
134 if self.service is not None:
135 # Notify the client that we're not done yet.
136 #
137 # 2010-02-09 barry: this actually kind of sucks because the
138 # granularity is too coarse. Some plugins will post_cleanup()
139 # very quickly, others will take a long time. Unfortunately,
140 # the computerjanitor.Plugin API doesn't support a more
141 # granular feedback. Plugin.get_plugin(..., callback=foo)
142 # doesn't really cut it because that only gets called during
143 # get_plugins().
144 self.service.cleanup_status(plugin.__class__.__name__)
145 if dry_run:
146 # For testing purposes.
147 time.sleep(SLEEPY_TIME)
148 else:
149 try:
150 plugin.post_cleanup()
151 except SystemError as error:
152 # apt will raise a SystemError if some other package
153 # manager is already running. Turn this into a dbus
154 # derived exception so the client will be properly
155 # informed.
156 log.exception(
157 'plugin.post_cleanup(): {0}'.format(plugin))
158 raise PackageCleanupError(str(error))
159 except:
160 log.exception(
161 'plugin.post_cleanup(): {0}'.format(plugin))
162 raise
163 # Now we're done.
164 if self.service is not None:
165 self.service.cleanup_status('')
166 # Reload list of crufts.
167 self.application.refresh_apt_cache()
168 self.load()
0169
=== added directory 'computerjanitord/data'
=== added file 'computerjanitord/data/com.ubuntu.ComputerJanitor.conf'
--- computerjanitord/data/com.ubuntu.ComputerJanitor.conf 1970-01-01 00:00:00 +0000
+++ computerjanitord/data/com.ubuntu.ComputerJanitor.conf 2011-02-28 19:00:59 +0000
@@ -0,0 +1,15 @@
1<!DOCTYPE busconfig PUBLIC
2 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
3 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
4<busconfig>
5 <policy user="root">
6 <allow own="com.ubuntu.ComputerJanitor"/>
7 </policy>
8
9 <policy context="default">
10 <allow send_interface="com.ubuntu.ComputerJanitor"/>
11 <allow receive_interface="com.ubuntu.ComputerJanitor"
12 receive_sender="com.ubuntu.ComputerJanitor"/>
13 </policy>
14
15</busconfig>
016
=== added file 'computerjanitord/data/com.ubuntu.ComputerJanitor.service'
--- computerjanitord/data/com.ubuntu.ComputerJanitor.service 1970-01-01 00:00:00 +0000
+++ computerjanitord/data/com.ubuntu.ComputerJanitor.service 2011-02-28 19:00:59 +0000
@@ -0,0 +1,4 @@
1[D-BUS Service]
2Name=com.ubuntu.ComputerJanitor
3Exec=/usr/share/computerjanitor/janitord
4User=root
05
=== added file 'computerjanitord/data/com.ubuntu.computerjanitor.policy'
--- computerjanitord/data/com.ubuntu.computerjanitor.policy 1970-01-01 00:00:00 +0000
+++ computerjanitord/data/com.ubuntu.computerjanitor.policy 2011-02-28 19:00:59 +0000
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE policyconfig PUBLIC
3 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
4 "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
5<policyconfig>
6
7 <vendor>ComputerJanitor</vendor>
8 <vendor_url>https://launchpad.net/computer-janitor</vendor_url>
9
10 <action id="com.ubuntu.computerjanitor.updatesystem">
11 <description>Clean up packages that are no longer necessary</description>
12 <message>Removing unused packages requires authentication</message>
13 <defaults>
14 <allow_inactive>no</allow_inactive>
15 <allow_active>auth_admin_keep</allow_active>
16 </defaults>
17 </action>
18
19</policyconfig>
020
=== added file 'computerjanitord/errors.py'
--- computerjanitord/errors.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/errors.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,95 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Exceptions for the Computer Janitor daemon."""
16
17
18from __future__ import absolute_import, unicode_literals
19
20__metaclass__ = type
21__all__ = [
22 'DuplicateCruftError',
23 'LandmarkPackageError',
24 'MissingLandmarkError',
25 'NoSuchCruftError',
26 'NonDownloadableError',
27 'PermissionDeniedError',
28 ]
29
30
31import dbus
32import computerjanitor
33
34
35class DBusServiceBaseException(computerjanitor.Exception, dbus.DBusException):
36 """Base class exception for the Computer Janitor DBus service."""
37
38
39class PermissionDeniedError(DBusServiceBaseException):
40 """Permission denied by policy."""
41
42
43class CruftError(DBusServiceBaseException):
44 """Cruft exceptions passed back to dbus client."""
45
46 _errmsg = None
47
48 def __init__(self, cruft_name):
49 self.cruft_name = cruft_name
50
51 def __str__(self):
52 return self._errmsg.format(self)
53
54
55class DuplicateCruftError(CruftError):
56 """Duplicate cruft name with different cleanup."""
57
58 _errmsg = 'Duplicate cruft with different cleanup: {0.cruft_name}'
59
60
61class NoSuchCruftError(CruftError):
62 """There is no cruft by the given name."""
63
64 _errmsg = 'No such cruft: {0.cruft_name}'
65
66
67class LandmarkPackageError(DBusServiceBaseException):
68 """Base class for problems with the landmark packages."""
69
70 _errmsg = None
71
72 def __init__(self, package):
73 self.package = package
74
75 def __str__(self):
76 # gettext translation needs to be called at run time.
77 return self._errmsg.format(self)
78
79
80class MissingLandmarkError(LandmarkPackageError):
81 """A landmark package could not be found."""
82
83 _errmsg = 'Landmark package {0.package} is missing'
84
85
86class NonDownloadableError(LandmarkPackageError):
87 """A landmark package is not downloadable."""
88
89 _errmsg = 'Landmark package {0.package} is not downloadable'
90
91
92class PackageCleanupError(DBusServiceBaseException):
93 """Could not complete plugin post-cleanup."""
94
95 _errmsg = 'Post-cleanup exception'
096
=== added file 'computerjanitord/main.py'
--- computerjanitord/main.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/main.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,120 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Main entry point for Computer Janitor dbus daemon."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'main',
22 ]
23
24
25import os
26import gobject
27import logging
28import argparse
29import warnings
30import dbus.mainloop.glib
31import logging.handlers
32
33from computerjanitorapp import __version__, setup_gettext
34from computerjanitord.service import Service
35
36_ = setup_gettext()
37
38
39# 2010-02-09 barry: computerjanitor.package_cruft has a DeprecationWarning,
40# but that really needs to be fixed in that package, which in turn needs to be
41# ripped out of update-manager.
42warnings.filterwarnings('ignore', category=DeprecationWarning,
43 module='computerjanitor.package_cruft')
44
45
46# As per aptdaemon, when run as a dbus service, we must add the necessary
47# paths to $PATH.
48REQUIRED_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
49
50
51class Options:
52 """Command line options."""
53
54 def __init__(self):
55 self.parser = argparse.ArgumentParser(
56 description=_("""\
57 Computer janitor dbus daemon. You must run this daemon as root.
58 """))
59 self.parser.add_argument(
60 '--version', action='version',
61 version=_('Computer Janitor dbus daemon {version}'.format(
62 version=__version__)))
63 self.parser.add_argument(
64 '-n', '--dry-run', action='store_true',
65 help=_("""\
66 Only pretend to do anything permanent. This is useful for testing
67 and debugging."""))
68 self.parser.add_argument(
69 '-f', '--state-file', metavar='FILE',
70 help=_('Store ignored state in FILE instead of the default.'))
71 self.arguments = self.parser.parse_args()
72
73
74class ASCIIFormatter(logging.Formatter):
75 """Force the log messages to ASCII."""
76 def format(self, record):
77 message = logging.Formatter.format(self, record)
78 return message.encode('ascii', 'replace')
79
80
81def main():
82 """Main entry point."""
83 # Set up logging.
84 if os.environ.get('COMPUTER_JANITOR_DEBUG') is not None:
85 level = logging.DEBUG
86 else:
87 level = logging.INFO
88 # When running under dbus, $PATH will not be set. However it must be in
89 # order to find dpkg(1) and friends. Hard code it in the same way that
90 # aptdaemon does.
91 if os.environ.get('PATH') is None:
92 os.putenv('PATH', REQUIRED_PATH)
93 logging.basicConfig(level=level)
94 log = logging.getLogger('computerjanitor')
95 # SysLogHandler does not recognize unicode arguments.
96 syslog = logging.handlers.SysLogHandler(b'/dev/log')
97 # syslog will provide a timestamp.
98 formatter = ASCIIFormatter('computerjanitord:%(levelname)s: %(message)s')
99 syslog.setFormatter(formatter)
100 syslog.setLevel(level)
101 log.addHandler(syslog)
102 options = Options()
103 # Ensure that we're running as root. Do this here instead of ../janitord
104 # so that we can still run 'janitord --version' and 'janitord --help' as
105 # non-root.
106 if os.getuid() != 0:
107 options.parser.error('You must run this as root')
108 # No return.
109 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
110 # pyflakes complains about this line, but we need it anyway to keep the
111 # dbus service object referenced during the running of the main loop.
112 server = Service(options)
113 try:
114 gobject.MainLoop().run()
115 except KeyboardInterrupt:
116 pass
117
118
119if __name__ == '__main__':
120 main()
0121
=== added file 'computerjanitord/service.py'
--- computerjanitord/service.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/service.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,288 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""dbus service for cleaning up crufty packages that are no longer needed."""
16
17
18from __future__ import absolute_import, unicode_literals
19
20__metaclass__ = type
21__all__ = [
22 'Service',
23 ]
24
25
26import atexit
27import logging
28
29import glib
30import dbus.service
31
32from computerjanitord.application import Application
33from computerjanitord.authenticator import Authenticator
34from computerjanitord.collector import Collector
35from computerjanitord.errors import NoSuchCruftError, PermissionDeniedError
36from computerjanitord.state import State, DEFAULT_STATE_FILE
37
38
39log = logging.getLogger('computerjanitor')
40MISSING = object()
41
42DBUS_INTERFACE_NAME = 'com.ubuntu.ComputerJanitor'
43PRIVILEGE = 'com.ubuntu.computerjanitor.updatesystem'
44
45
46class Service(dbus.service.Object):
47 """Backend dbus service that handles removing crufty packages."""
48
49 def __init__(self, options):
50 """Create the dbus service.
51
52 :param options: The command line options class.
53 :type options: `Options`
54 """
55 self.dry_run = options.arguments.dry_run
56 self.state_file = (DEFAULT_STATE_FILE
57 if options.arguments.state_file is None
58 else options.arguments.state_file)
59 self.application = Application()
60 self.state = State()
61 self.state.load(self.state_file)
62 self.collector = Collector(self.application, service=self)
63 self.authenticator = Authenticator()
64 bus_name = dbus.service.BusName(
65 DBUS_INTERFACE_NAME, bus=dbus.SystemBus())
66 dbus.service.Object.__init__(self, bus_name, '/')
67 # We can't use the decorator because that doesn't work with methods;
68 # self doesn't get passed to the handler.
69 atexit.register(self._exit_handler)
70
71 def _exit_handler(self):
72 """Ensure that the state file is saved at exit."""
73 if not self.dry_run:
74 self.state.save(self.state_file)
75
76 def _authenticate(self, sender, connection):
77 """Authenticate via PolicyKit.
78
79 :param sender: The dbus client sender.
80 :param connection: The dbus client connection.
81 :raises PermissionDeniedError: when the authentication fails.
82 """
83 if not self.authenticator.authenticate(sender, connection, PRIVILEGE):
84 log.error('Permission denied: {0} for {1} on {2}'.format(
85 PRIVILEGE, sender, connection))
86 raise PermissionDeniedError(PRIVILEGE)
87 log.debug('Permission granted: {0} for {1} on {2}'.format(
88 PRIVILEGE, sender, connection))
89
90 @dbus.service.method(DBUS_INTERFACE_NAME,
91 out_signature='as')
92 def find(self):
93 """Find all the non-whitelisted cruft on the system.
94
95 Because this is a read-only interface it does not need authorization
96 to be called.
97
98 :return: A list of matching cruft names.
99 """
100 return list(cruft.get_name() for cruft in self.collector.cruft)
101
102 @dbus.service.method(DBUS_INTERFACE_NAME,
103 out_signature='b')
104 def find_async(self):
105 """Find all the non-whitelisted cruft on the system, asynchronously.
106
107 Because this is a read-only interface it does not need authorization
108 to be called. Use this method when you can't wait for the cruft
109 searching process to complete, since it might take some time. To
110 respond when the cruft has been found, set up a `find_finished` signal
111 handler.
112
113 :return: True
114 """
115 glib.timeout_add_seconds(1, self._find_async)
116 return True
117
118 def _find_async(self):
119 """Find all cruft asynchronously and call the signal handler."""
120 cruft = list(cruft.get_name() for cruft in self.collector.cruft)
121 self.find_finished(cruft)
122 # Only call the callback once.
123 return False
124
125 @dbus.service.signal(DBUS_INTERFACE_NAME, signature='as')
126 def find_finished(self, cruft):
127 """dbus signal called when `_find_async()` completes."""
128 log.debug('find_finished: {0}'.format(cruft))
129
130 @dbus.service.method(DBUS_INTERFACE_NAME,
131 out_signature='as',
132 # Must wrap these in str() because Python < 2.6.5
133 # does not like unicode keyword arguments.
134 sender_keyword=str('sender'),
135 connection_keyword=str('connection'))
136 def load(self, sender=None, connection=None):
137 """Load the state file."""
138 self._authenticate(sender, connection)
139 self.state.load(self.state_file)
140 return list(self.state.ignore)
141
142 @dbus.service.method(DBUS_INTERFACE_NAME,
143 # Must wrap these in str() because Python < 2.6.5
144 # does not like unicode keyword arguments.
145 sender_keyword=str('sender'),
146 connection_keyword=str('connection'))
147 def save(self, sender=None, connection=None):
148 """Save the state file."""
149 self._authenticate(sender, connection)
150 if not self.dry_run:
151 self.state.save(self.state_file)
152
153 @dbus.service.method(DBUS_INTERFACE_NAME,
154 in_signature='s',
155 # Must wrap these in str() because Python < 2.6.5
156 # does not like unicode keyword arguments.
157 sender_keyword=str('sender'),
158 connection_keyword=str('connection'))
159 def ignore(self, name, sender=None, connection=None):
160 """Ignore the named cruft.
161
162 :param name: The name of the cruft to ignore.
163 :type filename: string
164 """
165 # Make sure this is known cruft first.
166 cruft = self.collector.cruft_by_name.get(name, MISSING)
167 if cruft is MISSING:
168 log.error('ignore(): No such cruft: {0}'.format(name))
169 raise NoSuchCruftError(name)
170 self._authenticate(sender, connection)
171 if not self.dry_run:
172 self.state.ignore.add(name)
173
174 @dbus.service.method(DBUS_INTERFACE_NAME,
175 in_signature='s',
176 # Must wrap these in str() because Python < 2.6.5
177 # does not like unicode keyword arguments.
178 sender_keyword=str('sender'),
179 connection_keyword=str('connection'))
180 def unignore(self, name, sender=None, connection=None):
181 """Unignore the named cruft.
182
183 :param name: The name of the cruft to unignore.
184 :type filename: string
185 """
186 cruft = self.collector.cruft_by_name.get(name, MISSING)
187 if cruft is MISSING:
188 log.error('ignore(): No such cruft: {0}'.format(name))
189 raise NoSuchCruftError(name)
190 self._authenticate(sender, connection)
191 if not self.dry_run:
192 # Don't worry if we're already not ignoring the cruft (i.e. don't
193 # raise a KeyError here if 'name' is not in the set).
194 self.state.ignore.discard(name)
195
196 @dbus.service.method(DBUS_INTERFACE_NAME,
197 out_signature='as')
198 def ignored(self):
199 """Return the list of ignored cruft.
200
201 :return: The names of the ignored cruft.
202 :rtype: list of strings
203 """
204 return list(self.state.ignore)
205
206 @dbus.service.method(DBUS_INTERFACE_NAME,
207 in_signature='s',
208 out_signature='s')
209 def get_description(self, name):
210 """Return the description of the named cruft.
211
212 :param name: The cruft name.
213 :type name: string
214 :return: The description of the cruft.
215 :rtype: string
216 """
217 cruft = self.collector.cruft_by_name.get(name, MISSING)
218 if cruft is MISSING:
219 log.error('get_description(): No such cruft: {0}'.format(name))
220 raise NoSuchCruftError(name)
221 return cruft.get_description()
222
223 @dbus.service.method(DBUS_INTERFACE_NAME,
224 in_signature='s',
225 out_signature='s')
226 def get_shortname(self, name):
227 """Return the short name of the named cruft.
228
229 :param name: The cruft name.
230 :type name: string
231 :return: The short nameof the cruft.
232 :rtype: string
233 """
234 cruft = self.collector.cruft_by_name.get(name, MISSING)
235 if cruft is MISSING:
236 log.error('get_shortname(): No such cruft: {0}'.format(name))
237 raise NoSuchCruftError(name)
238 return cruft.get_shortname()
239
240 @dbus.service.method(DBUS_INTERFACE_NAME,
241 in_signature='s',
242 out_signature='st')
243 def get_details(self, name):
244 """Return some extra details about the named cruft.
245
246 :param name: The cruft name.
247 :type name: string
248 :return: Some extra details about the named cruft, specifically its
249 'type' and the amount of disk space it consumes. The type is
250 simply the name of the cruft instance's class.
251 :rtype: string, uint64
252 """
253 cruft = self.collector.cruft_by_name.get(name, MISSING)
254 if cruft is MISSING:
255 log.error('get_shortname(): No such cruft: {0}'.format(name))
256 raise NoSuchCruftError(name)
257 return cruft.__class__.__name__, cruft.get_disk_usage()
258
259 @dbus.service.method(DBUS_INTERFACE_NAME,
260 in_signature='as', # array of strings
261 # Must wrap these in str() because Python < 2.6.5
262 # does not like unicode keyword arguments.
263 sender_keyword=str('sender'),
264 connection_keyword=str('connection'))
265 def clean(self, names, sender=None, connection=None):
266 """Clean the named crufts.
267
268 :param names: The names of the cruft to clean.
269 :type names: list of strings
270 """
271 self._authenticate(sender, connection)
272 self.collector.clean(names, self.dry_run)
273
274 @dbus.service.signal(DBUS_INTERFACE_NAME,
275 signature='s')
276 def cleanup_status(self, cruft):
277 """Signal cleanup status.
278
279 This signal is used to incrementally inform clients that some cleanup
280 work is being done. It is called at the beginning of the cleanup
281 process and after each plugin has completed its `post_cleanup()`
282 method.
283
284 :param done: The name of the next piece of cruft to be cleaned up, or
285 the empty string when there's nothing left to do.
286 :type done: string
287 """
288 log.debug('cleanup_status: {0}'.format(cruft))
0289
=== added file 'computerjanitord/state.py'
--- computerjanitord/state.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/state.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,92 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Maintaining package ignored state."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'DEFAULT_STATE_FILE',
22 'State',
23 ]
24
25
26import ConfigParser
27import textwrap
28
29
30DEFAULT_STATE_FILE = '/var/lib/computer-janitor/state.dat'
31
32
33class State:
34 """Maintain the state of cruft which should be ignored.
35
36 The file's format is a `ConfigParser` style .ini file. Each section is a
37 cruft's `.get_name()` value and contains a single boolean setting
38 `ignore`. If true, then the cruft is ignored.
39
40 For backward compatibility purposes, the setting `enabled` is also
41 recognized (on read only). If `enabled` is false then the cruft is
42 ignored.
43 """
44
45 def __init__(self):
46 self.ignore = set()
47
48 def load(self, filename):
49 """Load ignored state from a file.
50
51 This reset any previously determined state and re-initializes it with
52 the state stored in the file.
53
54 :param filename: The file to load.
55 :type filename: string
56 """
57 parser = ConfigParser.ConfigParser()
58 parser.read(filename)
59 # Reset the set of ignored packages.
60 self.ignore = set()
61 for cruft_name in parser.sections():
62 # For backwards compatibility, recognize both the 'ignore' setting
63 # and the 'enabled' setting. We only write the former.
64 try:
65 ignore = parser.getboolean(cruft_name, 'ignore')
66 except ConfigParser.NoOptionError:
67 try:
68 ignore = not parser.getboolean(cruft_name, 'enabled')
69 except ConfigParser.NoOptionError:
70 # No other settings are recognized.
71 ignore = False
72 if ignore:
73 self.ignore.add(cruft_name)
74
75 def save(self, filename):
76 """Save the ignored state to a file.
77
78 Only the packages being ignored are stored to the file, and writing
79 overwrites the previous contents of the file.
80
81 :param filename: The file to load.
82 :type filename: string
83 """
84 # It's easier just to write the .ini file directly instead of using
85 # the ConfigParser interface. This way we can guarantee sort order
86 # and can automatically cull unignored packages from the file.
87 with open(filename, 'w') as fp:
88 for cruft_name in self.ignore:
89 print >> fp, textwrap.dedent("""\
90 [{0}]
91 ignore: true
92 """.format(cruft_name))
093
=== added directory 'computerjanitord/tests'
=== added file 'computerjanitord/tests/__init__.py'
=== added directory 'computerjanitord/tests/data'
=== added file 'computerjanitord/tests/data/empty'
=== added directory 'computerjanitord/tests/data/etc'
=== added directory 'computerjanitord/tests/data/etc/apt'
=== added file 'computerjanitord/tests/data/etc/apt/sources.list'
--- computerjanitord/tests/data/etc/apt/sources.list 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/data/etc/apt/sources.list 2011-02-28 19:00:59 +0000
@@ -0,0 +1,1 @@
1deb http://archive.ubuntu.com/ubuntu intrepid main restricted
02
=== added directory 'computerjanitord/tests/data/var'
=== added directory 'computerjanitord/tests/data/var/cache'
=== added directory 'computerjanitord/tests/data/var/cache/apt'
=== added directory 'computerjanitord/tests/data/var/cache/apt/archives'
=== added directory 'computerjanitord/tests/data/var/cache/apt/archives/partial'
=== added directory 'computerjanitord/tests/data/var/lib'
=== added directory 'computerjanitord/tests/data/var/lib/apt'
=== added directory 'computerjanitord/tests/data/var/lib/apt/lists'
=== added file 'computerjanitord/tests/data/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_intrepid_restricted_binary-i386_Packages'
--- computerjanitord/tests/data/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_intrepid_restricted_binary-i386_Packages 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/data/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_intrepid_restricted_binary-i386_Packages 2011-02-28 19:00:59 +0000
@@ -0,0 +1,54 @@
1Package: dash
2Priority: required
3Section: shells
4Installed-Size: 236
5Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
6Original-Maintainer: Gerrit Pape <pape@smarden.org>
7Architecture: all
8Version: 0.5.5.1-3ubuntu1
9Depends: debianutils (>= 2.15), dpkg (>= 1.15.0)
10Pre-Depends: libc6 (>= 2.11~20100104-0ubuntu2)
11Filename: pool/main/d/dash/dash_0.5.5.1-3ubuntu1_all.deb
12Size: 104190
13MD5sum: a7f08fe3ee941d06c0d98e5e99c02190
14SHA1: d2dda78f9a6f82c58c01d34f18b94aebfcb33f19
15SHA256: 69709747f854ac1bd671dff37b47c18e589b095ecb8f3116beaed7fc0eeb657e
16Description: POSIX-compliant shell
17 The Debian Almquist Shell (dash) is a POSIX-compliant shell derived
18 from ash.
19 .
20 Since it executes scripts faster than bash, and has fewer library
21 dependencies (making it more robust against software or hardware
22 failures), it is used as the default system shell on Debian systems.
23Homepage: http://gondor.apana.org.au/~herbert/dash/
24Bugs: https://bugs.launchpad.net/ubuntu/+filebug
25Origin: Ubuntu
26Supported: 5y
27Task: minimal
28
29Package: gzip
30Essential: yes
31Priority: required
32Section: utils
33Installed-Size: 284
34Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
35Original-Maintainer: Bdale Garbee <bdale@gag.com>
36Architecture: all
37Version: 1.3.12-9ubuntu1
38Pre-Depends: libc6 (>= 2.4)
39Suggests: less
40Filename: pool/main/g/gzip/gzip_1.3.12-9ubuntu1_all.deb
41Size: 107030
42MD5sum: f64beb93d2d1a3348cfc47f1fd176ee1
43SHA1: 3dd3e56f551fb85ba2ad385df463adeff1fff2d9
44SHA256: 2545f0a28514535006adf9ee8576ca3be2aa6da3d890047f92eeda18c0e3aa57
45Description: GNU compression utilities
46 This package provides the standard GNU file compression utilities, which
47 are also the default compression tools for Debian. They typically operate
48 on files with names ending in '.gz', but can also decompress files ending
49 in '.Z' created with 'compress'.
50Bugs: https://bugs.launchpad.net/ubuntu/+filebug
51Origin: Ubuntu
52Supported: 5y
53Task: minimal
54
055
=== added directory 'computerjanitord/tests/data/var/lib/apt/lists/partial'
=== added directory 'computerjanitord/tests/data/var/lib/dpkg'
=== added file 'computerjanitord/tests/data/var/lib/dpkg/status'
--- computerjanitord/tests/data/var/lib/dpkg/status 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/data/var/lib/dpkg/status 2011-02-28 19:00:59 +0000
@@ -0,0 +1,28 @@
1Package: dash-nodownload
2Priority: required
3Section: shells
4Installed-Size: 236
5Maintainer: Ubuntu Core Developers <ubuntu-devel-discuss@lists.ubuntu.com>
6Original-Maintainer: Gerrit Pape <pape@smarden.org>
7Architecture: all
8Version: 0.5.5.1-3ubuntu1
9Depends: debianutils (>= 2.15), dpkg (>= 1.15.0)
10Pre-Depends: libc6 (>= 2.11~20100104-0ubuntu2)
11Filename: pool/main/d/dash/dash_0.5.5.1-3ubuntu1_all.deb
12Size: 104190
13MD5sum: a7f08fe3ee941d06c0d98e5e99c02190
14SHA1: d2dda78f9a6f82c58c01d34f18b94aebfcb33f19
15SHA256: 69709747f854ac1bd671dff37b47c18e589b095ecb8f3116beaed7fc0eeb657e
16Description: POSIX-compliant shell
17 The Debian Almquist Shell (dash) is a POSIX-compliant shell derived
18 from ash.
19 .
20 Since it executes scripts faster than bash, and has fewer library
21 dependencies (making it more robust against software or hardware
22 failures), it is used as the default system shell on Debian systems.
23Homepage: http://gondor.apana.org.au/~herbert/dash/
24Bugs: https://bugs.launchpad.net/ubuntu/+filebug
25Origin: Ubuntu
26Supported: 5y
27Task: minimal
28
029
=== added file 'computerjanitord/tests/test_all.py'
--- computerjanitord/tests/test_all.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/test_all.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,41 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test suite for Computer Janitor daemon (dbus backend)."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'test_suite',
22 ]
23
24
25import unittest
26
27from computerjanitord.tests import test_application
28from computerjanitord.tests import test_authenticator
29from computerjanitord.tests import test_collector
30from computerjanitord.tests import test_state
31from computerjanitord.tests import test_whitelist
32
33
34def test_suite():
35 suite = unittest.TestSuite()
36 suite.addTests(test_application.test_suite())
37 suite.addTests(test_authenticator.test_suite())
38 suite.addTests(test_collector.test_suite())
39 suite.addTests(test_state.test_suite())
40 suite.addTests(test_whitelist.test_suite())
41 return suite
042
=== added file 'computerjanitord/tests/test_application.py'
--- computerjanitord/tests/test_application.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/test_application.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,105 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test the plugin application interfaces."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'ApplicationTestSetupMixin',
22 'test_suite',
23 ]
24
25
26import os
27import apt
28import apt_pkg
29import unittest
30import warnings
31import pkg_resources
32
33from contextlib import contextmanager
34
35import computerjanitord.application
36
37from computerjanitord.application import (
38 Application, MissingLandmarkError, NonDownloadableError)
39
40
41@contextmanager
42def landmarks(*packages):
43 # Hack the module global list of known landmark packages.
44 old_landmarks = computerjanitord.application.LANDMARK_PACKAGES[:]
45 computerjanitord.application.LANDMARK_PACKAGES[:] = packages
46 yield
47 computerjanitord.application.LANDMARK_PACKAGES[:] = old_landmarks
48
49
50class MockCruft:
51 def __init__(self, name):
52 self.name = name
53
54 def get_name(self):
55 warnings.warn('.get_name() is deprecated; use .name',
56 DeprecationWarning)
57 return self.name
58
59
60class ApplicationTestSetupMixin:
61 """Set up an `Application` instance with test data in its apt_cache."""
62
63 def setUp(self):
64 self.data_dir = os.path.abspath(
65 pkg_resources.resource_filename('computerjanitord.tests', 'data'))
66 # Make the test insensitive to the platform's architecture.
67 apt_pkg.Config.set('APT::Architecture', 'i386')
68 self.cache = apt.Cache(rootdir=self.data_dir)
69 self.app = Application(self.cache)
70
71 def tearDown(self):
72 # Clear the cache.
73 cache_dir = os.path.join(self.data_dir, 'var', 'cache', 'apt')
74 for filename in os.listdir(cache_dir):
75 if filename.endswith('.bin'):
76 os.remove(os.path.join(cache_dir, filename))
77
78
79class TestApplication(unittest.TestCase, ApplicationTestSetupMixin):
80 """Test the `Application` interface."""
81
82 def setUp(self):
83 ApplicationTestSetupMixin.setUp(self)
84
85 def tearDown(self):
86 ApplicationTestSetupMixin.tearDown(self)
87
88 def test_verify_apt_cache_good_path(self):
89 # All essential packages are in the cache by default.
90 self.assertEqual(self.app.verify_apt_cache(), None)
91
92 def test_verify_apt_cache_with_nondownloadable_landmark(self):
93 # Test that a missing landmark file causes an exception.
94 with landmarks('gzip', 'dash-nodownload'):
95 self.assertRaises(NonDownloadableError, self.app.verify_apt_cache)
96
97 def test_verify_apt_cache_with_missing_landmark(self):
98 with landmarks('gzip', 'dash', 'i-am-not-here'):
99 self.assertRaises(MissingLandmarkError, self.app.verify_apt_cache)
100
101
102def test_suite():
103 suite = unittest.TestSuite()
104 suite.addTests(unittest.makeSuite(TestApplication))
105 return suite
0106
=== added file 'computerjanitord/tests/test_authenticator.py'
--- computerjanitord/tests/test_authenticator.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/test_authenticator.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,97 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test the authenticator."""
16
17import unittest
18
19from computerjanitord.authenticator import Authenticator
20
21SUCCESS_PID = 801
22FAILURE_PID = 999
23
24AUTHENTICATED_USER = 'desktop-user'
25IMPOSTER_USER = 'imposter'
26EXPECTED_PRIVILEGE = 'com.ubuntu.computerjanitor.cleanpackages'
27BOGUS_PRIVILEGE = 'com.example.evil-corp.killsystem'
28
29
30class MockPolicyKit(object):
31 """Mock the PolicyKit's CheckAuthorization() method."""
32
33 def CheckAuthorization(self, subject, privilege, details, flags,
34 cancellation_id):
35 """See `policykit.CheckAuthorization()`.
36
37 :return: (is_authorized, is_challenge, details)
38 """
39 if privilege == BOGUS_PRIVILEGE:
40 return False, False, ''
41 assert isinstance(subject, tuple) and len(subject) == 2, (
42 'subject is not a 2-tuple')
43 assert subject[0] == 'unix-process', 'Badly formed subject'
44 assert isinstance(subject[1], dict), 'Badly formed subject details'
45 assert subject[1]['start-time'] == 0, 'subject missing start-time'
46 if subject[1]['pid'] == SUCCESS_PID:
47 return True, False, ''
48 else:
49 return False, False, ''
50
51
52class TestableAuthenticator(Authenticator):
53 """See `Authenticator`."""
54
55 def _get_policykit_proxy(self):
56 """See `Authenticator`."""
57 return MockPolicyKit()
58
59 def _get_sender_pid(self, connection, sender):
60 """See `Authenticator`."""
61 if sender == AUTHENTICATED_USER:
62 return SUCCESS_PID
63 else:
64 return FAILURE_PID
65
66
67class TestAuthenticator(unittest.TestCase):
68 """Tests of the PolicyKit authenticator."""
69
70 def setUp(self):
71 """See `unittest.TestCase`."""
72 self.authenticator = TestableAuthenticator()
73 self.connection = object()
74
75 def tearDown(self):
76 """See `unittest.TestCase`."""
77
78 def test_good_path(self):
79 # Test for successful authentication.
80 self.assertTrue(self.authenticator.authenticate(
81 AUTHENTICATED_USER, self.connection, EXPECTED_PRIVILEGE))
82
83 def test_bogus_privilege(self):
84 # Test for bogus privilege fails.
85 self.assertFalse(self.authenticator.authenticate(
86 AUTHENTICATED_USER, self.connection, BOGUS_PRIVILEGE))
87
88 def test_unauthorized(self):
89 # Test for some imposter not being able to authenticate.
90 self.assertFalse(self.authenticator.authenticate(
91 IMPOSTER_USER, self.connection, EXPECTED_PRIVILEGE))
92
93
94def test_suite():
95 suite = unittest.TestSuite()
96 suite.addTests(unittest.makeSuite(TestAuthenticator))
97 return suite
098
=== added file 'computerjanitord/tests/test_collector.py'
--- computerjanitord/tests/test_collector.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/test_collector.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,225 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test the cruft collector."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'test_suite',
22 ]
23
24
25import os
26import shutil
27import apt_pkg
28import tempfile
29import unittest
30import pkg_resources
31import multiprocessing
32
33from computerjanitor.plugin import Plugin
34from computerjanitord.collector import Collector
35from computerjanitord.errors import DuplicateCruftError, PackageCleanupError
36from computerjanitord.tests.test_application import ApplicationTestSetupMixin
37
38
39LOCK_FILE = pkg_resources.resource_filename(
40 'computerjanitord.tests',
41 os.path.join('data', 'var', 'lib', 'dpkg', 'status'))
42
43
44class MockCruft:
45 """Mock cruft that supports the required `get_name()` interface."""
46
47 def __init__(self, name):
48 self.name = name
49
50 def get_name(self):
51 return self.name
52
53
54class MockPlugin(Plugin):
55 cruft_class = MockCruft
56
57 def __init__(self, prefix, shortnames):
58 super(MockPlugin, self).__init__()
59 self.prefix = prefix
60 self.shortnames = shortnames
61
62 def get_cruft(self):
63 for shortname in self.shortnames:
64 yield self.cruft_class('{0}:{1}'.format(self.prefix, shortname))
65
66
67class SawAppPlugin(Plugin):
68 """All this plugin does is set a marker attribute on the `Application`.
69
70 This proves that access to the application through the plugin works.
71 """
72 def get_cruft(self):
73 self.app.saw_app_plugin = True
74 return []
75
76
77class LockingPlugin(Plugin):
78 """All this plugin does is acquire a fake apt lock after cleanup."""
79
80 def get_cruft(self):
81 return []
82
83 def post_cleanup(self):
84 # If the lock cannot be acquired, an exception is raised.
85 with apt_pkg.FileLock(LOCK_FILE):
86 # Do something.
87 pass
88
89
90class MockPluginManager:
91 def __init__(self, app, plugin_dirs):
92 # Ignore plugin_dirs
93 self.app = app
94
95 def get_plugins(self):
96 shortnames = ('one', 'two', 'three')
97 for prefix in ('foo', 'bar', 'baz'):
98 plugin = MockPlugin(prefix, shortnames)
99 plugin.set_application(self.app)
100 yield plugin
101 for plugin_class in (SawAppPlugin, LockingPlugin):
102 plugin = plugin_class()
103 plugin.set_application(self.app)
104 yield plugin
105
106
107class MockCruftExtra(MockCruft):
108 """Cruft with a different class."""
109
110
111class MockPluginExtra(MockPlugin):
112 """A mock plugin that returns cruft with a different class."""
113
114 cruft_class = MockCruftExtra
115
116
117class IgnoredDuplicateCruftPluginManager(MockPluginManager):
118 """Add an additional piece of ignorable duplication cruft."""
119
120 def __init__(self, app, plugin_dirs):
121 self.app = app
122 # Ignore plugin_dirs
123
124 def get_plugins(self):
125 yield MockPlugin('one', ('foo', 'bar'))
126 yield MockPlugin('one', ('baz', 'foo'))
127
128
129class BadDuplicateCruftPluginManager(MockPluginManager):
130 """Add an additional piece of bad duplicate cruft."""
131 def __init__(self, app, plugin_dirs):
132 self.app = app
133 # Ignore plugin_dirs
134
135 def get_plugins(self):
136 yield MockPlugin('one', ('foo', 'bar'))
137 yield MockPluginExtra('one', ('baz', 'foo'))
138
139
140class TestCollector(unittest.TestCase, ApplicationTestSetupMixin):
141 """Test the cruft collector."""
142
143 def setUp(self):
144 # Set up the test data Application.
145 ApplicationTestSetupMixin.setUp(self)
146 self.tempdir = tempfile.mkdtemp()
147 whitelist_dirs = (self.tempdir,)
148 with open(os.path.join(self.tempdir, 'one.whitelist'), 'w') as fp:
149 print >> fp, 'foo:two'
150 print >> fp, 'bar:one'
151 print >> fp, 'baz:three'
152 self.collector = Collector(self.app, MockPluginManager, whitelist_dirs)
153
154 def tearDown(self):
155 shutil.rmtree(self.tempdir)
156 ApplicationTestSetupMixin.tearDown(self)
157
158 def test_cruft_collector(self):
159 cruft_names = set(cruft.get_name() for cruft in self.collector.cruft)
160 self.assertEqual(cruft_names, set(('foo:one', 'foo:three',
161 'bar:two', 'bar:three',
162 'baz:one', 'baz:two')))
163
164 def test_plugin_needs_application(self):
165 # SawAppPlugin returned by the MockPluginManager sets this attribute
166 # on the Application.
167 self.assertTrue(self.app.saw_app_plugin)
168
169 def test_collector_name_mapping(self):
170 cruft_keys = set(self.collector.cruft_by_name)
171 cruft_names = set(cruft.get_name() for cruft in self.collector.cruft)
172 self.assertEqual(cruft_keys, cruft_names)
173
174 def test_cleanup_lock(self):
175 # Pretend we're running synaptic at the same time. We have to acquire
176 # this fake-synaptic lock in a subprocess.
177 lock_event = multiprocessing.Event()
178 continue_event = multiprocessing.Event()
179 class AptLockThread(multiprocessing.Process):
180 def run(self):
181 with apt_pkg.FileLock(LOCK_FILE):
182 lock_event.set()
183 continue_event.wait(1.0)
184 apt_locker = AptLockThread()
185 apt_locker.start()
186 lock_event.wait(1.0)
187 self.assertRaises(PackageCleanupError, self.collector.clean, [])
188 continue_event.set()
189
190
191class TestDuplicateCruftCollector(
192 unittest.TestCase, ApplicationTestSetupMixin):
193
194 def setUp(self):
195 # Set up the test data Application.
196 ApplicationTestSetupMixin.setUp(self)
197
198 def tearDown(self):
199 ApplicationTestSetupMixin.tearDown(self)
200
201 def test_duplicate_cruft_error(self):
202 self.assertRaises(DuplicateCruftError, Collector,
203 self.app, BadDuplicateCruftPluginManager, [])
204
205 def test_duplicate_cruft_error_message(self):
206 try:
207 Collector(self.app, BadDuplicateCruftPluginManager, [])
208 except DuplicateCruftError as error:
209 self.assertEqual(
210 str(error),
211 'Duplicate cruft with different cleanup: one:foo')
212 else:
213 raise AssertionError('DuplicateCruftError expected')
214
215 def test_ignored_duplicate_cruft(self):
216 collector = Collector(self.app, IgnoredDuplicateCruftPluginManager, [])
217 self.assertEqual(list(cruft.get_name() for cruft in collector.cruft),
218 ['one:foo', 'one:bar', 'one:baz'])
219
220
221def test_suite():
222 suite = unittest.TestSuite()
223 suite.addTests(unittest.makeSuite(TestCollector))
224 suite.addTests(unittest.makeSuite(TestDuplicateCruftCollector))
225 return suite
0226
=== added file 'computerjanitord/tests/test_state.py'
--- computerjanitord/tests/test_state.py 1970-01-01 00:00:00 +0000
+++ computerjanitord/tests/test_state.py 2011-02-28 19:00:59 +0000
@@ -0,0 +1,141 @@
1# Copyright (C) 2008-2011 Canonical, Ltd.
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, version 3 of the License.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14
15"""Test the package state."""
16
17from __future__ import absolute_import, unicode_literals
18
19__metaclass__ = type
20__all__ = [
21 'test_suite',
22 ]
23
24
25import os
26import difflib
27import tempfile
28import textwrap
29import unittest
30
31from computerjanitord.state import State
32
33NL = '\n'
34
35
36class TestState(unittest.TestCase):
37 """Test the `State` class."""
38
39 def setUp(self):
40 self.state = State()
41 # Create a temporary file with some enabled and disabled packages.
42 fd, self._state_file = tempfile.mkstemp()
43 os.close(fd)
44 with open(self._state_file, 'w') as fp:
45 print >> fp, textwrap.dedent("""\
46 [deb:foo]
47 ignore: false
48 [deb:bar]
49 ignore: true
50 [deb:baz]
51 ignore: true
52 """)
53 fd, self._state_file_old = tempfile.mkstemp()
54 os.close(fd)
55 with open(self._state_file_old, 'w') as fp:
56 print >> fp, textwrap.dedent("""\
57 [deb:qux]
58 enabled: false
59 [deb:fno]
60 enabled: true
61 [deb:bla]
62 enabled: true
63 """)
64 fd, self._write_file = tempfile.mkstemp()
65 os.close(fd)
66
67 def tearDown(self):
68 os.remove(self._state_file)
69 os.remove(self._state_file_old)
70 os.remove(self._write_file)
71
72 def assertEqualNdiff(self, expected, got):
73 expected_lines = expected.splitlines()
74 got_lines = got.splitlines()
75 self.assertEqual(
76 expected, got,
77 '\n' + NL.join(difflib.ndiff(expected_lines, got_lines)))
78
79 def test_initially_no_previously_ignored(self):
80 self.assertEqual(self.state.ignore, set())
81
82 def test_load_state(self):
83 self.state.load(self._state_file)
84 self.assertEqual(self.state.ignore, set(('deb:bar', 'deb:baz')))
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches