pax_global_header00006660000000000000000000000064123772504700014521gustar00rootroot0000000000000052 comment=a149cb0feca85fabb4e9f321b9eb74f302c9ca39 trash-cli-0.12.9.14/000077500000000000000000000000001237725047000137235ustar00rootroot00000000000000trash-cli-0.12.9.14/.gitignore000066400000000000000000000004431237725047000157140ustar00rootroot00000000000000*.pyc *.swp *.tmp .coverage .DS_Store .local/ .svn /bin/ /build/ /command-test/sandbox/ /command-test/test-volume.img /cover/ /dist/ /foo /env/ /nose-*.egg/ /sandbox/ /scripts/ /test-disk.img /test-disk.dmg /test-disk/ /topdir/ /trash_cli.egg-info/ /XDG_DATA_HOME/ /fake-vol /xdh/ /MANIFEST trash-cli-0.12.9.14/COPYING000066400000000000000000000431101237725047000147550ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. trash-cli-0.12.9.14/CREDITS.txt000066400000000000000000000025731237725047000155700ustar00rootroot00000000000000Author ------ Andrea Francia andreafrancia@users.sourceforge.net Thanks to --------- - Einar Orn Olason: Developmemt of first version of the empty-trash command - Rahul Sundaram, http://fedoraproject.org/wiki/RahulSundaram Packaged trash-cli for Fedora (https://bugzilla.redhat.com/show_bug.cgi?id=508750) - Michele Barbiero, http://greedyandlounge.blogspot.com,Shuren @ irc://irc.azzurra.org/linux-help Reporting of bugs. - jpschewe -at_ users.sourceforge.net: Reporting many bugs and submitting patches. - slim http://www.hartnup.net/wordpress: Helped choosing commands names - BobbyShaftoe http://stackoverflow.com/users/38426/bobbyshaftoe Helped choosing commands names - ceretullis http://www.breakingrobots.net: Helped choosing commands names - grapefrukt http://grapefrukt.com/blog : Helped choosing commands names - joel.neely http://joelneely.wordpress.com : Helped choosing commands names - My Wife Ely (Elisa Zuccolo): Thanks for tolerating me when I work at trash-cli. - Ben Finney: Helped choosing commands names, find out the grammar problems with trash-file command. - Ken VanDine: Helped choosing commands names - Christoph Bloch: Helped choosing commands names - Massimo "Submax82" Cavalleri: Packaged on Slackware - Steve Stalcup: for mantain the Debian and Ubuntu packages If you think you should appear in this page please tell me. trash-cli-0.12.9.14/DONE.txt000066400000000000000000000014161237725047000152130ustar00rootroot00000000000000trash-list - it checks that the $topdir/.Trash is a sticky dir - it reports when $topdir/.Trash is not sticky - it checks that the $topdir/.Trash is not a symbolic link - and it warns the user otherwise - it lists contents from both Home TrashDir and Volume TrashDirs (both methods) - it warns when an empty .trashinfo is found - it warns when an unreadable .trashinfo is found - is tolerant with .trashinfo that does not contains a DeletionDate - is tolerant with .trashinfo that contains an invalid DeletionDate - it warns when a .trashinfo does not contains the Path entry - print version information on --version trash-empty: - when run with `days` argument it should skip .trashinfo with invalid date - print help on --help - print version on --version trash-cli-0.12.9.14/HISTORY.txt000066400000000000000000000034131237725047000156260ustar00rootroot000000000000000.12.9: - Switched to distutils.core (instead of setuptools) - Now `trash-put -v` will warning if it found a unsticky .Trash dir. - New trash-rm command - (Internal) Swtiched from realpath to abspath 0.12.7: - fixed trash-empty crashed with GetoptError in short_has_arg(): option -2 not recognized (see https://bugs.launchpad.net/ubuntu/+source/trash-cli/+bug/1015877 ) - fixed inclusion of README.rst when creating distribution package 0.12.6: - add Donate button on README 0.12.4.24: - Fixes a packaging problem of the previous release which prevented the installation via easy_install and/or pip (see https://github.com/andreafrancia/trash-cli/issues/5) - Fixes the name of the man page for restore-trash. 0.12.4: - Reintroduced `trash` command as alias to `trash-put` - Now trash-list checks for $topdir/.Trash having sticky bit and not being a symlink and warns when these requirements are not met. - Now trash-list handles empty, unreadable or malformed .trashinfo - Now `trash-emtpy ` skips .trashinfos with invalid DeletionDates - Removed Unipath dependency - Switched from googlecode to github - Removed tests written in Bash - Complete rewrite of trash-list and trash-empty 0.11.3: - Now works also on Mac OS X - Fixed #55: restore-trash sets all-write permissions for the destination directory - Volumes detection: Now uses "df -P" output as fallback when getmnt fails. - Fixed #54. Now restore trash refuses to overwrite a file. Used code adapted from a patch written by Christian.Oudard 0.11.2: Fixed #45: Cannot build RPM package with 0.11.1.2 0.11.1.2: Fixed problems running setup.py 0.11.1: Updated version number to make easy_install happy 0.11.0: Fixed serious bug in trash-put: now the dot `.' and dot-dot `..' are skipped. trash-cli-0.12.9.14/MANIFEST.in000066400000000000000000000001161237725047000154570ustar00rootroot00000000000000include requirements-dev.txt include ez_setup.py include README.rst graft man trash-cli-0.12.9.14/README.rst000066400000000000000000000076671237725047000154320ustar00rootroot00000000000000trash-cli - Command Line Interface to FreeDesktop.org Trash. ============================================================ |Donate|_ trash-cli trashes files recording the original path, deletion date, and permissions. It uses the same trashcan used by KDE, GNOME, and XFCE, but you can invoke it from the command line (and scripts). It provides these commands:: trash-put trashes files and directories. trash-empty empty the trashcan(s). trash-list list trashed file. restore-trash restore a trashed file. Usage ----- Trash a file:: $ trash-put foo List trashed files:: $ trash-list 2008-06-01 10:30:48 /home/andrea/bar 2008-06-02 21:50:41 /home/andrea/bar 2008-06-23 21:50:49 /home/andrea/foo Search for a file in the trashcan:: $ trash-list | grep foo 2007-08-30 12:36:00 /home/andrea/foo 2007-08-30 12:39:41 /home/andrea/foo Restore a trashed file:: $ restore-trash 0 2007-08-30 12:36:00 /home/andrea/foo 1 2007-08-30 12:39:41 /home/andrea/bar 2 2007-08-30 12:39:41 /home/andrea/bar2 3 2007-08-30 12:39:41 /home/andrea/foo2 4 2007-08-30 12:39:41 /home/andrea/foo What file to restore [0..4]: 4 $ ls foo foo Remove all files from the trashcan:: $ trash-empty Remove only the files that have been deleted before ago:: $ trash-empty Example:: $ date Tue Feb 19 20:26:52 CET 2008 $ trash-list 2008-02-19 20:11:34 /home/einar/today 2008-02-18 20:11:34 /home/einar/yesterday 2008-02-10 20:11:34 /home/einar/last_week $ trash-empty 7 $ trash-list 2008-02-19 20:11:34 /home/einar/today 2008-02-18 20:11:34 /home/einar/yesterday $ trash-empty 1 $ trash-list 2008-02-19 20:11:34 /home/einar/today Using it as 'rm' alias ---------------------- `trash-put` accept all the options that GNU `rm` does, if you prefer (I don't) you can set up this alias in your .bashrc:: alias rm='trash-put' At the present the semantic of trash-put is sligthly different from the one of `rm`, for example, while `rm` requires `-R` for deleting directories `trash-put` does not. This may change in future. Keep in mind that Bash aliases are used only in interactive shells, so using this alias should not interfere with scripts that expects to use `rm`. Installation (the easy way) --------------------------- Requirements: - Python 2.7 - setuptools (use `apt-get install python-setuptools` on Debian) Installation command:: easy_install trash-cli Installation from sources ------------------------- :: # grab the latest sources wget https://github.com/andreafrancia/trash-cli/tarball/master # unpack and install tar xfz andreafrancia-trash-cli-xxxxxxxx.tar.gz cd andreafrancia-trash-cli-xxxxxxxx sudo python setup.py install Bugs and feedback ----------------- If you discover a bug please report it to: https://github.com/andreafrancia/trash-cli/issues You can reach me via email at me@andreafrancia.it . For twitter use @andreafrancia or #trashcli Development ----------- Environment setup:: virtualenv env --no-site-packages source env/bin/activate pip install -r requirements-dev.txt Running tests:: nosetests unit_tests # run only unit tests nosetests integration_tests # run all integration tests nosetests -A 'not stress_test' # run all tests but stress tests nosetests # run all tests Check the installation process before release:: python check_release_installation.py Profiling unit tests:: pip install gprof2dot nosetests --with-profile --profile-stats-file stats.pf --profile-restrict=unit_tests unit_tests gprof2dot -w -f pstats stats.pf | dot -Tsvg >| stats.svg open stats.svg .. |Donate| image:: https://www.paypalobjects.com/en_GB/i/btn/btn_donate_SM.gif .. _Donate: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=93L6PYT4WBN5A trash-cli-0.12.9.14/TODO.txt000066400000000000000000000034751237725047000152420ustar00rootroot00000000000000Feature: trash-rm TODO: - man pages: - add test for checking man page installation - add DONATION to all pages - make all BUGS to point to github - unificate "SEE ALSO" for all man pages - add a test for trash-put and method 1 trashdir - duplication: - remove duplication (man page example and actual behaviour) - remove duplication about BUGS in man page - setup.py: - test is a setuptools keyword, it does not work with distutils - 'data_files' is a distutils keyword, it does not work with setuptools Features backlog: - trash-list should handle .trashinfo from home trashdir that contains relative path - trash-list should print also the orphan files - support alias rmdir=trash-put - don't use world writable trash - enable trash-put home fallback - trash-empty removes files until a certain amount of space has been restored - trash-list --raw - trash-list - trash-list -R # recursive - trash-restore # restore the newest - trash-restore # restore to a certain destination - trash-restore --trashed-on="Apr 15, 08" - trash-restore --version - trash-empty --from=/.Trash - trash-rm '*.o' - trash-empty should empty even the mac trash directory - TRASH_DATE=2012-09-12 trash-put Test to be ported to nosetests: - trash-put: - trash in home trashcan - print version - trash in volume trashcan 1 - trash in volume trashcan 2 - it should trash to $topdir/.Trash-UID when $topdir/.Trash is not suitable - when $topdir/.Trash does not exists - when $topdir/.Trash is a symlink - when $topdir/.Trash is not sticky - should refuse to create the $topdir/.Trash/$uid directory if the $topdir/.Trash is not sticky trash-cli-0.12.9.14/bugs.txt000066400000000000000000000003731237725047000154270ustar00rootroot00000000000000Bug reported on external trackers: x trash-empty crashed with GetoptError in short_has_arg(): option -2 not recognized, url: https://bugs.launchpad.net/ubuntu/+source/trash-cli/+bug/1015877 status: solved on Thu Jun 21 12:50:03 CEST 2012 trash-cli-0.12.9.14/check_release_installation.py000066400000000000000000000066561237725047000216500ustar00rootroot00000000000000TARGET_HOST = '192.168.56.101' import nose from nose.tools import assert_equals, assert_not_equals from ssh import Connection from trashcli.trash import version def main(): check_connection() check_installation(normal_installation) check_installation(easy_install_installation) def check_installation(installation_method): tc = LinuxBox('root@' + TARGET_HOST, installation_method) print "== Cleaning any prior software installation" tc.clean_any_prior_installation() print "== Copying software" tc.copy_tarball() print "== Installing software" tc.install_software() print "== Checking all program were installed" tc.check_all_programs_are_installed() class LinuxBox: def __init__(self, address, installation_method): self.ssh = Connection(address) self.executables = [ 'trash-put', 'trash-list', 'trash-rm', 'trash-empty', 'restore-trash', 'trash'] self.tarball="trash-cli-%s.tar.gz" % version self.installation_method = installation_method def clean_any_prior_installation(self): "clean any prior installation" for executable in self.executables: self._remove_executable(executable) self._assert_command_removed(executable) def _remove_executable(self, executable): self.ssh.run('rm -f $(which %s)' % executable).assert_succesful() def _assert_command_removed(self, executable): result = self.ssh.run('which %s' % executable) command_not_existent_exit_code_for_which = 1 assert_equals(result.exit_code, command_not_existent_exit_code_for_which, 'Which returned: %s\n' % result.exit_code + 'and reported: %s' % result.stdout ) def copy_tarball(self): self.ssh.put('dist/%s' % self.tarball) def install_software(self): def run_checked(command): result = self.ssh.run(command) result.assert_succesful() self.installation_method(self.tarball, run_checked) def check_all_programs_are_installed(self): for command in self.executables: result = self.ssh.run('%(command)s --version' % locals()) assert_not_equals(127, result.exit_code, "Exit code was: %s, " % result.exit_code + "Probably command not found, command: %s" % command) def normal_installation(tarball, check_run): directory = strip_end(tarball, '.tar.gz') check_run('tar xfvz %s' % tarball) check_run('cd %s && ' 'python setup.py install' % directory) def easy_install_installation(tarball, check_run): check_run('easy_install %s' % tarball) def strip_end(text, suffix): if not text.endswith(suffix): return text return text[:-len(suffix)] def check_connection(): suite = nose.loader.TestLoader().loadTestsFromTestClass(TestConnection) nose.run(suite=suite) class TestConnection: def __init__(self): self.ssh = Connection(TARGET_HOST) def test_should_report_stdout(self): result = self.ssh.run('echo', 'foo') assert_equals('foo\n', result.stdout) def test_should_report_stderr(self): result = self.ssh.run('echo bar 1>&2') assert_equals('bar\n', result.stderr) def test_should_report_exit_code(self): result = self.ssh.run("exit 134") assert_equals(134, result.exit_code) if __name__ == '__main__': main() trash-cli-0.12.9.14/docs/000077500000000000000000000000001237725047000146535ustar00rootroot00000000000000trash-cli-0.12.9.14/docs/MountListFromHal.wiki000066400000000000000000000010101237725047000207370ustar00rootroot00000000000000About using HAL (Hardware Abstraction Layer) to get the list of mount points. ============================================================================= This [http://davyd.livejournal.com/206645.html article] explain how to use HAL from python. This [https://www.dfwpython.org/repo/Projects/DBUS/dtest.py python scripts] communicates with HAL trouch DBUS and uses the 'org.freedesktop.Hal.Device.Volume' name. =Depedencies= Using this method will add some dependencies: * hal ? * libhal1 * dbus python module trash-cli-0.12.9.14/docs/about-accessing-the-windows-recycle-bin.txt000066400000000000000000000010721237725047000251230ustar00rootroot00000000000000Some references about accessing Recycle Bin of Windows ====================================================== Seems that ReactOS projects has implemented a library for dealing with the Windows specific trashcan data structure: http://svn.reactos.org/svn/reactos/trunk/reactos/lib/recyclebin/ How to use SHFileOperation in python using pywin32: http://timgolden.me.uk/python/win32_how_do_i/copy-a-file.html How to programmatically send folders and/or files to the recycle bin (in C#): http://www.fmsinc.com/free/NewTips/NET/sendfoldertoreceyclebin.asp trash-cli-0.12.9.14/docs/about-listing-osx-trashcan.txt000066400000000000000000000013741237725047000226120ustar00rootroot00000000000000In OS X the trash can directories are: - ~/.Trash - $topdr/.Trashes/$uid Afert installing the `osx-trash` gem the behaviour can be described with: $ ls ~/.Trash $ touch foo $ trash foo $ ls ~/.Trash foo $ touch foo $ trash foo $ ls ~/.Trash foo foo 17-32-40 Where 17-32-40 is HH:MM:SS. If you "Put back" a file it is moved to the original path but it still mantain the new name (e.g. "foo 17-32-40"). The deletion time is put before the last dot: $ touch foo.tar.gz; trash foo.tar.gz $ touch foo.tar.gz; trash foo.tar.gz $ ls ~/.Trash foo.tar 17-36-13.gz foo.tar.gz The original path is stored in the ~/.Trash/.DS_Store. More details in http://superuser.com/questions/108301/deleting-a-single-file-from-the-trash-in-mac-os-x-snow-leopard trash-cli-0.12.9.14/docs/about-shredding.txt000066400000000000000000000077721237725047000205100ustar00rootroot00000000000000Introduction ============ See also issue #37 A feature not typically available in other trash-emptying implementations is the ability to use arbitrary shell utilities (such as srm, shred, and the like) to remove files. It'd be nice if the empty-trash command took a flag (say, '-s') to provide for such functionality. Suggested Command Line ====================== :: empty-trash --shred This command would shred all the trashed files and the trashinfo files in all trash directories. Things to be defined: - How the secure shredding should be implemented? Calling an external command or using some python library? - No extant Python shredding library. The only [http://reconstructor.aperantis.com/index.php?option=com_remository&Itemid=33&func=fileinfo&id=166 other implementation] I could find just calls wipe. - What to do if the existing command is not found? - Take a command-line option to select a supported method, and perhaps a config-file option. Otherwise, look for one of the supported methods and remember which one was available last time? - I was talking about how the trash-empty should behave if the external is not available. In any case this would be defined clearly when we wrote the use cases. Use cases ========= User want to shred all trashed files of all trash directories. So he/she types the command:: $ trash-empty --shred The trash-empty command will silently shred all trashinfo and trashed files from all trashed directory. The wiping is implemented calling the `shred` command from the coreutils for each file to be wiped. Exceptions ---------- Suppose that: * `shred` is not available in $PATH. * the trashcan contains at least a trashed file. The behaviour should be $ trash-empty --shred trash-empty: Unable to shred `filename' (shred: command not found). You need shred from coreutils. $ echo $(($?==0)) 1 Permission Denied ----------------- Suppose that: * `shred` is available in $PATH. * `shred` permissions are `--x` (could not be read from the user) * the trashcan contains at least two trashed file. The behaviour should be: $ trash-empty --shred trash-empty: Unable to shred `filename' (shred: Permission denied). $ echo $(($?==0)) 1 The error message should be displayed once and the command should not attempt to re-execute shred with others files (even if there are multiple files in trash directories). User shouldn't install additional software ------------------------------------------ The shred operation would be implemented against the `shred` command line utility. The `shred` was chosen because is part of the coreutils which is virtually installed in all major GNU/Linux distributions. User shouldn't configure the software ------------------------------------- We don't add the support to multiple commands because this would increase the complexity of the user interface of the `trash-empty` command and the other choices does not offer something very different from `shred`. If someone feel the urge of doing arbitrary command on all trashed files we can always add a `for-each-trashed-files` command or use a combination of an hypothetic `list-all-trashed-files` and `xargs` command. Implementation details ====================== Things to do: - create a TrashedFile.securepurge() method - implement command line options handling in empty-trash command - create a DeletionUtility abstraction, and implement methods for the three utilities below? See also ======== Secure deleting is implemented in these shell utilities: * shred (part of the coreutils package) * srm (part of secure_deletion package from http://www.thc.org) * [http://wipe.sourceforge.net/ wipe] Limitations =========== This features could be less useful that one can think. As stated in the [http://www.gnu.org/software/coreutils/manual/html_node/shred-invocation.html shred documentation] (in the "Please note") the shredding operation could not be safe on many cases such as use of RAID, and the Ext3 in data=journal mode. trash-cli-0.12.9.14/docs/about-the-generic-naming-issue.txt000066400000000000000000000024071237725047000233160ustar00rootroot00000000000000How the command names changed? ============================== The earlier versions of trash-cli uses these commands names: * trash * empty-trash * list-trash * restore-trash When the trash-cli project was proposed to Fedora the Red Hat peoples complaint about the commmand name 'trash' to be too generic and they refused to include the package in Fedora. The discussion started on [1] and followed in [2]. The the commands names where then renamed from *-trash to trash-* that will exploit the shell TAB-completion. This change was suggested by Behdad Esfahbod (see [3]) After those discussion on the upstream the names of the commands where changed to: * trash # trashes files and directories. * trash-empty # empty the trashcan(s). * trash-list # list trashed files. * restore-trash # restore a trashed file. A summary of all these discussion is available at [4]. After that the packagers lose theirs interest and the trash-cli was not packaged. [1] https://bugzilla.redhat.com/show_bug.cgi?id=448122 [2] https://www.redhat.com/archives/fedora-devel-list/2008-October/msg00216.html [3] https://www.redhat.com/archives/fedora-devel-list/2008-October/msg00231.html [4] http://fedoraproject.org/wiki/FWN/Issue147 (Fedora Weely News #147) trash-cli-0.12.9.14/docs/back-links.txt000066400000000000000000000042611237725047000174350ustar00rootroot00000000000000List of web pages that cite trash-cli, with the date when I discovered the page, sorted by discovery date (newest before): - 2012-07-19 http://shuffleos.com/5835/trashcli-command-line-utility-access-trashcan-ubuntu-linux-terminal/ - 2012-07-18 http://crunchbanglinux.org/forums/topic/20818/trashbin-cli/ - 2012-07-02 http://www.hecticgeek.com/2012/06/access-trash-command-line-ubuntu-linux-trash-cli/ - 2009-07-24 http://www.emacswiki.org/emacs/SystemTrash - 2009-03-29 2008-12-27 http://blog.goo.ne.jp/xxxfishbonexxx/e/45508969110d67f141226b68eab205ce japanese? blog post about installing? trash-cli - 2009-03-29 2008-10-16 http://linuxgazette.net/156/misc/lg/deleted_file_recovery.html - 2009-03-29 http://qa.debian.org/popcon.php?package=trash-cli - 2009-03-29 http://elonen.iki.fi/code/misc-notes/remove-duplicate-files/index.html?showcomments=1 Antonio in the comments. - 2009-03-01 http://varlogrant.blogspot.com/2009/01/making-plans.html - 2009-03-01 http://y_z.scripts.mit.edu/wp/2009/02/03/when-you-release-shift-too-early - 2009-03-01 http://sanelz.blogspot.com/2009/02/trashing-stuff.html - 2009-03-01 http://alltheshortnamesweretakenalready.blogspot.com/2009/01/bash-script-implementation-of-trashcan.html - 2008-12-25 (Blog about Ubuntu in some language that uses kanji) http://ubulog.blogspot.com/2008/10/ubuntu.html - 2008-12-24 https://www.ohloh.net/p/trash-cli - 2008-12-23 http://article.gmane.org/gmane.compw.window-managers.windowmaker.devel/1045 - 2008-12-23 http://ubuntuforums.org/showthread.php?t=1003522 - 2008-12-23 http://www.downloadplex.com/Linux/System-Utilities/File-Disk-Management/trash-cli_70540.html - 2008-11-16 http://feeding.cloud.geek.nz/2008/06/preventing-accidental-deletion-of.html - 2008-11-09 (in french) http://www.thierryb.net/site/TRASH-CLI-utiliser-la-corbeille-en.html - 2008-10-14 http://ubulog.blogspot.com/2008/10/ubuntu.html - 2008-10-14 http://d.hatena.ne.jp/f99aq/20081013/1223882323 - http://linux.softpedia.com/get/System/Filesystems/trash-cli-39712.shtml - http://syswall.wordpress.com/2008/07/11/trash-cli/ - http://freshmeat.net/projects/bluetrash/ - http://www.linux.com/feature/138331 - http://directory.fsf.org/project/trash/ trash-cli-0.12.9.14/docs/how-to-build-and-upload-a-new-release.txt000066400000000000000000000006651237725047000244020ustar00rootroot00000000000000How to build a release ====================== Update the version number: vim trashcli/trash.py version="$(python setup.py --version)" Run all tests: nosetests Test the installation from tarball: python setup.py sdist python check_release_installation.py Register and upload: python setup.py register python setup.py sdist upload Now you can tag the repo status: git tag "${version:?}" -EOF trash-cli-0.12.9.14/docs/proposed-new-interface/000077500000000000000000000000001237725047000212335ustar00rootroot00000000000000trash-cli-0.12.9.14/docs/proposed-new-interface/trash-cli.txt000066400000000000000000000024041237725047000236620ustar00rootroot00000000000000NAME trash-cli - Command Line Interface to FreeDesktop.org Trash. DESCRIPTION trash-cli provides the following commands to manage the trash: * trash trashes files and directories. * trash-empty empty the trashcan(s). * trash-list list trashed file. * trash-restore restore a trashed file. * trash-admin administrate trashcan(s). For each file the name, original path, deletion date, and permissions are recorded. The trash command allow trash multiple files with the same name. These command uses the same Trashcan of last versions of KDE, GNOME and XFCE. EXAMPLES Trash a file: $ trash /home/andrea/foobar List trashed files: $ trash-list 2008-06-01 10:30:48 /home/andrea/bar 2008-06-02 21:50:41 /home/andrea/bar 2008-06-23 21:50:49 /home/andrea/foo Restore a trashed file: $ trash-restore /home/andrea/foo Empty the trashcan: $ trash-empty AUTHORS REPORTING BUGS COPYRIGHT SEE ALSO trash(1), trash-restore(1), trash-empty(1), trash-list(1), trash-admin(1), trashinfo(5). trash-cli-0.12.9.14/docs/proposed-new-interface/trash-empty.txt000066400000000000000000000046651237725047000242640ustar00rootroot00000000000000NAME trash-emptycan - remove all trashed files from the trashcan. SYNOPSIS trash-emptycan [OPTIONS]... DESCRIPTION Restore the trashed file specified by SOURCE in DEST (or its original location). -d, --deletion-date=DATE Choose the trashed file with the specified original location and with the specified deletion date. --trash-id The SOURCE param should be interpreted as trash ids. --version output version information and exit --help display this help and exit TRASH ID A TrashId is a URL referring to a specific item in a specific trash directory. The URL has this form: trash:TRASH_DIR/ID Where 'trash:' is the scheme part. TRASH_DIR is the Trash directory containing the item, can contains slashes '/'. ID is the Item part and can not contains slashes. The TrashId refer to the trashed file whith TRASH_DIR/info/ID.trashinfo as .trashinfo file. TRASH_DIR/files/ID as original file. USE CASES Restore a trashed file in its original location: $ trash-restore /home/andrea/foobar Restore a trashed file in case of multiple entries matching the original location specified: $ trash-restore /home/andrea/foo trash-restore: Cannot restore '/home/andrea/foo' multiple entries with same location exists: 2008-06-23T21:50:41 /home/andrea/foo 2008-06-23T21:50:49 /home/andrea/foo $ trash-restore --last /home/andrea/foo Or $ trash-restore --deletion-date=2008-06-23T21:50:49 /home/andrea/foo Restore a trashed file in a different location: $ trash-restore /home/andrea/foo ./bar Restoring by trash-id. $ trash-list --show-trash-id trash:/home/andrea/.local/share/Trash/foo trash:/home/andrea/.local/share/Trash/foo_1 trash:/home/andrea/.local/share/Trash/foo_2 $ trash-restore --trash-id trash:/home/andrea/.local/share/Trash/foo_2 AUTHORS REPORTING BUGS COPYRIGHT SEE ALSO trash-cli(1), trash(1), trashinfo(5), trash-restore(1), trash-empty(1). trash-cli-0.12.9.14/docs/proposed-new-interface/trash-list.txt000066400000000000000000000032631237725047000240720ustar00rootroot00000000000000NAME trash-list - list trashed files. SYNOPSIS trash-list [OPTIONS]... [DIR]... DESCRIPTION List files trashed from DIR (or the current directory by default). -l do not recurse subfolders -r, --recursive recurse subfolders (default) -a, --all list all file trashed in all trashcans --version output version information and exit --help display this help and exit EXAMPLES List all trashed files: $ trash-list 2008-06-23T21:57:26 /home/andrea/src/bluetrash/pippo 2008-06-23T21:50:41 /home/andrea/foobar 2008-06-23T21:50:49 /home/andrea/foobar 2008-06-23T21:53:13 /media/disk/adsfljasldj List all file trashed from the current directory (recursive): $ pwd /home/andrea $ trash-list . 2008-06-23T21:57:26 /home/andrea/src/bluetrash/pippo 2008-06-23T21:50:41 /home/andrea/foobar 2008-06-23T21:50:49 /home/andrea/foobar List all file trashed from the current directory (not recursive): $ pwd /home/andrea $ trash-list -l . 2008-06-23T21:50:41 /home/andrea/foobar 2008-06-23T21:50:49 /home/andrea/foobar List all files removed from a specific directory: $ trash-list /tmp/ 2008-06-23T21:50:41 /tmp/foobar1 2008-06-23T21:50:49 /tmp/foobar2 AUTHORS REPORTING BUGS COPYRIGHT SEE ALSO trash-cli(1), trash(1), trashinfo(5), trash-restore(1), trash-empty(1). trash-cli-0.12.9.14/docs/proposed-new-interface/trash-restore.txt000066400000000000000000000046511237725047000246040ustar00rootroot00000000000000NAME trash-restore - restore a trashed files. SYNOPSIS trash-restore [OPTIONS]... SOURCE [DEST] DESCRIPTION Restore the trashed file specified by SOURCE in DEST (or its original location). -d, --deleted-on=DATE Choose the trashed file with the specified original location and with the specified deletion date. --trash-id The SOURCE param should be interpreted as trash ids. --version output version information and exit --help display this help and exit TRASH ID A TrashId is a URL referring to a specific item in a specific trash directory. The URL has this form: trash:TRASH_DIR/ID Where 'trash:' is the scheme part. TRASH_DIR is the Trash directory containing the item, can contains slashes '/'. ID is the Item part and can not contains slashes. The TrashId refer to the trashed file whith TRASH_DIR/info/ID.trashinfo as .trashinfo file. TRASH_DIR/files/ID as original file. USE CASES Restore a trashed file in its original location: $ trash-restore /home/andrea/foobar Restore a trashed file in case of multiple entries matching the original location specified: $ trash-restore /home/andrea/foo trash-restore: Cannot restore '/home/andrea/foo' multiple entries with same location exists: 2008-06-23T21:50:41 /home/andrea/foo 2008-06-23T21:50:49 /home/andrea/foo $ trash-restore --last /home/andrea/foo Or $ trash-restore --deletion-date=2008-06-23T21:50:49 /home/andrea/foo Restore a trashed file in a different location: $ trash-restore /home/andrea/foo ./bar Restoring by trash-id. $ trash-list --show-trash-id trash:/home/andrea/.local/share/Trash/foo trash:/home/andrea/.local/share/Trash/foo_1 trash:/home/andrea/.local/share/Trash/foo_2 $ trash-restore --trash-id trash:/home/andrea/.local/share/Trash/foo_2 AUTHORS REPORTING BUGS COPYRIGHT SEE ALSO trash-cli(1), trash(1), trashinfo(5), trash-restore(1), trash-empty(1). trash-cli-0.12.9.14/docs/similar-projects.txt000066400000000000000000000102771237725047000207120ustar00rootroot00000000000000DELSAFE ======= URL: http://homepage.esoterica.pt/~nx0yew/delsafe/ Use a different approach to the same problem. While 'trash' provide a extra command that put files in the trash, 'delsafe' modifies the semantics of the existing commands. Apparently 'delsafe' overrides the library calls of unlink, unlinkat, rename, open and fopen modifying the semantics of these functions. A program that was designed to works with the original functions may not work well with the overridden functions. For example is the KDE filemanager which as two commands: - delete file - trash file Using 'delsafe' these commands will become: - delete file --> trash file in the delsafe trash - trash file --> trash file in the KDE trash I prefer the 'trash' approach because don't change the semantics of the existing program letting the user chose when putting files in the trash and when removing them. Personally I don't like the model where each deletion should recorded somewhere. 'trash' is intended to be used only in interactive shells, if a program decides to delete, or trunk a file the information loss risk IMHO should be managed in the context of this program. trash.sh ======== URL: http://fresh.t-systems-sfr.com/unix/src/privat2/trashcan-3.2.tar.gz/ Comments: - This program seems not compliant with the FreeDesktopTrashSpecification. - It compresses trashed file instead of just moving it. - Like trash-cli can trash and restore directories. - Seems that the development was ended in 2003. Trashcan for Rox-Filer ====================== URL: http://usuarios.lycos.es/proyectarr/ Features: * Has a graphical user interface. * Does not support the FreeDesktopTrashSpecification. * Implemented in bash using zenity. trash by skymt ============== URL: http://mysite.verizon.net/skymt/trash/ Language: Python I reviewed the version [http://mysite.verizon.net/skymt/trash/trash-0.2.tar.gz 0.2] Is the only other CLI implementation of FreeDesktopTrashSpecification I ever found till now (2008-06-29) Features: - Operation supported: * trash files and directories: yes * listing trashed files: no * restoring trashed files: no * emptying the trashcan: no - Support of volumes trashcans: no - Can be used as alias='rm': no Notes: - *(Better than trash-cli)* Seems much simpler of trash-cli.\ - *(Better than trash-cli)* Prints sensible output if the trashcan directories permission are wrong. - Not object oriented. cn by Richard Neill ------------------- URL: http://www.richardneill.org/source.php#cn License: GPLv3 Reviewed on: 2008-06-29 Features: - Tries to conform to the FreeDesktopTrashSpecification - Works also with the older version of KDE and GNOME TrashCan implementation - Can be used as alias='rm': no - It has a manpage. Trash Specification: - Operation supported: - trash files and directories: yes - listing trashed files: yes - restoring trashed files: yes - emptying trashcan: yes - Support of volumes trashcans: no - Can be used as alias='rm': no FreeDesktop.org specification deviations: - I don't think it creates the trashinfo file in an atomic way. File Rename Utils ================= URL: http://filerenameutils.sourceforge.net/ License: ? Reviewed on: 2008-06-30 Features: - Conform to FreeDesktopTrashSpecification : yes - Operation supported: - trash files and directories: yes - listing trashed files: yes - restoring trashed files: yes - emptying trashcan: yes - Support of volumes trashcans: ? - Can be used as alias='rm': no Gnome Trashcan ============== As of Nautilus 2.22, GNOME support the XDG Trash specification through GIO and the GVFS-Trash backend. You can find the code for this backend here: http://svn.gnome.org/viewvc/gvfs/trunk/daemon/ and GIO is in GLib: http://svn.gnome.org/viewvc/glib/trunk/gio/ Safe RM ======= URL: http://www.safe-rm.org.nz/ Safe RM does not talk about Trash Cans. The program alert you whenewer you attempt to delete files known to be important. rmtrash ======= website: http://www.nightproductions.net/cli.htm trashcan utility for Mac OS X. I think it support only trashing files (not trash listing, or restoring). Feedback ======== Feel free to leave any comment (mailing me) for any error or else. -EOF trash-cli-0.12.9.14/docs/some-output-of-df-command-on-different-systems.txt000066400000000000000000000060231237725047000264060ustar00rootroot00000000000000The __mount_list method rely on the *df* command. The interface of the *df* command may vary from system to system. We support only the system that comply with the [http://www.opengroup.org/onlinepubs/009695399/utilities/df.html|POSIX specification of the df command]. {{{ # uname -a OSF1 x.invalid.it V4.0 1229 alpha # df -P Filesystem 512-blocks Used Available Capacity Mounted on root_domain#root 262144 167054 80832 68% / /proc 0 0 0 100% /proc usr_domain#usr 11239984 3372768 7796624 31% /usr var_domain#var 2077088 183980 1861744 9% /var /dev/rzb16a 22934504 5001884 15639168 25% /home2 extern:/atom 102398304 44759584 57638720 44% /home2/atom # }}} Example 2: {{{ File system blocchi di 1K Usati Disponib. Uso% Montato su /dev/sdb5 10883628 5202528 5681100 48% / varrun 3062924 104 3062820 1% /var/run varlock 3062924 0 3062924 0% /var/lock udev 3062924 168 3062756 1% /dev devshm 4500000 25640 4474360 1% /dev/shm lrm 3062924 43040 3019884 2% /lib/modules/2.6.24-19-generic/volatile /dev/sdb4 1035692 113336 869744 12% /boot /dev/mapper/var 16577536 1128080 15449456 7% /var /dev/md0 20481928 7523196 12958732 37% /usr /dev/md1 15346084 1109936 14236148 8% /chroot /dev/sda3 50169624 29642452 20527172 60% /home /dev/mapper/spazio1-LV1 426396964 276310360 150086604 65% /media/LV1 /dev/sda4 240686472 27295300 213391172 12% /media/archivio /dev/mapper/ibox 18929088 15733744 3195344 84% /media/ibox gvfs-fuse-daemon 10883628 5202528 5681100 48% /home/lem/.gvfs }}} Example 3: {{{ File system 1024-blocks Used Available Capacity Montato su /dev/sdb5 10883628 5202528 5681100 48% / varrun 3062924 104 3062820 1% /var/run varlock 3062924 0 3062924 0% /var/lock udev 3062924 168 3062756 1% /dev devshm 4500000 25640 4474360 1% /dev/shm lrm 3062924 43040 3019884 2% /lib/modules/2.6.24-19-generic/volatile /dev/sdb4 1035692 113336 869744 12% /boot /dev/mapper/var 16577536 1127952 15449584 7% /var /dev/md0 20481928 7523196 12958732 37% /usr /dev/md1 15346084 1109936 14236148 8% /chroot /dev/sda3 50169624 29642452 20527172 60% /home /dev/mapper/spazio1-LV1 426396964 276310360 150086604 65% /media/LV1 /dev/sda4 240686472 27295300 213391172 12% /media/archivio /dev/mapper/ibox 18929088 15733744 3195344 84% /media/ibox gvfs-fuse-daemon 10883628 5202528 5681100 48% /home/lem/.gvfs }}}trash-cli-0.12.9.14/docs/trashspec-0.7.html000066400000000000000000000621731237725047000200500ustar00rootroot00000000000000 Trash specification

The FreeDesktop.org Trash specification

Written by Mikhail Ramendik <mr@ramendik.ru>

Content by David Faure <dfaure@trolltech.com>, Alexander Larsson <alexl@redhat.com> and others on the FreeDesktop.org mailing list

Version 0.7

Abstract

The purpose of this Specification is to provide a common way in which all “Trash can” implementations should store, list, and undelete trashed files. By complying with this Specification, various Trash implementations will be able to work with the same devices and use the same Trash storage. For example, if one implementation sends a file into the Trash can, another will be able to list it, undelete it, or clear it from the Trash.

Introduction

An ability to recover accidentally deleted files has become the de facto standard for today's desktop user experience.

Users do not expect that anything they delete is permanently gone. Instead, they are used to a “Trash can” metaphor. A deleted document ends up in a “Trash can”, and stays there at least for some time — until the can is manually or automatically cleaned.

This system has its own problems. Notably, cleaning disk space becomes a two-step operation — delete files and empty trash; this can lead to confusion for inexperienced users (“what's taking up my space?!”). Also, it is not easy to adapt the system to a multiuser environment. Besides, there is a potential for abuse by uneducated users — anecdotal evidence says they sometimes store important documents in the Trash can, and lose them when it gets cleaned!

However, the benefits of this system are so great, and the user expectation for it so high, that it definitely should be implemented on a free desktop system. And in fact, several implementations already exist — some as command line utilities, some as preloaded libraries, and some as parts of major desktop environments. For example, both Gnome and KDE have their own trash mechanisms.

This Specification is to provide a common way in which all Trash can implementations should store trashed files. By complying with this Specification, various Trash implementations will be able to work with the same devices and use the same Trash storage.

This is important, at least, for shared network resources, removable devices, and in cases when different implementations are used on the same machine at different moments (i.e. some users prefer Gnome, others prefer KDE, and yet others are command-line fans).

Scope and limitations

This Specification only describes the Trash storage. It does not limit the ways in which the actual implementations should operate, as long as they use the same Trash storage. Command line utilities, desktop-integrated solutions and preloaded libraries can work with this specification. 1

This Specification is geared towards the Unix file system tree approach. However, with slight modifications, it can easily be used with another kind of file system tree (for example, with drive letters).

A multi-user environment, where users have specific numeric identifiers, is essential for this Specification.

File systems and logon systems can be case-sensitive or non-case-sensitive; therefore, systems should generally not allow user names that differ only in case.

Definitions

Trash, or Trash can — the storage of files that were trashed (“deleted”) by the user. These files can be listed, undeleted, or cleaned from the trash can.

Trashing — a “delete” operation in which files are transferred into the Trash can.

Erasing — an operation in which files (possibly already in the Trash can) are removed (unlinked) from the file system. An erased file is generally considered to be non-recoverable; the space used by this file is freed. [A “shredding” operation, physically overwriting the data, may or may not accompany an erasing operation; the question of shredding is beyond the scope of this document].

Original location — the name and location that a file (currently in the trash) had prior to getting trashed.

Original filename — the name that a file (currently in the trash) had prior to getting trashed.

Top directory , $topdir — the directory where a file system is mounted. “/” is the top directory for the root file system, but not for the other mounted file systems. For example, separate FSes can be mounted on “/home”, “/mnt/flash”, etc. In this text, the designation “$topdir” is used for “any top directory”.

User identifier , $uid — the numeric user identifier for a user. $uid is used here as “the numeric user identifier of the user who is currently logged on”.

Trash directory — a directory where trashed files, as well as the information on their original name/location and time of trashing, are stored. There may be several trash directories on one system; this Specification defines their location and contents. In this text, the designation “$trash” is used for “any trash directory”.

“Home trash” directory — a user's main trash directory. Its name and location is defined in this document.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Trash directories

A system can have one or more trash directories. The contents of any trash directory are to be compliant with the same standard, described below.

For every user2 a “home trash” directory MUST be available3. Its name and location are $XDG_DATA_HOME/Trash ; $XDG_DATA_HOME is the base directory for user-specific data, as defined in the Desktop Base Directory Specification .

The “home trash” should function as the user's main trash directory. Files that the user trashes from the same file system (device/partition) should be stored here (see the next section for the storage details). A “home trash” directory SHOULD be automatically created for any new user. If this directory is needed for a trashing operation but does not exist, the implementation SHOULD automatically create it, without any warnings or delays.

The implementation MAY also support trashing files from the rest of the system (including other partitions, shared network resources, and removable devices) into the “home trash” directory . This is a “failsafe” method: trashing works for all file locations, the user can not fill up any space except the home directory, and as other users generally do not have access to it, no security issues arise.

However, this solution leads to costly file copying (between partitions, over the network, from a removable device, etc.) A delay instead of a quick “delete” operation can be unpleasant to users.

An implementation may choose not to support trashing in some of these cases (notably on network resources and removable devices). This is what some well known operating systems do.

It may also choose to provide trashing in the “top directories” of some or all mounted resources. This trashing is done in two ways, described below as (1) and (2).

(1) An administrator can create an $topdir/.Trash directory. The permissions on this directories should permit all users who can trash files at all to write in it.; and the “sticky bit” in the permissions must be set, if the file system supports it.

When trashing a file from a non-home partition/device4 , an implementation (if it supports trashing in top directories) MUST check for the presence of $topdir/.Trash.

When preparing a list of all trashed files (i.e. to show to the user), an implementation also MUST check for .Trash in all top directories that are known to it.

If this directory is present, the implementation MUST, by default, check for the “sticky bit”. (It MAY provide a way for the administrator, and only the administrator, to disable this checking for a particular top directory, in order to support file systems that do not have the “sticky bit”).

The implementation also MUST check that this directory is not a symbolic link.

If any of these checks fail, the implementation MUST NOT use this directory for either trashing or undeleting files, even is an appropriate $uid directory (see below) already exists in it. Besides, the implementation SHOULD report the failed check to the administrator, and MAY also report it to the user.

The following paragraph applies ONLY to the case when the implementation supports trashing in the top directory, and a $topdir/.Trash exists and has passed the checks:

If the directory exists and passes the checks, a subdirectory of the $topdir/.Trash directory is to be used as the user's trash directory for this partition/device. The name of this subdirectory is the numeric identifier of the current user ($topdir/.Trash/$uid). When trashing a file, if this directory does not exist for the current user, the implementation MUST immediately create it, without any warnings or delays for the user.

(2) If an $topdir/.Trash directory is absent, an $topdir/.Trash-$uid directory is to be used as the user's trash directory for this device/partition. $uid is the user's numeric identifier.

The following paragraph applies ONLY to the case when the implementation supports trashing in the top directory, and a $topdir/.Trash does not exist or has not passed the checks:

When trashing a file, if an $topdir/.Trash-$uid directory does not exist, the implementation MUST immediately create it, without any warnings or delays for the user.

When trashing a file, if this directory does not exist for the current user, the implementation MUST immediately create it, without any warnings or delays for the user.

Notes. If an implementation provides trashing in top directories at all, it MUST support both (1) and (2).

If an implementation does NOT provide such trashing, and does provide the user with some interface to view and/or undelete trashed files, it SHOULD make a “best effort” to show files trashed in top directories (by both methods) to the user, among other trashed files or in a clearly accessible separate way.

When trashing a file, if the method (1) fails at any point — i.e. the $topdir/.Trash directory does not exist, or it fails the checks, or the system refuses to create an $uid directory in it — the implementation MUST, by default, fall back to method (2), described below. Except for the case when $topdir/.Trash fails the checks, the fallback must be immediate, without any warnings or delays. The implementation MAY, however, a way for the administrator to disable (2) completely.

If both (1) and (2) fail (i.e. no $topdir/.Trash directory exists, and an attempt to create $topdir/.Trash-$uid fails), the implementation MUST either trash the file into the user's “home trash” or refuse to trash it. The choice between these options can be pre-determined, or it can depend on the particular situation (i.e. No trashing of very large files). However, if an implementation refuses to trash a file after a user action that generally causes trashing, it MUST clearly warn the user about this, and request confirmation for the action.

For showing trashed files, implementations SHOULD support (1) and (2) at the same time (i.e. if both $topdir/.Trash/$uid and $topdir/.Trash-$uid are present, it should list trashed files from both of them).

Contents of a trash directory

The previous section has described the location of trash directories. This section concerns the contents of any trash directory (including the “home trash” directory). This trash directory will be named “$trash” here.

A trash directory contains two subdirectories, named info and files.

The $trash/files directory contains the files and directories that were trashed. When a file or directory is trashed, it MUST be moved into this directory5 . The names of files in this directory are to be determined by the implementation; the only limitation is that they must be unique within the directory. Even if a file with the same name and location gets trashed many times, each subsequent trashing must not overwrite a previous copy. The access rights, access time, modification time and extended attributes (if any) for a file/directory in $trash/files SHOULD be the same as the file/directory had before getting trashed.

IMPORTANT NOTE. While an implementation may choose to base filenames in the $trash/files directory on the original filenames, this is never to be taken for granted6. A filename in the $trash/files directory MUST NEVER be used to recover the original filename; use the info file (see below) for that. (If an info file corresponding to a file/directory in $trash/files is not available, this is an emergency case, and MUST be clearly presented as such to the user or to the system administrator).

The $trash/info directory contains an “information file” for every file and directory in $trash/files. This file MUST have exactly the same name as the file or directory in $trash/files, plus the extension “.trashinfo”7.

The format of this file is similar to the format of a desktop entry file, as described in the Desktop Entry Specification . Its first line must be [Trash Info].

It also must have two lines that are key/value pairs as described in the Desktop Entry Specification:

  • The key “Path” contains the original location of the file/directory, as either an absolute pathname (starting with the slash character “/”) or a relative pathname (starting with any other character). A relative pathname is to be from the directory in which the trash directory resides (i.e., from $XDG_DATA_HOME for the “home trash” directory); it MUST not include a “..” directory, and for files not “under” that directory, absolute pathnames must be used. The system SHOULD only support absolute pathnames in the “home trash” directory, not in the directories under $topdir.

    The value type for this key is “string”; it should store the file name as the sequence of bytes produced by the file system, with characters escaped as in URLs (as defined by RFC 2396, section 2).

  • The key “DeletionDate” contains the date and time when the file/directory was trashed. The date and time are to be in the YYYY-MM-DDThh:mm:ss format (see RFC 3339). The time zone should be the user's (or filesystem's) local time. The value type for this key is “string”.

Example:

[Trash Info]
Path=foo/bar/meow.bow-wow
DeletionDate=20040831T22:32:08

The implementation MUST ignore any other lines in this file, except the first line (must be [Trash Info]) and these two key/value pairs. If a string that starts with “Path=” or “DeletionDate=” occurs several times, the first occurence is to be used.8

Note that $trash/info has no subdirectories. For a directory in $trash/files, only an information file for its own name is needed. This is because, when a subdirectory gets trashed, it must be moved to $trash/files with its entire contents. The names of the files and directories within the directory MUST NOT be altered; the implementation also SHOULD preserve the access and modification time for them.

When trashing a file or directory, the implementation MUST create the corresponding file in $trash/info first. When trashing a file or directory, the implementation MUST create thecorresponding file in $trash/info first. Moreover, it MUST try to do this in an atomic fashion, so that if two processes try trash files with the same filename this will result in two different trash files. On Unix-line systems this is done by generating a filename, and then opening with O_EXCL. If that succeeds the creation was atomic (at least on the same machine), if it fails you need to pick another filename.

Implementation notes

The names of the files/directories in $trash/info SHOULD be somehow related to original file names. This can help manual recovery in emergency cases (for example, if the corresponding info file is lost).

When trashing a file or directory, the implementation should check whether the user has the necessary permissions to delete it, before starting the trashing operation itself.

When copying, rather than moving, a file into the trash (i.e. When trashing to the “home trash” from a different partition), exact preservation of permissions might be impossible. Notably, a file.directory that was owned by another user will now be owned by this user (changing owners is usually only available to root). This should not cause the trashing operation to fail.

In this same situation, setting the permissions should be done after writing the copied file, as they may make it unwriteable..

A trashing operation might be refused because of insufficient permissions, even when the user does have the right to delete a file or directory. This may happen when the user has the right to delete a file/directory, but not to read it (or, in the case of a directory, to list it). In this case, the best solution is probably to warn the user, offering options to delete the file/directory or leave it alone.

Automatic trash cleaning may, and probably eventually should, be implemented. But the implementation should be somehow known to the user.

If a directory was trashed in its entirety, it is easiest to undelete it or remove it from the trash only in its entirety as well, not as separate files. The user might not have the permissions to delete some files in it even while he does have the permission to delete the directory!

Important note on scope. This specification currently does NOT define trashing on remote machines where multiuser permissions are implemented but the numeric user ID is not supported, like FTP sites and CIFS shares. In systems implementing this specification, trashing of files from such machines is to be done only to the user's home trash directory (if at all). A future version may address this limitation.

Administrativia

Status of this document

This document is, at this moment, only a draft. It will hopefully become an official or semi-official FreeDesktop.org specification in the future.

Date of first public distribution: August 30, 2004. This document will serve as evidence of prior art for any patent filed after this date.

Copyright and License

Copyright (C) 2004 Mikhail Ramendik , mr@ramendik.ru .

The originators of the ideas that are described here did not object to this copyright. The author is ready to transfer the copyright to a standards body that would be committed to keeping this specification, or any successor to it, an open standard.

The license: Use and distribute as you wish. If you make a modified version and redistribute it, (a) keep the name of the author and contributors somewhere, and (b) indicate that this is a modified version.

Implementation under any license at all is explicitly allowed.

Location

http://www.ramendik.ru/docs/trashspec.html . If this document gets hosted by FreeDesktop.org, a link to the page will still be available at this location.

http://www.ramendik.ru/docs/trashspec.0.5.html is the permanent location of this version.

Version history

0.1 “First try”, August 30, 2004. Initial draft. “Implementation notes” not written as yet.

0.2 August 30, 2004. Updated with feedback by Alexander Larsson <alexl@redhat.com> and by Dave Cridland <dave@cridland.net>

0.3 September 8, 2004. Changed the name and location of the “home trash” directory, and introduced the generic term “home trash”. Changed the trash info file format to a .desktop-like one. Added directions on creation of info files and copying of trashed files. Changed user names to user ids. Added implementation notes. Added a copyright notice.

0.4 September 9, 2004. Changed [Trash entry] to [Trash info] and fixed some typo's

0.5 September 9, 2004. Changed [Trash info] to [Trash Info]

0.6 October 8, 2004. Corrections by Alexander Larsson <alexl@redhat.com> . Also added “note on scope”. Cleaned up HTML. Added a link to this document on the freedesktop.org standards page

0.7 April 12, 2005. Added URL-style encoding for the name of the deleted file, as implemented in KDE 3.4



1However, developers of preloaded libraries should somehow work around the case when a desktop environment also supporting the Trash specification is run on top of them. “Double trashing” and “trashing of the trash” should be avoided.

2To be more precise, for every user who can use the trash facility. In general, all human users, and possibly some “robotic” ones like ftp, should be able to use the trash facility.

3Note the dot in the beginning, and for case sensitive file systems, note the case.

4To be more precise, from a partition/device different from the one on which $XDG_DATA_HOME resides.

5“$trash/files/”, not into “$trash/” as in many existing implementations!

6At least because another implementation might trash files into the same trash directory

7For example, if the file in $trash/files is named foo.bar , the corresponding file in $trash/info must be named foo.bar.trashinfo

8This provides for future extension

trash-cli-0.12.9.14/docs/who-packaged-trash-cli.txt000066400000000000000000000024031237725047000216310ustar00rootroot00000000000000Which are the distributions packaged trash-cli? =============================================== - Fedora: not included (2009-01-04) - Ubuntu: (included since 2008-07-16) - Bug: https://bugs.launchpad.net/ubuntu/+bug/219601 - Included in jaunty(universe) and intrepid(universe) - OpenSUSE: (seems not included) (2009-01-04) - Added in the OpenSuse whishlist http://en.opensuse.org/Wishlist_Base#T (2009-01-04) - Debian: - Included in [http://packages.debian.org/unstable/utils/trash-cli sid] as of version 0.1.10.28-2 (2009-01-04) - Included in [http://packages.debian.org/lenny/trash-cli lenny] as of version 0.1.10.28-2 (2009-01-04) - Mandriva: (seems not included) (2009-01-04) - Archlinux: (seems included) http://aur.archlinux.org/packages/trash-cli/ (2009-01-04) - Gentoo: (not yet included) - Inclusion request: http://bugs.gentoo.org/238831 - FreeBSD: ? - foresight linux: included - updated on: 2009-04-13 - trash-cli version: 0.1.10.r55 - source package: http://www.rpath.org/repos/foresight/troveInfo?t=trash-cli%3Asource - binary package: http://www.rpath.org/repos/foresight/troveInfo?t=trash-cli - Other? If you've packaged trash-cli for another system (or need help doing so) let us know leaving a comment or mailing me. trash-cli-0.12.9.14/install-rpm.sh000077500000000000000000000003601237725047000165230ustar00rootroot00000000000000python setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES # 'brp-compress' gzips the man pages without distutils knowing... fix this sed -i -e 's@man/man\([[:digit:]]\)/\(.\+\.[[:digit:]]\)$@man/man\1/\2.gz@g' INSTALLED_FILES trash-cli-0.12.9.14/integration_tests/000077500000000000000000000000001237725047000174705ustar00rootroot00000000000000trash-cli-0.12.9.14/integration_tests/__init__.py000066400000000000000000000000001237725047000215670ustar00rootroot00000000000000trash-cli-0.12.9.14/integration_tests/assert_equals_with_unidiff.py000066400000000000000000000012771237725047000254630ustar00rootroot00000000000000# Copyright (C) 2009-2011 Andrea Francia Trivolzio(PV) Italy def assert_equals_with_unidiff(expected, actual): def unidiff(expected, actual): import difflib expected=expected.splitlines(1) actual=actual.splitlines(1) diff=difflib.unified_diff(expected, actual, fromfile='Expected', tofile='Actual', lineterm='\n', n=10) return ''.join(diff) from nose.tools import assert_equals assert_equals(expected, actual, "\n" "Expected:%s\n" % repr(expected) + " Actual:%s\n" % repr(actual) + unidiff(expected, actual)) trash-cli-0.12.9.14/integration_tests/describe_trash_list.py000066400000000000000000000236271237725047000240700ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy import os from trashcli.trash import ListCmd from files import (write_file, require_empty_dir, make_sticky_dir, make_unsticky_dir, make_unreadable_file, make_empty_file, make_parent_for) from nose.tools import istest from .output_collector import OutputCollector from trashinfo import ( a_trashinfo, a_trashinfo_without_date, a_trashinfo_without_path, a_trashinfo_with_invalid_date) from textwrap import dedent class Setup(object): def setUp(self): require_empty_dir('XDG_DATA_HOME') require_empty_dir('topdir') self.user = TrashListUser( environ = {'XDG_DATA_HOME': 'XDG_DATA_HOME'}) self.home_trashcan = FakeTrashDir('XDG_DATA_HOME/Trash') self.add_trashinfo = self.home_trashcan.add_trashinfo def when_dir_is_sticky(self, path): make_sticky_dir(path) def when_dir_exists_unsticky(self, path): make_unsticky_dir(path) @istest class describe_trash_list(Setup): @istest def should_output_the_help_message(self): self.user.run('trash-list', '--help') self.user.should_read_output(dedent("""\ Usage: trash-list [OPTIONS...] List trashed files Options: --version show program's version number and exit -h, --help show this help message and exit Report bugs to http://code.google.com/p/trash-cli/issues """)) @istest def should_output_nothing_when_trashcan_is_empty(self): self.user.run_trash_list() self.user.should_read_output('') @istest def should_output_deletion_date_and_path(self): self.add_trashinfo('/aboslute/path', '2001-02-03T23:55:59') self.user.run_trash_list() self.user.should_read_output( "2001-02-03 23:55:59 /aboslute/path\n") @istest def should_output_info_for_multiple_files(self): self.add_trashinfo("/file1", "2000-01-01T00:00:01") self.add_trashinfo("/file2", "2000-01-01T00:00:02") self.add_trashinfo("/file3", "2000-01-01T00:00:03") self.user.run_trash_list() self.user.should_read_output( "2000-01-01 00:00:01 /file1\n" "2000-01-01 00:00:02 /file2\n" "2000-01-01 00:00:03 /file3\n") @istest def should_output_unknown_dates_with_question_marks(self): self.home_trashcan.having_file(a_trashinfo_without_date()) self.user.run_trash_list() self.user.should_read_output("????-??-?? ??:??:?? /path\n") @istest def should_output_invalid_dates_using_question_marks(self): self.home_trashcan.having_file(a_trashinfo_with_invalid_date()) self.user.run_trash_list() self.user.should_read_output("????-??-?? ??:??:?? /path\n") @istest def should_warn_about_empty_trashinfos(self): self.home_trashcan.touch('empty.trashinfo') self.user.run_trash_list() self.user.should_read_error( "Parse Error: XDG_DATA_HOME/Trash/info/empty.trashinfo: " "Unable to parse Path.\n") @istest def should_warn_about_unreadable_trashinfo(self): self.home_trashcan.having_unreadable('unreadable.trashinfo') self.user.run_trash_list() self.user.should_read_error( "[Errno 13] Permission denied: " "'XDG_DATA_HOME/Trash/info/unreadable.trashinfo'\n") @istest def should_warn_about_unexistent_path_entry(self): self.home_trashcan.having_file(a_trashinfo_without_path()) self.user.run_trash_list() self.user.should_read_error( "Parse Error: XDG_DATA_HOME/Trash/info/1.trashinfo: " "Unable to parse Path.\n") self.user.should_read_output('') @istest class with_a_top_trash_dir(Setup): def setUp(self): super(type(self),self).setUp() self.top_trashdir1 = FakeTrashDir('topdir/.Trash/123') self.user.set_fake_uid(123) self.user.add_volume('topdir') @istest def should_list_its_contents_if_parent_is_sticky(self): self.when_dir_is_sticky('topdir/.Trash') self.and_contains_a_valid_trashinfo() self.user.run_trash_list() self.user.should_read_output("2000-01-01 00:00:00 topdir/file1\n") @istest def and_should_warn_if_parent_is_not_sticky(self): self.when_dir_exists_unsticky('topdir/.Trash') self.and_dir_exists('topdir/.Trash/123') self.user.run_trash_list() self.user.should_read_error("TrashDir skipped because parent not sticky: topdir/.Trash/123\n") @istest def but_it_should_not_warn_when_the_parent_is_unsticky_but_there_is_no_trashdir(self): self.when_dir_exists_unsticky('topdir/.Trash') self.but_does_not_exists_any('topdir/.Trash/123') self.user.run_trash_list() self.user.should_read_error("") @istest def should_ignore_trash_from_a_unsticky_topdir(self): self.when_dir_exists_unsticky('topdir/.Trash') self.and_contains_a_valid_trashinfo() self.user.run_trash_list() self.user.should_read_output("") @istest def it_should_ignore_Trash_is_a_symlink(self): self.when_is_a_symlink_to_a_dir('topdir/.Trash') self.and_contains_a_valid_trashinfo() self.user.run_trash_list() self.user.should_read_output('') @istest def and_should_warn_about_it(self): self.when_is_a_symlink_to_a_dir('topdir/.Trash') self.and_contains_a_valid_trashinfo() self.user.run_trash_list() self.user.should_read_error('TrashDir skipped because parent not sticky: topdir/.Trash/123\n') def but_does_not_exists_any(self, path): assert not os.path.exists(path) def and_dir_exists(self, path): os.mkdir(path) assert os.path.isdir(path) def and_contains_a_valid_trashinfo(self): self.top_trashdir1.add_trashinfo('file1', '2000-01-01T00:00:00') def when_is_a_symlink_to_a_dir(self, path): dest = "%s-dest" % path os.mkdir(dest) rel_dest = os.path.basename(dest) os.symlink(rel_dest, path) @istest class describe_when_a_file_is_in_alternate_top_trashdir(Setup): @istest def should_list_contents_of_alternate_trashdir(self): self.user.set_fake_uid(123) self.user.add_volume('topdir') self.top_trashdir2 = FakeTrashDir('topdir/.Trash-123') self.top_trashdir2.add_trashinfo('file', '2000-01-01T00:00:00') self.user.run_trash_list() self.user.should_read_output("2000-01-01 00:00:00 topdir/file\n") @istest class describe_trash_list_with_raw_option: def setup(self): self.having_XDG_DATA_HOME('XDG_DATA_HOME') self.running('trash-list', '--raw') @istest def output_should_contains_trashinfo_paths(self): from nose import SkipTest; raise SkipTest() self.having_trashinfo('foo.trashinfo') self.output_should_contain_line( 'XDG_DATA_HOME/Trash/info/foo.trashinfo') @istest def output_should_contains_backup_copy_paths(self): from nose import SkipTest; raise SkipTest() self.having_trashinfo('foo.trashinfo') self.output_should_contain_line( 'XDG_DATA_HOME/Trash/files/foo') def having_XDG_DATA_HOME(self, value): self.XDG_DATA_HOME = value def running(self, *argv): user = TrashListUser( environ = {'XDG_DATA_HOME': self.XDG_DATA_HOME}) user.run(argv) self.output = user.output() def output_should_contain_line(self, line): assert line in self.output_lines() def output_lines(self): return [line.rstrip('\n') for line in self.output.splitlines()] class FakeTrashDir: def __init__(self, path): self.path = path + '/info' self.number = 1 def touch(self, path_relative_to_info_dir): make_empty_file(self.join(path_relative_to_info_dir)) def having_unreadable(self, path_relative_to_info_dir): path = self.join(path_relative_to_info_dir) make_unreadable_file(path) def join(self, path_relative_to_info_dir): import os return os.path.join(self.path, path_relative_to_info_dir) def having_file(self, contents): path = '%(info_dir)s/%(name)s.trashinfo' % { 'info_dir' : self.path, 'name' : str(self.number)} make_parent_for(path) write_file(path, contents) self.number += 1 self.path_of_last_file_added = path def add_trashinfo(self, escaped_path_entry, formatted_deletion_date): self.having_file(a_trashinfo(escaped_path_entry, formatted_deletion_date)) class TrashListUser: def __init__(self, environ={}): self.stdout = OutputCollector() self.stderr = OutputCollector() self.environ = environ self.fake_getuid = self.error self.volumes = [] def run_trash_list(self): self.run('trash-list') def run(self,*argv): from trashcli.trash import FileSystemReader file_reader = FileSystemReader() file_reader.list_volumes = lambda: self.volumes ListCmd( out = self.stdout, err = self.stderr, environ = self.environ, getuid = self.fake_getuid, file_reader = file_reader, list_volumes = lambda: self.volumes, ).run(*argv) def set_fake_uid(self, uid): self.fake_getuid = lambda: uid def add_volume(self, mount_point): self.volumes.append(mount_point) def error(self): raise ValueError() def should_read_output(self, expected_value): self.stdout.assert_equal_to(expected_value) def should_read_error(self, expected_value): self.stderr.assert_equal_to(expected_value) def output(self): return self.stdout.getvalue() trash-cli-0.12.9.14/integration_tests/files.py000066400000000000000000000035261237725047000211520ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import assert_equals from trashcli.trash import has_sticky_bit import os, shutil def having_file(path): dirname=os.path.dirname(path) if dirname != '': make_dirs(dirname) open(path,'w').close() assert os.path.isfile(path) make_empty_file = having_file def write_file(filename, contents=''): parent = os.path.dirname(filename) if not os.path.isdir(parent): os.makedirs(parent) file(filename, 'w').write(contents) assert_equals(file(filename).read(), contents) def require_empty_dir(path): if os.path.exists(path): shutil.rmtree(path) make_dirs(path) assert os.path.isdir(path) assert_equals([], list(os.listdir(path))) def having_empty_dir(path): require_empty_dir(path) def make_dirs(path): if not os.path.isdir(path): os.makedirs(path) assert os.path.isdir(path) def make_parent_for(path): parent = os.path.dirname(path) make_dirs(parent) def make_sticky_dir(path): os.mkdir(path) set_sticky_bit(path) def make_unsticky_dir(path): os.mkdir(path) unset_sticky_bit(path) def make_dir_unsticky(path): assert_is_dir(path) unset_sticky_bit(path) def assert_is_dir(path): assert os.path.isdir(path) def set_sticky_bit(path): import stat os.chmod(path, os.stat(path).st_mode | stat.S_ISVTX) def unset_sticky_bit(path): import stat os.chmod(path, os.stat(path).st_mode & ~ stat.S_ISVTX) assert not has_sticky_bit(path) def touch(path): open(path,'a+').close() def ensure_non_sticky_dir(path): import os assert os.path.isdir(path) assert not has_sticky_bit(path) def make_unreadable_file(path): write_file(path, '') import os os.chmod(path, 0) from nose.tools import assert_raises with assert_raises(IOError): file(path).read() trash-cli-0.12.9.14/integration_tests/output_collector.py000066400000000000000000000012031237725047000234440ustar00rootroot00000000000000from assert_equals_with_unidiff import assert_equals_with_unidiff class OutputCollector: def __init__(self): from StringIO import StringIO self.stream = StringIO() self.getvalue = self.stream.getvalue def write(self,data): self.stream.write(data) def assert_equal_to(self, expected): return self.should_be(expected) def should_be(self, expected): assert_equals_with_unidiff(expected, self.stream.getvalue()) def should_match(self, regex): text = self.stream.getvalue() from nose.tools import assert_regexp_matches assert_regexp_matches(text, regex) trash-cli-0.12.9.14/integration_tests/test_file_descriptions.py000066400000000000000000000030171237725047000246070ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import describe from .files import require_empty_dir, having_file from nose.tools import assert_equals import os class TestDescritions: def setUp(self): require_empty_dir('sandbox') def test_on_directories(self): assert_equals("directory", describe('.')) assert_equals("directory", describe("..")) assert_equals("directory", describe("sandbox")) def test_on_dot_directories(self): assert_equals("`.' directory", describe("sandbox/.")) assert_equals("`.' directory", describe("./.")) def test_on_dot_dot_directories(self): assert_equals("`..' directory", describe("./..")) assert_equals("`..' directory", describe("sandbox/..")) def test_name_for_regular_files_non_empty_files(self): write_file("sandbox/non-empty", "contents") assert_equals("regular file", describe("sandbox/non-empty")) def test_name_for_empty_file(self): having_file('sandbox/empty') assert_equals("regular empty file", describe("sandbox/empty")) def test_name_for_symbolic_links(self): os.symlink('nowhere', "sandbox/symlink") assert_equals("symbolic link", describe("sandbox/symlink")) def test_name_for_non_existent_entries(self): assert not os.path.exists('non-existent') assert_equals("non existent", describe('non-existent')) def write_file(path, contents): f = open(path, 'w') f.write(contents) f.close() trash-cli-0.12.9.14/integration_tests/test_filesystem.py000066400000000000000000000027351237725047000232740ustar00rootroot00000000000000# Copyright (C) 2008-2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import has_sticky_bit, mkdirs, FileSystemReader from .files import require_empty_dir, having_file, set_sticky_bit import os class TestWithInSandbox: def test_mkdirs_with_default_mode(self): mkdirs("sandbox/test-dir/sub-dir") assert os.path.isdir("sandbox/test-dir/sub-dir") def test_has_sticky_bit_returns_true(self): having_file( "sandbox/sticky") run('chmod +t sandbox/sticky') assert has_sticky_bit('sandbox/sticky') def test_has_sticky_bit_returns_false(self): having_file( "sandbox/non-sticky") run('chmod -t sandbox/non-sticky') assert not has_sticky_bit("sandbox/non-sticky") def setUp(self): require_empty_dir('sandbox') is_sticky_dir=FileSystemReader().is_sticky_dir class Test_is_sticky_dir: def test_dir_non_sticky(self): mkdirs('sandbox/dir'); assert not is_sticky_dir('sandbox/dir') def test_dir_sticky(self): mkdirs('sandbox/dir'); set_sticky_bit('sandbox/dir') assert is_sticky_dir('sandbox/dir') def test_non_dir_but_sticky(self): having_file('sandbox/dir'); set_sticky_bit('sandbox/dir') assert not is_sticky_dir('sandbox/dir') def setUp(self): require_empty_dir('sandbox') def run(command): import subprocess assert subprocess.call(command.split()) == 0 trash-cli-0.12.9.14/integration_tests/test_listing_all_trashinfo_in_a_trashdir.py000066400000000000000000000027601237725047000303520ustar00rootroot00000000000000from trashcli.trash import TrashDirectory from files import require_empty_dir from files import write_file from nose.tools import assert_equals, assert_items_equal from mock import Mock class TestWhenListingTrashinfo: def setUp(self): require_empty_dir('sandbox') self.trash_dir = TrashDirectory('sandbox', '/') self.logger = Mock() self.trash_dir.logger = self.logger def test_should_list_a_trashinfo(self): write_file('sandbox/info/foo.trashinfo') result = self.list_trashinfos() assert_equals(['sandbox/info/foo.trashinfo'], result) def test_should_list_multiple_trashinfo(self): write_file('sandbox/info/foo.trashinfo') write_file('sandbox/info/bar.trashinfo') write_file('sandbox/info/baz.trashinfo') result = self.list_trashinfos() assert_items_equal(['sandbox/info/foo.trashinfo', 'sandbox/info/baz.trashinfo', 'sandbox/info/bar.trashinfo'], result) def test_should_ignore_non_trashinfo(self): write_file('sandbox/info/not-a-trashinfo') result = self.list_trashinfos() assert_equals([], result) def test_non_trashinfo_should_reported_as_a_warn(self): write_file('sandbox/info/not-a-trashinfo') self.list_trashinfos() self.logger.warning.assert_called_with('Non .trashinfo file in info dir') def list_trashinfos(self): return list(self.trash_dir.all_info_files()) trash-cli-0.12.9.14/integration_tests/test_persist.py000066400000000000000000000054041237725047000225750ustar00rootroot00000000000000# Copyright (C) 2008-2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import TrashDirectory from trashcli.trash import TrashInfo from integration_tests.files import require_empty_dir from nose.tools import assert_equals, assert_true import os from datetime import datetime from textwrap import dedent join = os.path.join class TestTrashDirectory_persit_trash_info: def setUp(self): self.trashdirectory_base_dir = os.path.realpath("./sandbox/testTrashDirectory") require_empty_dir(self.trashdirectory_base_dir) self.instance=TrashDirectory(self.trashdirectory_base_dir, "/") def test_persist_trash_info_first_time(self): trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) basename=os.path.basename(trash_info.path) content=trash_info.render() (trash_info_file, trash_info_id) = self.instance.persist_trash_info(basename,content) assert_equals('dummy-path', trash_info_id) assert_equals(join(self.trashdirectory_base_dir,'info', 'dummy-path.trashinfo'), trash_info_file) assert_equals(dedent("""\ [Trash Info] Path=dummy-path DeletionDate=2007-01-01T00:00:00 """), read(trash_info_file)) def test_persist_trash_info_first_100_times(self): self.test_persist_trash_info_first_time() for i in range(1,100) : trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) basename=os.path.basename(trash_info.path) content=trash_info.render() (trash_info_file, trash_info_id)=self.instance.persist_trash_info(basename,content) assert_equals('dummy-path'+"_" + str(i), trash_info_id) assert_equals(dedent("""\ [Trash Info] Path=dummy-path DeletionDate=2007-01-01T00:00:00 """), read(trash_info_file)) def test_persist_trash_info_other_times(self): self.test_persist_trash_info_first_100_times() for i in range(101,200) : trash_info=TrashInfo("dummy-path", datetime(2007,01,01)) basename=os.path.basename(trash_info.path) content=trash_info.render() (trash_info_file, trash_info_id)=self.instance.persist_trash_info(basename,content) assert_true(trash_info_id.startswith("dummy-path_")) assert_equals(dedent("""\ [Trash Info] Path=dummy-path DeletionDate=2007-01-01T00:00:00 """), read(trash_info_file)) test_persist_trash_info_first_100_times.stress_test = True test_persist_trash_info_other_times.stress_test = True def read(path): return file(path).read() trash-cli-0.12.9.14/integration_tests/test_restore_trash.py000066400000000000000000000066031237725047000237720ustar00rootroot00000000000000import os from nose.tools import istest from trashcli.trash import RestoreCmd from .files import require_empty_dir from .output_collector import OutputCollector from trashinfo import a_trashinfo @istest class describe_restore_trash: @istest def it_should_do_nothing_when_no_file_have_been_found_in_current_dir(self): self.when_running_restore_trash() self.output_should_match('No files trashed from current dir.+') @istest def it_should_show_the_file_deleted_from_the_current_dir(self): self.having_a_trashed_file('/foo/bar') self.when_running_restore_trash(from_dir='/foo') self.output_should_match( ' 0 2000-01-01 00:00:01 /foo/bar\n.*\n') self.error_should_be('') @istest def it_should_restore_the_file_selected_by_the_user(self): self.having_a_file_trashed_from_current_dir('foo') self.when_running_restore_trash( from_dir=os.getcwd(), with_user_typing = '0') self.file_should_have_been_restored('foo') @istest def it_should_exit_gracefully_when_user_selects_nothing(self): self.having_a_trashed_file('/foo/bar') self.when_running_restore_trash( from_dir='/foo', with_user_typing = '') self.output_should_match( '.*\nExiting\n') self.error_should_be('') @istest def it_should_refuse_overwriting_existing_file(self): self.having_a_file_trashed_from_current_dir('foo') file('foo', 'a+').close() os.chmod('foo', 000) self.when_running_restore_trash(from_dir=current_dir(), with_user_typing = '0') self.error_should_be('Refusing to overwrite existing file "foo".\n') def setUp(self): require_empty_dir('XDG_DATA_HOME') trashcan = TrashCan('XDG_DATA_HOME/Trash') self.having_a_trashed_file = trashcan.make_trashed_file out = OutputCollector() err = OutputCollector() self.when_running_restore_trash = RestoreTrashRunner(out, err, 'XDG_DATA_HOME') self.output_should_match = out.should_match self.error_should_be = err.should_be def having_a_file_trashed_from_current_dir(self, filename): self.having_a_trashed_file(os.path.join(os.getcwd(), filename)) if os.path.exists(filename): os.remove(filename) assert not os.path.exists(filename) def file_should_have_been_restored(self, filename): assert os.path.exists(filename) def current_dir(): return os.getcwd() class RestoreTrashRunner: def __init__(self, out, err, XDG_DATA_HOME): self.environ = {'XDG_DATA_HOME': XDG_DATA_HOME} self.out = out self.err = err def __call__(self, from_dir='/', with_user_typing=''): RestoreCmd( stdout = self.out, stderr = self.err, environ = self.environ, exit = [].append, input = lambda msg: with_user_typing, curdir = lambda: from_dir ).run() class TrashCan: def __init__(self, path): self.path = path def make_trashed_file(self, path): from .files import write_file write_file('%s/info/foo.trashinfo' % self.path, a_trashinfo(path)) write_file('%s/files/foo' % self.path) trash-cli-0.12.9.14/integration_tests/test_trash_empty.py000066400000000000000000000214461237725047000234470ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import (assert_equals, assert_items_equal, istest) from trashcli.trash import EmptyCmd from StringIO import StringIO import os from files import write_file, require_empty_dir, make_dirs, set_sticky_bit from files import having_file from mock import MagicMock @istest class WhenCalledWithoutArguments: def setUp(self): require_empty_dir('XDG_DATA_HOME') self.info_dir_path = 'XDG_DATA_HOME/Trash/info' self.files_dir_path = 'XDG_DATA_HOME/Trash/files' self.environ = {'XDG_DATA_HOME':'XDG_DATA_HOME'} now = MagicMock(side_effect=RuntimeError) self.empty_cmd = EmptyCmd( out = StringIO(), err = StringIO(), environ = self.environ, now = now, list_volumes = no_volumes, ) def user_run_trash_empty(self): self.empty_cmd.run('trash-empty') @istest def it_should_remove_an_info_file(self): self.having_a_trashinfo_in_trashcan('foo.trashinfo') self.user_run_trash_empty() self.assert_dir_empty(self.info_dir_path) @istest def it_should_remove_all_the_infofiles(self): self.having_three_trashinfo_in_trashcan() self.user_run_trash_empty() self.assert_dir_empty(self.info_dir_path) @istest def it_should_remove_the_backup_files(self): self.having_one_trashed_file() self.user_run_trash_empty() self.assert_dir_empty(self.files_dir_path) @istest def it_should_keep_unknown_files_found_in_infodir(self): self.having_file_in_info_dir('not-a-trashinfo') self.user_run_trash_empty() self.assert_dir_contains(self.info_dir_path, 'not-a-trashinfo') @istest def but_it_should_remove_orphan_files_from_the_files_dir(self): self.having_orphan_file_in_files_dir() self.user_run_trash_empty() self.assert_dir_empty(self.files_dir_path) @istest def it_should_purge_also_directories(self): os.makedirs("XDG_DATA_HOME/Trash/files/a-dir") self.user_run_trash_empty() def assert_dir_empty(self, path): assert len(os.listdir(path)) == 0 def assert_dir_contains(self, path, filename): assert os.path.exists(os.path.join(path, filename)) def having_a_trashinfo_in_trashcan(self, basename_of_trashinfo): having_file(os.path.join(self.info_dir_path, basename_of_trashinfo)) def having_three_trashinfo_in_trashcan(self): self.having_a_trashinfo_in_trashcan('foo.trashinfo') self.having_a_trashinfo_in_trashcan('bar.trashinfo') self.having_a_trashinfo_in_trashcan('baz.trashinfo') assert_items_equal(['foo.trashinfo', 'bar.trashinfo', 'baz.trashinfo'], os.listdir(self.info_dir_path)) def having_one_trashed_file(self): self.having_a_trashinfo_in_trashcan('foo.trashinfo') having_file(self.files_dir_path +'/foo') self.files_dir_should_not_be_empty() def files_dir_should_not_be_empty(self): assert len(os.listdir(self.files_dir_path)) != 0 def having_file_in_info_dir(self, filename): having_file(os.path.join(self.info_dir_path, filename)) def having_orphan_file_in_files_dir(self): complete_path = os.path.join(self.files_dir_path, 'a-file-without-any-associated-trashinfo') having_file(complete_path) assert os.path.exists(complete_path) @istest class When_invoked_with_N_days_as_argument: def setUp(self): require_empty_dir('XDG_DATA_HOME') self.xdg_data_home = 'XDG_DATA_HOME' self.environ = {'XDG_DATA_HOME':'XDG_DATA_HOME'} self.now = MagicMock(side_effect=RuntimeError) self.empty_cmd=EmptyCmd( out = StringIO(), err = StringIO(), environ = self.environ, now = self.now, list_volumes = no_volumes, ) def user_run_trash_empty(self, *args): self.empty_cmd.run('trash-empty', *args) def set_clock_at(self, yyyy_mm_dd): self.now.side_effect = lambda:date(yyyy_mm_dd) def date(yyyy_mm_dd): from datetime import datetime return datetime.strptime(yyyy_mm_dd, '%Y-%m-%d') @istest def it_should_keep_files_newer_than_N_days(self): self.having_a_trashed_file('foo', '2000-01-01') self.set_clock_at('2000-01-01') self.user_run_trash_empty('2') self.file_should_have_been_kept_in_trashcan('foo') @istest def it_should_remove_files_older_than_N_days(self): self.having_a_trashed_file('foo', '1999-01-01') self.set_clock_at('2000-01-01') self.user_run_trash_empty('2') self.file_should_have_been_removed_from_trashcan('foo') @istest def it_should_kept_files_with_invalid_deletion_date(self): self.having_a_trashed_file('foo', 'Invalid Date') self.set_clock_at('2000-01-01') self.user_run_trash_empty('2') self.file_should_have_been_kept_in_trashcan('foo') def having_a_trashed_file(self, name, date): contents = "DeletionDate=%sT00:00:00\n" % date write_file(self.trashinfo(name), contents) def trashinfo(self, name): return '%(dirname)s/Trash/info/%(name)s.trashinfo' % { 'dirname' : self.xdg_data_home, 'name' : name } def file_should_have_been_kept_in_trashcan(self, trashinfo_name): assert os.path.exists(self.trashinfo(trashinfo_name)) def file_should_have_been_removed_from_trashcan(self, trashinfo_name): assert not os.path.exists(self.trashinfo(trashinfo_name)) class TestEmptyCmdWithMultipleVolumes: def setUp(self): require_empty_dir('topdir') self.empty=EmptyCmd( out = StringIO(), err = StringIO(), environ = {}, getuid = lambda: 123, list_volumes = lambda: ['topdir'],) def test_it_removes_trashinfos_from_method_1_dir(self): self.make_proper_top_trash_dir('topdir/.Trash') having_file('topdir/.Trash/123/info/foo.trashinfo') self.empty.run('trash-empty') assert not os.path.exists('topdir/.Trash/123/info/foo.trashinfo') def test_it_removes_trashinfos_from_method_2_dir(self): having_file('topdir/.Trash-123/info/foo.trashinfo') self.empty.run('trash-empty') assert not os.path.exists('topdir/.Trash-123/info/foo.trashinfo') def make_proper_top_trash_dir(self, path): make_dirs(path) set_sticky_bit(path) from textwrap import dedent class TestTrashEmpty_on_help: def test_help_output(self): err, out = StringIO(), StringIO() cmd = EmptyCmd(err = err, out = out, environ = {}, list_volumes = no_volumes,) cmd.run('trash-empty', '--help') assert_equals(out.getvalue(), dedent("""\ Usage: trash-empty [days] Purge trashed files. Options: --version show program's version number and exit -h, --help show this help message and exit Report bugs to http://code.google.com/p/trash-cli/issues """)) class TestTrashEmpty_on_version(): def test_it_print_version(self): err, out = StringIO(), StringIO() cmd = EmptyCmd(err = err, out = out, environ = {}, version = '1.2.3', list_volumes = no_volumes,) cmd.run('trash-empty', '--version') assert_equals(out.getvalue(), dedent("""\ trash-empty 1.2.3 """)) class describe_trash_empty_command_line__on_invalid_options(): def setUp(self): self.err, self.out = StringIO(), StringIO() self.cmd = EmptyCmd( err = self.err, out = self.out, environ = {}, list_volumes = no_volumes) def it_should_fail(self): self.exit_code = self.cmd.run('trash-empty', '-2') exit_code_for_command_line_usage = 64 assert_equals(exit_code_for_command_line_usage, self.exit_code) def it_should_complain_to_the_standard_error(self): self.exit_code = self.cmd.run('trash-empty', '-2') assert_equals(self.err.getvalue(), dedent("""\ trash-empty: invalid option -- '2' """)) def test_with_a_different_option(self): self.cmd.run('trash-empty', '-3') assert_equals(self.err.getvalue(), dedent("""\ trash-empty: invalid option -- '3' """)) def no_volumes(): return [] trash-cli-0.12.9.14/integration_tests/test_trash_put.py000066400000000000000000000143641237725047000231220ustar00rootroot00000000000000# Copyright (C) 2009-2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import TrashPutCmd import os from nose.tools import istest, assert_equals, assert_not_equals from nose.tools import assert_in from .files import having_file, require_empty_dir, having_empty_dir from .files import make_sticky_dir from trashcli.fstab import FakeFstab class TrashPutTest: def setUp(self): self.prepare_fixture() def prepare_fixture(self): require_empty_dir('sandbox') self.environ = {'XDG_DATA_HOME': 'sandbox/XDG_DATA_HOME' } from .output_collector import OutputCollector self.out = OutputCollector() self.err = OutputCollector() self.fstab = FakeFstab() self.stderr_should_be = self.err.should_be self.output_should_be = self.out.should_be def run_trashput(self, *argv): cmd = TrashPutCmd( stdout = self.out, stderr = self.err, environ = self.environ, fstab = self.fstab ) self.exit_code = cmd.run(list(argv)) self.stderr = self.err.getvalue() @istest class trash_put_stderr(TrashPutTest): @istest def should_tell_where_a_file_is_trashed(self): having_file('foo') self.run_trashput('trash-put', '-v', 'foo') assert_in("trash-put: `foo' trashed in sandbox/XDG_DATA_HOME/Trash", self.stderr.splitlines()) from textwrap import dedent @istest class TestUnsecureTrashDirMessages(TrashPutTest): def setUp(self): TrashPutTest.setUp(self) having_empty_dir('fake-vol') self.fstab.add_mount('fake-vol') having_file('fake-vol/foo') @istest def when_is_unsticky(self): having_empty_dir('fake-vol/.Trash') self.run_trashput('trash-put', '-v', 'fake-vol/foo') assert_line_in_text( 'trash-put: found unsecure .Trash dir (should be sticky): ' 'fake-vol/.Trash', self.stderr) @istest def when_it_is_not_a_dir(self): having_file('fake-vol/.Trash') self.run_trashput('trash-put', '-v', 'fake-vol/foo') assert_line_in_text( 'trash-put: found unusable .Trash dir (should be a dir): ' 'fake-vol/.Trash', self.stderr) @istest def when_is_a_symlink(self): make_sticky_dir('fake-vol/link-destination') os.symlink('link-destination', 'fake-vol/.Trash') self.run_trashput('trash-put', '-v', 'fake-vol/foo') assert_line_in_text( 'trash-put: found unsecure .Trash dir (should not be a symlink): ' 'fake-vol/.Trash', self.stderr) def assert_line_in_text(line, text): assert_in(line, text.splitlines(), dedent('''\ Line not found in text Line: %s Text: --- %s---''') %(repr(line), text)) def should_fail(func): from nose.tools import assert_raises with assert_raises(AssertionError): func() @istest class exit_code(TrashPutTest): @istest def should_be_zero_on_success(self): having_file('foo') self.run_trashput('trash-put', 'foo') self.exit_code_should_be_successfull() @istest def should_be_non_zero_on_failure(self): self.run_trashput('trash-put', 'non-existent') self.exit_code_should_be_not_successfull() def exit_code_should_be_successfull(self): assert_equals(0, self.exit_code) def exit_code_should_be_not_successfull(self): assert_not_equals(0, self.exit_code) @istest class when_deleting_a_file(TrashPutTest): def setUp(self): self.prepare_fixture() having_file('sandbox/foo') self.run_trashput('trash-put', 'sandbox/foo') @istest def it_should_remove_the_file(self): file_should_have_been_deleted('sandbox/foo') @istest def it_should_remove_it_silently(self): self.output_should_be('') def a_trashinfo_file_should_have_been_created(self): file('sandbox/XDG_DATA_HOME/Trash/info/foo.trashinfo').read() @istest class when_fed_with_dot_arguments(TrashPutTest): def setUp(self): self.prepare_fixture() def test_dot_argument_is_skipped(self): having_file('other_argument') self.run_trashput("trash-put", ".", "other_argument") # the dot directory shouldn't be operated, but a diagnostic message # shall be writtend on stderr self.stderr_should_be( "trash-put: cannot trash directory `.'\n") # the remaining arguments should be processed assert not exists('other_argument') def test_dot_dot_argument_is_skipped(self): having_file('other_argument') self.run_trashput("trash-put", "..", "other_argument") # the dot directory shouldn't be operated, but a diagnostic message # shall be writtend on stderr self.stderr_should_be( "trash-put: cannot trash directory `..'\n") # the remaining arguments should be processed assert not exists('other_argument') def test_dot_argument_is_skipped_even_in_subdirs(self): having_empty_dir('sandbox/') having_file('other_argument') self.run_trashput("trash-put", "sandbox/.", "other_argument") # the dot directory shouldn't be operated, but a diagnostic message # shall be writtend on stderr self.stderr_should_be( "trash-put: cannot trash `.' directory `sandbox/.'\n") # the remaining arguments should be processed assert not exists('other_argument') assert exists('sandbox') def test_dot_dot_argument_is_skipped_even_in_subdirs(self): having_empty_dir('sandbox') having_file('other_argument') self.run_trashput("trash-put", "sandbox/..", "other_argument") # the dot directory shouldn't be operated, but a diagnostic message # shall be writtend on stderr self.stderr_should_be( "trash-put: cannot trash `..' directory `sandbox/..'\n") # the remaining arguments should be processed assert not exists('other_argument') assert exists('sandbox') def file_should_have_been_deleted(path): import os assert not os.path.exists('sandbox/foo') exists = os.path.exists trash-cli-0.12.9.14/integration_tests/test_trash_rm.py000066400000000000000000000057071237725047000227310ustar00rootroot00000000000000from StringIO import StringIO from mock import Mock, ANY from nose.tools import assert_false, assert_raises from files import require_empty_dir, write_file from trashcli.rm import Main, ListTrashinfos from trashinfo import a_trashinfo_with_path class TestTrashRm: def test_integration(self): trash_rm = Main() trash_rm.environ = {'XDG_DATA_HOME':'sandbox/xdh'} trash_rm.list_volumes = lambda:[] trash_rm.getuid = 123 trash_rm.stderr = StringIO() self.add_trashinfo_for(1, 'to/be/deleted') self.add_trashinfo_for(2, 'to/be/kept') trash_rm.run(['trash-rm', 'delete*']) self.assert_trashinfo_has_been_deleted(1) def setUp(self): require_empty_dir('sandbox/xdh') def add_trashinfo_for(self, index, path): write_file(self.trashinfo_from_index(index), a_trashinfo_with_path(path)) def trashinfo_from_index(self, index): return 'sandbox/xdh/Trash/info/%s.trashinfo' % index def assert_trashinfo_has_been_deleted(self, index): import os filename = self.trashinfo_from_index(index) assert_false(os.path.exists(filename), 'File "%s" still exists' % filename) class TestListing: def setUp(self): require_empty_dir('sandbox') self.out = Mock() self.listing = ListTrashinfos(self.out) self.index = 0 def test_should_report_original_location(self): self.add_trashinfo('/foo') self.listing.list_from_home_trashdir('sandbox/Trash') self.out.assert_called_with('/foo', ANY) def test_should_report_trashinfo_path(self): self.add_trashinfo(trashinfo_path='sandbox/Trash/info/a.trashinfo') self.listing.list_from_home_trashdir('sandbox/Trash') self.out.assert_called_with(ANY, 'sandbox/Trash/info/a.trashinfo') def test_should_handle_volume_trashdir(self): self.add_trashinfo(trashinfo_path='sandbox/.Trash/123/info/a.trashinfo') self.listing.list_from_volume_trashdir('sandbox/.Trash/123', '/fake/vol') self.out.assert_called_with(ANY, 'sandbox/.Trash/123/info/a.trashinfo') def test_should_absolutize_relative_path_for_volume_trashdir(self): self.add_trashinfo(path='foo/bar', trashdir='sandbox/.Trash/501') self.listing.list_from_volume_trashdir('sandbox/.Trash/501', '/fake/vol') self.out.assert_called_with('/fake/vol/foo/bar', ANY) def add_trashinfo(self, path='unspecified/original/location', trashinfo_path=None, trashdir='sandbox/Trash'): trashinfo_path = trashinfo_path or self._trashinfo_path(trashdir) write_file(trashinfo_path, a_trashinfo_with_path(path)) def _trashinfo_path(self, trashdir): path = '%s/info/%s.trashinfo' % (trashdir, self.index) self.index +=1 return path trash-cli-0.12.9.14/integration_tests/test_trash_rm_script.py000066400000000000000000000013701237725047000243050ustar00rootroot00000000000000from nose.tools import istest, assert_equals, assert_in import subprocess from subprocess import STDOUT, PIPE, check_output, call, Popen from assert_equals_with_unidiff import assert_equals_with_unidiff as assert_equals from textwrap import dedent from pprint import pprint @istest class WhenNoArgs: def setUp(self): process = Popen(['python', 'trashcli/rm.py'], env={'PYTHONPATH':'.'}, stdin=None, stdout=PIPE, stderr=PIPE) (self.stdout, self.stderr) = process.communicate() process.wait() self.returncode = process.returncode def test_should_print_usage_on_standard_error(self): assert_in("Usage:", self.stderr.splitlines()) trash-cli-0.12.9.14/integration_tests/trashinfo.py000066400000000000000000000014471237725047000220450ustar00rootroot00000000000000def a_trashinfo(escaped_path_entry, formatted_deletion_date = '2000-01-01T00:00:01'): return ("[Trash Info]\n" + "Path=%s\n" % escaped_path_entry + "DeletionDate=%s\n" % formatted_deletion_date) def a_trashinfo_without_date(): return ("[Trash Info]\n" "Path=/path\n") def a_trashinfo_with_invalid_date(): return ("[Trash Info]\n" "Path=/path\n" "DeletionDate=Wrong Date") def a_trashinfo_without_path(): return ("[Trash Info]\n" "DeletionDate='2000-01-01T00:00:00'\n") def a_trashinfo_with_date(date): return ("[Trash Info]\n" "DeletionDate=%s\n" % date) def a_trashinfo_with_path(path): return ("[Trash Info]\n" "Path=%s\n" % path) trash-cli-0.12.9.14/man/000077500000000000000000000000001237725047000144765ustar00rootroot00000000000000trash-cli-0.12.9.14/man/man1/000077500000000000000000000000001237725047000153325ustar00rootroot00000000000000trash-cli-0.12.9.14/man/man1/restore-trash.1000066400000000000000000000040521237725047000202170ustar00rootroot00000000000000.\" Copyright (C) 2008 Steve Stalcup .\" .\" This manual page is free software. It is distributed under the .\" terms of the GNU General Public License as published by the Free .\" Software Foundation; either version 2 of the License, or (at your .\" option) any later version. .\" .\" This manual page is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public License .\" along with this manual page; if not, write to the Free Software .\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 .\" USA .\" .TH "RESTORE-TRASH" "1" .SH "NAME" restore-trash \- Restore for Command line trash utility. .SH "SYNOPSIS" .B restore-trash .SH "DESCRIPTION" .PP Use for restore a trashed file or directory, in the original path. This command is a part of trash-cli package that provides a command line interface trashcan utility compliant with the FreeDesktop.org Trash Specification. It remembers the name, original path, deletion date, and permissions of each trashed file. .SH "EXAMPLES" .nf $ restore-trash 0 2007-08-30 12:36:00 /home/andrea/foo 1 2007-08-30 12:39:41 /home/andrea/bar 2 2007-08-30 12:39:41 /home/andrea/bar2 3 2007-08-30 12:39:41 /home/andrea/foo2 4 2007-08-30 12:39:41 /home/andrea/foo What file to restore [0..4]: 4 $ ls foo foo .fi .SH "BUGS" Report bugs to http://code.google.com/p/trash-cli/issues .SH "AUTHORS" Trash was written by Andrea Francia and Einar Orn Olason . This manual page was written by Steve Stalcup . Changes made by Massimo Cavalleri . .SH "SEE ALSO" trash-put(1), trash-list(1), trash-empty(1), and the FreeDesktop.org Trash Specification at http://www.ramendik.ru/docs/trashspec.html. .br Both are released under the \s-1GNU\s0 General Public License, version 2 or later. trash-cli-0.12.9.14/man/man1/trash-empty.1000066400000000000000000000045701237725047000176770ustar00rootroot00000000000000.\" Copyright (C) 2008 Steve Stalcup .\" .\" This manual page is free software. It is distributed under the .\" terms of the GNU General Public License as published by the Free .\" Software Foundation; either version 2 of the License, or (at your .\" option) any later version. .\" .\" This manual page is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public License .\" along with this manual page; if not, write to the Free Software .\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 .\" USA .\" .TH "TRASH-EMPTY" "1" .SH "NAME" trash-empty \- Empty for Command line trash utility. .SH "SYNOPSIS" .B trash-empty .RI [ arguments ] .SH "DESCRIPTION" .PP Remove for ever any trashed file and trashed directory. This command is a part of trash-cli package that provides a command line interface trashcan utility compliant with the FreeDesktop.org Trash Specification. It remembers the name, original path, deletion date, and permissions of each trashed file. .SH "ARGUMENTS" .TP To remove all trashed files, use 'emtpy-trash'. .TP To remove files that have been in the trash more than a given number of days, use 'trash-empty x', 'x' representing the number of days. .SH "EXAMPLES" .nf $ date Tue Feb 19 20:26:52 CET 2008 $ trash-list 2008-02-19 20:11:34 /home/einar/today 2008-02-18 20:11:34 /home/einar/yesterday 2008-02-10 20:11:34 /home/einar/last_week $ trash-empty 7 $ trash-list 2008-02-19 20:11:34 /home/einar/today 2008-02-18 20:11:34 /home/einar/yesterday $ trash-empty 1 $ trash-list 2008-02-19 20:11:34 /home/einar/today $ trash-empty $ trash-list .fi .SH "BUGS" Report bugs to http://code.google.com/p/trash-cli/issues .SH "AUTHORS" Trash was written by Andrea Francia and Einar Orn Olason . This manual page was written by Steve Stalcup . Changes made by Massimo Cavalleri . .SH "SEE ALSO" trash-put(1), trash-restore(1), trash-list(1), and the FreeDesktop.org Trash Specification at http://www.ramendik.ru/docs/trashspec.html. .br Both are released under the \s-1GNU\s0 General Public License, version 2 or later. trash-cli-0.12.9.14/man/man1/trash-list.1000066400000000000000000000035351237725047000175140ustar00rootroot00000000000000.\" Copyright (C) 2008 Steve Stalcup .\" .\" This manual page is free software. It is distributed under the .\" terms of the GNU General Public License as published by the Free .\" Software Foundation; either version 2 of the License, or (at your .\" option) any later version. .\" .\" This manual page is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public License .\" along with this manual page; if not, write to the Free Software .\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 .\" USA .\" .TH "trash-list" "1" .SH "NAME" trash-list \- List trashed files. .SH "SYNOPSIS" .B trash-list [OPTIONS]... [PATH]... .SH "DESCRIPTION" .PP List all the contents of the trashcan. .SH "EXAMPLES" List all trashed files or grep output: .nf $ trash-list 2008-06-23 21:57:26 /home/andrea/src/bluetrash/pippo 2008-06-23 21:50:41 /home/andrea/foobar 2008-06-23 21:50:49 /home/andrea/foobar 2008-06-23 21:53:13 /media/disk/adsfljasldj $ trash-list | grep foo 2008-06-23 21:50:41 /home/andrea/foobar 2008-06-23 21:50:49 /home/andrea/foobar .fi .SH "BUGS" Report bugs to http://code.google.com/p/trash-cli/issues .SH "AUTHORS" Trash was written by Andrea Francia and Einar Orn Olason . This manual page was written by Steve Stalcup . Changes made by Massimo Cavalleri . .SH "SEE ALSO" trash-put(1), trash-restore(1), trash-empty(1), and the FreeDesktop.org Trash Specification at http://www.ramendik.ru/docs/trashspec.html. .br Both are released under the \s-1GNU\s0 General Public License, version 2 or later. trash-cli-0.12.9.14/man/man1/trash-put.1000066400000000000000000000034721237725047000173510ustar00rootroot00000000000000.\" Copyright (C) 2008 Steve Stalcup .\" .\" This manual page is free software. It is distributed under the .\" terms of the GNU General Public License as published by the Free .\" Software Foundation; either version 2 of the License, or (at your .\" option) any later version. .\" .\" This manual page is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public License .\" along with this manual page; if not, write to the Free Software .\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 .\" USA .\" .TH "TRASH" "1" .SH "NAME" trash \- Command line trash utility. .SH "SYNOPSIS" .B trash .RI [ arguments ] \&... .SH "DESCRIPTION" .PP Trash-cli package provides a command line interface trashcan utility compliant with the FreeDesktop.org Trash Specification. It remembers the name, original path, deletion date, and permissions of each trashed file. .SH "ARGUMENTS" .TP Names of files or directory to move in the trashcan. .SH "EXAMPLES" .nf $ cd /home/andrea/ $ touch foo bar $ trash foo bar .fi .SH "BUGS" Report bugs to http://code.google.com/p/trash-cli/issues .SH "AUTHORS" Trash was written by Andrea Francia and Einar Orn Olason . This manual page was written by Steve Stalcup . Changes made by Massimo Cavalleri . .SH "SEE ALSO" trash-list(1), trash-restore(1), trash-empty(1), and the FreeDesktop.org Trash Specification at http://www.ramendik.ru/docs/trashspec.html. .br Both are released under the \s-1GNU\s0 General Public License, version 2 or later. trash-cli-0.12.9.14/man/man1/trash-rm.1000066400000000000000000000021131237725047000171460ustar00rootroot00000000000000.\" Copyright (C) 2012 Andrea Francia .TH "TRASH-RM" "1" .SH "NAME" trash-rm \- Removes files matching a pattern from the trash can .SH "SYNOPSIS" .B trash-rm .RI [ PATTERN ] .SH "DESCRIPTION" .PP Remove from the trash can all the files matching the PATTERN. .SH "EXAMPLE" .nf $ trash-rm foo # Removes all files with name 'foo' in trash can $ trash-rm '*.c' # Removes all files ending with '.o' in trash can .fi .SH "BUGS" Report bugs to http://code.google.com/p/trash-cli/issues .SH "CREDITS" Trash was written by Andrea Francia . The original version of trash-empty was contributed by Einar Orn Olason . The first version of manual pages was written by Steve Stalcup and was modified by Massimo Cavalleri . .SH "COPYRIGHT" Both are released under the \s-1GNU\s0 General Public License, version 2. .SH "SEE ALSO" trash-put(1), trash-list(1), trash-empty(1), restore-trash(1), and the FreeDesktop.org Trash Specification at http://www.ramendik.ru/docs/trashspec.html. .br trash-cli-0.12.9.14/requirements-dev.txt000066400000000000000000000000301237725047000177540ustar00rootroot00000000000000nose==1.1.2 mock==0.8.0 trash-cli-0.12.9.14/setup.cfg000077500000000000000000000004451237725047000155520ustar00rootroot00000000000000[bdist_rpm] ;; Uncomment if your platform automatically gzips man pages install_script = install-rpm.sh doc_files = README.txt [egg_info] tag_build = tag_svn_revision = 1 [aliases] release = egg_info -RDb '' [nosetests] nocapture=1 testmatch=((?:^|[b_.-])(:?[Tt]est|describe_|it_should)) trash-cli-0.12.9.14/setup.py000066400000000000000000000047671237725047000154530ustar00rootroot00000000000000# Copyright (C) 2007-2012 Andrea Francia Trivolzio(PV) Italy from distutils.core import setup import sys def main(): sys.path.append('.') from trashcli import trash bin_dir.add_script('trash-list' , 'trashcli.cmds', 'list') bin_dir.add_script('trash' , 'trashcli.cmds', 'put') bin_dir.add_script('trash-put' , 'trashcli.cmds', 'put') bin_dir.add_script('restore-trash', 'trashcli.cmds', 'restore') bin_dir.add_script('trash-empty' , 'trashcli.cmds', 'empty') bin_dir.add_script('trash-rm' , 'trashcli.rm' , 'main') setup( name = 'trash-cli' , version = trash.version , author = 'Andrea Francia' , author_email = 'andrea@andreafrancia.it' , url = 'https://github.com/andreafrancia/trash-cli', description = 'Command line interface to FreeDesktop.org Trash.', long_description = file("README.rst").read(), license = 'GPL v2', packages = ['trashcli'], scripts = bin_dir.created_scripts, data_files = [('share/man/man1', ['man/man1/trash-empty.1', 'man/man1/trash-list.1', 'man/man1/restore-trash.1', 'man/man1/trash-put.1', 'man/man1/trash-rm.1'])], ) from textwrap import dedent class BinDir: def __init__(self, write_file, make_file_executable, make_dir): self.write_file = write_file self.make_file_executable = make_file_executable self.make_dir = make_dir self.created_scripts = [] def add_script(self, name, module, main_function): script_contents = dedent("""\ #!/usr/bin/env python from __future__ import absolute_import import sys from %(module)s import %(main_function)s as main sys.exit(main()) """) % locals() self.make_dir('bin') executable = 'bin/' + name self.write_file(executable, script_contents) self.make_file_executable(executable) self.created_scripts.append(executable) import os,stat def make_file_executable(path): os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR) def write_file(name, contents): file(name, 'w').write(contents) def make_dir(path): if not os.path.isdir(path): os.mkdir(path) bin_dir = BinDir(write_file, make_file_executable, make_dir) if __name__ == '__main__': main() trash-cli-0.12.9.14/ssh.py000066400000000000000000000023561237725047000151000ustar00rootroot00000000000000from nose.tools import assert_equals import subprocess class Connection: def __init__(self, target_host): self.target_host = target_host def run(self, *user_command): ssh_invocation = ['ssh', self.target_host, '-oVisualHostKey=false'] command = ssh_invocation + list(user_command) exit_code, stderr, stdout = self._run_command(command) return self.ExecutionResult(stdout, stderr, exit_code) def put(self, source_file): scp_command = ['scp', source_file, self.target_host + ':'] exit_code, stderr, stdout = self._run_command(scp_command) assert 0 == exit_code def _run_command(self, command): process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout,stderr = process.communicate() exit_code = process.poll() return exit_code, stderr, stdout class ExecutionResult: def __init__(self, stdout, stderr, exit_code): self.stdout = stdout self.stderr = stderr self.exit_code = exit_code def assert_no_err(self): assert_equals('', self.stderr) def assert_succesful(self): assert self.exit_code == 0 trash-cli-0.12.9.14/tasks/000077500000000000000000000000001237725047000150505ustar00rootroot00000000000000trash-cli-0.12.9.14/tasks/make-disk.osx000077500000000000000000000002561237725047000174560ustar00rootroot00000000000000mkdir --parent test-disk hdiutil detach ./test-disk || true rm -f test-disk.dmg hdiutil create -size 1M -fs HFS+ test-disk hdiutil attach test-disk.dmg -mountpoint test-disk trash-cli-0.12.9.14/tasks/make-test-disk000077500000000000000000000011541237725047000176210ustar00rootroot00000000000000#!/bin/bash # Copyright (C) 2007-2009 Andrea Francia Trivolzio(PV) Italy set -o errexit mount_point="test-disk" device_image="test-disk.img" main() { clean-up create-image mount-the-image echo "test-volume mounted as '$mount_point'" } clean-up() { sudo umount "$mount_point" || true rm -fv "$device_image" } create-image() { dd if=/dev/zero of="$device_image" bs=$((1024*1024)) count=1 /sbin/mke2fs -F "$device_image" } mount-the-image() { mkdir --parents "$mount_point" sudo mount -t ext2 "$device_image" "$mount_point" -o loop sudo chmod a+rwx "$mount_point" } main trash-cli-0.12.9.14/tasks/test-scripts-installation000077500000000000000000000001461237725047000221420ustar00rootroot00000000000000# Clean up rm env/bin/trash-rm python setup.py develop --script-dir env/bin env/bin/trash-rm --help trash-cli-0.12.9.14/trashcli/000077500000000000000000000000001237725047000155345ustar00rootroot00000000000000trash-cli-0.12.9.14/trashcli/__init__.py000066400000000000000000000000501237725047000176400ustar00rootroot00000000000000from __future__ import absolute_import trash-cli-0.12.9.14/trashcli/cmds.py000066400000000000000000000020551237725047000170360ustar00rootroot00000000000000# Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy import sys,os def put(): from trashcli.trash import TrashPutCmd return TrashPutCmd( sys.stdout, sys.stderr ).run(sys.argv) def restore(): from trashcli.trash import RestoreCmd RestoreCmd( stdout = sys.stdout, stderr = sys.stderr, environ = os.environ, exit = sys.exit, input = raw_input ).run(sys.argv) def empty(): from trashcli.trash import EmptyCmd from trashcli.list_mount_points import mount_points return EmptyCmd( out = sys.stdout, err = sys.stderr, environ = os.environ, list_volumes = mount_points, ).run(*sys.argv) def list(): from trashcli.trash import ListCmd from trashcli.list_mount_points import mount_points ListCmd( out = sys.stdout, err = sys.stderr, environ = os.environ, getuid = os.getuid, list_volumes = mount_points, ).run(*sys.argv) trash-cli-0.12.9.14/trashcli/fs.py000066400000000000000000000031731237725047000165220ustar00rootroot00000000000000import os, shutil class FileSystemListing: def entries_if_dir_exists(self, path): if os.path.exists(path): for entry in os.listdir(path): yield entry def exists(self, path): return os.path.exists(path) class FileSystemReader(FileSystemListing): def is_sticky_dir(self, path): import os return os.path.isdir(path) and has_sticky_bit(path) def is_symlink(self, path): return os.path.islink(path) def contents_of(self, path): return file(path).read() class FileRemover: def remove_file(self, path): try: return os.remove(path) except OSError: shutil.rmtree(path) def remove_file_if_exists(self,path): if os.path.exists(path): self.remove_file(path) def contents_of(path): # TODO remove return FileSystemReader().contents_of(path) def has_sticky_bit(path): # TODO move to FileSystemReader import os import stat return (os.stat(path).st_mode & stat.S_ISVTX) == stat.S_ISVTX def parent_of(path): return os.path.dirname(path) def remove_file(path): if(os.path.exists(path)): try: os.remove(path) except: return shutil.rmtree(path) def move(path, dest) : return shutil.move(path, str(dest)) def mkdirs_using_mode(path, mode): if os.path.isdir(path): os.chmod(path, mode) return os.makedirs(path, mode) def list_files_in_dir(path): for entry in os.listdir(path): result = os.path.join(path, entry) yield result def mkdirs(path): if os.path.isdir(path): return os.makedirs(path) trash-cli-0.12.9.14/trashcli/fstab.py000066400000000000000000000035121237725047000172060ustar00rootroot00000000000000import os def volume_of(path) : return Fstab().volume_of(path) class AbstractFstab(object): def __init__(self, ismount): self.ismount = ismount def volume_of(self, path): volume_of = VolumeOf(ismount=self.ismount) return volume_of(path) def mount_points(self): return self.ismount.mount_points() class Fstab(AbstractFstab): def __init__(self): AbstractFstab.__init__(self, OsIsMount()) class FakeFstab: def __init__(self): self.ismount = FakeIsMount() self.volume_of = VolumeOf(ismount = self.ismount) self.volume_of.abspath = os.path.normpath def mount_points(self): return self.ismount.mount_points() def volume_of(self, path): volume_of = VolumeOf(ismount=self.ismount) return volume_of(path) def add_mount(self, path): self.ismount.add_mount(path) from trashcli.list_mount_points import mount_points as os_mount_points class OsIsMount: def __call__(self, path): return os.path.ismount(path) def mount_points(self): return os_mount_points() class FakeIsMount: def __init__(self): self.fakes = set(['/']) def add_mount(self, path): self.fakes.add(path) def __call__(self, path): if path == '/': return True path = os.path.normpath(path) if path in self.fakes: return True return False def mount_points(self): return self.fakes.copy() class VolumeOf: def __init__(self, ismount): self._ismount = ismount import os self.abspath = os.path.abspath def __call__(self, path): path = self.abspath(path) while path != os.path.dirname(path): if self._ismount(path): break path = os.path.dirname(path) return path trash-cli-0.12.9.14/trashcli/list_mount_points.py000066400000000000000000000047411237725047000217050ustar00rootroot00000000000000# Copyright (C) 2009-2011 Andrea Francia Trivolzio(PV) Italy def mount_points(): try: return list(mount_points_from_getmnt()) except AttributeError: return mount_points_from_df() def mount_points_from_getmnt(): for elem in _mounted_filesystems_from_getmnt(): yield elem.mount_dir def mount_points_from_df(): import subprocess df_output = subprocess.Popen(["df", "-P"], stdout=subprocess.PIPE).stdout return list(_mount_points_from_df_output(df_output)) def _mount_points_from_df_output(df_output): def skip_header(): df_output.readline() def chomp(string): return string.rstrip('\n') skip_header() for line in df_output: line = chomp(line) yield line.split(None, 5)[-1] def _mounted_filesystems_from_getmnt() : from ctypes import Structure, c_char_p, c_int, c_void_p, cdll, POINTER from ctypes.util import find_library import sys class Filesystem: def __init__(self, mount_dir, type, name) : self.mount_dir = mount_dir self.type = type self.name = name class mntent_struct(Structure): _fields_ = [("mnt_fsname", c_char_p), # Device or server for # filesystem. ("mnt_dir", c_char_p), # Directory mounted on. ("mnt_type", c_char_p), # Type of filesystem: ufs, # nfs, etc. ("mnt_opts", c_char_p), # Comma-separated options # for fs. ("mnt_freq", c_int), # Dump frequency (in days). ("mnt_passno", c_int)] # Pass number for `fsck'. if sys.platform == "cygwin": libc_name = "cygwin1.dll" else: libc_name = find_library("c") if libc_name == None : libc_name="/lib/libc.so.6" # fix for my Gentoo 4.0 libc = cdll.LoadLibrary(libc_name) libc.getmntent.restype = POINTER(mntent_struct) libc.fopen.restype = c_void_p f = libc.fopen("/proc/mounts", "r") if f==None: f = libc.fopen("/etc/mtab", "r") if f == None: raise IOError("Unable to open /proc/mounts nor /etc/mtab") while True: entry = libc.getmntent(f) if bool(entry) == False: libc.fclose(f) break yield Filesystem(entry.contents.mnt_dir, entry.contents.mnt_type, entry.contents.mnt_fsname) trash-cli-0.12.9.14/trashcli/rm.py000066400000000000000000000052061237725047000165270ustar00rootroot00000000000000import fnmatch import os, sys from trashcli.trash import TrashDir, parse_path from trashcli.trash import TrashDirs from trashcli.trash import TopTrashDirRules from trashcli.trash import CleanableTrashcan from trashcli.fs import FileSystemReader from trashcli.fs import FileRemover class Main: def run(self, argv): args = argv[1:] self.exit_code = 0 if not args: self.stderr.write('Usage:\n' ' trash-rm PATTERN\n' '\n' 'Please specify PATTERN\n') self.exit_code = 8 return trashcan = CleanableTrashcan(FileRemover()) cmd = Filter(trashcan.delete_trashinfo_and_backup_copy) cmd.use_pattern(args[0]) file_reader = FileSystemReader() listing = ListTrashinfos(cmd.delete_if_matches) top_trashdir_rules = TopTrashDirRules(file_reader) trashdirs = TrashDirs(self.environ, self.getuid, list_volumes = self.list_volumes, top_trashdir_rules = top_trashdir_rules) trashdirs.on_trash_dir_found = listing.list_from_volume_trashdir trashdirs.list_trashdirs() def main(): from trashcli.list_mount_points import mount_points main = Main() main.environ = os.environ main.getuid = os.getuid main.list_volumes = mount_points main.stderr = sys.stderr main.run(sys.argv) return main.exit_code class Filter: def __init__(self, trashcan): self.delete = trashcan def use_pattern(self, pattern): self.pattern = pattern def delete_if_matches(self, original_location, info_file): basename = os.path.basename(original_location) if fnmatch.fnmatchcase(basename, self.pattern): self.delete(info_file) class ListTrashinfos: def __init__(self, out): self.out = out def list_from_home_trashdir(self, trashdir_path): self.list_from_volume_trashdir(trashdir_path, '/') def list_from_volume_trashdir(self, trashdir_path, volume): self.volume = volume self.trashdir = TrashDir(FileSystemReader()) self.trashdir.open(trashdir_path, volume) self.trashdir.each_trashinfo(self._report_original_location) def _report_original_location(self, trashinfo_path): file_reader = FileSystemReader() trashinfo = file_reader.contents_of(trashinfo_path) path = parse_path(trashinfo) complete_path = os.path.join(self.volume, path) self.out(complete_path, trashinfo_path) if __name__ == '__main__': sys.exit(main()) trash-cli-0.12.9.14/trashcli/trash.py000066400000000000000000001276321237725047000172420ustar00rootroot00000000000000# Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy from __future__ import absolute_import version='0.12.9.14' import os import logging from .fstab import Fstab logger=logging.getLogger('trashcli.trash') logger.setLevel(logging.WARNING) logger.addHandler(logging.StreamHandler()) # Error codes (from os on *nix, hard coded for Windows): EX_OK = getattr(os, 'EX_OK' , 0) EX_USAGE = getattr(os, 'EX_USAGE', 64) EX_IOERR = getattr(os, 'EX_IOERR', 74) from .fs import list_files_in_dir class TrashDirectory: def __init__(self, path, volume): self.path = os.path.normpath(path) self.volume = volume class all_is_ok_checker: def valid_to_be_written(self, a, b): pass def check(self, a):pass self.checker = all_is_ok_checker() # events def warn_non_trashinfo(): self.logger.warning("Non .trashinfo file in info dir") self.on_non_trashinfo_found = warn_non_trashinfo self.logger = logger def __str__(self) : return self.name() def __repr__(self): return 'TrashDirectory(%s,%s)' % (repr(self.path), repr(self.volume)) def name(self): import re import posixpath result=self.path try: home_dir=os.environ['HOME'] home_dir = posixpath.normpath(home_dir) if home_dir != '': result=re.sub('^'+ re.escape(home_dir)+os.path.sep, '~' + os.path.sep,result) except KeyError: pass return result def store_absolute_paths(self): self.path_for_trash_info = PathForTrashInfo() self.path_for_trash_info.make_absolutes_paths() def store_relative_paths(self): self.path_for_trash_info = PathForTrashInfo() self.path_for_trash_info.make_paths_relatives_to(self.volume) def trash(self, path): path = os.path.normpath(path) from datetime import datetime trash_info_path = self.path_for_trash_info.for_file(path) trash_info = TrashInfo(trash_info_path, datetime.now()) basename = os.path.basename(trash_info.path) trashinfo_file_content = trash_info.render() (trash_info_file, trash_info_id) = self.persist_trash_info(basename, trashinfo_file_content) trashed_file = self._create_trashed_file(trash_info_id, os.path.abspath(path), trash_info.deletion_date) self.ensure_files_dir_exists() try : move(path, trashed_file.actual_path) except IOError as e : remove_file(trash_info_file) raise e return trashed_file def ensure_files_dir_exists(self): if not os.path.exists(self.files_dir) : mkdirs_using_mode(self.files_dir, 0700) @property def info_dir(self): """ The $trash_dir/info dir that contains the .trashinfo files as filesystem.Path. """ result = os.path.join(self.path, 'info') return result @property def files_dir(self): """ The directory where original file where stored. A Path instance. """ result = os.path.join(self.path, 'files') return result def all_info_files(self) : 'Returns a generator of "Path"s' try : for info_file in list_files_in_dir(self.info_dir): if not os.path.basename(info_file).endswith('.trashinfo') : self.on_non_trashinfo_found() else : yield info_file except OSError: # when directory does not exist pass def trashed_files(self) : # Only used by restore-trash for info_file in self.all_info_files(): try: yield self._create_trashed_file_from_info_file(info_file) except ValueError: self.logger.warning("Non parsable trashinfo file: %s" % info_file) except IOError as e: self.logger.warning(str(e)) def _create_trashed_file_from_info_file(self, info_file): trash_id = self.calc_id(info_file) trash_info2 = LazyTrashInfoParser(lambda:contents_of(info_file), self.volume) path = trash_info2.original_location() deletion_date = trash_info2.deletion_date() trashed_file = self._create_trashed_file( trash_id, path, deletion_date) return trashed_file def _create_trashed_file(self, trash_id, path, deletion_date): actual_path = self._calc_path_for_actual_file(trash_id) info_file = self._calc_path_for_info_file(trash_id) return TrashedFile(path, deletion_date, info_file, actual_path, self) @staticmethod def calc_id(trash_info_file): return os.path.basename(trash_info_file)[:-len('.trashinfo')] def _calc_path_for_actual_file(self, trash_id) : return os.path.join(self.files_dir, trash_id) def _calc_path_for_info_file(self, trash_id) : return os.path.join(self.info_dir, '%s.trashinfo' % trash_id) """ Create a .trashinfo file in the $trash/info directory. returns the created TrashInfoFile. """ def persist_trash_info(self,basename,content) : mkdirs_using_mode(self.info_dir, 0700) os.chmod(self.info_dir,0700) # write trash info index = 0 while True : if index == 0 : suffix = "" elif index < 100: suffix = "_%d" % index else : import random suffix = "_%d" % random.randint(0, 65535) base_id = basename trash_id = base_id + suffix trash_info_basename = trash_id+".trashinfo" dest = os.path.join(self.info_dir, trash_info_basename) try : handle = os.open(dest, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0600) os.write(handle, content) os.close(handle) self.logger.debug(".trashinfo created as %s." % dest) return (dest, trash_id) except OSError: self.logger.debug("Attempt for creating %s failed." % dest) index += 1 raise IOError() class PathForTrashInfo: def make_paths_relatives_to(self, topdir): self.topdir = topdir def make_absolutes_paths(self): self.topdir = None def for_file(self, path): self.normalized_path = os.path.normpath(path) basename = os.path.basename(self.normalized_path) parent = self._real_parent() if self.topdir != None: if (parent == self.topdir) or parent.startswith(self.topdir+os.path.sep) : parent = parent[len(self.topdir+os.path.sep):] result = os.path.join(parent, basename) return result def _real_parent(self): parent = os.path.dirname(self.normalized_path) return os.path.realpath(parent) class NullObject: def __getattr__(self, name): return lambda *argl,**args:None class HomeTrashCan: def __init__(self, environ): self.environ = environ def path_to(self, out): if 'XDG_DATA_HOME' in self.environ: out('%(XDG_DATA_HOME)s/Trash' % self.environ) elif 'HOME' in self.environ: out('%(HOME)s/.local/share/Trash' % self.environ) class GlobalTrashCan: """ Represent the TrashCan that contains all trashed files. This class is the facade used by all trashcli commands """ class NullReporter: def __getattr__(self,name): return lambda *argl,**args:None from datetime import datetime def __init__(self, home_trashcan, reporter = NullReporter(), getuid = os.getuid, fstab = Fstab(), now = datetime.now): self.getuid = getuid self.reporter = reporter self.fstab = fstab self.now = now self.home_trashcan = home_trashcan def trashed_files(self): """Return a generator of all TrashedFile(s).""" for trash_dir in self._trash_directories(): for trashedfile in trash_dir.trashed_files(): yield trashedfile def trash(self, file) : """ Trash a file in the appropriate trash directory. If the file belong to the same volume of the trash home directory it will be trashed in the home trash directory. Otherwise it will be trashed in one of the relevant volume trash directories. Each volume can have two trash directories, they are - $volume/.Trash/$uid - $volume/.Trash-$uid Firstly the software attempt to trash the file in the first directory then try to trash in the second trash directory. """ if self._should_skipped_by_specs(file): self.reporter.unable_to_trash_dot_entries(file) return for trash_dir in self._possible_trash_directories_for(file): try: class ValidationOutput: def not_valid_should_be_a_dir(_): raise TopDirNotPresent("topdir should be a directory: %s" % trash_dir.path) def not_valid_parent_should_not_be_a_symlink(_): raise TopDirIsSymLink("topdir can't be a symbolic link: %s" % trash_dir.path) def not_valid_parent_should_be_sticky(_): raise TopDirWithoutStickyBit("topdir should have the sticky bit: %s" % trash_dir.path) def is_valid(self): pass output = ValidationOutput() class FileSystem: def isdir(self, path): return os.path.isdir(path) def islink(self, path): return os.path.islink(path) def has_sticky_bit(self, path): return has_sticky_bit(path) trash_dir.checker.fs = FileSystem() trash_dir.checker.valid_to_be_written(trash_dir.path, output) except TopDirIsSymLink: self.reporter.found_unsercure_trash_dir_symlink( os.path.dirname(trash_dir.path)) except TopDirNotPresent: self.reporter.found_unusable_trash_dir_not_a_dir( os.path.dirname(trash_dir.path)) except TopDirWithoutStickyBit: self.reporter.found_unsecure_trash_dir_unsticky( os.path.dirname(trash_dir.path)) if self._file_could_be_trashed_in(file, trash_dir.path): try: trashed_file = trash_dir.trash(file) self.reporter.file_has_been_trashed_in_as( file, trashed_file.trash_directory.name(), trashed_file.original_file) return except (IOError, OSError), error: self.reporter.unable_to_trash_file_in_because( file, trash_dir.name(), str(error)) self.reporter.unable_to_trash_file(file) def _should_skipped_by_specs(self, file): basename = os.path.basename(file) return (basename == ".") or (basename == "..") def volume_of(self, path): return self.fstab.volume_of(path) def _file_could_be_trashed_in(self,file_to_be_trashed,trash_dir_path): return self.volume_of(trash_dir_path) == self.volume_of_parent(file_to_be_trashed) def _trash_directories(self) : """Return a generator of all TrashDirectories in the filesystem""" for td in self._home_trash_dir(): yield td for mount_point in self.fstab.mount_points(): volume = mount_point yield self._volume_trash_dir1(volume) yield self._volume_trash_dir2(volume) def _possible_trash_directories_for(self, file): for td in self._home_trash_dir(): yield td for td in self._trash_directories_for_volume(self.volume_of_parent(file)): yield td def volume_of_parent(self, file): return self.volume_of(parent_of(file)) def _trash_directories_for_volume(self, volume): yield self._volume_trash_dir1(volume) yield self._volume_trash_dir2(volume) def _home_trash_dir(self) : paths = [] self.home_trashcan.path_to(paths.append) result = [] for trash_dir_path in paths: trash_dir = TrashDirectory(trash_dir_path, self.volume_of(trash_dir_path)) trash_dir.volume_of = self.volume_of trash_dir.store_absolute_paths() result.append(trash_dir) return result def _volume_trash_dir1(self, volume): """ Return the method (1) volume trash dir ($topdir/.Trash/$uid). """ uid = self.getuid() trash_directory_path = os.path.join(volume, '.Trash', str(uid)) trash_dir = TrashDirectory(trash_directory_path,volume) trash_dir.volume_of = self.volume_of trash_dir.store_relative_paths() trash_dir.checker = TopTrashDirRules(None) return trash_dir def _volume_trash_dir2(self, volume) : """ Return the method (2) volume trash dir ($topdir/.Trash-$uid). """ uid = self.getuid() dirname=".Trash-%s" % str(uid) trash_directory_path = os.path.join(volume, dirname) trash_dir = TrashDirectory(trash_directory_path,volume) trash_dir.volume_of = self.volume_of trash_dir.store_relative_paths() return trash_dir class TrashedFile: """ Represent a trashed file. Each trashed file is persisted in two files: - $trash_dir/info/$id.trashinfo - $trash_dir/files/$id Properties: - path : the original path from where the file has been trashed - deletion_date : the time when the file has been trashed (instance of datetime) - info_file : the file that contains information (instance of Path) - actual_path : the path where the trashed file has been placed after the trash opeartion (instance of Path) - trash_directory : """ def __init__(self, path, deletion_date, info_file, actual_path, trash_directory) : if not os.path.isabs(path): raise ValueError("Absolute path required.") self.path = path self.deletion_date = deletion_date self.info_file = info_file self.actual_path = actual_path self.trash_directory = trash_directory self.original_file = actual_path def restore(self, dest=None) : if dest is not None: raise NotImplementedError("not yet supported") if os.path.exists(self.path): raise IOError('Refusing to overwrite existing file "%s".' % os.path.basename(self.path)) else: parent = os.path.dirname(self.path) mkdirs(parent) move(self.original_file, self.path) remove_file(self.info_file) class TrashInfo: def __init__(self, path, deletion_date): assert isinstance(deletion_date, datetime) self.path = path self.deletion_date = deletion_date def render(self) : import urllib result = "[Trash Info]\n" result += "Path=" + urllib.quote(self.path,'/') + "\n" result += "DeletionDate=" + self._format_date(self.deletion_date) + "\n" return result @staticmethod def _format_date(deletion_date): return deletion_date.strftime("%Y-%m-%dT%H:%M:%S") @staticmethod def parse(data): path = parse_path(data) deletion_date = parse_deletion_date(data) return TrashInfo(path, deletion_date) import os from .fs import remove_file, has_sticky_bit from .fs import move, mkdirs, mkdirs_using_mode, parent_of def getcwd_as_realpath(): return os.path.realpath(os.curdir) import sys class RestoreCmd: def __init__(self, stdout, stderr, environ, exit, input, curdir = getcwd_as_realpath, version = version): self.out = stdout self.err = stderr self.exit = exit self.input = input self.trashcan = GlobalTrashCan( home_trashcan = HomeTrashCan(environ)) self.curdir = curdir self.version = version def run(self, args=sys.argv): if '--version' in args[1:]: command = os.path.basename(args[0]) self.println('%s %s' %(command, self.version)) return trashed_files = [] self.for_all_trashed_file_in_dir(trashed_files.append, self.curdir()) if not trashed_files: self.report_no_files_found() else : for i, trashedfile in enumerate(trashed_files): self.println("%4d %s %s" % (i, trashedfile.deletion_date, trashedfile.path)) index=self.input("What file to restore [0..%d]: " % (len(trashed_files)-1)) if index == "" : self.println("Exiting") else : index = int(index) try: trashed_files[index].restore() except IOError as e: self.printerr(e) self.exit(1) def for_all_trashed_file_in_dir(self, action, dir): def is_trashed_from_curdir(trashedfile): return trashedfile.path.startswith(dir + os.path.sep) for trashedfile in filter(is_trashed_from_curdir, self.trashcan.trashed_files()) : action(trashedfile) def report_no_files_found(self): self.println("No files trashed from current dir ('%s')" % self.curdir()) def println(self, line): self.out.write(line + '\n') def printerr(self, msg): self.err.write('%s\n' % msg) from optparse import IndentedHelpFormatter class NoWrapFormatter(IndentedHelpFormatter) : def _format_text(self, text) : "[Does not] format a text, return the text as it is." return text class TrashPutCmd: def __init__(self, stdout, stderr, environ = os.environ, fstab = Fstab()): self.stdout = stdout self.stderr = stderr self.environ = environ self.fstab = fstab def run(self, argv): parser = self.get_option_parser(os.path.basename(argv[0])) (options, args) = parser.parse_args(argv[1:]) if len(args) <= 0: parser.error("Please specify the files to trash.") reporter = TrashPutReporter(self.get_logger(options.verbose,argv[0])) self.trashcan = GlobalTrashCan( reporter = reporter, fstab = self.fstab, home_trashcan = HomeTrashCan(self.environ)) self.trash_all(args) if reporter.all_files_have_been_trashed: return EX_OK else: return EX_IOERR def trash_all(self, args): for arg in args : self.trash(arg) def trash(self, arg): self.trashcan.trash(arg) def get_option_parser(self, program_name): from optparse import OptionParser parser = OptionParser(prog=program_name, usage="%prog [OPTION]... FILE...", description="Put files in trash", version="%%prog %s" % version, formatter=NoWrapFormatter(), epilog= """To remove a file whose name starts with a `-', for example `-foo', use one of these commands: trash -- -foo trash ./-foo Report bugs to http://code.google.com/p/trash-cli/issues""") parser.add_option("-d", "--directory", action="store_true", help="ignored (for GNU rm compatibility)") parser.add_option("-f", "--force", action="store_true", help="ignored (for GNU rm compatibility)") parser.add_option("-i", "--interactive", action="store_true", help="ignored (for GNU rm compatibility)") parser.add_option("-r", "-R", "--recursive", action="store_true", help="ignored (for GNU rm compatibility)") parser.add_option("-v", "--verbose", action="store_true", help="explain what is being done", dest="verbose") def patched_print_help(): encoding = parser._get_encoding(self.stdout) self.stdout.write(parser.format_help().encode(encoding, "replace")) def patched_error(msg): parser.print_usage(self.stderr) parser.exit(2, "%s: error: %s\n" % (program_name, msg)) def patched_exit(status=0, msg=None): if msg: self.stderr.write(msg) import sys sys.exit(status) parser.print_help = patched_print_help parser.error = patched_error parser.exit = patched_exit return parser def get_logger(self,verbose,argv0): import os.path class MyLogger: def __init__(self, stderr): self.program_name = os.path.basename(argv0) self.stderr=stderr def info(self,message): if verbose: self.emit(message) def warning(self,message): self.emit(message) def emit(self, message): self.stderr.write("%s: %s\n" % (self.program_name,message)) return MyLogger(self.stderr) class TrashPutReporter: def __init__(self, logger = NullObject()): self.logger = logger self.all_files_have_been_trashed = True def unable_to_trash_dot_entries(self,file): self.logger.warning("cannot trash %s `%s'" % (describe(file), file)) def unable_to_trash_file(self,f): self.logger.warning("cannot trash %s `%s'" % (describe(f), f)) self.all_files_have_been_trashed = False def file_has_been_trashed_in_as(self, trashee, trash_directory, destination): self.logger.info("`%s' trashed in %s" % (trashee, trash_directory)) def found_unsercure_trash_dir_symlink(self, trash_dir_path): self.logger.info("found unsecure .Trash dir (should not be a symlink): %s" % trash_dir_path) def found_unusable_trash_dir_not_a_dir(self, trash_dir_path): self.logger.info("found unusable .Trash dir (should be a dir): %s" % trash_dir_path) def found_unsecure_trash_dir_unsticky(self, trash_dir_path): self.logger.info("found unsecure .Trash dir (should be sticky): %s" % trash_dir_path) def unable_to_trash_file_in_because(self, file_to_be_trashed, trash_directory, error): self.logger.info("Failed to trash %s in %s, because :%s" % (file_to_be_trashed, trash_directory, error)) def describe(path): """ Return a textual description of the file pointed by this path. Options: - "symbolic link" - "directory" - "`.' directory" - "`..' directory" - "regular file" - "regular empty file" - "non existent" - "entry" """ if os.path.islink(path): return 'symbolic link' elif os.path.isdir(path): if path == '.': return 'directory' elif path == '..': return 'directory' else: if os.path.basename(path) == '.': return "`.' directory" elif os.path.basename(path) == '..': return "`..' directory" else: return 'directory' elif os.path.isfile(path): if os.path.getsize(path) == 0: return 'regular empty file' else: return 'regular file' elif not os.path.exists(path): return 'non existent' else: return 'entry' from .fs import FileSystemReader, contents_of, FileRemover class ListCmd: def __init__(self, out, err, environ, list_volumes, getuid, file_reader = FileSystemReader(), version = version): self.output = self.Output(out, err) self.err = self.output.err self.contents_of = file_reader.contents_of self.version = version top_trashdir_rules = TopTrashDirRules(file_reader) self.trashdirs = TrashDirs(environ, getuid, list_volumes = list_volumes, top_trashdir_rules=top_trashdir_rules) self.harvester = Harvester(file_reader) def run(self, *argv): parse=Parser() parse.on_help(PrintHelp(self.description, self.output.println)) parse.on_version(PrintVersion(self.output.println, self.version)) parse.as_default(self.list_trash) parse(argv) def list_trash(self): self.harvester.on_volume = self.output.set_volume_path self.harvester.on_trashinfo_found = self._print_trashinfo self.trashdirs.on_trashdir_skipped_because_parent_not_sticky = self.output.top_trashdir_skipped_because_parent_not_sticky self.trashdirs.on_trashdir_skipped_because_parent_is_symlink = self.output.top_trashdir_skipped_because_parent_is_symlink self.trashdirs.on_trash_dir_found = self.harvester._analize_trash_directory self.trashdirs.list_trashdirs() def _print_trashinfo(self, path): try: contents = self.contents_of(path) except IOError as e : self.output.print_read_error(e) else: deletion_date = parse_deletion_date(contents) or unknown_date() try: path = parse_path(contents) except ParseError: self.output.print_parse_path_error(path) else: self.output.print_entry(deletion_date, path) def description(self, program_name, printer): printer.usage('Usage: %s [OPTIONS...]' % program_name) printer.summary('List trashed files') printer.options( " --version show program's version number and exit", " -h, --help show this help message and exit") printer.bug_reporting() class Output: def __init__(self, out, err): self.out = out self.err = err def println(self, line): self.out.write(line+'\n') def error(self, line): self.err.write(line+'\n') def print_read_error(self, error): self.error(str(error)) def print_parse_path_error(self, offending_file): self.error("Parse Error: %s: Unable to parse Path." % (offending_file)) def top_trashdir_skipped_because_parent_not_sticky(self, trashdir): self.error("TrashDir skipped because parent not sticky: %s" % trashdir) def top_trashdir_skipped_because_parent_is_symlink(self, trashdir): self.error("TrashDir skipped because parent is symlink: %s" % trashdir) def set_volume_path(self, volume_path): self.volume_path = volume_path def print_entry(self, maybe_deletion_date, relative_location): import os original_location = os.path.join(self.volume_path, relative_location) self.println("%s %s" %(maybe_deletion_date, original_location)) def do_nothing(*argv, **argvk): pass class Parser: def __init__(self): self.default_action = do_nothing self.argument_action = do_nothing self.short_options = '' self.long_options = [] self.actions = dict() self._on_invalid_option = do_nothing def __call__(self, argv): program_name = argv[0] from getopt import getopt, GetoptError try: options, arguments = getopt(argv[1:], self.short_options, self.long_options) except GetoptError, e: invalid_option = e.opt self._on_invalid_option(program_name, invalid_option) else: for option, value in options: if option in self.actions: self.actions[option](program_name) return for argument in arguments: self.argument_action(argument) self.default_action() def on_invalid_option(self, action): self._on_invalid_option = action def on_help(self, action): self.add_option('help', action, 'h') def on_version(self, action): self.add_option('version', action) def add_option(self, long_option, action, short_aliases=''): self.long_options.append(long_option) self.actions['--' + long_option] = action for short_alias in short_aliases: self.add_short_option(short_alias, action) def add_short_option(self, short_option, action): self.short_options += short_option self.actions['-' + short_option] = action def on_argument(self, argument_action): self.argument_action = argument_action def as_default(self, default_action): self.default_action = default_action class CleanableTrashcan: def __init__(self, file_remover): self._file_remover = file_remover def delete_orphan(self, path_to_backup_copy): self._file_remover.remove_file(path_to_backup_copy) def delete_trashinfo_and_backup_copy(self, trashinfo_path): backup_copy = self._path_of_backup_copy(trashinfo_path) self._file_remover.remove_file_if_exists(backup_copy) self._file_remover.remove_file(trashinfo_path) def _path_of_backup_copy(self, path_to_trashinfo): from os.path import dirname as parent_of, join, basename trash_dir = parent_of(parent_of(path_to_trashinfo)) return join(trash_dir, 'files', basename(path_to_trashinfo)[:-len('.trashinfo')]) class ExpiryDate: def __init__(self, contents_of, now, trashcan): self._contents_of = contents_of self._now = now self._maybe_delete = self._delete_unconditionally self._trashcan = trashcan def set_max_age_in_days(self, arg): self.max_age_in_days = int(arg) self._maybe_delete = self._delete_according_date def delete_if_expired(self, trashinfo_path): self._maybe_delete(trashinfo_path) def _delete_according_date(self, trashinfo_path): contents = self._contents_of(trashinfo_path) ParseTrashInfo( on_deletion_date=IfDate( OlderThan(self.max_age_in_days, self._now), lambda: self._delete_unconditionally(trashinfo_path) ), )(contents) def _delete_unconditionally(self, trashinfo_path): self._trashcan.delete_trashinfo_and_backup_copy(trashinfo_path) class TrashDirs: def __init__(self, environ, getuid, list_volumes, top_trashdir_rules): self.getuid = getuid self.mount_points = list_volumes self.top_trashdir_rules = top_trashdir_rules self.home_trashcan = HomeTrashCan(environ) # events self.on_trash_dir_found = lambda trashdir, volume: None self.on_trashdir_skipped_because_parent_not_sticky = lambda trashdir: None self.on_trashdir_skipped_because_parent_is_symlink = lambda trashdir: None def list_trashdirs(self): self.emit_home_trashcan() self._for_each_volume_trashcan() def emit_home_trashcan(self): def return_result_with_volume(trashcan_path): self.on_trash_dir_found(trashcan_path, '/') self.home_trashcan.path_to(return_result_with_volume) def _for_each_volume_trashcan(self): for volume in self.mount_points(): self.emit_trashcans_for(volume) def emit_trashcans_for(self, volume): self.emit_trashcan_1_for(volume) self.emit_trashcan_2_for(volume) def emit_trashcan_1_for(self,volume): top_trashdir_path = os.path.join(volume, '.Trash/%s' % self.getuid()) class IsValidOutput: def not_valid_parent_should_not_be_a_symlink(_): self.on_trashdir_skipped_because_parent_is_symlink(top_trashdir_path) def not_valid_parent_should_be_sticky(_): self.on_trashdir_skipped_because_parent_not_sticky(top_trashdir_path) def is_valid(_): self.on_trash_dir_found(top_trashdir_path, volume) self.top_trashdir_rules.valid_to_be_read(top_trashdir_path, IsValidOutput()) def emit_trashcan_2_for(self, volume): alt_top_trashdir = os.path.join(volume, '.Trash-%s' % self.getuid()) self.on_trash_dir_found(alt_top_trashdir, volume) from datetime import datetime class EmptyCmd: def __init__(self, out, err, environ, list_volumes, now = datetime.now, file_reader = FileSystemReader(), getuid = os.getuid, file_remover = FileRemover(), version = version): self.out = out self.err = err self.file_reader = file_reader top_trashdir_rules = TopTrashDirRules(file_reader) self.trashdirs = TrashDirs(environ, getuid, list_volumes = list_volumes, top_trashdir_rules = top_trashdir_rules) self.harvester = Harvester(file_reader) self.version = version self._cleaning = CleanableTrashcan(file_remover) self._expiry_date = ExpiryDate(file_reader.contents_of, now, self._cleaning) def run(self, *argv): self.exit_code = EX_OK parse = Parser() parse.on_help(PrintHelp(self.description, self.println)) parse.on_version(PrintVersion(self.println, self.version)) parse.on_argument(self._expiry_date.set_max_age_in_days) parse.as_default(self._empty_all_trashdirs) parse.on_invalid_option(self.report_invalid_option_usage) parse(argv) return self.exit_code def report_invalid_option_usage(self, program_name, option): self.err.write( "{program_name}: invalid option -- '{option}'\n".format(**locals())) self.exit_code |= EX_USAGE def description(self, program_name, printer): printer.usage('Usage: %s [days]' % program_name) printer.summary('Purge trashed files.') printer.options( " --version show program's version number and exit", " -h, --help show this help message and exit") printer.bug_reporting() def is_int(self, text): try: int(text) return True except ValueError: return False def _empty_all_trashdirs(self): self.harvester.on_trashinfo_found = self._expiry_date.delete_if_expired self.harvester.on_orphan_found = self._cleaning.delete_orphan self.trashdirs.on_trash_dir_found = self.harvester._analize_trash_directory self.trashdirs.list_trashdirs() def println(self, line): self.out.write(line + '\n') class Harvester: def __init__(self, file_reader): self.file_reader = file_reader self.trashdir = TrashDir(self.file_reader) self.on_orphan_found = do_nothing self.on_trashinfo_found = do_nothing self.on_volume = do_nothing def _analize_trash_directory(self, trash_dir_path, volume_path): self.on_volume(volume_path) self.trashdir.open(trash_dir_path, volume_path) self.trashdir.each_trashinfo(self.on_trashinfo_found) self.trashdir.each_orphan(self.on_orphan_found) class IfDate: def __init__(self, date_criteria, then): self.date_criteria = date_criteria self.then = then def __call__(self, date2): if self.date_criteria(date2): self.then() class OlderThan: def __init__(self, days_ago, now): from datetime import timedelta self.limit_date = now() - timedelta(days=days_ago) def __call__(self, deletion_date): return deletion_date < self.limit_date class PrintHelp: def __init__(self, description, println): class Printer: def __init__(self, println): self.println = println def usage(self, usage): self.println(usage) self.println('') def summary(self, summary): self.println(summary) self.println('') def options(self, *line_describing_option): self.println('Options:') for line in line_describing_option: self.println(line) self.println('') def bug_reporting(self): self.println("Report bugs to http://code.google.com/p/trash-cli/issues") self.description = description self.printer = Printer(println) def __call__(self, program_name): self.description(program_name, self.printer) class PrintVersion: def __init__(self, println, version): self.println = println self.version = version def __call__(self, program_name): self.println("%s %s" % (program_name, self.version)) class TopTrashDirRules: def __init__(self, fs): self.fs = fs def valid_to_be_read(self, path, output): parent_trashdir = os.path.dirname(path) if not self.fs.exists(path): return if not self.fs.is_sticky_dir(parent_trashdir): output.not_valid_parent_should_be_sticky() return if self.fs.is_symlink(parent_trashdir): output.not_valid_parent_should_not_be_a_symlink() return else: output.is_valid() def valid_to_be_written(self, trash_dir_path, output): parent = os.path.dirname(trash_dir_path) if not self.fs.isdir(parent): output.not_valid_should_be_a_dir() return if self.fs.islink(parent): output.not_valid_parent_should_not_be_a_symlink() return if not self.fs.has_sticky_bit(parent): output.not_valid_parent_should_be_sticky() return output.is_valid() class TopDirWithoutStickyBit(IOError): pass class TopDirNotPresent(IOError): pass class TopDirIsSymLink(IOError): pass class Dir: def __init__(self, path, entries_if_dir_exists): self.path = path self.entries_if_dir_exists = entries_if_dir_exists def entries(self): return self.entries_if_dir_exists(self.path) def full_path(self, entry): return os.path.join(self.path, entry) class TrashDir: def __init__(self, file_reader): self.file_reader = file_reader def open(self, path, volume_path): self.trash_dir_path = path self.volume_path = volume_path self.files_dir = Dir(self._files_dir(), self.file_reader.entries_if_dir_exists) def each_orphan(self, action): for entry in self.files_dir.entries(): trashinfo_path = self._trashinfo_path_from_file(entry) file_path = self.files_dir.full_path(entry) if not self.file_reader.exists(trashinfo_path): action(file_path) def _entries_if_dir_exists(self, path): return self.file_reader.entries_if_dir_exists(path) def each_trashinfo(self, action): for entry in self._trashinfo_entries(): action(os.path.join(self._info_dir(), entry)) def _trashinfos(self): for entry in self._trashinfo_entries(): yield self._trashinfo(entry) def _trashinfo(self, entry): class TrashInfo: def __init__(self, info_dir, files_dir, entry, file_reader, volume_path): self.info_dir = info_dir self.files_dir = files_dir self.entry = entry def path_to_backup_copy(self): entry = self.entry[:-len('.trashinfo')] return os.path.join(self.files_dir, entry) def path_to_trashinfo(self): return os.path.join(self.info_dir, self.entry) return TrashInfo(self._info_dir(), self._files_dir(), entry, self.file_reader, self.volume_path) def _info_dir(self): return os.path.join(self.trash_dir_path, 'info') def _trashinfo_path_from_file(self, file_entry): return os.path.join(self._info_dir(), file_entry + '.trashinfo') def _files_dir(self): return os.path.join(self.trash_dir_path, 'files') def _trashinfo_entries(self, on_non_trashinfo=do_nothing): for entry in self._entries_if_dir_exists(self._info_dir()): if entry.endswith('.trashinfo'): yield entry else: on_non_trashinfo() class ParseError(ValueError): pass class LazyTrashInfoParser: def __init__(self, contents, volume_path): self.contents = contents self.volume_path = volume_path def deletion_date(self): return parse_deletion_date(self.contents()) def _path(self): return parse_path(self.contents()) def original_location(self): return os.path.join(self.volume_path, self._path()) def maybe_parse_deletion_date(contents): result = Basket(unknown_date()) ParseTrashInfo( on_deletion_date = lambda date: result.collect(date), on_invalid_date = lambda: result.collect(unknown_date()) )(contents) return result.collected def maybe_date(parsing_closure): try: date = parsing_closure() except ValueError: return unknown_date() else: if date: return date return unknown_date() def unknown_date(): return '????-??-?? ??:??:??' class ParseTrashInfo: def __init__(self, on_deletion_date = do_nothing, on_invalid_date = do_nothing, on_path = do_nothing): self.found_deletion_date = on_deletion_date self.found_invalid_date = on_invalid_date self.found_path = on_path def __call__(self, contents): from datetime import datetime import urllib for line in contents.split('\n'): if line.startswith('DeletionDate='): try: date = datetime.strptime(line, "DeletionDate=%Y-%m-%dT%H:%M:%S") except ValueError: self.found_invalid_date() else: self.found_deletion_date(date) if line.startswith('Path='): path=urllib.unquote(line[len('Path='):]) self.found_path(path) class Basket: def __init__(self, initial_value = None): self.collected = initial_value def collect(self, value): self.collected = value def parse_deletion_date(contents): result = Basket() ParseTrashInfo(on_deletion_date=result.collect)(contents) return result.collected def parse_path(contents): import urllib for line in contents.split('\n'): if line.startswith('Path='): return urllib.unquote(line[len('Path='):]) raise ParseError('Unable to parse Path') trash-cli-0.12.9.14/unit_tests/000077500000000000000000000000001237725047000161245ustar00rootroot00000000000000trash-cli-0.12.9.14/unit_tests/__init__.py000066400000000000000000000000001237725047000202230ustar00rootroot00000000000000trash-cli-0.12.9.14/unit_tests/test_fake_fstab.py000066400000000000000000000021011237725047000216140ustar00rootroot00000000000000from trashcli.fstab import FakeFstab from nose.tools import assert_equals from nose.tools import istest from nose.tools import assert_items_equal class TestFakeFstab: def setUp(self): self.fstab = FakeFstab() @istest def on_default(self): self.assert_mount_points_are('/') @istest def it_should_accept_fake_mount_points(self): self.fstab.add_mount('/fake') self.assert_mount_points_are('/', '/fake') @istest def root_is_not_duplicated(self): self.fstab.add_mount('/') self.assert_mount_points_are('/') @istest def test_something(self): fstab = FakeFstab() fstab.add_mount('/fake') assert_equals('/fake', fstab.volume_of('/fake/foo')) def assert_mount_points_are(self, *expected_mounts): expected_mounts = list(expected_mounts) actual_mounts = list(self.fstab.mount_points()) assert_items_equal(expected_mounts, list(self.fstab.mount_points()), 'Expected: %s\n' 'Found: %s\n' % (expected_mounts, actual_mounts)) trash-cli-0.12.9.14/unit_tests/test_fake_ismount.py000066400000000000000000000025751237725047000222320ustar00rootroot00000000000000from trashcli.fstab import FakeIsMount from nose.tools import istest from nose.tools import assert_false from nose.tools import assert_true @istest class OnDefault: def setUp(self): self.ismount = FakeIsMount() @istest def by_default_root_is_mount(self): assert_true(self.ismount('/')) @istest def while_by_default_any_other_is_not_a_mount_point(self): assert_false(self.ismount('/any/other')) @istest class WhenOneFakeVolumeIsDefined: def setUp(self): self.ismount = FakeIsMount() self.ismount.add_mount('/fake-vol') @istest def accept_fake_mount_point(self): assert_true(self.ismount('/fake-vol')) @istest def other_still_are_not_mounts(self): assert_false(self.ismount('/other')) @istest def dont_get_confused_by_traling_slash(self): assert_true(self.ismount('/fake-vol/')) @istest class WhenMultipleFakesMountPoints: def setUp(self): self.ismount = FakeIsMount() self.ismount.add_mount('/vol1') self.ismount.add_mount('/vol2') @istest def recognize_both(self): assert_true(self.ismount('/vol1')) assert_true(self.ismount('/vol2')) assert_false(self.ismount('/other')) @istest def should_handle_relative_volumes(): ismount = FakeIsMount() ismount.add_mount('fake-vol') assert_true(ismount('fake-vol')) trash-cli-0.12.9.14/unit_tests/test_global_trashcan.py000066400000000000000000000010401237725047000226530ustar00rootroot00000000000000from mock import Mock from nose.tools import istest from trashcli.trash import GlobalTrashCan class TestGlobalTrashCan: def setUp(self): self.reporter = Mock() self.trashcan = GlobalTrashCan( home_trashcan = Mock(), reporter = self.reporter, getuid = lambda:123, now = None) @istest def should_report_when_trash_fail(self): self.trashcan.trash('non-existent') self.reporter.unable_to_trash_file.assert_called_with('non-existent') trash-cli-0.12.9.14/unit_tests/test_joining_paths.py000066400000000000000000000010461237725047000223720ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import assert_equals def test_how_path_joining_works(): from os.path import join assert_equals('/another-absolute', join('/absolute', '/another-absolute')) assert_equals('/absolute/relative', join('/absolute', 'relative')) assert_equals('/absolute', join('relative', '/absolute')) assert_equals('relative/relative', join('relative', 'relative')) assert_equals('/absolute', join('', '/absolute')) assert_equals('/absolute', join(None, '/absolute')) trash-cli-0.12.9.14/unit_tests/test_list_all_trashinfo_contents.py000066400000000000000000000031131237725047000253300ustar00rootroot00000000000000from mock import Mock, call from nose.tools import assert_equals, assert_items_equal class TestListing: def setUp(self): self.trashdir = Mock() self.trashinfo_reader = Mock() self.listing = Listing(self.trashdir, self.trashinfo_reader) def test_it_should_read_all_trashinfo_from_home_dir(self): self.listing.read_home_trashdir('/path/to/trash_dir') self.trashdir.list_trashinfos.assert_called_with( trashdir='/path/to/trash_dir', list_to=self.trashinfo_reader) class TestTrashDirReader: def test_should_list_all_trashinfo_found(self): def files(path): yield 'file1'; yield 'file2' os_listdir = Mock(side_effect=files) trashdir = TrashDirReader(os_listdir) out = Mock() trashdir.list_trashinfos(trashdir='/path', list_to=out) assert_items_equal([call(trashinfo='/path/file1'), call(trashinfo='/path/file2')], out.mock_calls) class TrashDirReader: def __init__(self, os_listdir): self.os_listdir = os_listdir def list_trashinfos(self, trashdir, list_to): import os for entry in self.os_listdir(trashdir): full_path = os.path.join(trashdir, entry) list_to(trashinfo=full_path) class Listing: def __init__(self, trashdir, trashinfo_reader): self.trashdir = trashdir self.trashinfo_reader = trashinfo_reader def read_home_trashdir(self, path): self.trashdir.list_trashinfos(trashdir=path, list_to=self.trashinfo_reader) trash-cli-0.12.9.14/unit_tests/test_list_mount_points.py000066400000000000000000000031151237725047000233260ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy import unittest import StringIO from trashcli.list_mount_points import _mount_points_from_df_output class MountPointFromDirTest(unittest.TestCase): def test_should_skip_the_first_line(self): mount_points = _mount_points_from_df_output(StringIO.StringIO( 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' )) self.assertEquals([], list(mount_points)) def test_should_return_the_first_mount_point(self): mount_points = _mount_points_from_df_output(StringIO.StringIO( 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' '/dev/disk0s2 243862672 121934848 121671824 51% /\n' )) self.assertEquals(['/'], list(mount_points)) def test_should_return_multiple_mount_point(self): mount_points = _mount_points_from_df_output(StringIO.StringIO( 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' '/dev/disk0s2 243862672 121934848 121671824 51% /\n' '/dev/disk1s1 156287996 123044260 33243736 79% /Volumes/DISK\n' )) self.assertEquals(['/', '/Volumes/DISK'], list(mount_points)) def test_should_return_mount_point_with_white_spaces(self): mount_points = _mount_points_from_df_output(StringIO.StringIO( 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' '/dev/disk0s2 243862672 121934848 121671824 51% /\n' '/dev/disk1s1 156287996 123044260 33243736 79% /Volumes/with white spaces\n' )) self.assertEquals(['/', '/Volumes/with white spaces'], list(mount_points)) trash-cli-0.12.9.14/unit_tests/test_make_script.py000066400000000000000000000040701237725047000220370ustar00rootroot00000000000000from textwrap import dedent from nose.tools import assert_equals import mock from mock import Mock from setup import BinDir class TestMakeScript: def setUp(self): self.make_file_executable = Mock() self.write_file = Mock() self.make_dir = Mock() def capture(name, contents): self.name = name self.contents = contents self.write_file.side_effect = capture bindir = BinDir( make_file_executable = self.make_file_executable, write_file = self.write_file, make_dir = self.make_dir) bindir.add_script('trash-put', 'trashcli.cmds', 'put') def test_should_make_bin_dir(self): self.make_dir.assert_called_with('bin') def test_should_set_executable_permission(self): self.make_file_executable.assert_called_with('bin/trash-put') def test_should_write_the_script(self): self.write_file.assert_called_with( 'bin/trash-put', mock.ANY) def test_the_script_should_call_the_right_function_from_the_right_module(self): args, kwargs = self.write_file.call_args (_, contents) = args expected = dedent("""\ #!/usr/bin/env python from __future__ import absolute_import import sys from trashcli.cmds import put as main sys.exit(main()) """) assert_equals(expected, contents, "Expected:\n---\n%s---\n" "Actual :\n---\n%s---\n" % (expected, contents)) class TestListOfCreatedScripts: def setUp(self): self.bindir = BinDir( make_file_executable = Mock(), write_file = Mock(), make_dir = Mock()) def test_is_empty_on_start_up(self): assert_equals(self.bindir.created_scripts, []) def test_collect_added_script(self): self.bindir.add_script('foo-command', 'foo-module', 'main') assert_equals(self.bindir.created_scripts, ['bin/foo-command']) trash-cli-0.12.9.14/unit_tests/test_method1_security_check.py000066400000000000000000000023511237725047000241630ustar00rootroot00000000000000from mock import Mock from integration_tests.files import require_empty_dir from trashcli.trash import TopTrashDirRules class TestMethod1VolumeTrashDirectory: def setUp(self): require_empty_dir('sandbox') self.fs = Mock() self.fs.isdir.return_value = True self.fs.islink.return_value = False self.fs.has_sticky_bit.return_value = True self.checker = TopTrashDirRules(self.fs) self.out = Mock() def test_check_when_no_sticky_bit(self): self.fs.has_sticky_bit.return_value = False self.valid_to_be_written() self.out.not_valid_parent_should_be_sticky.assert_called_with() def test_check_when_no_dir(self): self.fs.isdir.return_value = False self.valid_to_be_written() self.out.not_valid_should_be_a_dir.assert_called_with() def test_check_when_is_symlink(self): self.fs.islink.return_value = True self.valid_to_be_written() self.out.not_valid_parent_should_not_be_a_symlink.assert_called_with() def test_check_pass(self): self.valid_to_be_written() self.out.is_valid() def valid_to_be_written(self): self.checker.valid_to_be_written('sandbox/trash-dir/123', self.out) trash-cli-0.12.9.14/unit_tests/test_parser.py000066400000000000000000000012671237725047000210370ustar00rootroot00000000000000from trashcli.trash import Parser from mock import MagicMock from nose.tools import istest @istest class describe_Parser(): @istest def it_calls_the_actions_passing_the_program_name(self): on_raw = MagicMock() parser = Parser() parser.add_option('raw', on_raw) parser(['trash-list', '--raw']) on_raw.assert_called_with('trash-list') @istest def how_getopt_works_with_an_invalid_option(self): invalid_option_callback = MagicMock() parser = Parser() parser.on_invalid_option(invalid_option_callback) parser(['command-name', '-x']) invalid_option_callback.assert_called_with('command-name', 'x') trash-cli-0.12.9.14/unit_tests/test_parsing_trashinfo_contents.py000066400000000000000000000064641237725047000252040ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import assert_equals, assert_raises from nose.tools import istest from datetime import datetime from mock import MagicMock from trashcli.trash import ParseTrashInfo @istest class describe_ParseTrashInfo2: @istest def it_should_parse_date(self): out = MagicMock() parse = ParseTrashInfo(on_deletion_date = out) parse('[Trash Info]\n' 'Path=foo\n' 'DeletionDate=1970-01-01T00:00:00\n') out.assert_called_with(datetime(1970,1,1,0,0,0)) @istest def it_should_parse_path(self): out = MagicMock() self.parse = ParseTrashInfo(on_path = out) self.parse( '[Trash Info]\n' 'Path=foo\n' 'DeletionDate=1970-01-01T00:00:00\n') out.assert_called_with('foo') from trashcli.trash import parse_deletion_date from trashcli.trash import parse_path def test_how_to_parse_date_from_trashinfo(): from datetime import datetime assert_equals(datetime(2000,12,31,23,59,58), parse_deletion_date('DeletionDate=2000-12-31T23:59:58')) assert_equals(datetime(2000,12,31,23,59,58), parse_deletion_date('DeletionDate=2000-12-31T23:59:58\n')) assert_equals(datetime(2000,12,31,23,59,58), parse_deletion_date('[Trash Info]\nDeletionDate=2000-12-31T23:59:58')) from trashcli.trash import maybe_parse_deletion_date UNKNOWN_DATE='????-??-?? ??:??:??' @istest class describe_maybe_parse_deletion_date: @istest def on_trashinfo_without_date_parse_to_unknown_date(self): assert_equals(UNKNOWN_DATE, maybe_parse_deletion_date(a_trashinfo_without_deletion_date())) @istest def on_trashinfo_with_date_parse_to_date(self): from datetime import datetime example_date_as_string='2001-01-01T00:00:00' same_date_as_datetime=datetime(2001,1,1) assert_equals(same_date_as_datetime, maybe_parse_deletion_date(make_trashinfo(example_date_as_string))) @istest def on_trashinfo_with_invalid_date_parse_to_unknown_date(self): invalid_date='A long time ago' assert_equals(UNKNOWN_DATE, maybe_parse_deletion_date(make_trashinfo(invalid_date))) def test_how_to_parse_original_path(): assert_equals('foo.txt', parse_path('Path=foo.txt')) assert_equals('/path/to/be/escaped', parse_path('Path=%2Fpath%2Fto%2Fbe%2Fescaped')) from trashcli.trash import LazyTrashInfoParser, ParseError class TestParsing: def test_1(self): parser = LazyTrashInfoParser(lambda:("[Trash Info]\n" "Path=/foo.txt\n"), volume_path = '/') assert_equals('/foo.txt', parser.original_location()) class TestLazyTrashInfoParser_with_empty_trashinfo: def setUp(self): self.parser = LazyTrashInfoParser(contents=an_empty_trashinfo, volume_path='/') def test_it_raises_error_on_parsing_original_location(self): with assert_raises(ParseError): self.parser.original_location() def a_trashinfo_without_deletion_date(): return ("[Trash Info]\n" "Path=foo.txt\n") def make_trashinfo(date): return ("[Trash Info]\n" "Path=foo.txt\n" "DeletionDate=%s" % date) def an_empty_trashinfo(): return '' trash-cli-0.12.9.14/unit_tests/test_restore_cmd.py000066400000000000000000000010531237725047000220420ustar00rootroot00000000000000from trashcli.trash import RestoreCmd from nose.tools import assert_equals from StringIO import StringIO class TestTrashRestoreCmd: def test_should_print_version(self): stdout = StringIO() cmd = RestoreCmd(stdout=stdout, stderr=None, environ=None, exit = None, input=None, version = '1.2.3') cmd.run(['trash-restore', '--version']) assert_equals('trash-restore 1.2.3\n', stdout.getvalue()) trash-cli-0.12.9.14/unit_tests/test_storing_paths.py000066400000000000000000000025251237725047000224250ustar00rootroot00000000000000from trashcli.trash import TrashDirectory from nose.tools import assert_equals class TestTrashInfoPath: def test_for_absolute_paths(self): self.dir = TrashDirectory('/volume/.Trash', '/volume') self.dir.store_absolute_paths() self.assert_path_for_trashinfo_is('/file' , '/file') self.assert_path_for_trashinfo_is('/file' , '/dir/../file') self.assert_path_for_trashinfo_is('/outside/file' , '/outside/file') self.assert_path_for_trashinfo_is('/volume/file' , '/volume/file') self.assert_path_for_trashinfo_is('/volume/dir/file' , '/volume/dir/file') def test_for_relative_paths(self): self.dir = TrashDirectory('/volume/.Trash', '/volume') self.dir.store_relative_paths() self.assert_path_for_trashinfo_is('/file' , '/file') self.assert_path_for_trashinfo_is('/file' , '/dir/../file') self.assert_path_for_trashinfo_is('/outside/file' , '/outside/file') self.assert_path_for_trashinfo_is('file' , '/volume/file') self.assert_path_for_trashinfo_is('dir/file' , '/volume/dir/file') def assert_path_for_trashinfo_is(self, expected_value, file_to_be_trashed): result = self.dir.path_for_trash_info.for_file(file_to_be_trashed) assert_equals(expected_value, result) trash-cli-0.12.9.14/unit_tests/test_trash.py000066400000000000000000000062431237725047000206630ustar00rootroot00000000000000# Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy from __future__ import absolute_import from trashcli.trash import TrashDirectory from trashcli.trash import TrashedFile from trashcli.trash import TrashInfo from datetime import datetime import os from unittest import TestCase from nose.tools import raises from nose.tools import assert_equals abspath = os.path.abspath import shutil class TestTrashInfo(TestCase) : def test_parse(self) : data = """[Trash Info] Path=home%2Fandrea%2Fprova.txt DeletionDate=2007-07-23T23:45:07""" result = TrashInfo.parse(data) self.assertEqual(result.path, "home/andrea/prova.txt") self.assert_(isinstance(result.deletion_date,datetime)) self.assertEqual(result.deletion_date, datetime(2007, 7, 23, 23, 45, 07)) def test_init(self) : instance = TrashInfo("path", datetime(2007, 7, 23, 23, 45, 07)) self.assertEquals("path", instance.path) self.assertEquals(datetime(2007, 7, 23, 23, 45, 07), instance.deletion_date) def test_init2(self) : instance = TrashInfo("path", datetime(2007, 7, 23, 23, 45, 07)) self.assertEquals("path", instance.path) self.assertEquals(datetime(2007, 7, 23, 23, 45, 07), instance.deletion_date) def test_format_date(self) : date = datetime(2007, 7, 23, 23, 45, 07) self.assertEquals("2007-07-23T23:45:07", TrashInfo._format_date(date)) class TestTrashedFile(TestCase) : __dummy_datetime=datetime(2007, 7, 23, 23, 45, 07) def setUp(self): self.xdg_data_home = ("sandbox/XDG_DATA_HOME") def test_init(self) : path = "/foo" deletion_date = datetime(2001,01,01) info_file = "/home/user/.local/share/Trash/info/foo.trashinfo" actual_path = ("/home/user/.local/share/Trash/files/foo") trash_directory = TrashDirectory('/home/user/.local/share/Trash', '/') instance = TrashedFile(path, deletion_date, info_file, actual_path, trash_directory) assert_equals(instance.path, path) assert_equals(instance.deletion_date, deletion_date) assert_equals(instance.info_file, info_file) assert_equals(instance.actual_path, actual_path) assert_equals(trash_directory, trash_directory) @raises(ValueError) def test_init_requires_absolute_paths(self): path = "./relative-path" deletion_date = datetime(2001,01,01) info_file = "/home/user/.local/share/Trash/info/foo.trashinfo" actual_path = "/home/user/.local/share/Trash/files/foo" trash_directory = TrashDirectory('/home/user/.local/share/Trash', '/') TrashedFile(path, deletion_date, info_file, actual_path, trash_directory) def test_restore_create_needed_directories(self): trash_dir = TrashDirectory(self.xdg_data_home, '/') trash_dir.store_absolute_paths() os.mkdir("sandbox/foo") touch("sandbox/foo/bar") instance = trash_dir.trash("sandbox/foo/bar") shutil.rmtree("sandbox/foo") instance.restore() assert os.path.exists("sandbox/foo/bar") def touch(path): open(path, 'a+').close() trash-cli-0.12.9.14/unit_tests/test_trash_dir_name.py000066400000000000000000000023361237725047000225200ustar00rootroot00000000000000from unittest import TestCase from nose.tools import assert_equals import os from trashcli.trash import TrashDirectory class TestTrashDirectory(TestCase) : def test_str_uses_tilde(self): os.environ['HOME']='/home/user' self.trash_dir = "/home/user/.local/share/Trash" self.assert_name_is('~/.local/share/Trash') def test_str_dont_uses_tilde(self): os.environ['HOME']='/home/user' self.trash_dir = "/not-in-home/Trash" self.assert_name_is('/not-in-home/Trash') def test_str_uses_tilde_with_trailing_slashes(self): os.environ['HOME']='/home/user/' self.trash_dir = "/home/user/.local/share/Trash" self.assert_name_is('~/.local/share/Trash') def test_str_uses_tilde_with_trailing_slash(self): os.environ['HOME']='/home/user////' self.trash_dir = "/home/user/.local/share/Trash" self.assert_name_is('~/.local/share/Trash') def test_str_with_empty_home(self): os.environ['HOME']='' self.trash_dir = "/foo/Trash" self.assert_name_is('/foo/Trash') def assert_name_is(self, expected_name): trash_dir = TrashDirectory(self.trash_dir, '/') assert_equals(expected_name, trash_dir.name()) trash-cli-0.12.9.14/unit_tests/test_trash_dirs_listing.py000066400000000000000000000106051237725047000234320ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import TrashDirs from nose.tools import istest, assert_in, assert_not_in from mock import Mock @istest class TestTrashDirs_listing: @istest def the_method_2_is_always_in(self): self.uid = 123 self.volumes = ['/usb'] assert_in('/usb/.Trash-123', self.trashdirs()) @istest def the_method_1_is_in_if_it_is_a_sticky_dir(self): self.uid = 123 self.volumes = ['/usb'] self.having_sticky_Trash_dir() assert_in('/usb/.Trash/123', self.trashdirs()) @istest def the_method_1_is_not_considered_if_not_sticky_dir(self): self.uid = 123 self.volumes = ['/usb'] self.having_non_sticky_Trash_dir() assert_not_in('/usb/.Trash/123', self.trashdirs()) @istest def should_return_home_trashcan_when_XDG_DATA_HOME_is_defined(self): self.environ['XDG_DATA_HOME'] = '~/.local/share' assert_in('~/.local/share/Trash', self.trashdirs()) def trashdirs(self): result = [] def append(trash_dir, volume): result.append(trash_dir) class FileReader: def is_sticky_dir(_, path): return self.Trash_dir_is_sticky def exists(_, path): return True def is_symlink(_, path): return False class FakeTopTrashDirRules: def valid_to_be_read(_, path, out): if self.Trash_dir_is_sticky: out.is_valid() else: out.not_valid_parent_should_be_sticky() trash_dirs = TrashDirs( environ=self.environ, getuid=lambda:self.uid, top_trashdir_rules = FakeTopTrashDirRules(), list_volumes = lambda: self.volumes, ) trash_dirs.on_trash_dir_found = append trash_dirs.list_trashdirs() return result def setUp(self): self.uid = -1 self.volumes = () self.Trash_dir_is_sticky = not_important_for_now() self.environ = {} def having_sticky_Trash_dir(self): self.Trash_dir_is_sticky = True def having_non_sticky_Trash_dir(self): self.Trash_dir_is_sticky = False def not_important_for_now(): None from nose.tools import assert_equals from mock import MagicMock from trashcli.trash import TopTrashDirRules @istest class Describe_AvailableTrashDirs_when_parent_is_unsticky: def setUp(self): self.fs = MagicMock() self.dirs = TrashDirs(environ = {}, getuid = lambda:123, top_trashdir_rules = TopTrashDirRules(self.fs), list_volumes = lambda: ['/topdir'], ) self.dirs.on_trashdir_skipped_because_parent_not_sticky = Mock() self.dirs.on_trashdir_skipped_because_parent_is_symlink = Mock() self.fs.is_sticky_dir.side_effect = ( lambda path: {'/topdir/.Trash':False}[path]) def test_it_should_report_skipped_dir_non_sticky(self): self.fs.exists.side_effect = ( lambda path: {'/topdir/.Trash/123':True}[path]) self.dirs.list_trashdirs() (self.dirs.on_trashdir_skipped_because_parent_not_sticky. assert_called_with('/topdir/.Trash/123')) def test_it_shouldnot_care_about_non_existent(self): self.fs.exists.side_effect = ( lambda path: {'/topdir/.Trash/123':False}[path]) self.dirs.list_trashdirs() assert_equals([], self.dirs.on_trashdir_skipped_because_parent_not_sticky.mock_calls) @istest class Describe_AvailableTrashDirs_when_parent_is_symlink: def setUp(self): self.fs = MagicMock() self.dirs = TrashDirs(environ = {}, getuid = lambda:123, top_trashdir_rules = TopTrashDirRules(self.fs), list_volumes = lambda: ['/topdir']) self.fs.exists.side_effect = (lambda path: {'/topdir/.Trash/123':True}[path]) self.symlink_error = Mock() self.dirs.on_trashdir_skipped_because_parent_is_symlink = self.symlink_error def test_it_should_skip_symlink(self): self.fs.is_sticky_dir.return_value = True self.fs.is_symlink.return_value = True self.dirs.list_trashdirs() self.symlink_error.assert_called_with('/topdir/.Trash/123') trash-cli-0.12.9.14/unit_tests/test_trash_new_tests.py000066400000000000000000000015061237725047000227530ustar00rootroot00000000000000# Copyright (C) 2007-2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import (assert_true, assert_equals) from unittest import TestCase from trashcli.trash import GlobalTrashCan from mock import Mock class TestGlobalTrashCan(TestCase): def test_the_attempt_of_deleting_a_dot_directory_should_signaled_as_error(self): argument="." class StubReporter: def __init__(self): self.has_been_called=False def unable_to_trash_dot_entries(self,file): self.has_been_called=True assert_equals(file, argument) reporter=StubReporter() trashcan = GlobalTrashCan( reporter=reporter, home_trashcan = Mock(), ) trashcan.trash('.') assert_true(reporter.has_been_called) trash-cli-0.12.9.14/unit_tests/test_trash_put.py000066400000000000000000000050251237725047000215500ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from trashcli.trash import TrashPutCmd from nose.tools import istest from StringIO import StringIO from integration_tests.assert_equals_with_unidiff import assert_equals_with_unidiff class TrashPutTest: def run(self, *arg): self.stderr = StringIO() self.stdout = StringIO() args = ['trash-put'] + list(arg) cmd = TrashPutCmd(self.stdout, self.stderr) self._collect_exit_code(lambda:cmd.run(args)) def _collect_exit_code(self, main_function): self.actual_exit_code = 0 try: result=main_function() if result is not None: self.actual_exit_code=result except SystemExit, e: self.actual_exit_code = e.code def stderr_should_be(self, expected_err): assert_equals_with_unidiff(expected_err, self._actual_stderr()) def stdout_should_be(self, expected_out): assert_equals_with_unidiff(expected_out, self._actual_stdout()) def _actual_stderr(self): return self.stderr.getvalue() def _actual_stdout(self): return self.stdout.getvalue() @istest class describe_TrashPutCmd(TrashPutTest): @istest def on_help_option_print_help(self): self.run('--help') self.stdout_should_be('''\ Usage: trash-put [OPTION]... FILE... Put files in trash Options: --version show program's version number and exit -h, --help show this help message and exit -d, --directory ignored (for GNU rm compatibility) -f, --force ignored (for GNU rm compatibility) -i, --interactive ignored (for GNU rm compatibility) -r, -R, --recursive ignored (for GNU rm compatibility) -v, --verbose explain what is being done To remove a file whose name starts with a `-', for example `-foo', use one of these commands: trash -- -foo trash ./-foo Report bugs to http://code.google.com/p/trash-cli/issues ''') @istest def it_should_skip_dot_entry(self): self.run('.') self.stderr_should_be("trash-put: cannot trash directory `.'\n") @istest def it_should_skip_dotdot_entry(self): self.run('..') self.stderr_should_be("trash-put: cannot trash directory `..'\n") @istest def it_should_print_usage_on_no_argument(self): self.run() self.stderr_should_be( 'Usage: trash-put [OPTION]... FILE...\n' '\n' 'trash-put: error: Please specify the files to trash.\n') self.stdout_should_be('') trash-cli-0.12.9.14/unit_tests/test_trash_put_reporter.py000066400000000000000000000006371237725047000234760ustar00rootroot00000000000000from nose.tools import assert_equals from nose.tools import istest from trashcli.trash import TrashPutReporter class TestTrashPutReporter: @istest def it_should_record_failures(self): reporter = TrashPutReporter() assert_equals(True, reporter.all_files_have_been_trashed) reporter.unable_to_trash_file('a file') assert_equals(False, reporter.all_files_have_been_trashed) trash-cli-0.12.9.14/unit_tests/test_trash_rm.py000066400000000000000000000026231237725047000213570ustar00rootroot00000000000000from nose.tools import istest, assert_items_equal from mock import Mock, call from trashcli.rm import Filter class TestTrashRmCmd: @istest def a_star_matches_all(self): self.cmd.use_pattern('*') self.cmd.delete_if_matches('/foo', 'info/foo') self.cmd.delete_if_matches('/bar', 'info/bar') assert_items_equal([ call('info/foo'), call('info/bar'), ], self.delete_trashinfo_and_backup_copy.mock_calls) @istest def basename_matches(self): self.cmd.use_pattern('foo') self.cmd.delete_if_matches('/foo', 'info/foo'), self.cmd.delete_if_matches('/bar', 'info/bar') assert_items_equal([ call('info/foo'), ], self.delete_trashinfo_and_backup_copy.mock_calls) @istest def example_with_star_dot_o(self): self.cmd.use_pattern('*.o') self.cmd.delete_if_matches('/foo.h', 'info/foo.h'), self.cmd.delete_if_matches('/foo.c', 'info/foo.c'), self.cmd.delete_if_matches('/foo.o', 'info/foo.o'), self.cmd.delete_if_matches('/bar.o', 'info/bar.o') assert_items_equal([ call('info/foo.o'), call('info/bar.o'), ], self.delete_trashinfo_and_backup_copy.mock_calls) def setUp(self): self.delete_trashinfo_and_backup_copy = Mock() self.cmd = Filter(self.delete_trashinfo_and_backup_copy) trash-cli-0.12.9.14/unit_tests/test_trashdir.py000066400000000000000000000052621237725047000213620ustar00rootroot00000000000000# Copyright (C) 2011 Andrea Francia Trivolzio(PV) Italy from nose.tools import assert_equals from trashcli.trash import TrashDir from trashcli.trash import TrashDirectory class TestTrashDir: def test_path(self): trash_dir = TrashDirectory('/Trash-501', '/') assert_equals('/Trash-501', trash_dir.path) class TestTrashDir_finding_orphans: def test(self): self.fs.create_fake_file('/info/foo.trashinfo') self.find_orphan() assert_equals([], self.orphan_found) def test2(self): self.fs.create_fake_file('/files/foo') self.find_orphan() assert_equals(['/files/foo'], self.orphan_found) def setUp(self): self.orphan_found=[] self.fs = FakeFileSystem() self.trashdir=TrashDir(self.fs) self.trashdir.open('/', None) def find_orphan(self): self.trashdir.each_orphan(self.orphan_found.append) class FakeFileSystem: def __init__(self): self.files={} self.dirs={} def contents_of(self, path): return self.files[path] def exists(self, path): return path in self.files def entries_if_dir_exists(self, path): return self.dirs.get(path, []) def create_fake_file(self, path, contents=''): import os self.files[path] = contents self.create_fake_dir(os.path.dirname(path), os.path.basename(path)) def create_fake_dir(self, dir_path, *dir_entries): self.dirs[dir_path] = dir_entries class TestFakeFileSystem: def setUp(self): self.fs = FakeFileSystem() def test_you_can_read_from_files(self): self.fs.create_fake_file('/path/to/file', "file contents") assert_equals('file contents', self.fs.contents_of('/path/to/file')) def test_when_creating_a_fake_file_it_creates_also_the_dir(self): self.fs.create_fake_file('/dir/file') assert_equals(set(('file',)), set(self.fs.entries_if_dir_exists('/dir'))) def test_you_can_create_multiple_fake_file(self): self.fs.create_fake_file('/path/to/file1', "one") self.fs.create_fake_file('/path/to/file2', "two") assert_equals('one', self.fs.contents_of('/path/to/file1')) assert_equals('two', self.fs.contents_of('/path/to/file2')) def test_no_file_exists_at_beginning(self): assert not self.fs.exists('/filename') def test_after_a_creation_the_file_exists(self): self.fs.create_fake_file('/filename') assert self.fs.exists('/filename') def test_create_fake_dir(self): self.fs.create_fake_dir('/etc', 'passwd', 'shadow', 'hosts') assert_equals(set(['passwd', 'shadow', 'hosts']), set(self.fs.entries_if_dir_exists('/etc'))) trash-cli-0.12.9.14/unit_tests/test_trashdirs_how_to_list_them.py000066400000000000000000000013611237725047000251700ustar00rootroot00000000000000from trashcli.trash import TrashDirs from mock import Mock, call from nose.tools import assert_equals class TestListTrashinfo: def test_howto_list_trashdirs(self): out = Mock() environ = {'HOME':'/home/user'} trashdirs = TrashDirs( environ = environ, getuid = lambda:123, list_volumes = lambda:['/vol', '/vol2'], top_trashdir_rules = Mock(), ) trashdirs.on_trash_dir_found = out trashdirs.list_trashdirs() assert_equals([call('/home/user/.local/share/Trash', '/'), call('/vol/.Trash-123', '/vol'), call('/vol2/.Trash-123', '/vol2')], out.mock_calls) trash-cli-0.12.9.14/unit_tests/test_volume_of.py000066400000000000000000000015211237725047000215270ustar00rootroot00000000000000from trashcli.fstab import VolumeOf from trashcli.fstab import FakeIsMount from nose.tools import assert_equals, istest import os @istest class TestVolumeOf: def setUp(self): self.ismount = FakeIsMount() self.volume_of = VolumeOf(ismount = self.ismount) self.volume_of.abspath = os.path.normpath @istest def return_the_containing_volume(self): self.ismount.add_mount('/fake-vol') assert_equals('/fake-vol', self.volume_of('/fake-vol/foo')) @istest def with_file_that_are_outside(self): self.ismount.add_mount('/fake-vol') assert_equals('/', self.volume_of('/foo')) @istest def it_work_also_with_relative_mount_point(self): self.ismount.add_mount('relative-fake-vol') assert_equals('relative-fake-vol', self.volume_of('relative-fake-vol/foo'))