diff options
author | Jonathan Dieter <jdieter@gmail.com> | 2007-07-11 18:37:03 +0300 |
---|---|---|
committer | Jonathan Dieter <jdieter@gmail.com> | 2007-07-11 18:37:03 +0300 |
commit | ec53c5c2b2f6ea31b22c47e4ae8f71c4eff2cd37 (patch) | |
tree | 8f6deaef1c82c121fc71d5b7b10fa2b0ac0fa853 /yum-presto-legacy | |
parent | dce0600bc64c793ba6e8f67c56c286d8d97e7c4c (diff) | |
download | presto-ec53c5c2b2f6ea31b22c47e4ae8f71c4eff2cd37.tar.gz presto-ec53c5c2b2f6ea31b22c47e4ae8f71c4eff2cd37.tar.xz presto-ec53c5c2b2f6ea31b22c47e4ae8f71c4eff2cd37.zip |
Complete rewrite of yum-presto. New xml format.
Signed-off-by: Jonathan Dieter <jdieter@gmail.com>
Diffstat (limited to 'yum-presto-legacy')
-rw-r--r-- | yum-presto-legacy/COPYING | 339 | ||||
-rw-r--r-- | yum-presto-legacy/ChangeLog | 68 | ||||
-rw-r--r-- | yum-presto-legacy/Makefile | 17 | ||||
-rw-r--r-- | yum-presto-legacy/README | 31 | ||||
-rw-r--r-- | yum-presto-legacy/presto.conf | 16 | ||||
-rw-r--r-- | yum-presto-legacy/presto.py | 163 | ||||
-rw-r--r-- | yum-presto-legacy/shared/deltarpm.py | 86 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestoDownload.py | 188 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestoLog.py | 92 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestoRepo.py | 615 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestoThread.py | 53 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestoTransaction.py | 140 | ||||
-rw-r--r-- | yum-presto-legacy/shared/prestomdparser.py | 169 |
13 files changed, 1977 insertions, 0 deletions
diff --git a/yum-presto-legacy/COPYING b/yum-presto-legacy/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/yum-presto-legacy/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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., 675 Mass Ave, Cambridge, MA 02139, 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) 19yy 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. + + <signature of Ty Coon>, 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. diff --git a/yum-presto-legacy/ChangeLog b/yum-presto-legacy/ChangeLog new file mode 100644 index 0000000..05c5c41 --- /dev/null +++ b/yum-presto-legacy/ChangeLog @@ -0,0 +1,68 @@ +* Tue May 1 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.10 + - Use new -a option to deltarpm to only check against a certain architecture. + This allows us to work completely correctly on x86_64. + - Add "*" to repository of deltarpm as it *doesn't* screw up depsolving. + +* Sun Apr 15 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.9 + - Modifications to make yum-presto compatible with both FC6 and Rawhide + - Removed "*" next to deltarpms because it screws up depsolving + - Fixed bug that showed log error message when running yum list as non super- + user + - Fixed bug that caused yum to hang forever when deltarpm downloads were + Ctrl-C'd + +* Fri Apr 6 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.8 + - Small bugfix + +* Thu Apr 5 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.6 + - Some minor changes to README and configuration file + +* Wed Apr 4 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.5 + - Many small bug fixes + - Improved logging - Large changes + +* Tue Apr 3 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.4 + - Add patch from Ahmed Kamal to put rebuilding of rpms into a + different thread + +* Fri Mar 30 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.3 + - Change to how presto.xml.gz stores sequence. An attempt to + save space doesn't always work, so get rid of it. + +* Thu Mar 29 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.2 + - Minor fixes to make rpmlint happy + - Minor fix so yum doesn't die when download fails + - Minor fix to allow public keys to be imported properly + - Update README + +* Wed Mar 28 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.0 + - Massive changes to downloading structure + - When unable to rebuild drpm, we now download full rpm + - Stop doing slow MD5 check and just check RPM header + while we have a prelink bug + +* Mon Mar 26 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.9 + - Fix another mirrorlist bug + - Minor optimization + - Added logging to /var/log/presto.log + - Fix another mirrorlist bug + - Fix bug where we sometimes die if delta repository doesn't exist + - Properly exit when unable to rebuild drpm + - Do full (slow) MD5 check when checking to see if we can + build RPM from disk + +* Sat Mar 24 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.3 + - Fixed bug that breaks yum install + +* Sat Mar 24 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.2 + - Fixed "not showing download error" bug + - Added --disablepresto yum command-line option + - Added code to trap the (hopefully) unlikely scenario where applydeltarpm + fails + - Show byte savings at end of yum update + +* Fri Mar 23 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.1 + - Fixed bug in handling mirrorlists in original repositories + +* Thu Mar 22 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.0 + - Initial release diff --git a/yum-presto-legacy/Makefile b/yum-presto-legacy/Makefile new file mode 100644 index 0000000..8826b30 --- /dev/null +++ b/yum-presto-legacy/Makefile @@ -0,0 +1,17 @@ +clean: + rm -f *.pyc *.pyo *~ + cd shared; rm -f *.pyc *.pyo *~ + +install: + mkdir -p $(DESTDIR)/usr/lib/yum-plugins + install -m 644 presto.py $(DESTDIR)/usr/lib/yum-plugins + mkdir -p $(DESTDIR)/etc/yum/pluginconf.d + install -m 644 presto.conf $(DESTDIR)/etc/yum/pluginconf.d + mkdir -p $(DESTDIR)/usr/share/presto + install -m 644 shared/prestoRepo.py $(DESTDIR)/usr/share/presto + install -m 644 shared/prestomdparser.py $(DESTDIR)/usr/share/presto + install -m 644 shared/prestoTransaction.py $(DESTDIR)/usr/share/presto + install -m 644 shared/prestoThread.py $(DESTDIR)/usr/share/presto + install -m 644 shared/prestoLog.py $(DESTDIR)/usr/share/presto + install -m 644 shared/prestoDownload.py $(DESTDIR)/usr/share/presto + install -m 644 shared/deltarpm.py $(DESTDIR)/usr/share/presto diff --git a/yum-presto-legacy/README b/yum-presto-legacy/README new file mode 100644 index 0000000..4e53751 --- /dev/null +++ b/yum-presto-legacy/README @@ -0,0 +1,31 @@ +Presto: A project to add delta rpm support into yum for Fedora users +http://hosted.fedoraproject.org/projects/presto. A list of presto-enabled +repositories is available there. + +Installation: +============= +1- Install yum-presto on your system (yum -y install yum-presto) +2- Either use a repository that already has presto enabled in it or modify + your .repo files so they contain the following: + deltaurl = http://your-presto-repository.com + where your-presto-repository.com is replaced with a valid presto repository +3- Now install an old rpm from a presto-enabled repository using rpm, then + try updating it using yum. The plugin should kick in, try to download the + drpm, reconstruct the full rpm, and yum should install that. + +Notes: +====== +Presto will read the deltaurl from two possible locations: +1. The repository's .repo file ("deltaurl = http://your-presto-repository.com") +2. Appended to /etc/yum/pluginconf.d/presto.conf in the form: + [repository] + deltaurl = http://your-presto-repository.com + +Presto.conf has the following options in [main]: +keepdeltas=1 Always keep deltas in cache no matter what keepcache + is set to. +neverkeepdeltas=1 Always remove deltas after creating full rpms. +exitondownloadfailure=0|1 If there is a problem downloading the deltarpm, exit + rather than trying to download the full rpm. +Note: If you specify neither keepdeltas nor neverkeepdeltas, presto will follow + the keepcache option in yum.conf. diff --git a/yum-presto-legacy/presto.conf b/yum-presto-legacy/presto.conf new file mode 100644 index 0000000..488f69f --- /dev/null +++ b/yum-presto-legacy/presto.conf @@ -0,0 +1,16 @@ +# Please go to http://hosted.fedoraproject.org/projects/presto for a list of +# presto-enabled repositories + +[main] +enabled=1 +neverkeepdeltas=1 + +# Setup for test server, enable if you are testing +#[updates] +#deltaurl=http://www.lesbg.com/jdieter/updates/fc6/i386 +# +#[extras] +#deltaurl=http://www.lesbg.com/jdieter/extras/fc6/i386 +# +#[development] +#deltaurl=http://www.lesbg.com/jdieter/updates/development/i386 diff --git a/yum-presto-legacy/presto.py b/yum-presto-legacy/presto.py new file mode 100644 index 0000000..48db24d --- /dev/null +++ b/yum-presto-legacy/presto.py @@ -0,0 +1,163 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# heavily modified from yum-deltarpm.py created by +# Lars Herrmann <herrmann@redhat.com> +# +# 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 Library 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. +# Copyright 2005 Duke University +# Copyright 2007 Jonathan Dieter + +from yum.plugins import TYPE_INTERACTIVE, PluginYumExit +from yum import config + +import os +import sys + +sys.path.append("/usr/share/presto") +import deltarpm +from prestoRepo import PrestoRepository +from prestomdparser import PrestoMDParser +import prestoTransaction +import prestoLog +import prestoDownload + +requires_api_version = '2.1' +LOG_FILE = "/var/log/presto.log" +plugin_type = (TYPE_INTERACTIVE,) + +rpm_size = 0 +drpm_size = 0 +drpm_count = 0 +log = None + +# Configuration stuff +def config_hook(conduit): + # Set up repository specific deltarpm url and mirrorlist + config.RepoConf.deltaurl = config.UrlListOption() + config.RepoConf.deltamirrorlist = config.UrlOption() + + # Add --disable-presto option + parser = conduit.getOptParser() + parser.add_option('', '--disablepresto', dest='disablepresto', + action='store_true', default=False, + help="disable Presto plugin and don't download any deltarpms") + +# Set up Presto repositories +def postreposetup_hook(conduit): + opts, commands = conduit.getCmdLine() + if not opts.disablepresto: + conduit.info(2, 'Setting up Presto') + for active_repo in conduit.getRepos().listEnabled(): + p_repo = PrestoRepository(active_repo, conduit) + p_repo.setup(conduit.getConf().cache) + + conduit.info(2, 'Reading Presto metadata in from local files') + for active_repo in conduit.getRepos().listEnabled(): + xml = active_repo.p_repo.getPrestoXML() + if active_repo.p_repo.enabled: + xmldata = active_repo.p_repo.repoXML.getData('deltas') + (ctype, csum) = xmldata.checksum + parser = PrestoMDParser(xml) + active_repo.p_repo.deltalist = parser.getDeltaList() + else: + conduit.info(5, '--disablepresto specified - Presto disabled') + + +def postresolve_hook(conduit): + global rpm_size + global drpm_size + global drpm_count + + opts, commands = conduit.getCmdLine() + if not opts.disablepresto: + # Cycle through packages to see if there's a deltarpm available + for newpkg in conduit.getTsInfo(): + if newpkg.ts_state != "e": + (chosen_drpm, installed, local, drpm_enabled) = prestoTransaction.find_available_drpms(conduit, newpkg) + + # If a drpm was found, change certain package information so it reflects + # the drpm, not the rpm. + if chosen_drpm != None: + newpkg.po.has_drpm = True + conduit.info(2, "Found deltarpm update for %s.%s %s:%s-%s" % (newpkg.name, newpkg.arch, newpkg.epoch, newpkg.version, newpkg.release)) + # In yum 3.0.x, this doesn't get defined if you run "yum update x" rather than "yum update" + rpm_size += int(newpkg.po.size) + drpm_size += int(chosen_drpm['size']) + newpkg.po.realpackagesize = newpkg.po.size + if hasattr(newpkg.po, 'packagesize'): + newpkg.po.packagesize = chosen_drpm['size'] + else: + newpkg.po.simple['packagesize'] = chosen_drpm['size'] + newpkg.po.deltasize = chosen_drpm['size'] + newpkg.po.deltarelativepath = chosen_drpm['drpm_filename'] + newpkg.po.deltachecksumtype = chosen_drpm['checksum_type'] + newpkg.po.deltachecksum = chosen_drpm['checksum'] + newpkg.po.deltalocalpath = newpkg.po.repo.deltasdir + "/" + os.path.basename(chosen_drpm['drpm_filename']) + newpkg.po.to = newpkg + newpkg.po.hasdrpm = True + newpkg.repoid = newpkg.po.repo.id + " *" + drpm_count += 1 + else: + if installed and drpm_enabled and not local: + try: + rpm_size += int(newpkg.po.size) + drpm_size += int(newpkg.po.size) + except: + pass + return + + +def predownload_hook(conduit): + global drpm_count + global log + + # Set up logging + log = prestoLog.PrestoLog(conduit, LOG_FILE) + + pkglist = conduit.getDownloadPackages() + + opts, commands = conduit.getCmdLine() + if not opts.disablepresto and drpm_count > 0: + conduit.info(2, "Downloading DeltaRPMs:") + + # Download deltarpms + problems = prestoDownload.downloadPkgs(conduit, pkglist, log) + + # If 'exitondownloaderror' is on, exit + if conduit.confBool('main', 'exitondownloaderror') and len(problems.keys()) > 0: + errstring = 'Error Downloading Packages:\n' + for key in problems.keys(): + errors = misc.unique(problems[key]) + for error in errors: + errstring += ' %s: %s\n' % (key, error) + raise PluginYumExit(errstring) + + else: + conduit.info(2, "Downloading RPMs:") + +def posttrans_hook(conduit): + global rpm_size + global drpm_size + global log + + log.close() + + if rpm_size > 0: + drpm_string = prestoTransaction.format_number(drpm_size) + rpm_string = prestoTransaction.format_number(rpm_size) + + conduit.info(2, "Size of all updates downloaded from Presto-enabled repositories: %s" % drpm_string) + conduit.info(2, "Size of updates that would have been downloaded if Presto wasn't enabled: %s" % rpm_string) + conduit.info(2, "This is a savings of %i percent" % (100 - ((drpm_size * 100) / rpm_size))) diff --git a/yum-presto-legacy/shared/deltarpm.py b/yum-presto-legacy/shared/deltarpm.py new file mode 100644 index 0000000..97a3cc6 --- /dev/null +++ b/yum-presto-legacy/shared/deltarpm.py @@ -0,0 +1,86 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# mostly taken from deltarpm.py created by +# Lars Herrmann <herrmann@redhat.com> +# and modified for Presto by +# Ahmed Kamal <email.ahmedkamal@googlemail.com> +# +# license: GPL (see COPYING file in distribution) +# +# this module provides a python wrapper around deltarpm tools written by suse +# +# TODO: catch exceptions wherever possible and raise useful ones ;) +# see TODO lines in methods + +APPLY='/usr/bin/applydeltarpm' + +import popen2 +import string +import os + +class Process: + """wrapper class to execute programs and return exitcode and output (stdout and stderr combined)""" + def __init__(self, conduit): + self.__stdout=None + self.__returncode=None + self.__command=None + self.__args=None + self.conduit = conduit + + def run(self, command, *args): + self.__command=command + self.__args=args + cmdline=command+" "+string.join(args, " ") + self.conduit.info(7, '%s.%s: executing %s' % (self.__class__, 'run', cmdline)) + pipe = popen2.Popen4(cmdline) + self.__stdout=pipe.fromchild.read() + retcode = pipe.wait() + if os.WIFEXITED(retcode): + self.__returncode = os.WEXITSTATUS(retcode) + else: + self.__returncode = retcode + # fallback to old implementation - works better ? + #stdoutp = os.popen(cmdline,'r',1) + #self.__stdout = stdoutp.read() + #retcode = stdoutp.close() + #if retcode is None: + # self.__returncode = 0 + #else: + # self.__returncode = retcode + + def getOutput(self): + return self.__stdout + + def returnCode(self): + return self.__returncode + +class DeltaRpmWrapper: + """wrapper around deltarpm binaries - implement methods for applying and verifying delta rpms + - raises exceptions if exitcode of binaries was != 0""" + + def __init__(self, conduit): + self.conduit = conduit + self.conduit.info(7, '%s.%s: created' % (self.__class__, '__init__')) + + def apply(self, arch, newrpmfile, deltarpmfile): + """wraps execution of applydeltarpm [-r oldrpm] deltarpm newrpm - + constructs file names and paths based on given RpmDescription and instance settings for directories""" + # TODO: test args for type == instance and __class__ == RpmDescription + self.conduit.info(7, '%s.apply(%s,%s)' % (self.__class__, newrpmfile, deltarpmfile)) + p=Process(self.conduit) + # targetrpm filename + p.run(APPLY, '-a', arch, deltarpmfile, newrpmfile) + if p.returnCode(): + # in case of error, raise exception + raise Exception("Could not apply deltarpm: %d" % (p.returnCode())) + return newrpmfile + + def verifySequence(self, arch, sequence): + """wraps execution of applydeltarpm [-r oldrpm] -s seqfilecontent - + constructs file names and paths based on given RpmDescription and instance settings for directories""" + self.conduit.info(7, '%s.verify(%s)' % (self.__class__, sequence)) + p = Process(self.conduit) + p.run(APPLY, '-a', arch, '-C', '-s', sequence) + if p.returnCode(): + # in case of error, raise exception + raise Exception("Could not verify sequence of deltarpm: %d" % (p.returnCode())) diff --git a/yum-presto-legacy/shared/prestoDownload.py b/yum-presto-legacy/shared/prestoDownload.py new file mode 100644 index 0000000..1c29608 --- /dev/null +++ b/yum-presto-legacy/shared/prestoDownload.py @@ -0,0 +1,188 @@ +# 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 Library 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. +# +# Copyright 2005 Duke University +# Copyright 2007 Jonathan Dieter + +import os +from yum import misc +from yum import Errors +from yum import types +from urlgrabber.grabber import URLGrabError +import deltarpm +import prestoThread +import Queue +import thread + +def verifyDelta(fo, po, conduit, raiseError): + """verifies the deltarpm is what we expect it to be + raiseError = defaults to 0 - if 1 then will raise + a URLGrabError if the file does not check out. + otherwise it returns false for a failure, true for success""" + + if type(fo) is types.InstanceType: + fo = fo.filename + + try: + verifyChecksum(fo, po.deltachecksumtype, po.deltachecksum) + except: + if raiseError: + raise URLGrabError(-1, 'Package does not match intended download') + else: + return False + + return True + + +def verifyChecksum(filename, checksumType, csum): + """Verify the checksum of the file versus the + provided checksum""" + + try: + + filesum = misc.checksum(checksumType, filename) + except Errors.MiscError, e: + raise URLGrabError(-3, 'Could not perform checksum') + + if filesum != csum: + raise URLGrabError(-1, 'Package does not match checksum') + + return 0 + +def reconstruct(conduit, po, log): + deltalocal = po.deltalocalpath + retlist = "" + + drpm = deltarpm.DeltaRpmWrapper(conduit) + + try: + drpm.apply(po.arch, po.localpath, deltalocal) + except: + retlist += "Error rebuilding rpm from %s! Will download full package.\n" % os.path.basename(deltalocal) + try: + os.unlink(po.localpath) + except: + pass + else: + # log success + log.log(po.oldpkg_string, po.newpkg_string, os.stat(po.localpath)[6], os.stat(deltalocal)[6]) + + #retlist += "Built %s from deltarpm\n" % os.path.basename(po.localpath) + # Check to see whether or not we should keep the drpms + # FIXME: Is there any way to see whether or not a Boolean option was not set? + if conduit.confBool('main', 'neverkeepdeltas'): + delete = True + elif conduit.confBool('main', 'keepdeltas'): + delete = False + elif conduit.getConf().keepcache != 0: + delete = False + else: + delete = True + + if delete: + try: + os.unlink(deltalocal) + except: + pass + return retlist + + + +def downloadPkgs(conduit, pkglist, log): + """download list of package objects handed to you, return errors""" + + opts, commands = conduit.getCmdLine() + + errors = {} + def adderror(po, msg): + errors.setdefault(po, []).append(msg) + + # Set up thread for applying drpms + queue = Queue.Queue(0) + lock = thread.allocate_lock() + curthread = prestoThread.ReconstructionThread(queue, lock, reconstruct) + curthread.start() + + # Check whether drpm is already downloaded + repo_cached = False + remote_pkgs = [] + for po in conduit.getDownloadPackages(): + if hasattr(po, 'has_drpm') and po.has_drpm: + local = po.deltalocalpath + if os.path.exists(local): + cursize = os.stat(local)[6] + totsize = long(po.deltasize) + try: + verifyChecksum(local, po.deltachecksumtype, po.deltachecksum) + except: + if po.repo.p_repo.cache: + repo_cached = True + adderror(po, 'package fails checksum but caching is ' + 'enabled for %s' % po.repo.p_repo.id) + + if cursize >= totsize: # otherwise keep it around for regetting + os.unlink(local) + else: + # Deltarpm is local and good, let's put it in the rebuild thread. + conduit.info(5, "using local copy of deltarpm for %s" % po) + queue.put((conduit, po, log)) + continue + remote_pkgs.append(po) + + # Download deltarpms and send them to another thread to be rebuilt + i = 0 + for po in remote_pkgs: + i += 1 + checkfunc = (verifyDelta, (po, conduit, 1), {}) + cache = po.repo.p_repo.http_caching != 'none' + dirstat = os.statvfs(po.repo.deltasdir) + if (dirstat.f_bavail * dirstat.f_bsize) <= long(po.size): + adderror(po, 'Insufficient space in download directory %s ' + 'to download' % po.repo.deltasdir) + continue + try: + text = '(%s/%s): %s' % (i, len(remote_pkgs), os.path.basename(po.deltarelativepath)) + deltalocal = po.repo.p_repo.getPackage(po, checkfunc=checkfunc, text=text, cache=cache) + except Errors.RepoError, e: + adderror(po, str(e)) + else: + queue.put((conduit, po, log)) + + if errors.has_key(po): + del errors[po] + + if hasattr(po, 'realpackagesize'): + po.packagesize = po.realpackagesize + del po.realpackagesize + + # Check for waiting messages from building thread + lock.acquire() + if curthread.messages != "": + conduit.info(2, curthread.messages[:-1]) + curthread.messages = "" + lock.release() + + conduit.info(2, "Rebuilding rpms from deltarpms") + + # Tell build thread that there are no more drpms and wait for it to exit + curthread.can_exit = True + queue.put(None) + curthread.join() + + if curthread.messages != "": + conduit.info(2, curthread.messages[:-1]) + curthread.messages = "" + + return errors + diff --git a/yum-presto-legacy/shared/prestoLog.py b/yum-presto-legacy/shared/prestoLog.py new file mode 100644 index 0000000..1323346 --- /dev/null +++ b/yum-presto-legacy/shared/prestoLog.py @@ -0,0 +1,92 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# 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 Library 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. +# Copyright 2005 Duke University + +class PrestoLog: + def __init__(self, conduit, log_filename): + # Open log file for reading + try: + log_file = open(log_filename, "r") + log_exists = True + except: + conduit.info(7, "Info: %s doesn't exist. Will create." % log_filename) + log_exists = False + + # Log file doesn't exist, create + if not log_exists: + try: + log_file = open(log_filename, "w") + log_file.close() + log_exists = True + except: + conduit.info(2, "Warning: Unable to write to %s" % log_filename) + if log_exists: + try: + log_file = open(log_filename, "r") + except: + conduit.info(2, "Warning: Unable to open %s for reading." % log_filename) + log_exists = False + + # Cycle through items already in log so we can come up with total savings + if log_exists: + self.total_rpm_size = 0 + self.total_drpm_size = 0 + + # Get rid of header line + log_file.readline() + + data = log_file.readline() + while data != "": + fc = data.rfind("-") + sc = data.rfind("-", 0, fc-1) + tc = data.rfind("-", 0, sc-1) + lc = data.rfind("-", 0, tc-1) + if lc != -1 and tc != -1 and sc != -1 and fc != -1: + self.total_rpm_size += int(data[lc+1:tc]) + self.total_drpm_size += int(data[tc+1:sc]) + data = log_file.readline() + log_file.close() + + try: + log_file = open(log_filename, "a") + except: + conduit.info(2, "Warning: Unable to open %s for writing." % log_filename) + self.log_file = None + else: + self.log_file = log_filename + log_file.close() + + def log(self, oldrpm_name, newrpm_name, rpm_size, drpm_size): + # Write data to log + self.total_rpm_size += rpm_size + self.total_drpm_size += drpm_size + if self.log_file != None: + try: + log_file = open(self.log_file, "a") + except: + pass + else: + log_file.write("%s => %s - %i - %i - %i - %i\n" % (oldrpm_name, newrpm_name, rpm_size, drpm_size, 100 - ((drpm_size * 100) / rpm_size), 100 - ((self.total_drpm_size * 100) / self.total_rpm_size))) + log_file.close() + + + def close(self): + if self.log_file != None: + try: + self.log_file.close() + except: + pass + diff --git a/yum-presto-legacy/shared/prestoRepo.py b/yum-presto-legacy/shared/prestoRepo.py new file mode 100644 index 0000000..bc1b188 --- /dev/null +++ b/yum-presto-legacy/shared/prestoRepo.py @@ -0,0 +1,615 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# mostly taken from yumRepo.py (part of yum) with a few minor modifications +# +# 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 Library 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. +# Copyright 2005 Duke University + +import os +import re +import time +import types +import urlparse + +from yum import Errors +from urlgrabber.grabber import URLGrabber +import urlgrabber.mirror +from urlgrabber.grabber import URLGrabError +from yum.repos import Repository +from yum import repoMDObject +from yum import parser +from yum import config +from yum import misc + +class PrestoRepository(Repository): + """ + This is an actual repository object + + Configuration attributes are pulled in from config.RepoConf. + """ + + def __init__(self, repo, conduit): + Repository.__init__(self, repo.id) + + # If there's a specific deltarpm url, use that + is_different = False + if conduit.confString(repo.id, 'deltaurl'): + self.baseurl = [conduit.confString(repo.id, 'deltaurl')] + is_different = True + conduit.info(5, 'Manual url set from presto.conf: %s' % self.baseurl) + elif repo.deltaurl != [] and repo.deltaurl != None: + self.baseurl = repo.deltaurl + is_different = True + conduit.info(5, 'Manual url set from repository conf file: %s' % self.baseurl) + else: + self.baseurl = repo.baseurl + + # If there's a specific mirrorlist, use that + if conduit.confString(repo.id, 'deltamirrorlist'): + self.mirrorlist = conduit.confString(repo.id, 'deltamirrorlist') + self.baseurl = None + is_different = True + conduit.info(5, 'Manual mirrorlist set from presto.conf: %s' % self.mirrorlist) + elif repo.deltamirrorlist != None: + self.mirrorlist = repo.deltamirrorlist + self.baseurl = None + is_different = True + conduit.info(5, 'Manual mirrorlist set from repository conf file: %s' % self.mirrorlist) + else: + if self.baseurl == repo.baseurl: + self.mirrorlist = repo.mirrorlist + else: + self.mirrorlist = None + + self.conduit = conduit + self.urls = [] + self.is_different = is_different + if is_different: + self.repoMDFile = 'repodata/prestomd.xml' + self.metadata_cookie_fn = 'presto_cachecookie' + else: + self.repoMDFile = 'repodata/repomd.xml' + self.metadata_cookie_fn = 'cachecookie' + self.repoXML = None + self.cache = 0 + self.mirrorlistparsed = 0 + self.yumvar = {} # empty dict of yumvariables for $string replacement + self._proxy_dict = {} + self.http_headers = {} + + # throw in some stubs for things that will be set by the config class + self.basecachedir = "" + self.cachedir = "" + self.pkgdir = "" + self.hdrdir = "" + self.enabled = True + + # holder for stuff we've grabbed + self.retrieved = { 'deltas':0 } + + # callbacks + self.keepalive = repo.keepalive + self.bandwidth = repo.bandwidth + self.retries = repo.retries + self.throttle = repo.throttle + self.proxy = repo.proxy + self.proxy_username = repo.proxy_username + self.proxy_password = repo.proxy_password + self.timeout = repo.timeout + self.http_caching = repo.http_caching + self.failovermethod = repo.failovermethod + self.metadata_expire = repo.metadata_expire + self.basecachedir = repo.basecachedir + self.callback = repo.callback + self.failure_obj = repo.failure_obj + self.mirror_failure_obj = repo.mirror_failure_obj + self.interrupt_callback = repo.interrupt_callback + self.drpm_list = {} + self.parent = repo + repo.p_repo = self + + + def __getProxyDict(self): + self.doProxyDict() + if self._proxy_dict: + return self._proxy_dict + return None + + # consistent access to how proxy information should look (and ensuring + # that it's actually determined for the repo) + proxy_dict = property(__getProxyDict) + + def ready(self): + """Returns true if this repository is setup and ready for use.""" + return self.repoXML is not None + + def __cmp__(self, other): + if self.id > other.id: + return 1 + elif self.id < other.id: + return -1 + else: + return 0 + + def __str__(self): + return self.id + + def _checksum(self, sumtype, file, CHUNK=2**16): + """takes filename, hand back Checksum of it + sumtype = md5 or sha + filename = /path/to/file + CHUNK=65536 by default""" + try: + return misc.checksum(sumtype, file, CHUNK) + except (Errors.MiscError, EnvironmentError), e: + raise Errors.RepoError, 'Error opening file for checksum: %s' % e + + def dump(self): + output = '[%s]\n' % self.id + vars = ['id', 'bandwidth', 'enabled', + 'keepalive', 'proxy', + 'proxy_password', 'proxy_username', + 'retries', 'throttle', 'timeout', 'mirrorlist', + 'cachedir' ] + vars.sort() + for attr in vars: + output = output + '%s = %s\n' % (attr, getattr(self, attr)) + output = output + 'baseurl =' + for url in self.urls: + output = output + ' %s\n' % url + + return output + + def check(self): + """self-check the repo information - if we don't have enough to move + on then raise a repo error""" + if len(self.urls) < 1: + raise Errors.RepoError, \ + 'Cannot find a valid deltaurl for repo: %s' % self.id + + def doProxyDict(self): + if self._proxy_dict: + return + + self._proxy_dict = {} # zap it + proxy_string = None + if self.proxy not in [None, '_none_']: + proxy_string = '%s' % self.proxy + if self.proxy_username is not None: + proxy_parsed = urlparse.urlsplit(self.proxy, allow_fragments=0) + proxy_proto = proxy_parsed[0] + proxy_host = proxy_parsed[1] + proxy_rest = proxy_parsed[2] + '?' + proxy_parsed[3] + proxy_string = '%s://%s@%s%s' % (proxy_proto, + self.proxy_username, proxy_host, proxy_rest) + + if self.proxy_password is not None: + proxy_string = '%s://%s:%s@%s%s' % (proxy_proto, + self.proxy_username, self.proxy_password, + proxy_host, proxy_rest) + + if proxy_string is not None: + self._proxy_dict['http'] = proxy_string + self._proxy_dict['https'] = proxy_string + self._proxy_dict['ftp'] = proxy_string + + def __headersListFromDict(self): + """Convert our dict of headers to a list of 2-tuples for urlgrabber.""" + headers = [] + + keys = self.http_headers.keys() + for key in keys: + headers.append((key, self.http_headers[key])) + + return headers + + def setupGrab(self): + """sets up the grabber functions with the already stocked in urls for + the mirror groups""" + + if self.failovermethod == 'roundrobin': + mgclass = urlgrabber.mirror.MGRandomOrder + else: + mgclass = urlgrabber.mirror.MirrorGroup + + headers = tuple(self.__headersListFromDict()) + + self.grabfunc = URLGrabber(keepalive=self.keepalive, + bandwidth=self.bandwidth, + retry=self.retries, + throttle=self.throttle, + progress_obj=self.callback, + proxies = self.proxy_dict, + failure_callback=self.failure_obj, + interrupt_callback=self.interrupt_callback, + timeout=self.timeout, + http_headers=headers, + reget='simple') + + self.grab = mgclass(self.grabfunc, self.urls, + failure_callback=self.mirror_failure_obj) + + def dirSetup(self): + """make the necessary dirs, if possible, raise on failure""" + + cachedir = os.path.join(self.parent.basecachedir, self.id) + deltasdir = os.path.join(cachedir, 'deltas') + self.parent.setAttribute('deltasdir', deltasdir) + self.setAttribute('cachedir', cachedir) + + cookie = cachedir + '/' + self.metadata_cookie_fn + self.setAttribute('metadata_cookie', cookie) + + for dir in [cachedir, self.parent.deltasdir]: + if self.cache == 0: + if os.path.exists(dir) and os.path.isdir(dir): + continue + else: + try: + os.makedirs(dir, mode=0755) + except OSError, e: + raise Errors.RepoError, \ + "Error making cache directory: %s error was: %s" % (dir, e) + else: + if not os.path.exists(dir): + raise Errors.RepoError, \ + "Cannot access repository dir %s" % dir + + def baseurlSetup(self): + """go through the baseurls and mirrorlists and populate self.urls + with valid ones, run self.check() at the end to make sure it worked""" + + goodurls = [] + if self.mirrorlist and not self.mirrorlistparsed: + mirrorurls = getMirrorList(self.mirrorlist, self.proxy_dict) + self.mirrorlistparsed = 1 + for url in mirrorurls: + url = parser.varReplace(url, self.yumvar) + self.baseurl.append(url) + + for url in self.baseurl: + url = parser.varReplace(url, self.yumvar) + (s,b,p,q,f,o) = urlparse.urlparse(url) + if s not in ['http', 'ftp', 'file', 'https']: + print 'not using ftp, http[s], or file for repos, skipping - %s' % (url) + continue + else: + goodurls.append(url) + + self.setAttribute('urls', goodurls) + self.check() + self.setupGrab() # update the grabber for the urls + + def __get(self, url=None, relative=None, local=None, start=None, end=None, + copy_local=0, checkfunc=None, text=None, reget='simple', cache=True): + """retrieve file from the mirrorgroup for the repo + relative to local, optionally get range from + start to end, also optionally retrieve from a specific baseurl""" + + # if local or relative is None: raise an exception b/c that shouldn't happen + # if url is not None - then do a grab from the complete url - not through + # the mirror, raise errors as need be + # if url is None do a grab via the mirror group/grab for the repo + # return the path to the local file + + # Turn our dict into a list of 2-tuples + headers = self.__headersListFromDict() + + # We will always prefer to send no-cache. + if not (cache or self.http_headers.has_key('Pragma')): + headers.append(('Pragma', 'no-cache')) + + headers = tuple(headers) + + if local is None or relative is None: + raise Errors.RepoError, \ + "get request for Repo %s, gave no source or dest" % self.id + + if self.cache == 1: + if os.path.exists(local): # FIXME - we should figure out a way + return local # to run the checkfunc from here + + else: # ain't there - raise + raise Errors.RepoError, \ + "Caching enabled but no local cache of %s from %s" % (local, + self) + if url is not None: + ug = URLGrabber(keepalive = self.keepalive, + bandwidth = self.bandwidth, + retry = self.retries, + throttle = self.throttle, + progress_obj = self.callback, + copy_local = copy_local, + reget = reget, + proxies = self.proxy_dict, + failure_callback = self.failure_obj, + interrupt_callback=self.interrupt_callback, + timeout=self.timeout, + checkfunc=checkfunc, + http_headers=headers, + ) + + remote = url + '/' + relative + + try: + result = ug.urlgrab(remote, local, + text=text, + range=(start, end), + ) + except URLGrabError, e: + raise Errors.RepoError, \ + "failed to retrieve %s from %s\nerror was %s" % (relative, self.id, e) + + else: + try: + result = self.grab.urlgrab(relative, local, + text = text, + range = (start, end), + copy_local=copy_local, + reget = reget, + checkfunc=checkfunc, + http_headers=headers, + ) + except URLGrabError, e: + raise Errors.RepoError, "failure: %s from %s: %s" % (relative, self.id, e) + + return result + + def getPackage(self, package, checkfunc = None, text = None, cache = True): + remote = package.deltarelativepath + local = package.deltalocalpath + if hasattr(package, 'basepath'): + basepath = package.basepath + else: + basepath = package.returnSimple('basepath') + + return self.__get(url=basepath, + relative=remote, + local=local, + checkfunc=checkfunc, + text=text, + cache=cache + ) + + def metadataCurrent(self): + """Check if there is a metadata_cookie and check its age. If the + age of the cookie is less than metadata_expire time then return true + else return False""" + + val = False + if os.path.exists(self.metadata_cookie): + cookie_info = os.stat(self.metadata_cookie) + if cookie_info[8] + self.metadata_expire > time.time(): + val = True + # WE ARE FROM THE FUTURE!!!! + elif cookie_info[8] > time.time(): + val = False + return val + + def setMetadataCookie(self): + """if possible, set touch the metadata_cookie file""" + + check = self.metadata_cookie + if not os.path.exists(self.metadata_cookie): + check = self.cachedir + + if os.access(check, os.W_OK): + fo = open(self.metadata_cookie, 'w+') + fo.close() + del fo + + + def setup(self, cache): + try: + self.cache = cache + self.baseurlSetup() + self.dirSetup() + except Errors.RepoError, e: + raise + + try: + self._loadRepoXML(text=self) + except Errors.RepoError, e: + raise Errors.RepoError, ('Cannot open/read %s file for repository: %s' % (self.repoMDFile, self)) + + + def _loadRepoXML(self, text=None): + """retrieve/check/read in repomd.xml from the repository""" + + remote = self.repoMDFile + if self.is_different: + local = self.cachedir + '/prestomd.xml' + else: + local = self.cachedir + '/repomd.xml' + + if self.repoXML is not None: + return + + if self.cache or self.metadataCurrent(): + if not os.path.exists(local): + raise Errors.RepoError, 'Cannot find %s file for %s' % (self.repoMDFile, self) + else: + result = local + else: + checkfunc = (self._checkRepoXML, (), {}) + try: + result = self.__get(relative=remote, + local=local, + copy_local=1, + text=text, + reget=None, + checkfunc=checkfunc, + cache=self.http_caching == 'all') + + + except URLGrabError, e: + raise Errors.RepoError, 'Error downloading file %s: %s' % (local, e) + # if we have a 'fresh' repomd.xml then update the cookie + self.setMetadataCookie() + + try: + self.repoXML = repoMDObject.RepoMD(self.id, result) + except Errors.RepoMDError, e: + raise Errors.RepoError, 'Error importing %s from %s: %s' % (self.repoMDFile, self, e) + + def _checkRepoXML(self, fo): + if type(fo) is types.InstanceType: + filepath = fo.filename + else: + filepath = fo + + try: + repoMDObject.RepoMD(self.id, filepath) + except Errors.RepoMDError, e: + raise URLGrabError(-1, 'Error importing %s for %s: %s' % (self.repoMDFile, self, e)) + + + def checkMD(self, fn, mdtype): + """check the metadata type against its checksum""" + + thisdata = self.repoXML.getData(mdtype) + + (r_ctype, r_csum) = thisdata.checksum # get the remote checksum + + if type(fn) == types.InstanceType: # this is an urlgrabber check + file = fn.filename + else: + file = fn + + try: + l_csum = self._checksum(r_ctype, file) # get the local checksum + except Errors.RepoError, e: + raise URLGrabError(-3, 'Error performing checksum') + + if l_csum == r_csum: + return 1 + else: + raise URLGrabError(-1, 'Metadata file does not match checksum') + + + + def retrieveMD(self, mdtype): + """base function to retrieve metadata files from the remote url + returns the path to the local metadata file of a 'mdtype' + mdtype must be 'deltas'.""" + try: + thisdata = self.repoXML.getData(mdtype) + except Errors.RepoMDError: + self.enabled = False + self.conduit.info(5, "No drpms available for %s" % self.id) + return + + (r_base, remote) = thisdata.location + fname = os.path.basename(remote) + local = self.cachedir + '/' + fname + + if self.retrieved.has_key(mdtype): + if self.retrieved[mdtype]: # got it, move along + return local + + if self.cache == 1: + if os.path.exists(local): + try: + self.checkMD(local, mdtype) + except URLGrabError, e: + raise Errors.RepoError, \ + "Caching enabled and local cache: %s does not match checksum" % local + else: + return local + + else: # ain't there - raise + raise Errors.RepoError, \ + "Caching enabled but no local cache of %s from %s" % (local, + self) + + if os.path.exists(local): + try: + self.checkMD(local, mdtype) + except URLGrabError, e: + pass + else: + self.retrieved[mdtype] = 1 + return local # it's the same return the local one + + try: + checkfunc = (self.checkMD, (mdtype,), {}) + local = self.__get(relative=remote, local=local, copy_local=1, + checkfunc=checkfunc, reget=None, + cache=self.http_caching == 'all') + except URLGrabError, e: + raise Errors.RepoError, \ + "Could not retrieve %s matching remote checksum from %s" % (local, self) + else: + self.retrieved[mdtype] = 1 + return local + + + def getPrestoXML(self): + """this gets you the path to the primary.xml file, retrieving it if we + need a new one""" + + return self.retrieveMD('deltas') + + def setCallback(self, callback): + self.callback = callback + self.setupGrab() + + def setFailureObj(self, failure_obj): + self.failure_obj = failure_obj + self.setupGrab() + + def setMirrorFailureObj(self, failure_obj): + self.mirror_failure_obj = failure_obj + self.setupGrab() + + def setInterruptCallback(self, callback): + self.interrupt_callback = callback + self.setupGrab() + +def getMirrorList(mirrorlist, pdict = None): + """retrieve an up2date-style mirrorlist file from a url, + we also s/$ARCH/$BASEARCH/ and move along + returns a list of the urls from that file""" + + returnlist = [] + if hasattr(urlgrabber.grabber, 'urlopen'): + urlresolver = urlgrabber.grabber + else: + import urllib + urlresolver = urllib + + scheme = urlparse.urlparse(mirrorlist)[0] + if scheme == '': + url = 'file://' + mirrorlist + else: + url = mirrorlist + + try: + fo = urlresolver.urlopen(url, proxies=pdict) + except urlgrabber.grabber.URLGrabError, e: + print "Could not retrieve mirrorlist %s error was\n%s" % (url, e) + fo = None + + if fo is not None: + content = fo.readlines() + for line in content: + if re.match('^\s*\#.*', line) or re.match('^\s*$', line): + continue + mirror = re.sub('\n$', '', line) # no more trailing \n's + (mirror, count) = re.subn('\$ARCH', '$BASEARCH', mirror) + returnlist.append(mirror) + + return returnlist + diff --git a/yum-presto-legacy/shared/prestoThread.py b/yum-presto-legacy/shared/prestoThread.py new file mode 100644 index 0000000..e090910 --- /dev/null +++ b/yum-presto-legacy/shared/prestoThread.py @@ -0,0 +1,53 @@ +# authors: Ahmed Kamal <email.ahmedkamal@googlemail.com> +# Jonathan Dieter <jdieter@gmail.com> +# +# 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 Library 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. +# Copyright 2007 Ahmed Kamal, Jonathan Dieter + +import threading + +class ReconstructionThread(threading.Thread): + def __init__(self, queue, lock, run_function): + threading.Thread.__init__(self) + self.run_function = run_function + self.queue = queue + self.lock = lock + self.can_exit = False + self.messages = "" + + def run(self): + while True: + try: + retval = self.queue.get(not self.can_exit) + except: + # If we're done with our drpms and no more are coming, let's + # blow this joint + break + if retval != None: + messages = apply(self.run_function, retval) + if self.can_exit: + # If there are not going to be any more new drpms, + # send messages directly to conduit + conduit = retval[0] + if self.messages != "": + conduit.info(2, self.messages[:-1]) + self.messages = "" + if messages != "": + conduit.info(2, messages[:-1]) + else: + # We may be downloading drpms still, so queue messages + self.lock.acquire() + self.messages += messages + self.lock.release() diff --git a/yum-presto-legacy/shared/prestoTransaction.py b/yum-presto-legacy/shared/prestoTransaction.py new file mode 100644 index 0000000..ffaad01 --- /dev/null +++ b/yum-presto-legacy/shared/prestoTransaction.py @@ -0,0 +1,140 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# format_number taken almost completely from progress_meter.py in yum +# +# 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 Library 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. +# Copyright 2005 Duke University +# Copyright 2007 Jonathan Dieter + +import os +import deltarpm + +def format_number(number, SI=False, space=''): + """Turn numbers into human-readable metric-like numbers""" + symbols = ['', # (none) + 'K', # kilo + 'M', # mega + 'G', # giga + 'T', # tera + 'P', # peta + 'E', # exa + 'Z', # zetta + 'Y'] # yotta + + if SI: step = 1000.0 + else: step = 1024.0 + + thresh = 999 + depth = 0 + + # we want numbers between + while number > thresh: + depth = depth + 1 + number = number / step + + # just in case someone needs more than 1000 yottabytes! + diff = depth - len(symbols) + 1 + if diff > 0: + depth = depth - diff + number = number * thresh**depth + + if type(number) == type(1) or type(number) == type(1L): + format = '%i%s%s' + elif number < 9.95: + # must use 9.95 for proper sizing. For example, 9.99 will be + # rounded to 10.0 with the .1f format string (which is too long) + format = '%.1f%s%s' + else: + format = '%.0f%s%s' + + return(format % (number, space, symbols[depth])) + + +def find_available_drpms(conduit, newpkg): + """Find any applicable drpms for newpkg + newpkg is a TransactionMember""" + + rpmdb = conduit.getRpmDB() + + is_local = False + + # Set p_repo to be packages delta repository or set to False if + # there is no delta repository + try: + p_repo = newpkg.po.repo.p_repo + drpm_enabled = p_repo.enabled + + po = newpkg.po + if hasattr(po, 'pkgtype') and po.pkgtype == 'local': + is_local = True + else: + local = po.localPkg() + if os.path.exists(local): + cursize = os.stat(local)[6] + totsize = long(po.size) + if not po.verifyLocalPkg(): + if cursize >= totsize: # otherwise keep it around for regetting + os.unlink(local) + else: + conduit.info(5, "using local copy of %s" % po) + is_local = True + + except: + conduit.info(5, "No Presto repository information for %s.%s %i:%s-%s" % (newpkg.name, newpkg.arch, int(newpkg.epoch), newpkg.version, newpkg.release)) + drpm_enabled = False + is_local = False + + chosen_drpm = None + + # First part of key when matching drpms + key1 = "%s*%s*%i*%s*%s" % (newpkg.name, newpkg.arch, int(newpkg.epoch), newpkg.version, newpkg.release) + + # Find any installed packages that match the ones we want to download + installed = rpmdb.searchNevra(newpkg.name, None, None, None, newpkg.arch) + + if installed == []: + is_installed = False + else: + is_installed = True + + + if is_installed and drpm_enabled and not is_local: + for oldpkg in installed: + # Generate second part of key for matching drpms, then full key + key2 = "%s*%s*%i*%s*%s" % (oldpkg.name, oldpkg.arch, int(oldpkg.epoch), oldpkg.version, oldpkg.release) + key = "%s!!%s" % (key1, key2) + + # Check whether we have a matching drpm + if p_repo.deltalist.has_key(key): + # Check whether or not we already have a matching drpm, then choose smallest of the two if we do + if chosen_drpm == None or p_repo.deltalist[key]['size'] < chosen_drpm['size']: + + # Get sequence code for drpm + sequence = p_repo.deltalist[key]['sequence'] + drpm = deltarpm.DeltaRpmWrapper(conduit) + + # Attempt to apply sequence code for drpm. If this fails, drpm will not apply cleanly, so + # don't even try to download it. + try: + drpm.verifySequence(newpkg.po.arch, sequence) + except: + conduit.info(5, "Verification of %s failed" % sequence) + else: + chosen_drpm = p_repo.deltalist[key] + chosen_drpm['baseurl'] = p_repo.baseurl[0] + newpkg.po.oldpkg_string = "%s.%s %s:%s-%s" % (oldpkg.name, oldpkg.arch, oldpkg.epoch, oldpkg.version, oldpkg.release) + newpkg.po.newpkg_string = "%s.%s %s:%s-%s" % (newpkg.name, newpkg.arch, newpkg.epoch, newpkg.version, newpkg.release) + + return (chosen_drpm, installed, is_local, drpm_enabled) diff --git a/yum-presto-legacy/shared/prestomdparser.py b/yum-presto-legacy/shared/prestomdparser.py new file mode 100644 index 0000000..6764f71 --- /dev/null +++ b/yum-presto-legacy/shared/prestomdparser.py @@ -0,0 +1,169 @@ +# author: Jonathan Dieter <jdieter@gmail.com> +# +# mostly taken from mdparser.py (part of yum) with a few minor modifications +# +# 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 Library 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. +# Copyright 2005 Duke University +# Portions copyright 2007 Jonathan Dieter + +import gzip +try: + from cElementTree import iterparse +except: + from xml.etree.cElementTree import iterparse + +from cStringIO import StringIO + +#TODO: document everything here + +class PrestoMDParser: + + def __init__(self, filename): + + # Set up mapping of meta types to handler classes + handlers = { + '{http://linux.duke.edu/metadata/common}metadata': DeltasEntry, + } + + self.total = None + self.count = 0 + self._handlercls = None + + # Read in type, set package node handler and get total number of + # packages + if filename[-3:] == '.gz': fh = gzip.open(filename, 'r') + else: fh = open(filename, 'r') + parser = iterparse(fh, events=('start', 'end')) + self.reader = parser.__iter__() + event, elem = self.reader.next() + self._handlercls = handlers.get(elem.tag, None) + if not self._handlercls: + raise ValueError('Unknown repodata type "%s" in %s' % ( + elem.tag, filename)) + + def getDeltaList(self): + for event, elem in self.reader: + if event == 'end' and elem.tag == '{http://linux.duke.edu/metadata/common}metadata': + return self._handlercls(elem) + + +class BaseEntry: + def __init__(self, elem): + self._p = {} + + def __getitem__(self, k): + return self._p[k] + + def keys(self): + return self._p.keys() + + def values(self): + return self._p.values() + + def has_key(self, k): + return self._p.has_key(k) + + def __str__(self): + out = StringIO() + keys = self.keys() + keys.sort() + for k in keys: + line = u'%s=%s\n' % (k, self[k]) + out.write(line.encode('utf8')) + return out.getvalue() + + def _bn(self, qn): + if qn.find('}') == -1: return qn + return qn.split('}')[1] + + def _prefixprops(self, elem, prefix): + ret = {} + for key in elem.attrib.keys(): + ret[prefix + '_' + self._bn(key)] = elem.attrib[key] + return ret + +class DeltasEntry(BaseEntry): + def __init__(self, deltas): + BaseEntry.__init__(self, deltas) + # Avoid excess typing :) + p = self._p + + for elem in deltas: + temp = {} + key1 = "" + key2 = "" + for child in elem: + name = self._bn(child.tag) + if name in ('name', 'arch'): + temp[name] = child.text + + elif name == 'version': + attrib = child.attrib + try: + attrib['epoch'] = int(attrib['epoch']) + except: + attrib['epoch'] = 0 + key1 = "%s*%s*%i*%s*%s" % (temp['name'], temp['arch'], attrib['epoch'], attrib['ver'], attrib['rel']) + + elif name == 'deltas': + for oldrpm in child: + temp2 = {} + value = {} + key = None + for oldrpm_child in oldrpm: + name = self._bn(oldrpm_child.tag) + if name in ('name', 'arch'): + temp2[name] = oldrpm_child.text + + elif name == 'version': + ch_attrib = oldrpm_child.attrib + try: + ch_attrib['epoch'] = int(ch_attrib['epoch']) + except: + ch_attrib['epoch'] = attrib['epoch'] + try: + ch_attrib['ver'] = ch_attrib['ver'] + except: + ch_attrib['ver'] = attrib['ver'] + if not temp2.has_key('name'): + temp2['name'] = temp['name'] + if not temp2.has_key('arch'): + temp2['arch'] = temp['arch'] + key2 = "%s*%s*%i*%s*%s" % (temp2['name'], temp2['arch'], ch_attrib['epoch'], ch_attrib['ver'], ch_attrib['rel']) + key = "%s!!%s" % (key1, key2) + p[key] = {} + + if name in ('sequence', 'drpm_filename', 'size'): + p[key][name] = oldrpm_child.text + + if name == "checksum": + p[key][name] = oldrpm_child.text + p[key]["%s_type" % name] = oldrpm_child.attrib['type'] + deltas.clear() + +def test(): + import sys + + parser = PrestoMDParser(sys.argv[1]) + + deltalist = parser.getDeltaList() + + print '-' * 40 + print deltalist + + print 'read: %s deltarpms ' % (len(deltalist.keys())) + +if __name__ == '__main__': + test() |