summaryrefslogtreecommitdiffstats
path: root/yum-presto
diff options
context:
space:
mode:
Diffstat (limited to 'yum-presto')
-rw-r--r--yum-presto/COPYING339
-rw-r--r--yum-presto/ChangeLog36
-rw-r--r--yum-presto/Makefile16
-rw-r--r--yum-presto/README26
-rw-r--r--yum-presto/presto.conf9
-rw-r--r--yum-presto/presto.py149
-rw-r--r--yum-presto/shared/deltarpm.py86
-rw-r--r--yum-presto/shared/prestoDownload.py171
-rw-r--r--yum-presto/shared/prestoLog.py71
-rw-r--r--yum-presto/shared/prestoRepo.py612
-rw-r--r--yum-presto/shared/prestoTransaction.py97
-rw-r--r--yum-presto/shared/prestomdparser.py166
12 files changed, 1778 insertions, 0 deletions
diff --git a/yum-presto/COPYING b/yum-presto/COPYING
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/yum-presto/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/ChangeLog b/yum-presto/ChangeLog
new file mode 100644
index 0000000..7f57c2a
--- /dev/null
+++ b/yum-presto/ChangeLog
@@ -0,0 +1,36 @@
+* Thu Mar 29 2007 Jonathan Dieter <jdieter@gmail.com> - 0.3.1
+ - 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/Makefile b/yum-presto/Makefile
new file mode 100644
index 0000000..a335109
--- /dev/null
+++ b/yum-presto/Makefile
@@ -0,0 +1,16 @@
+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/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/README b/yum-presto/README
new file mode 100644
index 0000000..7fdea95
--- /dev/null
+++ b/yum-presto/README
@@ -0,0 +1,26 @@
+Presto: A project to add delta rpm support into yum for Fedora users
+https://hosted.fedoraproject.org/projects/presto/wiki/WikiStart
+
+Installation:
+=============
+1- Install yum-presto on your system (yum -y install yum-presto)
+2- Now install an old rpm from updates or extras 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://repository.com")
+2. Appended to /etc/yum/pluginconf.d/presto.conf in the form:
+ [repository]
+ deltaurl = http://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/presto.conf b/yum-presto/presto.conf
new file mode 100644
index 0000000..899a4e2
--- /dev/null
+++ b/yum-presto/presto.conf
@@ -0,0 +1,9 @@
+[main]
+enabled=1
+neverkeepdeltas=1
+
+[updates]
+deltaurl=http://www.lesbg.com/jdieter/updates/fc6/i386/
+
+[extras]
+deltaurl=http://www.lesbg.com/jdieter/extras/fc6/i386/
diff --git a/yum-presto/presto.py b/yum-presto/presto.py
new file mode 100644
index 0000000..e9908ee
--- /dev/null
+++ b/yum-presto/presto.py
@@ -0,0 +1,149 @@
+# 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
+
+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
+
+# 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.simple['realpackagesize'] = newpkg.po.size
+ newpkg.po.simple['packagesize'] = chosen_drpm['size']
+ newpkg.po.simple['deltasize'] = chosen_drpm['size']
+ newpkg.po.simple['deltarelativepath'] = chosen_drpm['drpm_filename']
+ newpkg.po.simple['deltachecksumtype'] = chosen_drpm['checksum_type']
+ newpkg.po.simple['deltachecksum'] = chosen_drpm['checksum']
+ newpkg.po.simple['deltalocalpath'] = newpkg.po.repo.deltasdir + "/" + os.path.basename(chosen_drpm['drpm_filename'])
+ newpkg.po.to = newpkg
+ newpkg.realpkgtup = newpkg.pkgtup
+ newpkg.pkgtup = (newpkg.name + " *", newpkg.arch, newpkg.epoch, newpkg.version, newpkg.release)
+ newpkg.po.hasdrpm = True
+ drpm_count += 1
+ else:
+ if installed and drpm_enabled and not local:
+ try:
+ rpm_size += int(newpkg.po.simple['packagesize'])
+ drpm_size += int(newpkg.po.simple['packagesize'])
+ except:
+ pass
+ return
+
+
+def predownload_hook(conduit):
+ global drpm_count
+
+ pkglist = conduit.getDownloadPackages()
+
+ opts, commands = conduit.getCmdLine()
+ if not opts.disablepresto and drpm_count > 0:
+ # Download deltarpms
+ problems = prestoDownload.downloadPkgs(conduit, pkglist)
+
+ # If 'exitondownloaderror' is on, exit
+ if conduit.confBool('main', 'exitondownloaderror') and len(problems.keys()) > 0:
+ errstring = ''
+ 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)
+
+
+def posttrans_hook(conduit):
+ global rpm_size
+ global drpm_size
+ global LOG_FILE
+
+ if rpm_size > 0:
+ prestoLog.log(conduit, LOG_FILE, rpm_size, drpm_size)
+
+ conduit.info(2, "Size of all updates downloaded from Presto-enabled repositories: %i bytes" % drpm_size)
+ conduit.info(2, "Size of updates that would have been downloaded if Presto wasn't enabled: %i bytes" % rpm_size)
+ conduit.info(2, "This is a savings of %i percent" % (100 - ((drpm_size * 100) / rpm_size)))
diff --git a/yum-presto/shared/deltarpm.py b/yum-presto/shared/deltarpm.py
new file mode 100644
index 0000000..710a8bb
--- /dev/null
+++ b/yum-presto/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, 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, 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, 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, '-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/shared/prestoDownload.py b/yum-presto/shared/prestoDownload.py
new file mode 100644
index 0000000..340ad1c
--- /dev/null
+++ b/yum-presto/shared/prestoDownload.py
@@ -0,0 +1,171 @@
+# 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
+
+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.returnSimple('deltachecksumtype'), po.returnSimple('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 downloadPkgs(conduit, pkglist):
+ """download list of package objects handed to you, return errors"""
+
+ opts, commands = conduit.getCmdLine()
+
+ errors = {}
+ def adderror(po, msg):
+ errors.setdefault(po, []).append(msg)
+
+ # Check whether drpm is already downloaded
+ repo_cached = False
+ remote_pkgs = []
+ rebuild_pkgs = []
+ for po in conduit.getDownloadPackages():
+ if hasattr(po, 'has_drpm') and po.has_drpm:
+ po.to.pkgtup = po.to.realpkgtup
+ local = po.returnSimple('deltalocalpath')
+ if os.path.exists(local):
+ cursize = os.stat(local)[6]
+ totsize = long(po.returnSimple('deltasize'))
+ try:
+ verifyChecksum(local, po.returnSimple('deltachecksumtype'), po.returnSimple('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 list
+ conduit.info(5, "using local copy of deltarpm for %s" % po)
+ rebuild_pkgs.append(po)
+ continue
+ remote_pkgs.append(po)
+
+ # Download deltarpms
+ 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
+ po.simple['reallocalpath'] = po.localpath
+ po.localpath = po.returnSimple('deltalocalpath')
+ po.simple['realrelativepath'] = po.returnSimple('relativepath')
+ po.simple['relativepath'] = po.returnSimple('deltarelativepath')
+ try:
+ text = '(%s/%s): %s' % (i, len(remote_pkgs), os.path.basename(po.returnSimple('deltarelativepath')))
+ deltalocal = po.repo.p_repo.getPackage(po, checkfunc=checkfunc, text=text, cache=cache)
+ except Errors.RepoError, e:
+ adderror(po, str(e))
+ else:
+ rebuild_pkgs.append(po)
+ po.simple['deltalocalpath'] = deltalocal
+
+ if errors.has_key(po):
+ del errors[po]
+
+ po.simple['relativepath'] = po.returnSimple('realrelativepath')
+ po.localpath = po.returnSimple('reallocalpath')
+ if po.simple.has_key('realpackagesize'):
+ po.simple['packagesize'] = po.returnSimple('realpackagesize')
+ del po.simple['realpackagesize']
+ del po.simple['realrelativepath']
+ del po.simple['reallocalpath']
+
+ # Rebuild rpms from downloaded deltarpms
+ for po in rebuild_pkgs:
+ deltalocal = po.returnSimple('deltalocalpath')
+ drpm = deltarpm.DeltaRpmWrapper(conduit)
+ try:
+ conduit.info(2, "Building %s from %s" % (os.path.basename(po.localpath), os.path.basename(deltalocal)))
+ drpm.apply(po.localpath, deltalocal)
+ except:
+ conduit.info(2, "Error rebuilding rpm from %s! Will download full package." % os.path.basename(deltalocal))
+ try:
+ os.unlink(po.localpath)
+ except:
+ pass
+ else:
+ # Set package type to local, so yum doesn't try to download it later
+ # po.pkgtype = "local" # If we set this, we can't auto-install public keys
+ # and yum is smart enough to detect the full rpm and
+ # not redownload it.
+
+ # 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 errors
+
+
diff --git a/yum-presto/shared/prestoLog.py b/yum-presto/shared/prestoLog.py
new file mode 100644
index 0000000..3c0c1e6
--- /dev/null
+++ b/yum-presto/shared/prestoLog.py
@@ -0,0 +1,71 @@
+# 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
+
+def log(conduit, LOG_FILE, rpm_size, drpm_size):
+ # Open log file for reading
+ try:
+ log_file = open(LOG_FILE, "r")
+ log_exists = True
+ except:
+ conduit.info(5, "Info: %s doesn't exist. Will create." % LOG_FILE)
+ log_exists = False
+
+ # Log file doesn't exist, create
+ if not log_exists:
+ try:
+ log_file = open(LOG_FILE, "w")
+ log_file.write("Download Size (without DRPM),Download Size (with DRPM),Percentage Savings,Total Percentage Savings\n")
+ log_file.close()
+ log_exists = True
+ except:
+ conduit.info(2, "Warning: Unable to write to %s" % LOG_FILE)
+ if log_exists:
+ try:
+ log_file = open(LOG_FILE, "r")
+ except:
+ conduit.info(2, "Warning: Unable to open %s for reading." % LOG_FILE)
+ log_exists = False
+
+ # Cycle through items already in log so we can come up with total savings
+ if log_exists:
+ total_rpm_size = 0
+ total_drpm_size = 0
+
+ # Get rid of header line
+ log_file.readline()
+
+ data = log_file.readline()
+ while data != "":
+ fc = data.find(",")
+ sc = data.find(",", fc + 1)
+ total_rpm_size += int(data[:fc])
+ total_drpm_size += int(data[fc + 1:sc])
+ data = log_file.readline()
+ log_file.close()
+ total_rpm_size += rpm_size
+ total_drpm_size += drpm_size
+
+ try:
+ log_file = open(LOG_FILE, "a")
+ except:
+ conduit.info(2, "Warning: Unable to open %s for writing." % LOG_FILE)
+ log_exists = False
+
+ # Write data to log
+ if log_exists:
+ log_file.write("%i,%i,%i,%i\n" % (rpm_size, drpm_size, 100 - ((drpm_size * 100) / rpm_size), 100 - ((total_drpm_size * 100) / total_rpm_size)))
+ log_file.close()
diff --git a/yum-presto/shared/prestoRepo.py b/yum-presto/shared/prestoRepo.py
new file mode 100644
index 0000000..582dc2f
--- /dev/null
+++ b/yum-presto/shared/prestoRepo.py
@@ -0,0 +1,612 @@
+# 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 != []:
+ 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.returnSimple('relativepath')
+ local = package.localPkg()
+ 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/shared/prestoTransaction.py b/yum-presto/shared/prestoTransaction.py
new file mode 100644
index 0000000..3d387a4
--- /dev/null
+++ b/yum-presto/shared/prestoTransaction.py
@@ -0,0 +1,97 @@
+# 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
+
+import os
+import deltarpm
+
+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']
+ if int(oldpkg.epoch) == 0:
+ seq = "%s-%s-%s-%s" % (oldpkg.name, oldpkg.version, oldpkg.release, sequence)
+ else:
+ seq = "%s-%i:%s-%s-%s" % (oldpkg.name, int(oldpkg.epoch), oldpkg.version, oldpkg.release, 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(seq)
+ chosen_drpm = p_repo.deltalist[key]
+ chosen_drpm['baseurl'] = p_repo.baseurl[0]
+ except:
+ conduit.info(5, "Verification of %s failed" % seq)
+
+ return (chosen_drpm, installed, is_local, drpm_enabled)
diff --git a/yum-presto/shared/prestomdparser.py b/yum-presto/shared/prestomdparser.py
new file mode 100644
index 0000000..9dbcc1d
--- /dev/null
+++ b/yum-presto/shared/prestomdparser.py
@@ -0,0 +1,166 @@
+# 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
+from 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()