diff options
author | Jonathan Dieter <jdieter@gmail.com> | 2007-06-19 20:58:07 +0300 |
---|---|---|
committer | Jonathan Dieter <jdieter@gmail.com> | 2007-06-19 20:58:07 +0300 |
commit | dce0600bc64c793ba6e8f67c56c286d8d97e7c4c (patch) | |
tree | 71c559e031b3c10ba56a187e0a017f09d4d25137 /presto-utils | |
parent | 93b2295180471308e969640472bdc601d1f10015 (diff) | |
download | presto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.tar.gz presto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.tar.xz presto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.zip |
Many bugfixes and a few enhancements
Signed-off-by: Jonathan Dieter <jdieter@gmail.com>
Diffstat (limited to 'presto-utils')
-rw-r--r-- | presto-utils/COPYING | 339 | ||||
-rw-r--r-- | presto-utils/ChangeLog | 6 | ||||
-rw-r--r-- | presto-utils/Makefile | 11 | ||||
-rw-r--r-- | presto-utils/README | 22 | ||||
-rwxr-xr-x | presto-utils/createdeltarpms | 2 | ||||
-rwxr-xr-x | presto-utils/createprestorepo | 2 | ||||
-rw-r--r-- | presto-utils/dumpMetadata.py | 497 | ||||
-rwxr-xr-x | presto-utils/gendeltarpms.py | 324 | ||||
-rwxr-xr-x | presto-utils/genprestometadata.py | 525 | ||||
-rw-r--r-- | presto-utils/packagelist.py | 386 |
10 files changed, 2114 insertions, 0 deletions
diff --git a/presto-utils/COPYING b/presto-utils/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/presto-utils/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/presto-utils/ChangeLog b/presto-utils/ChangeLog new file mode 100644 index 0000000..1640457 --- /dev/null +++ b/presto-utils/ChangeLog @@ -0,0 +1,6 @@ +* Tue Jun 19 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.0 + - Now works with createrepo and modifyrepo + - Many bugfixes + +* Tue May 7 2007 Jonathan Dieter <jdieter@gmail.com> - 0.1.0 + - Initial release diff --git a/presto-utils/Makefile b/presto-utils/Makefile new file mode 100644 index 0000000..9e8c6eb --- /dev/null +++ b/presto-utils/Makefile @@ -0,0 +1,11 @@ +clean: + rm -f *.pyc *.pyo *~ + +install: + install -m 755 createprestorepo $(DESTDIR)/usr/bin/ + install -m 755 createdeltarpms $(DESTDIR)/usr/bin/ + mkdir -p $(DESTDIR)/usr/share/createprestorepo + install -m 755 genprestometadata.py $(DESTDIR)/usr/share/createprestorepo + install -m 644 dumpMetadata.py $(DESTDIR)/usr/share/createprestorepo + install -m 755 gendeltarpms.py $(DESTDIR)/usr/share/createprestorepo + install -m 644 packagelist.py $(DESTDIR)/usr/share/createprestorepo diff --git a/presto-utils/README b/presto-utils/README new file mode 100644 index 0000000..a81187a --- /dev/null +++ b/presto-utils/README @@ -0,0 +1,22 @@ +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. + +createprestorepo: The presto repository creater + +Installation: +============= +1- Untar the package +2- Run 'make install' + +Running: +1- First run 'createdeltarpms <repo dir> <drpm dir>' where <repo dir> is the + base directory for your repository and <drpm dir> is the subdirectory you + want to create the deltarpms into +2- Run 'createprestorepo <repo dir>' where <repo dir> is the base directory for + your repository. + +WARNING: createprestorepo does *NOT* yet know how to deal with the metadata + created by createrepo. You will have to manually move metadata from + .olddata to repodata after running both createprestorepo and + createrepo. diff --git a/presto-utils/createdeltarpms b/presto-utils/createdeltarpms new file mode 100755 index 0000000..99bedcc --- /dev/null +++ b/presto-utils/createdeltarpms @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/share/createprestorepo/gendeltarpms.py "$@" diff --git a/presto-utils/createprestorepo b/presto-utils/createprestorepo new file mode 100755 index 0000000..5334a80 --- /dev/null +++ b/presto-utils/createprestorepo @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/share/createprestorepo/genprestometadata.py "$@" diff --git a/presto-utils/dumpMetadata.py b/presto-utils/dumpMetadata.py new file mode 100644 index 0000000..0ec2c20 --- /dev/null +++ b/presto-utils/dumpMetadata.py @@ -0,0 +1,497 @@ +#!/usr/bin/python -t +# base classes and functions for dumping out package Metadata +# +# 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 2004 Duke University + +# $Id: dumpMetadata.py,v 1.36 2006/02/21 20:10:08 pnasrat Exp $ + +import os +import rpm +import exceptions +import md5 +import sha +import types +import struct +import re +import stat + +# done to fix gzip randomly changing the checksum +import gzip +from zlib import error as zlibError +from gzip import write32u, FNAME + +__all__ = ["GzipFile","open"] + +class GzipFile(gzip.GzipFile): + def _write_gzip_header(self): + self.fileobj.write('\037\213') # magic header + self.fileobj.write('\010') # compression method + fname = self.filename[:-3] + flags = 0 + if fname: + flags = FNAME + self.fileobj.write(chr(flags)) + write32u(self.fileobj, long(0)) + self.fileobj.write('\002') + self.fileobj.write('\377') + if fname: + self.fileobj.write(fname + '\000') + + +def _gzipOpen(filename, mode="rb", compresslevel=9): + return GzipFile(filename, mode, compresslevel) + + + +def returnFD(filename): + try: + fdno = os.open(filename, os.O_RDONLY) + except OSError: + raise MDError, "Error opening file" + return fdno + +def returnHdr(ts, package): + """hand back the rpm header or raise an Error if the pkg is fubar""" + opened_here = 0 + try: + if type(package) is types.StringType: + opened_here = 1 + fdno = os.open(package, os.O_RDONLY) + else: + fdno = package # let's assume this is an fdno and go with it :) + except OSError: + raise MDError, "Error opening file" + ts.setVSFlags((rpm._RPMVSF_NOSIGNATURES|rpm.RPMVSF_NOMD5|rpm.RPMVSF_NEEDPAYLOAD)) + try: + hdr = ts.hdrFromFdno(fdno) + except rpm.error: + raise MDError, "Error opening package" + if type(hdr) != rpm.hdr: + raise MDError, "Error opening package" + ts.setVSFlags(0) + + if opened_here: + os.close(fdno) + del fdno + + return hdr + +def getChecksum(sumtype, file, CHUNK=2**16): + """takes filename, hand back Checksum of it + sumtype = md5 or sha + filename = /path/to/file + CHUNK=65536 by default""" + + # chunking brazenly lifted from Ryan Tomayko + opened_here = 0 + try: + if type(file) is not types.StringType: + fo = file # assume it's a file-like-object + else: + opened_here = 1 + fo = open(file, 'rb', CHUNK) + + if sumtype == 'md5': + sum = md5.new() + elif sumtype == 'sha': + sum = sha.new() + else: + raise MDError, 'Error Checksumming file, wrong checksum type %s' % sumtype + chunk = fo.read + while chunk: + chunk = fo.read(CHUNK) + sum.update(chunk) + + if opened_here: + fo.close() + del fo + + return sum.hexdigest() + except: + raise MDError, 'Error opening file for checksum: %s' % file + + +def utf8String(string): + """hands back a unicoded string""" + if string is None: + return '' + elif isinstance(string, unicode): + return string + try: + x = unicode(string, 'ascii') + return string + except UnicodeError: + encodings = ['utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2'] + for enc in encodings: + try: + x = unicode(string, enc) + except UnicodeError: + pass + else: + if x.encode(enc) == string: + return x.encode('utf-8') + newstring = '' + for char in string: + if ord(char) > 127: + newstring = newstring + '?' + else: + newstring = newstring + char + return newstring + + +def byteranges(file): + """takes an rpm file or fileobject and returns byteranges for location of the header""" + opened_here = 0 + if type(file) is not types.StringType: + fo = file + else: + opened_here = 1 + fo = open(file, 'r') + #read in past lead and first 8 bytes of sig header + fo.seek(104) + # 104 bytes in + binindex = fo.read(4) + # 108 bytes in + (sigindex, ) = struct.unpack('>I', binindex) + bindata = fo.read(4) + # 112 bytes in + (sigdata, ) = struct.unpack('>I', bindata) + # each index is 4 32bit segments - so each is 16 bytes + sigindexsize = sigindex * 16 + sigsize = sigdata + sigindexsize + # we have to round off to the next 8 byte boundary + disttoboundary = (sigsize % 8) + if disttoboundary != 0: + disttoboundary = 8 - disttoboundary + # 112 bytes - 96 == lead, 8 = magic and reserved, 8 == sig header data + hdrstart = 112 + sigsize + disttoboundary + + fo.seek(hdrstart) # go to the start of the header + fo.seek(8,1) # read past the magic number and reserved bytes + + binindex = fo.read(4) + (hdrindex, ) = struct.unpack('>I', binindex) + bindata = fo.read(4) + (hdrdata, ) = struct.unpack('>I', bindata) + + # each index is 4 32bit segments - so each is 16 bytes + hdrindexsize = hdrindex * 16 + # add 16 to the hdrsize to account for the 16 bytes of misc data b/t the + # end of the sig and the header. + hdrsize = hdrdata + hdrindexsize + 16 + + # header end is hdrstart + hdrsize + hdrend = hdrstart + hdrsize + if opened_here: + fo.close() + del fo + return (hdrstart, hdrend) + + +class MDError(exceptions.Exception): + def __init__(self, args=None): + exceptions.Exception.__init__(self) + self.args = args + + + +class RpmMetaData: + """each drpm is one object, you pass it an rpm file + it opens the file, and pulls the information out in bite-sized chunks :) + """ + + mode_cache = {} + + def __init__(self, ts, basedir, filename, options, is_drpm): + try: + stats = os.stat(os.path.join(basedir, filename)) + self.size = stats[6] + self.mtime = stats[8] + del stats + except OSError, e: + raise MDError, "Error Stat'ing file %s %s" % (basedir, filename) + self.options = options + self.localurl = options['baseurl'] + self.relativepath = filename + fd = returnFD(os.path.join(basedir, filename)) + self.hdr = returnHdr(ts, fd) + os.lseek(fd, 0, 0) + fo = os.fdopen(fd, 'rb') + self.pkgid = self.doChecksumCache(fo) + fo.seek(0) + (self.rangestart, self.rangeend) = byteranges(fo) + self.is_drpm = False + if is_drpm: + fo.seek(self.rangeend) + self._getOldInfo(fo) + self.is_drpm = True + del fo + del fd + + def arch(self): + if self.tagByName('sourcepackage') == 1: + return 'src' + else: + return self.tagByName('arch') + + def _stringToNEVR(self, string): + i = string.rfind("-", 0, string.rfind("-")-1) + name = string[:i] + (epoch, ver, rel) = self._stringToVersion(string[i+1:]) + return (name, epoch, ver, rel) + + def _getLength(self, in_data): + length = 0 + for val in in_data: + length = length * 256 + length += ord(val) + return length + + def _getOldInfo(self, fo): + try: + compobj = gzip.GzipFile("", "rb", 9, fo) + except: + raise zlibError("Data not stored in gzip format") + + if compobj.read(4)[:3] != "DLT": + raise Exception("Not a deltarpm") + + nevr_length = self._getLength(compobj.read(4)) + nevr = compobj.read(nevr_length).strip("\x00") + seq_length = self._getLength(compobj.read(4)) + seq = compobj.read(seq_length) + hex_seq = "" + for char in seq: + hex_seq += str("%02x" % ord(char)) + self.oldnevrstring = nevr + self.oldnevr = self._stringToNEVR(nevr) + self.sequence = hex_seq + compobj.close() + + def _stringToVersion(self, strng): + i = strng.find(':') + if i != -1: + epoch = strng[:i] + else: + epoch = '0' + j = strng.find('-') + if j != -1: + if strng[i + 1:j] == '': + version = None + else: + version = strng[i + 1:j] + release = strng[j + 1:] + else: + if strng[i + 1:] == '': + version = None + else: + version = strng[i + 1:] + release = None + return (epoch, version, release) + + ########### + # Title: Remove duplicates from a sequence + # Submitter: Tim Peters + # From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 + + def _uniq(self,s): + """Return a list of the elements in s, but without duplicates. + + For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3], + unique("abcabc") some permutation of ["a", "b", "c"], and + unique(([1, 2], [2, 3], [1, 2])) some permutation of + [[2, 3], [1, 2]]. + + For best speed, all sequence elements should be hashable. Then + unique() will usually work in linear time. + + If not possible, the sequence elements should enjoy a total + ordering, and if list(s).sort() doesn't raise TypeError it's + assumed that they do enjoy a total ordering. Then unique() will + usually work in O(N*log2(N)) time. + + If that's not possible either, the sequence elements must support + equality-testing. Then unique() will usually work in quadratic + time. + """ + + n = len(s) + if n == 0: + return [] + + # Try using a dict first, as that's the fastest and will usually + # work. If it doesn't work, it will usually fail quickly, so it + # usually doesn't cost much to *try* it. It requires that all the + # sequence elements be hashable, and support equality comparison. + u = {} + try: + for x in s: + u[x] = 1 + except TypeError: + del u # move on to the next method + else: + return u.keys() + + # We can't hash all the elements. Second fastest is to sort, + # which brings the equal elements together; then duplicates are + # easy to weed out in a single pass. + # NOTE: Python's list.sort() was designed to be efficient in the + # presence of many duplicate elements. This isn't true of all + # sort functions in all languages or libraries, so this approach + # is more effective in Python than it may be elsewhere. + try: + t = list(s) + t.sort() + except TypeError: + del t # move on to the next method + else: + assert n > 0 + last = t[0] + lasti = i = 1 + while i < n: + if t[i] != last: + t[lasti] = last = t[i] + lasti += 1 + i += 1 + return t[:lasti] + + # Brute force is all that's left. + u = [] + for x in s: + if x not in u: + u.append(x) + return u + + def tagByName(self, tag): + data = self.hdr[tag] + if type(data) is types.ListType: + if len(data) > 0: + return data[0] + else: + return '' + else: + return data + + def listTagByName(self, tag): + """take a tag that should be a list and make sure it is one""" + lst = [] + data = self.hdr[tag] + if data is None: + return lst + + if type(data) is types.ListType: + lst.extend(data) + else: + lst.append(data) + return lst + + def epoch(self): + if self.hdr['epoch'] is None: + return 0 + else: + return self.tagByName('epoch') + + def doChecksumCache(self, fo): + """return a checksum for a package: + - check if the checksum cache is enabled + if not - return the checksum + if so - check to see if it has a cache file + if so, open it and return the first line's contents + if not, grab the checksum and write it to a file for this pkg + """ + if not self.options['cache']: + return getChecksum(self.options['sumtype'], fo) + + csumtag = os.path.basename(self.relativepath) + ".cache" + csumfile = '%s/%s' % (self.options['cachedir'], csumtag) + if os.path.exists(csumfile) and self.mtime <= os.stat(csumfile)[8]: + csumo = open(csumfile, 'r') + checksum = csumo.readline() + csumo.close() + + else: + checksum = getChecksum(self.options['sumtype'], fo) + csumo = open(csumfile, 'w') + csumo.write(checksum) + csumo.close() + + return checksum + + + +def generateXML(doc, node, formatns, drpmObj, sumtype, pkgDeltas): + """takes an xml doc object and a package metadata entry node, populates a + package node with the md information""" + name = drpmObj.tagByName('name') + arch = drpmObj.arch() + epoch = str(drpmObj.epoch()) + ver = str(drpmObj.tagByName('version')) + rel = str(drpmObj.tagByName('release')) + if not pkgDeltas.has_key('%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)): + pkgNode = node.newChild(None, "package", None) + pkgNode.newProp('type', 'rpm') + pkgNode.newChild(None, 'name', name) + pkgNode.newChild(None, 'arch', arch) + version = pkgNode.newChild(None, 'version', None) + version.newProp('epoch', epoch) + version.newProp('ver', ver) + version.newProp('rel', rel) + deltas = pkgNode.newChild(None, 'deltas', None) + pkgDeltas['%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)] = deltas + else: + deltas = pkgDeltas['%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)] + (oldname, oldepoch, oldver, oldrel) = drpmObj.oldnevr + drpmNode = deltas.newChild(None, "oldrpm", None) + if name != oldname: + drpmNode.newChild(None, 'name', oldname) + # oldrpm arch is not stored in drpm, so we can only work within same arch + version = drpmNode.newChild(None, 'version', None) + if epoch != oldepoch: + version.newProp('epoch', oldepoch) + if ver != oldver: + version.newProp('ver', oldver) + version.newProp('rel', oldrel) + drpmNode.newChild(None, 'drpm_filename', drpmObj.relativepath) + drpmNode.newChild(None, 'size', str(drpmObj.size)) + drpmNode.newChild(None, 'sequence', '%s-%s' % (drpmObj.oldnevrstring, drpmObj.sequence)) + checksum = drpmNode.newChild(None, 'checksum', drpmObj.pkgid) + checksum.newProp('type', drpmObj.options['sumtype']) + + +def repoXML(node, cmds): + """generate the repomd.xml file that stores the info on the other files""" + sumtype = cmds['sumtype'] + workfiles = [(cmds['prestofile'], 'deltas')] + + + for (file, ftype) in workfiles: + zfo = _gzipOpen(os.path.join(cmds['outputdir'], cmds['tempdir'], file)) + uncsum = getChecksum(sumtype, zfo) + zfo.close() + csum = getChecksum(sumtype, os.path.join(cmds['outputdir'], cmds['tempdir'], file)) + timestamp = os.stat(os.path.join(cmds['outputdir'], cmds['tempdir'], file))[8] + data = node.newChild(None, 'data', None) + data.newProp('type', ftype) + location = data.newChild(None, 'location', None) + if cmds['baseurl'] is not None: + location.newProp('xml:base', cmds['baseurl']) + location.newProp('href', os.path.join(cmds['finaldir'], file)) + checksum = data.newChild(None, 'checksum', csum) + checksum.newProp('type', sumtype) + timestamp = data.newChild(None, 'timestamp', str(timestamp)) + unchecksum = data.newChild(None, 'open-checksum', uncsum) + unchecksum.newProp('type', sumtype) + diff --git a/presto-utils/gendeltarpms.py b/presto-utils/gendeltarpms.py new file mode 100755 index 0000000..7db9d4f --- /dev/null +++ b/presto-utils/gendeltarpms.py @@ -0,0 +1,324 @@ +#!/usr/bin/python -t +# -*- mode: Python; indent-tabs-mode: nil; -*- +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import errno, os, sys +import fnmatch, re +import rpmUtils.transaction, rpmUtils.miscutils +import commands +import string +import getopt +from dumpMetadata import byteranges +from packagelist import RpmItem, DrpmItem, PackageList + +import gzip +from zlib import error as zlibError +from gzip import write32u, FNAME + +DRPMWORTHKEEPINGTHRESH=0.5 +DEBUG=0 +__version__ = "0.2.0" + +def errorprint(stuff): + print >> sys.stderr, stuff + +def _(args): + """Stub function for translation""" + return args + +def usage(retval=1): + print _(""" + createdeltarpms [options] directory-of-packages directory-of-deltarpms + + Options: + -d, --dist-update <dir> = optional directory containing old distribution + to create an update from + -c, --count <number> = optional maximum number of deltarpms to create + per package. Default is maximum available + -n, --no-first = Don't create deltarpm against first rpm if number exceeds + count. Useful if you are running a continually-updated + distribution rather than one with set release cycles. + -q, --quiet = run quietly + -v, --verbose = run verbosely + -h, --help = show this help + -V, --version = output version + """) + + sys.exit(retval) + +def getDeltaNevr(filename): + """Get nevr for src rpm of a deltarpm. Return a tuple of (n, e, v, r)""" + def _getLength(in_data): + length = 0 + for val in in_data: + length = length * 256 + length += ord(val) + return length + + def _stringToVersion(strng): + i = strng.find(':') + if i != -1: + epoch = strng[:i] + else: + epoch = '0' + j = strng.find('-') + if j != -1: + if strng[i + 1:j] == '': + version = None + else: + version = strng[i + 1:j] + release = strng[j + 1:] + else: + if strng[i + 1:] == '': + version = None + else: + version = strng[i + 1:] + release = None + return (epoch, version, release) + + def _stringToNEVR(string): + i = string.rfind("-", 0, string.rfind("-")-1) + name = string[:i] + (epoch, ver, rel) = _stringToVersion(string[i+1:]) + return (name, epoch, ver, rel) + + (range_start, range_end) = byteranges(filename) + fd = open(filename, "r") + fd.seek(range_end) + try: + compobj = gzip.GzipFile("", "rb", 9, fd) + except: + raise zlibError("Data not stored in gzip format") + + if compobj.read(4)[:3] != "DLT": + raise Exception("Not a deltarpm") + + nevr_length = _getLength(compobj.read(4)) + nevr = compobj.read(nevr_length).strip("\x00") + oldnevr = _stringToNEVR(nevr) + compobj.close() + return oldnevr + + +def getFileList(path, ext, filelist): + """Return all files in path matching ext, store them in filelist, + recurse dirs. Returns a list object""" + + extlen = len(ext) + totalpath = os.path.normpath(path) + dir_list = os.listdir(totalpath) + + for d in dir_list: + if os.path.isdir(totalpath + '/' + d): + filelist = getFileList(os.path.join(totalpath, d), ext, filelist) + elif string.lower(d[-extlen:]) == '%s' % (ext): + filelist.append(os.path.join(totalpath, d)) + + return filelist + +def createPrestoRepo(base_dir, drpm_dir, dst_dir=None, DrpmsPerPackage=0, DoFirst=True): + ts = rpmUtils.transaction.initReadOnlyTransaction() + changed = False + + # Create list of .rpm files. + # We don't use "glob", so sub-directories are supported. + print 'Using base dir: %s' % base_dir + print 'Using destination dir: %s' % drpm_dir + + try: + rpm_files = getFileList(base_dir, ".rpm", []) + except OSError, e: + errorprint(_("Error: Unable to find directory %s: %s" % (base_dir, e))) + return False + if dst_dir != None: + try: + dst_rpm_files = getFileList(dst_dir, ".rpm", []) + except OSError, e: + errorprint(_("Error: Unable to find directory %s: %s" % (dst_dir, e))) + return False + try: + drpm_files = getFileList(drpm_dir, ".drpm", []) + except: + drpm_files = [] + if not len(rpm_files): + print ' Nothing found.' + return changed + + if dst_dir != None and not len(dst_rpm_files): + print ' No new rpms found.' + return changed + + # Check whether rel_dir exists, and if it doesn't, create it + if not os.access(drpm_dir, os.F_OK): + os.makedirs(drpm_dir, 0755) + elif not os.access(drpm_dir, os.W_OK): + print 'ERROR: Unable to write to %s' % drpm_dir + sys.exit(1) + + # Add all rpms to PackageList + rpm_list = PackageList(base_dir, drpm_dir, DRPMWORTHKEEPINGTHRESH, DEBUG) + for f in rpm_files: + try: + hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) + except: + print "Unable to open %s" % f + else: + nm = hdr['name'] + "." + hdr['arch'] + e = hdr['epoch'] + if e is None: + e = "0" + + nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) + rpm = RpmItem(nevra, f) + rpm_list.addRpm(rpm) + + # If we have a new distro, add its rpms with epoch bumped +5 + if dst_dir != None: + for f in dst_rpm_files: + try: + hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) + except: + print "Unable to open %s" % f + else: + nm = hdr['name'] + "." + hdr['arch'] + e = hdr['epoch'] + if e is None: + e = "0" + e = str(int(e) + 5) + + nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) + rpm = RpmItem(nevra, f) + rpm_list.addRpm(rpm) + + # Add all deltarpms to PackageList + for f in drpm_files: + try: + hdr = rpmUtils.miscutils.hdrFromPackage(ts, f) + except: + print "Unable to open %s" % f + else: + (sn, se, sv, sr) = getDeltaNevr(f) + + nm = hdr['name'] + "." + hdr['arch'] + e = hdr['epoch'] + if e is None: + e = "0" + + dst_nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch']) + src_nevra = (sn, se, sv, sr, hdr['arch']) + drpm = DrpmItem(src_nevra, dst_nevra, f) + rpm_list.addDrpm(drpm) + + if DEBUG: + rpm_list.dump() + + # Build deltarpms + rpm_list.makeDeltaRpms(DrpmsPerPackage, DoFirst) + return True + +def parseArgs(args): + """ + Parse the command line args return a commands dict and directory. + Sanity check all the things being passed in. + """ + cmds = {} + cmds['quiet'] = 0 + cmds['verbose'] = 0 + cmds['dist-update'] = None + cmds['count'] = None + cmds['do-first'] = 1 + try: + gopts, argsleft = getopt.getopt(args, 'hqvVd:c:n', ['help', 'quiet', 'verbose', 'version', 'dist-update=', 'count=', 'no-first']) + except getopt.error, e: + errorprint(_('Options Error: %s.') % e) + usage() + + try: + for arg,a in gopts: + if arg in ['-h','--help']: + usage(retval=0) + elif arg in ['-V', '--version']: + print '%s' % __version__ + sys.exit(0) + except ValueError, e: + errorprint(_('Options Error: %s') % e) + usage() + + + # make sure our dir makes sense before we continue + if len(argsleft) > 2: + errorprint(_('Error: You may only specify one rpm directory and one deltarpm directory.')) + usage() + elif len(argsleft) == 0: + errorprint(_('Error: Must specify an rpm directory to index and a destination deltarpm directory.')) + usage() + else: + directories = argsleft + + try: + for arg,a in gopts: + if arg in ['-v', '--verbose']: + cmds['verbose'] = 1 + elif arg in ["-q", '--quiet']: + cmds['quiet'] = 1 + elif arg in ["-n", '--no-first']: + cmds['do-first'] = 0 + elif arg in ['-d', '--dist-update']: + if cmds['dist-update'] is not None: + errorprint(_('Error: Only one directory for distribution updates allowed.')) + usage() + else: + cmds['dist-update'] = a + elif arg in ['-c', '--count']: + if cmds['count'] is not None: + errorprint(_('Error: Only one count allowed.')) + usage() + else: + cmds['count'] = int(a) + + except ValueError, e: + errorprint(_('Options Error: %s') % e) + usage() + + rpm_directory = directories[0] + drpm_directory = directories[1] + + rpm_directory = os.path.normpath(rpm_directory) + drpm_directory = os.path.normpath(drpm_directory) + + # Fixup first directory + directories[0] = rpm_directory + directories[1] = drpm_directory + + # Set count + if cmds['count'] == None: + cmds['count'] = 0 + + return cmds, directories + +if __name__ == '__main__': + cmds, directories = parseArgs(sys.argv[1:]) + if cmds['dist-update'] != None: + if createPrestoRepo(cmds['dist-update'], directories[1], directories[0], cmds['count'], cmds['do-first']): + sys.exit(0) + else: + sys.exit(1) + else: + if createPrestoRepo(directories[0], directories[1], None, cmds['count'], cmds['do-first']): + sys.exit(0) + else: + sys.exit(1) + diff --git a/presto-utils/genprestometadata.py b/presto-utils/genprestometadata.py new file mode 100755 index 0000000..6788ffc --- /dev/null +++ b/presto-utils/genprestometadata.py @@ -0,0 +1,525 @@ +#!/usr/bin/python -t +# primary functions and glue for generating the repository metadata +# + +# 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 2004 Duke University +# Copyright 2007 Jonathan Dieter + + +import os +import sys +import getopt +import rpm +import libxml2 +import string +import fnmatch +import urlgrabber +import commands +import shutil + +import dumpMetadata +from dumpMetadata import _gzipOpen +__version__ = '0.2.0' + +def errorprint(stuff): + print >> sys.stderr, stuff + +def _(args): + """Stub function for translation""" + return args + +def usage(retval=1): + print _(""" + createrepo [options] directory-of-packages + + Options: + -u, --baseurl <url> = optional base url location for all files + -o, --outputdir <dir> = optional directory to output to + -x, --exclude = files globs to exclude, can be specified multiple times + -m, --merge = merge with repository created by createrepo + -q, --quiet = run quietly + -v, --verbose = run verbosely + -c, --cachedir <dir> = specify which dir to use for the checksum cache + -h, --help = show this help + -V, --version = output version + -p, --pretty = output xml files in pretty format. + """) + + sys.exit(retval) + +class MetaDataGenerator: + def __init__(self, cmds): + self.cmds = cmds + self.ts = rpm.TransactionSet() + self.pkgcount = 0 + self.newrpms = {} + self.files = [] + + def getFileList(self, basepath, path, ext, filelist): + """Return all files in path matching ext, store them in filelist, + recurse dirs. Returns a list object""" + + extlen = len(ext) + totalpath = os.path.normpath(os.path.join(basepath, path)) + try: + dir_list = os.listdir(totalpath) + except OSError, e: + errorprint(_('Error accessing directory %s, %s') % (totalpath, e)) + sys.exit(1) + + for d in dir_list: + if os.path.isdir(totalpath + '/' + d): + filelist = self.getFileList(basepath, os.path.join(path, d), ext, filelist) + else: + if string.lower(d[-extlen:]) == '%s' % (ext): + if totalpath.find(basepath) == 0: + relativepath = totalpath.replace(basepath, "", 1) + relativepath = relativepath.lstrip("/") + filelist.append(os.path.join(relativepath, d)) + else: + raise "basepath '%s' not found in path '%s'" % (basepath, totalpath) + + return filelist + + + def trimRpms(self, files): + badrpms = [] + for file in files: + for glob in self.cmds['excludes']: + if fnmatch.fnmatch(file, glob): + # print 'excluded: %s' % file + if file not in badrpms: + badrpms.append(file) + for file in badrpms: + if file in files: + files.remove(file) + return files + + def doPkgMetadata(self, directory): + """all the heavy lifting for the package metadata""" + + # rpms we're going to be dealing with + files = self.getFileList(self.cmds['basedir'], directory, '.drpm', []) + files = self.trimRpms(files) + self.pkgcount = len(files) + self.openMetadataDocs() + self.writeMetadataDocs(files) + self.closeMetadataDocs() + + + def openMetadataDocs(self): + self._setupPresto() + + def _setupPresto(self): + # setup the base metadata doc + self.prestodoc = libxml2.newDoc("1.0") + self.prestoroot = self.prestodoc.newChild(None, "metadata", None) + basens = self.prestoroot.newNs('http://linux.duke.edu/metadata/common', None) + self.formatns = self.prestoroot.newNs('http://linux.duke.edu/metadata/rpm', 'rpm') + self.prestoroot.setNs(basens) + prestofilepath = os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile']) + if self.cmds['merge'] == 1: + self.prestofile = open(prestofilepath, 'w') + else: + self.prestofile = _gzipOpen(prestofilepath, 'w') + self.prestofile.write('<?xml version="1.0" encoding="UTF-8"?>\n') + self.prestofile.write('<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="%s">\n' % + self.pkgcount) + + + def writeMetadataDocs(self, files, current=0): + for file in files: + current+=1 + try: + mdobj = dumpMetadata.RpmMetaData(self.ts, self.cmds['basedir'], file, self.cmds, True) + if not self.cmds['quiet']: + if self.cmds['verbose']: + print '%d/%d - %s' % (current, len(files), file) + else: + sys.stdout.write('\r' + ' ' * 80) + sys.stdout.write("\r%d/%d - %s" % (current, self.pkgcount, file)) + sys.stdout.flush() + except dumpMetadata.MDError, e: + errorprint('\n%s - %s' % (e, file)) + continue + else: + try: + dumpMetadata.generateXML(self.prestodoc, self.prestoroot, self.formatns, mdobj, self.cmds['sumtype'], self.newrpms) + except dumpMetadata.MDError, e: + errorprint(_('\nAn error occurred creating presto metadata: %s') % e) + continue + return current + + + def closeMetadataDocs(self): + if not self.cmds['quiet']: + print '' + + # save them up to the tmp locations: + if not self.cmds['quiet']: + print _('Saving Presto metadata') + output = self.prestoroot.serialize('UTF-8', self.cmds['pretty']) + output = output[output.find("\n")+1:] + self.prestofile.write(output) + self.prestofile.write("\n") + self.prestofile.close() + self.prestodoc.freeDoc() + + def doRepoMetadata(self): + if self.cmds['prestomdfile'] == None: # Merge + shutil.copyfile(os.path.join(self.cmds['outputdir'], self.cmds['finaldir'], "repomd.xml"), os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], "repomd.xml")) + command = "/usr/bin/modifyrepo %s %s" % (os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile']), os.path.join(self.cmds['outputdir'], self.cmds['tempdir'])) + (code, out) = commands.getstatusoutput(command) + if code: + errorprint(_('\nAn error occurred trying to run modifyrepo: %s') % e) + sys.exit(1) + os.unlink(os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile'])) + self.cmds['prestofile'] = "%s.gz" % self.cmds['prestofile'] + self.cmds['prestomdfile'] = "repomd.xml" + else: + """wrapper to generate the prestomd.xml file that stores the info on the other files""" + repodoc = libxml2.newDoc("1.0") + reporoot = repodoc.newChild(None, "repomd", None) + repons = reporoot.newNs('http://linux.duke.edu/metadata/repo', None) + reporoot.setNs(repons) + repofilepath = os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestomdfile']) + + try: + dumpMetadata.repoXML(reporoot, self.cmds) + except dumpMetadata.MDError, e: + errorprint(_('Error generating repo xml file: %s') % e) + sys.exit(1) + + try: + repodoc.saveFormatFileEnc(repofilepath, 'UTF-8', 1) + except: + errorprint(_('Error saving temp file for rep xml: %s') % repofilepath) + sys.exit(1) + + del repodoc + +class SplitMetaDataGenerator(MetaDataGenerator): + + def __init__(self, cmds): + MetaDataGenerator.__init__(self, cmds) + self.initialdir = self.cmds['basedir'] + + def _getFragmentUrl(self, url, fragment): + import urlparse + urlparse.uses_fragment.append('media') + if not url: + return url + (scheme, netloc, path, query, fragid) = urlparse.urlsplit(url) + return urlparse.urlunsplit((scheme, netloc, path, query, str(fragment))) + + def doPkgMetadata(self, directories): + """all the heavy lifting for the package metadata""" + import types + if type(directories) == types.StringType: + MetaDataGenerator.doPkgMetadata(self, directories) + return + filematrix = {} + for mydir in directories: + filematrix[mydir] = self.getFileList(os.path.join(self.initialdir, mydir), '.', '.rpm', []) + self.trimRpms(filematrix[mydir]) + self.pkgcount += len(filematrix[mydir]) + + mediano = 1 + current = 0 + self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], mediano) + self.cmds['basedir'] = os.path.join(self.initialdir, directories[0]) + self.openMetadataDocs() + for mydir in directories: + self.cmds['basedir'] = os.path.join(self.initialdir, mydir) + self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], mediano) + current = self.writeMetadataDocs(filematrix[mydir], current) + mediano += 1 + self.cmds['basedir'] = os.path.join(self.initialdir, directories[0]) + self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], 1) + self.closeMetadataDocs() + + +def checkAndMakeDir(dir): + """ + check out the dir and make it, if possible, return 1 if done, else return 0 + """ + if os.path.exists(dir): + if not os.path.isdir(dir): + errorprint(_('%s is not a dir') % dir) + result = False + else: + if not os.access(dir, os.W_OK): + errorprint(_('%s is not writable') % dir) + result = False + else: + result = True + else: + try: + os.mkdir(dir) + except OSError, e: + errorprint(_('Error creating dir %s: %s') % (dir, e)) + result = False + else: + result = True + return result + +def parseArgs(args): + """ + Parse the command line args return a commands dict and directory. + Sanity check all the things being passed in. + """ + cmds = {} + cmds['quiet'] = 0 + cmds['verbose'] = 0 + cmds['excludes'] = [] + cmds['baseurl'] = None + cmds['sumtype'] = 'sha' + cmds['merge'] = 0 + cmds['pretty'] = 0 + cmds['cachedir'] = None + cmds['basedir'] = os.getcwd() + cmds['cache'] = False + cmds['split'] = False + cmds['outputdir'] = "" + cmds['file-pattern-match'] = ['.*bin\/.*', '^\/etc\/.*', '^\/usr\/lib\/sendmail$'] + cmds['dir-pattern-match'] = ['.*bin\/.*', '^\/etc\/.*'] + + try: + gopts, argsleft = getopt.getopt(args, 'phqVvms:x:u:c:o:', ['help', 'exclude=', + 'quiet', 'verbose', 'cachedir=', 'basedir=', + 'baseurl=', 'checksum=', + 'version', 'pretty', 'split', 'outputdir=']) + except getopt.error, e: + errorprint(_('Options Error: %s.') % e) + usage() + + try: + for arg,a in gopts: + if arg in ['-h','--help']: + usage(retval=0) + elif arg in ['-V', '--version']: + print '%s' % __version__ + sys.exit(0) + elif arg == '--split': + cmds['split'] = True + except ValueError, e: + errorprint(_('Options Error: %s') % e) + usage() + + + # make sure our dir makes sense before we continue + if len(argsleft) > 1 and not cmds['split']: + errorprint(_('Error: Only one directory allowed per run.')) + usage() + elif len(argsleft) == 0: + errorprint(_('Error: Must specify a directory to index.')) + usage() + else: + directories = argsleft + + try: + for arg,a in gopts: + if arg in ['-v', '--verbose']: + cmds['verbose'] = 1 + elif arg in ["-q", '--quiet']: + cmds['quiet'] = 1 + elif arg in ['-u', '--baseurl']: + if cmds['baseurl'] is not None: + errorprint(_('Error: Only one baseurl allowed.')) + usage() + else: + cmds['baseurl'] = a + elif arg in ['-x', '--exclude']: + cmds['excludes'].append(a) + elif arg in ['-p', '--pretty']: + cmds['pretty'] = 1 + elif arg in ['-m', '--merge']: + cmds['merge'] = 1 + elif arg in ['-c', '--cachedir']: + cmds['cache'] = True + cmds['cachedir'] = a + elif arg == '--basedir': + cmds['basedir'] = a + elif arg in ['-o','--outputdir']: + cmds['outputdir'] = a + + except ValueError, e: + errorprint(_('Options Error: %s') % e) + usage() + + directory = directories[0] +# Fix paths + directory = os.path.normpath(directory) + if cmds['split']: + pass + elif os.path.isabs(directory): + cmds['basedir'] = directory + directory = '.' + else: + cmds['basedir'] = os.path.realpath(os.path.join(cmds['basedir'], directory)) + directory = '.' + if not cmds['outputdir']: + cmds['outputdir'] = cmds['basedir'] + if cmds['cachedir']: + a = cmds ['cachedir'] + if not os.path.isabs(a): + a = os.path.join(cmds['basedir'] ,a) + if not checkAndMakeDir(a): + errorprint(_('Error: cannot open/write to cache dir %s' % a)) + usage() + cmds['cachedir'] = a + + #setup some defaults + if cmds['merge'] == 1: + cmds['prestofile'] = 'deltas.xml' + cmds['prestomdfile'] = None + else: + cmds['prestofile'] = 'presto.xml.gz' + cmds['prestomdfile'] = 'prestomd.xml' + cmds['tempdir'] = '.repodata' + cmds['finaldir'] = 'repodata' + cmds['olddir'] = '.olddata' + + # Fixup first directory + directories[0] = directory + return cmds, directories + +def main(args): + cmds, directories = parseArgs(args) + directory = directories[0] + # start the sanity/stupidity checks + if not os.path.exists(os.path.join(cmds['basedir'], directory)): + errorprint(_('Directory must exist')) + sys.exit(1) + + if not os.path.isdir(os.path.join(cmds['basedir'], directory)): + errorprint(_('Directory of packages must be a directory.')) + sys.exit(1) + + if not os.access(cmds['outputdir'], os.W_OK): + errorprint(_('Directory must be writable.')) + sys.exit(1) + + if cmds['split']: + oldbase = cmds['basedir'] + cmds['basedir'] = os.path.join(cmds['basedir'], directory) + if not checkAndMakeDir(os.path.join(cmds['outputdir'], cmds['tempdir'])): + sys.exit(1) + + if not checkAndMakeDir(os.path.join(cmds['outputdir'], cmds['finaldir'])): + sys.exit(1) + + if os.path.exists(os.path.join(cmds['outputdir'], cmds['olddir'])): + errorprint(_('Old data directory exists, please remove: %s') % cmds['olddir']) + sys.exit(1) + + # make sure we can write to where we want to write to: + for direc in ['tempdir', 'finaldir']: + for file in ['prestofile', 'prestomdfile']: + if cmds[file] != None: + filepath = os.path.join(cmds['outputdir'], cmds[direc], cmds[file]) + if os.path.exists(filepath): + if not os.access(filepath, os.W_OK): + errorprint(_('error in must be able to write to metadata files:\n -> %s') % filepath) + usage() + + if cmds['split']: + cmds['basedir'] = oldbase + mdgen = SplitMetaDataGenerator(cmds) + mdgen.doPkgMetadata(directories) + else: + mdgen = MetaDataGenerator(cmds) + mdgen.doPkgMetadata(directory) + mdgen.doRepoMetadata() + + if os.path.exists(os.path.join(cmds['outputdir'], cmds['finaldir'])): + try: + os.rename(os.path.join(cmds['outputdir'], cmds['finaldir']), + os.path.join(cmds['outputdir'], cmds['olddir'])) + except: + errorprint(_('Error moving final %s to old dir %s' % (os.path.join(cmds['outputdir'], cmds['finaldir']), + os.path.join(cmds['outputdir'], cmds['olddir'])))) + sys.exit(1) + + try: + os.rename(os.path.join(cmds['outputdir'], cmds['tempdir']), + os.path.join(cmds['outputdir'], cmds['finaldir'])) + except: + errorprint(_('Error moving final metadata into place')) + # put the old stuff back + os.rename(os.path.join(cmds['outputdir'], cmds['olddir']), + os.path.join(cmds['outputdir'], cmds['finaldir'])) + sys.exit(1) + + for file in ['prestofile', 'prestomdfile']: + if cmds[file]: + fn = os.path.basename(cmds[file]) + else: + continue + oldfile = os.path.join(cmds['outputdir'], cmds['olddir'], fn) + if os.path.exists(oldfile): + try: + os.remove(oldfile) + except OSError, e: + errorprint(_('Could not remove old metadata file: %s') % oldfile) + errorprint(_('Error was %s') % e) + sys.exit(1) + + + # Move everything else back from olddir (eg. repoview files) + olddir = os.path.join(cmds['outputdir'], cmds['olddir']) + finaldir = os.path.join(cmds['outputdir'], cmds['finaldir']) + for file in os.listdir(olddir): + oldfile = os.path.join(olddir, file) + finalfile = os.path.join(finaldir, file) + if os.path.exists(finalfile): + # Hmph? Just leave it alone, then. + try: + if os.path.isdir(oldfile): + shutil.rmtree(oldfile) + else: + os.remove(oldfile) + except OSError, e: + errorprint(_('Could not remove old non-metadata file: %s') % oldfile) + errorprint(_('Error was %s') % e) + sys.exit(1) + else: + try: + os.rename(oldfile, finalfile) + except OSError, e: + errorprint(_('Could not restore old non-metadata file: %s -> %s') % (oldfile, finalfile)) + errorprint(_('Error was %s') % e) + sys.exit(1) + +#XXX: fix to remove tree as we mung basedir + try: + os.rmdir(os.path.join(cmds['outputdir'], cmds['olddir'])) + except OSError, e: + errorprint(_('Could not remove old metadata dir: %s') % cmds['olddir']) + errorprint(_('Error was %s') % e) + errorprint(_('Please clean up this directory manually.')) + +if __name__ == "__main__": + if len(sys.argv) > 1: + if sys.argv[1] == 'profile': + import hotshot + p = hotshot.Profile(os.path.expanduser("~/createprestorepo.prof")) + p.run('main(sys.argv[2:])') + p.close() + else: + main(sys.argv[1:]) + else: + main(sys.argv[1:]) diff --git a/presto-utils/packagelist.py b/presto-utils/packagelist.py new file mode 100644 index 0000000..e9019f9 --- /dev/null +++ b/presto-utils/packagelist.py @@ -0,0 +1,386 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Copyright (c) 2007 Jonathan Dieter + +import rpmUtils.miscutils +import os +import commands + +class RpmItem: + def __init__(self, nevra, f): + (self.name, self.epoch, self.version, self.release, self.arch) = nevra + self.epoch = str(self.epoch) + self.filename = f + + def getNevra(self): + return (self.name, self.epoch, self.version, self.release, self.arch) + + def __eq__(self, b): + if b == None: + return False + try: + # An RpmItem is equal to another RpmItem if the nevra's are equal + if self.getNevra() == b.getNevra(): + return True + else: + return False + except: + # An RpmItem is equal to a DrpmItem if the nevra is equal to the drpm's destination nevra + if self.getNevra() == b.getDstNevra(): + return True + else: + return False + +class DrpmItem: + def __init__(self, src_nevra, dst_nevra, f): + (self.src_name, self.src_epoch, self.src_version, self.src_release, self.src_arch) = src_nevra + (self.dst_name, self.dst_epoch, self.dst_version, self.dst_release, self.dst_arch) = dst_nevra + self.src_epoch = str(self.src_epoch) + self.dst_epoch = str(self.dst_epoch) + self.filename = f + + def exists(self): + return self.filename != None + + def getSrcNevra(self): + return (self.src_name, self.src_epoch, self.src_version, self.src_release, self.src_arch) + + def getDstNevra(self): + return (self.dst_name, self.dst_epoch, self.dst_version, self.dst_release, self.dst_arch) + + def getSrcDstNevra(self): + return [self.getSrcNevra(), self.getDstNevra()] + + def __eq__(self, b): + if b == None: + return False + try: + # A DrpmItem is equal to another DrpmItem if both source and destination nevra's are equal + if self.getSrcDstNevra() == b.getSrcDstNevra(): + return True + else: + return False + except: + # A DrpmItem is equal to an RpmItem if the destination nevra is equal to the rpm's nevra + if self.getDstNevra() == b.getNevra(): + return True + else: + return False + + +class PackageList: + def __init__(self, base_dir, drpm_dir, threshold, debug): + self.base_dir = base_dir + self.drpm_dir = drpm_dir + self.threshold = threshold + self.debug = debug + self.rpm_list = {} + self.sorted = False + + def addRpm(self, rpm): + """Add RpmItem to PackageList""" + self.sorted = False + rpm_key = "%s.%s" % (rpm.name, rpm.arch) + self.rpm_list.setdefault(rpm_key, {}) + self.rpm_list[rpm_key].setdefault("rpms", []) + found = False + for found_rpm in self.rpm_list[rpm_key]["rpms"]: + if rpm == found_rpm: + found = True + break + if not found: + self.rpm_list[rpm_key]["rpms"].append(rpm) + + def addDrpm(self, drpm): + """Add DrpmItem to PackageList""" + self.sorted = False + rpm_key = "%s.%s" % (drpm.src_name, drpm.src_arch) + self.rpm_list.setdefault(rpm_key, {}) + self.rpm_list[rpm_key].setdefault("drpms", []) + found = False + for found_drpm in self.rpm_list[rpm_key]["drpms"]: + if drpm == found_drpm: + found = True + break + if not found: + self.rpm_list[rpm_key]["drpms"].append(drpm) + + def sort(self): + """Sort all rpms and deltarpms in reverse order. This is necessary before running any + methods that try to work with rpms and deltarpms""" + self.sorted = True + for rpm_type in self.rpm_list.values(): + for key in rpm_type.keys(): + if key == "rpms" and len(rpm_type["rpms"]) > 1: + def sortByEVR(rpm1, rpm2): + (n1,e1,v1,r1,a1) = rpm1.getNevra() + (n2,e2,v2,r2,a2) = rpm2.getNevra() + rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2)) + if rc == 0: + return 0 + if rc > 0: + return -1 + if rc < 0: + return 1 + rpm_type["rpms"].sort(sortByEVR) # highest first in list + elif key == "drpms" and len(rpm_type["drpms"]) > 1: + def sortByEVR(drpm1, drpm2, src=False): + if src: + (n1,e1,v1,r1,a1) = drpm1.getSrcNevra() + (n2,e2,v2,r2,a2) = drpm2.getSrcNevra() + else: + (n1,e1,v1,r1,a1) = drpm1.getDstNevra() + (n2,e2,v2,r2,a2) = drpm2.getDstNevra() + rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2)) + if rc == 0: + if src: + return 0 + else: + return sortByEVR(drpm1, drpm2, True) + if rc > 0: + return -1 + if rc < 0: + return 1 + rpm_type["drpms"].sort(sortByEVR) # highest first in list + + def dump(self): + """Dump list of all packages and their rpms and deltarpms""" + if not self.sorted: + self.sort() + temp_list = [] + for x in self.rpm_list.keys(): + temp_list.append(x) + temp_list.sort() + for x in temp_list: + print x + if self.rpm_list[x].has_key("rpms"): + print " RPMS:" + for rpm in self.rpm_list[x]["rpms"]: + print " %s:%s-%s" % (rpm.epoch, rpm.version, rpm.release) + if self.rpm_list[x].has_key("drpms"): + print " DRPMS:" + for drpm in self.rpm_list[x]["drpms"]: + print " %s:%s-%s => %s:%s-%s" % (drpm.src_epoch, drpm.src_version, drpm.src_release, drpm.dst_epoch, drpm.dst_version, drpm.dst_release) + + def __doWork(self, nevra1, nevra2, f1, deltaCommand): + (n1,e1,v1,r1,a1) = nevra1 + (n2,e2,v2,r2,a2) = nevra2 + + deltaRPMName = os.path.join(self.drpm_dir, '%s-%s-%s_%s-%s.%s.drpm' % (n1, v2, r2, v1, r1, a1)) + deltaCommand += deltaRPMName + if self.debug: + print deltaCommand + + if os.path.exists("%s.dontdelta" % deltaRPMName): + f = open("%s.dontdelta" % deltaRPMName, "r") + drpmsize = f.readline()[:-1] + drpmsize = int(drpmsize) + if not self.__drpmIsWorthKeeping(drpmsize, f1): + return False + else: + if self.debug: + print "Original drpm is now worth creating...deleting %s%s.dontdelta" % (self.base_dir, deltaRPMName) + try: + os.unlink("%s.dontdelta" % deltaRPMName) + except Exception, e: + print "Error deleting %s.dontdelta" % os.path.basename(deltaRPMName), str(e) + + (code, out) = commands.getstatusoutput(deltaCommand) + if code: + print "Error genDeltaRPM for %s: exitcode was %s - Reported Error: %s" % (n1, code, out) + return False + else: + # Check whether or not we should keep the drpm + drpmsize = os.path.getsize(deltaRPMName) + if not self.__drpmIsWorthKeeping(drpmsize, f1): + if self.debug: + print 'deleting %s' % (deltaRPMName) + f = open("%s.dontdelta" % deltaRPMName, "w") + f.write("%i\n" % os.path.getsize(deltaRPMName)) + f.close() + try: + os.unlink(deltaRPMName) + except Exception, e: + print "Error deleting deltarpm %s" % os.path.basename(deltaRPMName), str(e) + return False + else: + if e1 == e2: + print 'Generated delta rpm for %s.%s - %s-%s => %s-%s' % (n1, a1, v2, r2, v1, r1) + else: + print 'Generated delta rpm for %s.%s - %s:%s-%s => %s:%s-%s' % (n1, a1, e2, v2, r2, e1, v1, r1) + drpm = DrpmItem(nevra2, nevra1, deltaRPMName) + self.addDrpm(drpm) + self.sort() + if self.debug: + self.dump() + return True + + def __combineDeltaRpm(self, combine_list, dstrpm): + nevra1 = combine_list[-1].getDstNevra() + nevra2 = combine_list[0].getSrcNevra() + f1 = dstrpm.filename + + deltaCommand = 'combinedeltarpm ' + for drpm in combine_list: + deltaCommand += "%s " % drpm.filename + + return self.__doWork(nevra1, nevra2, f1, deltaCommand) + + + def __buildDeltaRpm(self, srcrpm, dstrpm): + nevra1 = dstrpm.getNevra() + nevra2 = srcrpm.getNevra() + + f1 = dstrpm.filename + f2 = srcrpm.filename + deltaCommand = 'makedeltarpm %s %s ' % (f2, f1) + + return self.__doWork(nevra1, nevra2, f1, deltaCommand) + + def __makeDeltaRpm(self, src_nevra, dstrpm, package): + foundSrc = False + foundDst = False + tempQueue = [] + if package.has_key("drpms"): + for drpm in package["drpms"]: + if drpm.getDstNevra() == dstrpm.getNevra() and drpm.getSrcNevra() == src_nevra: + return True + if drpm.getSrcNevra() == src_nevra: + for item in tempQueue: + if drpm.getDstNevra() == item.getSrcNevra(): + combine_list = [drpm, item] + return self.__combineDeltaRpm(combine_list, dstrpm) + elif drpm.getDstNevra() == dstrpm.getNevra(): + tempQueue.append(drpm) + + for rpm in package["rpms"]: + if rpm.getNevra() == src_nevra: + return self.__buildDeltaRpm(rpm, dstrpm) + + print "Found nothing!" + return False + + + def makeDeltaRpms(self, DrpmsPerPackage=0, DoFirst=True, OnlyLastPackage=True): + """Create deltarpms for packages. + Attributes: + OnlyLastPackage (Boolean): Only create deltarpms to point to last destination package + DrpmsPerPackage (Int): Number of deltarpms to make for each package (0 = all possible) + DoFirst (Boolean): Create deltarpm for first source package""" + if not self.sorted: + self.sort() + temp_list = [] + for x in self.rpm_list.keys(): + temp_list.append(x) + temp_list.sort() + if self.debug: + print "Building drpms" + for rpm_name in temp_list: + package = self.rpm_list[rpm_name] + if not package.has_key("rpms") or len(package["rpms"]) == 0: + if self.debug: + print "No rpms for %s: Doing nothing" % rpm_name + continue + + if (not package.has_key("drpms") or len(package["drpms"]) == 0) and len(package["rpms"]) == 1: + if self.debug: + print "Only one rpm available for %s: Doing nothing" % rpm_name + continue + + build_list = [] + + for rpm in package["rpms"]: + if [rpm.getNevra(), 1] not in build_list: + build_list.append([rpm.getNevra(), 1]) + + if package.has_key("drpms"): + for drpm in package["drpms"]: + if [drpm.getSrcNevra(), 1] not in build_list and [drpm.getSrcNevra(), 0] not in build_list: + build_list.append([drpm.getSrcNevra(), 0]) + if [drpm.getDstNevra(), 1] not in build_list and [drpm.getDstNevra(), 0] not in build_list: + build_list.append([drpm.getDstNevra(), 0]) + + def sortByEVR(rpm1, rpm2): + (n1,e1,v1,r1,a1) = rpm1[0] + (n2,e2,v2,r2,a2) = rpm2[0] + rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2)) + if rc == 0: + return 0 + if rc > 0: + return -1 + if rc < 0: + return 1 + build_list.sort(sortByEVR) + + if OnlyLastPackage: + if build_list[0][1] != 1: + continue + + rpm = None + for x in package["rpms"]: + if x.getNevra() == build_list[0][0]: + rpm = x + break + if rpm == None: + continue + + newpkg_list = [rpm] + else: + newpkg_list = [] + for build in build_list[:-1]: + if build[1] != 1: + continue + + rpm = None + for x in package["rpms"]: + if x.getNevra() == build[0]: + rpm = x + break + if rpm == None: + continue + + newpkg_list.append(rpm) + + for newpkg in newpkg_list: + start = False + count = 0 + do_last = True + for build in build_list[:-1]: + if start: + self.__makeDeltaRpm(build[0], newpkg, package) + if not self.sorted: + self.sort() + count += 1 + if DrpmsPerPackage != 0: + if count >= DrpmsPerPackage and not DoFirst: + do_last = False + break + elif count >= DrpmsPerPackage - 1 and DoFirst: + break + if build[0] == newpkg.getNevra(): + start = True + + if DoFirst == 1 and start and do_last: + self.__makeDeltaRpm(build_list[-1][0], newpkg, package) + if not self.sorted: + self.sort() + return + + def __drpmIsWorthKeeping(self, drpmsize, newrpm): + newsize = os.path.getsize(newrpm) + # Delete the drpm if it's too large + if drpmsize > self.threshold * newsize: + return False + return True + |