diff options
author | Luo Jinghua <sunmoon1997@gmail.com> | 2010-10-22 06:56:49 +0800 |
---|---|---|
committer | Luo Jinghua <sunmoon1997@gmail.com> | 2010-10-22 06:56:49 +0800 |
commit | bce5cfd226d4c6f6cab4aa0352b7f10c9fcc1e55 (patch) | |
tree | ee2a5c554b356ab98b272e7ac3bf9c1e008fc7b5 |
Initial import of patch.py
81 files changed, 11001 insertions, 0 deletions
diff --git a/doc/.svn/all-wcprops b/doc/.svn/all-wcprops new file mode 100644 index 0000000..9732235 --- /dev/null +++ b/doc/.svn/all-wcprops @@ -0,0 +1,47 @@ +K 25 +svn:wc:ra_dav:version-url +V 26 +/svn/!svn/ver/71/trunk/doc +END +example.hg.diff +K 25 +svn:wc:ra_dav:version-url +V 42 +/svn/!svn/ver/66/trunk/doc/example.hg.diff +END +unified_diff_format.svg +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/!svn/ver/14/trunk/doc/unified_diff_format.svg +END +LICENSE +K 25 +svn:wc:ra_dav:version-url +V 34 +/svn/!svn/ver/71/trunk/doc/LICENSE +END +unified_diff_format.png +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/!svn/ver/30/trunk/doc/unified_diff_format.png +END +example.svn.diff +K 25 +svn:wc:ra_dav:version-url +V 43 +/svn/!svn/ver/58/trunk/doc/example.svn.diff +END +example.diff.diff +K 25 +svn:wc:ra_dav:version-url +V 44 +/svn/!svn/ver/66/trunk/doc/example.diff.diff +END +example.python25.diff +K 25 +svn:wc:ra_dav:version-url +V 48 +/svn/!svn/ver/65/trunk/doc/example.python25.diff +END diff --git a/doc/.svn/entries b/doc/.svn/entries new file mode 100644 index 0000000..b29c59c --- /dev/null +++ b/doc/.svn/entries @@ -0,0 +1,266 @@ +10 + +dir +101 +http://python-patch.googlecode.com/svn/trunk/doc +http://python-patch.googlecode.com/svn + + + +2010-03-20T16:18:30.009768Z +71 +techtonik + + + + + + + + + + + + + + +7155d8b8-9951-0410-8dfa-c5fb0ae76a41 + +example.hg.diff +file + + + + +2010-10-21T22:54:45.939691Z +d63e53a407cba3f3f0e5d18bdce1ce97 +2009-12-27T20:32:13.407374Z +66 +techtonik + + + + + + + + + + + + + + + + + + + + + +724 + +unified_diff_format.svg +file + + + + +2010-10-21T22:54:45.940691Z +1bdd3e2986dfc9d5099784897958aa00 +2008-07-11T12:00:28.935567Z +14 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +94250 + +LICENSE +file + + + + +2010-10-21T22:54:45.941691Z +a7644cf8c728b63df84c31c2b0c15bd4 +2010-03-20T16:18:30.009768Z +71 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +1091 + +unified_diff_format.png +file + + + + +2010-10-21T22:54:45.941691Z +f2f27df7de8ac06965efcdf93645c115 +2008-07-15T17:51:27.402836Z +30 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +72047 + +example.svn.diff +file + + + + +2010-10-21T22:54:45.942691Z +c1beca06b1b50ab458b984a37525a415 +2009-12-26T13:57:50.469186Z +58 +techtonik + + + + + + + + + + + + + + + + + + + + + +3961 + +example.diff.diff +file + + + + +2010-10-21T22:54:45.942691Z +388005acf8945d9097c8c08b42411055 +2009-12-27T20:32:13.407374Z +66 +techtonik + + + + + + + + + + + + + + + + + + + + + +828 + +example.python25.diff +file + + + + +2010-10-21T22:54:45.942691Z +9157a884ab28936000447c21b15be270 +2009-12-27T14:14:18.469868Z +65 +techtonik + + + + + + + + + + + + + + + + + + + + + +686 + diff --git a/doc/.svn/prop-base/LICENSE.svn-base b/doc/.svn/prop-base/LICENSE.svn-base new file mode 100644 index 0000000..6e84194 --- /dev/null +++ b/doc/.svn/prop-base/LICENSE.svn-base @@ -0,0 +1,5 @@ +K 12 +svn:keywords +V 22 +Id Date HeadURL Author +END diff --git a/doc/.svn/prop-base/unified_diff_format.png.svn-base b/doc/.svn/prop-base/unified_diff_format.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/doc/.svn/prop-base/unified_diff_format.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/doc/.svn/prop-base/unified_diff_format.svg.svn-base b/doc/.svn/prop-base/unified_diff_format.svg.svn-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/doc/.svn/prop-base/unified_diff_format.svg.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/doc/.svn/text-base/LICENSE.svn-base b/doc/.svn/text-base/LICENSE.svn-base new file mode 100644 index 0000000..a3055dd --- /dev/null +++ b/doc/.svn/text-base/LICENSE.svn-base @@ -0,0 +1,22 @@ +MIT License +----------- + +Copyright (c) 2008-2010 anatoly techtonik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/doc/.svn/text-base/example.diff.diff.svn-base b/doc/.svn/text-base/example.diff.diff.svn-base new file mode 100644 index 0000000..8f35356 --- /dev/null +++ b/doc/.svn/text-base/example.diff.diff.svn-base @@ -0,0 +1,20 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 30_default_charset_utf8.dpatch by <debacle@debian.org> +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Use UTF-8 as default charset + +@DPATCH@ + +diff -uraN trac-0.11.5.orig/trac/mimeview/api.py trac-0.11.5/trac/mimeview/api.py +--- trac-0.11.5.orig/trac/mimeview/api.py 2009-06-30 21:18:58.000000000 +0200 ++++ trac-0.11.5/trac/mimeview/api.py 2009-09-28 22:02:35.000000000 +0200 +@@ -579,7 +579,7 @@ + annotators = ExtensionPoint(IHTMLPreviewAnnotator) + converters = ExtensionPoint(IContentConverter) + +- default_charset = Option('trac', 'default_charset', 'iso-8859-15', ++ default_charset = Option('trac', 'default_charset', 'utf-8', + """Charset to be used when in doubt.""") + + tab_width = IntOption('mimeviewer', 'tab_width', 8, diff --git a/doc/.svn/text-base/example.hg.diff.svn-base b/doc/.svn/text-base/example.hg.diff.svn-base new file mode 100644 index 0000000..46462b6 --- /dev/null +++ b/doc/.svn/text-base/example.hg.diff.svn-base @@ -0,0 +1,22 @@ +diff -r b2d9961ff1f5 TODO +--- a/TODO Sat Dec 26 16:36:37 2009 +0200 ++++ b/TODO Sun Dec 27 22:28:17 2009 +0200 +@@ -7,3 +7,7 @@ + - remove files
+ - svn diff
+ - hg diff
++
++Source and target file conflicts:
++- two same source files in the same patch
++- one source and later one target file with the same name (that exists)
+diff -r b2d9961ff1f5 test commit/review system test +--- a/test commit/review system test Sat Dec 26 16:36:37 2009 +0200 ++++ b/test commit/review system test Sun Dec 27 22:28:17 2009 +0200 +@@ -1,4 +1,4 @@ + something to
+ change in
+-this file
+-for review +\ No newline at end of file ++this file <-- this should be removed!!! ARRGH! BASTARD, HOW DARE YOU TO MESS WITH PROJECT HISTORY!
++for review
diff --git a/doc/.svn/text-base/example.python25.diff.svn-base b/doc/.svn/text-base/example.python25.diff.svn-base new file mode 100644 index 0000000..dc4cc6d --- /dev/null +++ b/doc/.svn/text-base/example.python25.diff.svn-base @@ -0,0 +1,22 @@ +--- diff.py Sun Dec 27 16:08:28 2009
++++ trunk/diff.py Sun Dec 27 15:46:58 2009
+@@ -7,7 +7,7 @@
+
+ """
+
+-import sys, os, datetime, difflib, optparse
++import sys, os, time, difflib, optparse
+
+ def main():
+
+@@ -29,8 +29,8 @@
+ n = options.lines
+ fromfile, tofile = args
+
+- fromdate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ")
+- todate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ")
++ fromdate = time.ctime(os.stat(fromfile).st_mtime)
++ todate = time.ctime(os.stat(tofile).st_mtime)
+ fromlines = open(fromfile, 'U').readlines()
+ tolines = open(tofile, 'U').readlines()
+
diff --git a/doc/.svn/text-base/example.svn.diff.svn-base b/doc/.svn/text-base/example.svn.diff.svn-base new file mode 100644 index 0000000..4f447e5 --- /dev/null +++ b/doc/.svn/text-base/example.svn.diff.svn-base @@ -0,0 +1,86 @@ +Index: trac/versioncontrol/svn_fs.py +=================================================================== +--- trac/versioncontrol/svn_fs.py (revision 8986) ++++ trac/versioncontrol/svn_fs.py (working copy) +@@ -289,7 +289,7 @@ + repos = fs_repos + else: + repos = CachedRepository(self.env.get_db_cnx, fs_repos, None, +- self.log) ++ self.log, self.env) + repos.has_linear_changesets = True + if authname: + authz = SubversionAuthorizer(self.env, weakref.proxy(repos), +Index: trac/versioncontrol/cache.py +=================================================================== +--- trac/versioncontrol/cache.py (revision 8986) ++++ trac/versioncontrol/cache.py (working copy) +@@ -18,7 +18,7 @@ + import os + import posixpath + +-from trac.core import TracError ++from trac.core import * + from trac.util.datefmt import utc, to_timestamp + from trac.util.translation import _ + from trac.versioncontrol import Changeset, Node, Repository, Authorizer, \ +@@ -36,19 +36,42 @@ + CACHE_METADATA_KEYS = (CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV) + + ++class ICacheChangesetListener(Interface): ++ """Cached changeset operations""" ++ ++ def edit_changeset(cset): ++ """Called when changeset is about to be cached. ++ Returns altered data to cache or None if unchanged. cset usually ++ contains cset.date, cset.author, cset.message and cset.rev ++ """ ++ ++class CacheManager(Component): ++ """Provide interface to plug-in into cache operations""" ++ ++ observers = ExtensionPoint(ICacheChangesetListener) ++ ++ def check_changeset(self, cset): ++ for observer in self.observers: ++ res = observer.edit_changeset(cset) ++ if res != None: ++ cset = res ++ return cset ++ ++ + class CachedRepository(Repository): + + has_linear_changesets = False + + scope = property(lambda self: self.repos.scope) + +- def __init__(self, getdb, repos, authz, log): ++ def __init__(self, getdb, repos, authz, log, env): + Repository.__init__(self, repos.name, authz, log) + if callable(getdb): + self.getdb = getdb + else: + self.getdb = lambda: getdb + self.repos = repos ++ self.cache_mgr = CacheManager(env) + + def close(self): + self.repos.close() +@@ -77,6 +100,7 @@ + + def sync_changeset(self, rev): + cset = self.repos.get_changeset(rev) ++ cset = self.cache_mgr.check_changeset(cset) + db = self.getdb() + cursor = db.cursor() + cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " +@@ -182,6 +206,7 @@ + self.log.info("Trying to sync revision [%s]" % + next_youngest) + cset = self.repos.get_changeset(next_youngest) ++ cset = self.cache_mgr.check_changeset(cset) + try: + cursor.execute("INSERT INTO revision " + " (rev,time,author,message) " diff --git a/doc/.svn/text-base/unified_diff_format.png.svn-base b/doc/.svn/text-base/unified_diff_format.png.svn-base Binary files differnew file mode 100644 index 0000000..2f7dadf --- /dev/null +++ b/doc/.svn/text-base/unified_diff_format.png.svn-base diff --git a/doc/.svn/text-base/unified_diff_format.svg.svn-base b/doc/.svn/text-base/unified_diff_format.svg.svn-base new file mode 100644 index 0000000..97bd819 --- /dev/null +++ b/doc/.svn/text-base/unified_diff_format.svg.svn-base @@ -0,0 +1,1453 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + width="744.09448" + height="1052.3622" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="unified_diff_format.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <metadata + id="metadata321"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + inkscape:window-height="977" + inkscape:window-width="1280" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="1" + inkscape:cx="326.14062" + inkscape:cy="773.5073" + inkscape:window-x="-4" + inkscape:window-y="-4" + inkscape:current-layer="g3096" /> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective323" /> + </defs> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect3986" + y="119.08554" + x="29.101612" + height="24.043991" + width="542.27417" /> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4079" + y="643.3783" + x="29.101664" + height="21.006182" + width="542.31421" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.49999997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4077" + y="530.40411" + x="29.101612" + height="112.92788" + width="542.34729" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.49999997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4075" + y="313.9101" + x="29.101612" + height="217.22546" + width="542.17633" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4073" + y="162.67993" + x="29.101612" + height="154.54366" + width="542.27075" /> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4071" + y="141.73454" + x="29.101612" + height="22.392969" + width="542.25354" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4419" + y="683.94727" + x="29.101612" + height="299.80609" + width="542.34729" /> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4421" + y="663.87445" + x="29.101664" + height="21.006186" + width="542.31421" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4536" + d="M 29.242421,164.12501 L 29.242421,174.27697 L 157.24347,174.27697 L 157.24347,164.12501 L 29.242421,164.12501 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4559" + d="M 29.242421,317.36739 L 29.242421,327.51935 L 157.24347,327.51935 L 157.24347,317.36739 L 29.242421,317.36739 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4561" + d="M 29.242421,531.36739 L 29.242421,541.51935 L 157.24347,541.51935 L 157.24347,531.36739 L 29.242421,531.36739 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4563" + d="M 29.242421,684.86231 L 29.242421,695.01427 L 157.24347,695.01427 L 157.24347,684.86231 L 29.242421,684.86231 z" /> + <text + style="font-size:40.86742401px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + xml:space="preserve" + id="text3113" + y="55.509674" + x="25.596079" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan3117" + y="55.509674" + x="25.596079">Unified Diff/Patch Format</tspan></text> + <g + id="g4529" + transform="matrix(1.0472513,0,0,1.0032667,-0.2576625,-0.3375146)"> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3125" + y="75.54467" + x="28.284271" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + xml:space="preserve" + id="text3897" + y="91.36142" + x="41.263729"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3899" + y="91.36142" + x="41.263729">comments are ignored</tspan></text> + </g> + <g + id="g4520" + transform="matrix(1.0472513,0,0,1.0032667,-0.3960063,-0.3375146)"> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3908" + y="75.54467" + x="160.10918" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3910" + y="87.320808" + x="220.56581"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3912" + y="87.320808" + x="220.56581">the first file that</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3914" + y="97.320808" + x="220.56581">exists is used</tspan></text> + </g> + <g + id="g4504" + transform="matrix(1.0472513,0,0,1.0032667,-0.5343536,-0.3375146)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3926" + y="75.54467" + x="291.93408" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3928" + y="87.320808" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3960" + y="87.320808" + x="352.39072">may contain several</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3964" + y="97.320808" + x="352.39072">hunks for each file</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3958" + y="107.32081" + x="352.39072" /></text> + </g> + <g + id="g4486" + transform="matrix(1.0472513,0,0,1.0032667,-0.2576418,-0.3375146)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3970" + y="75.54467" + x="423.36264" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3972" + y="87.320808" + x="483.81927"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3976" + y="87.320808" + x="483.81927">-line_from,total_before</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3978" + y="97.320808" + x="483.81927">+line_after,total_after</tspan></text> + </g> + <g + id="g4567" + transform="matrix(1.0472513,0,0,1.0032667,423.10537,67.948198)"> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4569" + y="75.54467" + x="160.10918" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4571" + y="87.320808" + x="220.56581"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4583" + y="87.320808" + x="220.56581">--- filename \t comment</tspan></text> + </g> + <g + id="g4587" + transform="matrix(1.0472513,0,0,1.0032667,283.99712,828.76607)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4589" + y="75.54467" + x="291.93408" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4591" + y="91.348259" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4597" + y="91.348259" + x="352.39072">line ends may differ</tspan></text> + </g> + <g + id="g3056" + transform="translate(6.1391031e-6,-2.2888184e-5)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" + id="rect4606" + y="180.776" + x="590.77991" + height="41.044823" + width="128.00391" /> + <text + transform="scale(1.0216856,0.9787747)" + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4608" + y="197.62608" + x="640.2099"><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4622" + y="197.62608" + x="640.2099">for the format like</tspan><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4626" + y="207.87631" + x="640.2099">-line_from +line_after</tspan><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4616" + y="218.12654" + x="640.2099">total_xxx = 1</tspan></text> + </g> + <g + id="g4640" + transform="matrix(1.0472513,0,0,1.0032667,284.28523,864.45712)"> + <g + id="g4670" + transform="translate(-0.2751091,3.9869783)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4642" + y="73.76046" + x="291.93408" + height="40.911179" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4644" + y="87.320808" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4668" + y="87.320808" + x="354.79697">"\ No newline at end of </tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5786" + y="97.320808" + x="352.39072">file" marker is used if</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5800" + y="107.32081" + x="352.39072">file ends without newline</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5784" + y="117.32081" + x="352.39072" /></text> + </g> + </g> + <text + sodipodi:linespacing="125%" + style="font-size:8.1734848px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" + xml:space="preserve" + id="text5802" + y="1030.8047" + x="37.980774" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan5804" + y="1030.8047" + x="37.980774">http://en.wikipedia.org/wiki/Diff#Unified_format</tspan></text> + <text + sodipodi:linespacing="125%" + style="font-size:8.1734848px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" + xml:space="preserve" + id="text5806" + y="1041.0216" + x="37.980774" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan5808" + y="1041.0216" + x="37.980774">http://techtonik.rainforce.org</tspan></text> + <text + sodipodi:linespacing="120%" + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + xml:space="preserve" + id="text4629" + transform="scale(0.960765,1.0408372)" + y="126.03973" + x="39.688015"><tspan + id="tspan4631" + y="126.03973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4633" + y="126.03973" + x="39.688015">Index: src/plugins/contrib/devpak_plugin/updatedlg.cpp</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4635" + dx="0" + y="126.03973" + x="305.19858" /></tspan><tspan + id="tspan4637" + y="135.84792" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4639" + y="135.84792" + x="39.688015">===================================================================</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4641" + dx="0" + y="135.84792" + x="369.11774" /></tspan><tspan + id="tspan4643" + y="145.65608" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4645" + y="145.65608" + x="39.688015">--- src/plugins/contrib/devpak_plugin/updatedlg.cpp (revision 5106)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4647" + dx="0" + y="145.65608" + x="369.11774" /></tspan><tspan + id="tspan4649" + y="155.46426" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4651" + y="155.46426" + x="39.688015">+++ src/plugins/contrib/devpak_plugin/updatedlg.cpp (working copy)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4653" + dx="0" + y="155.46426" + x="364.20087" /></tspan><tspan + id="tspan4655" + y="165.27246" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4657" + y="165.27246" + x="39.688015">@@ -94,11 +94,13 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4659" + dx="0" + y="165.27246" + x="133.10837" /></tspan><tspan + id="tspan4661" + y="175.08063" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4663" + y="175.08063" + x="39.688015"> lst->InsertColumn(1, _("Version"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4665" + dx="0" + y="175.08063" + x="236.36249" /></tspan><tspan + id="tspan4667" + y="184.88881" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4669" + y="184.88881" + x="39.688015"> lst->InsertColumn(2, _("Installed"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4671" + dx="0" + y="184.88881" + x="246.19618" /></tspan><tspan + id="tspan4673" + y="194.69701" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4675" + y="194.69701" + x="39.688015"> lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4677" + dx="0" + y="194.69701" + x="324.866" /></tspan><tspan + id="tspan4679" + y="204.5052" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4681" + y="204.5052" + x="39.688015">+ lst->InsertColumn(4, _("Rev"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4683" + dx="0" + y="204.5052" + x="216.69504" /></tspan><tspan + id="tspan4685" + y="214.31337" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4687" + y="214.31337" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4689" + dx="0" + y="214.31337" + x="44.604874" /></tspan><tspan + id="tspan4691" + y="224.12157" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4693" + y="224.12157" + x="39.688015">- lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4695" + dx="0" + y="224.12157" + x="536.29102" /></tspan><tspan + id="tspan4697" + y="233.92973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4699" + y="233.92973" + x="39.688015">+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4701" + dx="0" + y="233.92973" + x="565.79218" /></tspan><tspan + id="tspan4703" + y="243.73793" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4705" + y="243.73793" + x="39.688015"> lst->SetColumnWidth(1, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4707" + dx="0" + y="243.73793" + x="197.02759" /></tspan><tspan + id="tspan4709" + y="253.54611" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4711" + y="253.54611" + x="39.688015"> lst->SetColumnWidth(2, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4713" + dx="0" + y="253.54611" + x="197.02759" /></tspan><tspan + id="tspan4715" + y="263.35431" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4717" + y="263.35431" + x="39.688015"> lst->SetColumnWidth(3, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4719" + dx="0" + y="263.35431" + x="197.02759" /></tspan><tspan + id="tspan4721" + y="273.16251" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4723" + y="273.16251" + x="39.688015">+ lst->SetColumnWidth(4, 40);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4725" + dx="0" + y="273.16251" + x="197.02759" /></tspan><tspan + id="tspan4727" + y="282.97067" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4729" + y="282.97067" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4731" + dx="0" + y="282.97067" + x="49.521736" /></tspan><tspan + id="tspan4733" + y="292.77884" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4735" + y="292.77884" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4737" + dx="0" + y="292.77884" + x="44.604874" /></tspan><tspan + id="tspan4739" + y="302.58707" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4741" + y="302.58707" + x="39.688015"> void UpdateDlg::AddRecordToList(UpdateRec* rec)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4743" + dx="0" + y="302.58707" + x="275.69739" /></tspan><tspan + id="tspan4745" + y="312.3952" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4747" + y="312.3952" + x="39.688015">@@ -111,8 +113,20 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4749" + dx="0" + y="312.3952" + x="138.02525" /></tspan><tspan + id="tspan4751" + y="322.2034" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4753" + y="322.2034" + x="39.688015"> lst->SetItem(idx, 1, rec->version);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4755" + dx="0" + y="322.2034" + x="236.36249" /></tspan><tspan + id="tspan4757" + y="332.01157" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4759" + y="332.01157" + x="39.688015"> lst->SetItem(idx, 2, rec->installed_version);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4761" + dx="0" + y="332.01157" + x="285.5311" /></tspan><tspan + id="tspan4763" + y="341.81973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4765" + y="341.81973" + x="39.688015"> lst->SetItem(idx, 3, rec->size);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4767" + dx="0" + y="341.81973" + x="221.61188" /></tspan><tspan + id="tspan4769" + y="351.6279" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4771" + y="351.6279" + x="39.688015">+ lst->SetItem(idx, 4, rec->revision);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4773" + dx="0" + y="351.6279" + x="241.27933" /></tspan><tspan + id="tspan4775" + y="361.4361" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4777" + y="361.4361" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4779" + dx="0" + y="361.4361" + x="49.521736" /></tspan><tspan + id="tspan4781" + y="371.24423" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4783" + y="371.24423" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4785" + dx="0" + y="371.24423" + x="44.604874" /></tspan><tspan + id="tspan4787" + y="381.05243" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4789" + y="381.05243" + x="39.688015">+wxString UpdateDlg::GetListColumnText(int idx, int col) {</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4791" + dx="0" + y="381.05243" + x="324.866" /></tspan><tspan + id="tspan4793" + y="390.8606" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4795" + y="390.8606" + x="39.688015">+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4797" + dx="0" + y="390.8606" + x="339.61658" /></tspan><tspan + id="tspan4799" + y="400.66876" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4801" + y="400.66876" + x="39.688015">+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4803" + dx="0" + y="400.66876" + x="511.70673" /></tspan><tspan + id="tspan4805" + y="410.47693" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4807" + y="410.47693" + x="39.688015">+ wxListItem info;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4809" + dx="0" + y="410.47693" + x="142.94209" /></tspan><tspan + id="tspan4811" + y="420.28513" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4813" + y="420.28513" + x="39.688015">+ info.SetId(index);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4815" + dx="0" + y="420.28513" + x="152.77583" /></tspan><tspan + id="tspan4817" + y="430.09326" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4819" + y="430.09326" + x="39.688015">+ info.SetColumn(col);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4821" + dx="0" + y="430.09326" + x="162.60954" /></tspan><tspan + id="tspan4823" + y="439.90146" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4825" + y="439.90146" + x="39.688015">+ info.SetMask(wxLIST_MASK_TEXT);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4827" + dx="0" + y="439.90146" + x="216.69504" /></tspan><tspan + id="tspan4829" + y="449.70963" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4831" + y="449.70963" + x="39.688015">+ lst->GetItem(info);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4833" + dx="0" + y="449.70963" + x="157.6927" /></tspan><tspan + id="tspan4835" + y="459.51776" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4837" + y="459.51776" + x="39.688015">+ return info.GetText();</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4839" + dx="0" + y="459.51776" + x="172.44328" /></tspan><tspan + id="tspan4841" + y="469.32596" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4843" + y="469.32596" + x="39.688015">+}</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4845" + dx="0" + y="469.32596" + x="49.521736" /></tspan><tspan + id="tspan4847" + y="479.13412" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4849" + y="479.13412" + x="39.688015">+</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4851" + dx="0" + y="479.13412" + x="44.604874" /></tspan><tspan + id="tspan4853" + y="488.94229" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4855" + y="488.94229" + x="39.688015"> void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4857" + dx="0" + y="488.94229" + x="403.53577" /></tspan><tspan + id="tspan4859" + y="498.75046" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4861" + y="498.75046" + x="39.688015"> {</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4863" + dx="0" + y="498.75046" + x="49.521736" /></tspan><tspan + id="tspan4865" + y="508.55865" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4867" + y="508.55865" + x="39.688015"> wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4869" + dx="0" + y="508.55865" + x="339.61658" /></tspan><tspan + id="tspan4871" + y="518.36682" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4873" + y="518.36682" + x="39.688015">@@ -393,7 +407,9 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4875" + dx="0" + y="518.36682" + x="133.10837" /></tspan><tspan + id="tspan4877" + y="528.17499" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4879" + y="528.17499" + x="39.688015"> if (index == -1)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4881" + dx="0" + y="528.17499" + x="142.94209" /></tspan><tspan + id="tspan4883" + y="537.98315" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4885" + y="537.98315" + x="39.688015"> return 0;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4887" + dx="0" + y="537.98315" + x="128.19151" /></tspan><tspan + id="tspan4889" + y="547.79132" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4891" + y="547.79132" + x="39.688015"> wxString title = lst->GetItemText(index);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4893" + dx="0" + y="547.79132" + x="265.86365" /></tspan><tspan + id="tspan4895" + y="557.59955" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4897" + y="557.59955" + x="39.688015">- return FindRecByTitle(title, m_Recs, m_RecsCount);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4899" + dx="0" + y="557.59955" + x="310.11542" /></tspan><tspan + id="tspan4901" + y="567.40765" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4903" + y="567.40765" + x="39.688015">+ wxString version = GetListColumnText(index, 1);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4905" + dx="0" + y="567.40765" + x="295.36481" /></tspan><tspan + id="tspan4907" + y="577.21588" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4909" + y="577.21588" + x="39.688015">+ wxString revision = GetListColumnText(index, 4);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4911" + dx="0" + y="577.21588" + x="300.28168" /></tspan><tspan + id="tspan4913" + y="587.02405" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4915" + y="587.02405" + x="39.688015">+ return FindRec(title, version, revision, m_Recs, m_RecsCount);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4917" + dx="0" + y="587.02405" + x="369.11774" /></tspan><tspan + id="tspan4919" + y="596.83228" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4921" + y="596.83228" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4923" + dx="0" + y="596.83228" + x="49.521736" /></tspan><tspan + id="tspan4925" + y="606.6405" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4927" + y="606.6405" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4929" + dx="0" + y="606.6405" + x="44.604874" /></tspan><tspan + id="tspan4931" + y="616.44867" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4933" + y="616.44867" + x="39.688015"> void UpdateDlg::DownloadFile(bool dontInstall)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4935" + dx="0" + y="616.44867" + x="270.78052" /></tspan><tspan + id="tspan4937" + y="626.25684" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4939" + y="626.25684" + x="39.688015">Index: src/plugins/contrib/devpak_plugin/manifest.xml</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4941" + dx="0" + y="626.25684" + x="300.28168" /></tspan><tspan + id="tspan4943" + y="636.06506" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4945" + y="636.06506" + x="39.688015">===================================================================</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4947" + dx="0" + y="636.06506" + x="369.11774" /></tspan><tspan + id="tspan4949" + y="645.87329" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4951" + y="645.87329" + x="39.688015">--- src/plugins/contrib/devpak_plugin/manifest.xml (revision 5106)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4953" + dx="0" + y="645.87329" + x="364.20087" /></tspan><tspan + id="tspan4955" + y="655.68146" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4957" + y="655.68146" + x="39.688015">+++ src/plugins/contrib/devpak_plugin/manifest.xml (working copy)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4959" + dx="0" + y="655.68146" + x="359.28403" /></tspan><tspan + id="tspan4961" + y="665.48969" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4963" + y="665.48969" + x="39.688015">@@ -2,18 +2,19 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4965" + dx="0" + y="665.48969" + x="123.27467" /></tspan><tspan + id="tspan4967" + y="675.29791" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4969" + y="675.29791" + x="39.688015"> <CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4971" + dx="0" + y="675.29791" + x="206.86131" /></tspan><tspan + id="tspan4973" + y="685.10602" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4975" + y="685.10602" + x="39.688015"> <SdkVersion major="1" minor="10" release="0" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4977" + dx="0" + y="685.10602" + x="295.36481" /></tspan><tspan + id="tspan4979" + y="694.91425" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4981" + y="694.91425" + x="39.688015"> <Plugin name="DevPakUpdater"></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4983" + dx="0" + y="694.91425" + x="206.86131" /></tspan><tspan + id="tspan4985" + y="704.72247" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4987" + y="704.72247" + x="39.688015">- <Value title="Dev-C++ DevPak updater/installer" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4989" + dx="0" + y="704.72247" + x="329.78284" /></tspan><tspan + id="tspan4991" + y="714.5307" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4993" + y="714.5307" + x="39.688015">- <Value version="0.1" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4995" + dx="0" + y="714.5307" + x="197.02759" /></tspan><tspan + id="tspan4997" + y="724.33887" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4999" + y="724.33887" + x="39.688015">+ <Value title="DevPak updater/installer" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5001" + dx="0" + y="724.33887" + x="290.44797" /></tspan><tspan + id="tspan5003" + y="734.14709" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5005" + y="734.14709" + x="39.688015">+ <Value version="0.2" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5007" + dx="0" + y="734.14709" + x="197.02759" /></tspan><tspan + id="tspan5009" + y="743.95526" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5011" + y="743.95526" + x="39.688015"> <Value description="Installs selected DevPaks from the Internet" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5013" + dx="0" + y="743.95526" + x="413.36951" /></tspan><tspan + id="tspan5015" + y="753.76343" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5017" + y="753.76343" + x="39.688015"> <Value author="Yiannis Mandravellos" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5019" + dx="0" + y="753.76343" + x="275.69739" /></tspan><tspan + id="tspan5021" + y="763.57166" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5023" + y="763.57166" + x="39.688015"> <Value authorEmail="info@codeblocks.org" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5025" + dx="0" + y="763.57166" + x="295.36481" /></tspan><tspan + id="tspan5027" + y="773.37988" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5029" + y="773.37988" + x="39.688015"> <Value authorWebsite="http://www.codeblocks.org/" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5031" + dx="0" + y="773.37988" + x="339.61658" /></tspan><tspan + id="tspan5033" + y="783.18805" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5035" + y="783.18805" + x="39.688015"> <Value thanksTo="Dev-C++ community.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5037" + dx="0" + y="783.18805" + x="256.02994" /></tspan><tspan + id="tspan5039" + y="792.99628" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5041" + y="792.99628" + x="39.688015">- Julian R Seward for libbzip2.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5043" + dx="0" + y="792.99628" + x="310.11542" /></tspan><tspan + id="tspan5045" + y="802.8045" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5047" + y="802.8045" + x="39.688015">- libbzip2 copyright notice:</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5049" + dx="0" + y="802.8045" + x="295.36481" /></tspan><tspan + id="tspan5051" + y="812.61267" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5053" + y="812.61267" + x="39.688015">- bzip2 and associated library libbzip2, are</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5055" + dx="0" + y="812.61267" + x="374.03461" /></tspan><tspan + id="tspan5057" + y="822.42084" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5059" + y="822.42084" + x="39.688015">- copyright (C) 1996-2000 Julian R Seward.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5061" + dx="0" + y="822.42084" + x="364.20087" /></tspan><tspan + id="tspan5063" + y="832.22906" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5065" + y="832.22906" + x="39.688015">- All rights reserved." /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5067" + dx="0" + y="832.22906" + x="285.5311" /></tspan><tspan + id="tspan5069" + y="842.03729" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5071" + y="842.03729" + x="39.688015">+ Julian R Seward for libbzip2.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5073" + dx="0" + y="842.03729" + x="226.52878" /></tspan><tspan + id="tspan5075" + y="851.84546" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5077" + y="851.84546" + x="39.688015">+</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5079" + dx="0" + y="851.84546" + x="44.604874" /></tspan><tspan + id="tspan5081" + y="861.65369" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5083" + y="861.65369" + x="39.688015">+ libbzip2 copyright notice:</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5085" + dx="0" + y="861.65369" + x="211.77817" /></tspan><tspan + id="tspan5087" + y="871.46185" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5089" + y="871.46185" + x="39.688015">+ bzip2 and associated library libbzip2, are</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5091" + dx="0" + y="871.46185" + x="290.44797" /></tspan><tspan + id="tspan5093" + y="881.27008" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5095" + y="881.27008" + x="39.688015">+ copyright (C) 1996-2000 Julian R Seward.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5097" + dx="0" + y="881.27008" + x="280.61423" /></tspan><tspan + id="tspan5099" + y="891.07825" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5101" + y="891.07825" + x="39.688015">+ All rights reserved." /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5103" + dx="0" + y="891.07825" + x="201.94443" /></tspan><tspan + id="tspan5105" + y="900.88647" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5107" + y="900.88647" + x="39.688015"> <Value license="GPL" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5109" + dx="0" + y="900.88647" + x="197.02759" /></tspan><tspan + id="tspan5111" + y="910.6947" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5113" + y="910.6947" + x="39.688015"> </Plugin></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5115" + dx="0" + y="910.6947" + x="108.52407" /></tspan><tspan + id="tspan5117" + y="920.50287" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5119" + y="920.50287" + x="39.688015">-</CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5121" + dx="0" + y="920.50287" + x="211.77817" /></tspan><tspan + id="tspan5123" + y="930.31104" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5125" + y="930.31104" + x="39.688015">+</CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5127" + dx="0" + y="930.31104" + x="211.77817" /></tspan><tspan + id="tspan5129" + y="940.11926" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5131" + y="940.11926" + x="39.688015">\ No newline at end of file</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5133" + dx="0" + y="940.11926" + x="172.44328" /></tspan><tspan + id="tspan5135" + y="940.11926" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5137" + y="949.92749" + x="39.688015" /></tspan></text> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3015" + y="250.75372" + x="640.2099" + transform="scale(1.0216856,0.9787747)" /> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + y="250.75372" + x="640.2099" + transform="scale(1.0216856,0.9787747)" + id="text3035" /> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3067" + y="248.54395" + x="640.2099" + transform="scale(1.0216856,0.9787747)"><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3069" + y="248.54395" + x="640.2099" /></text> + <g + transform="matrix(1.0472513,0,0,1.0032667,147.41284,155.66249)" + id="g3096"> + <g + id="g3106" + transform="translate(0,5.9804637)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3098" + y="70.67984" + x="423.36264" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3100" + y="87.320808" + x="483.81927">line numbers start from 1</text> + </g> + </g> +</svg> diff --git a/doc/LICENSE b/doc/LICENSE new file mode 100644 index 0000000..a3055dd --- /dev/null +++ b/doc/LICENSE @@ -0,0 +1,22 @@ +MIT License +----------- + +Copyright (c) 2008-2010 anatoly techtonik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/doc/example.diff.diff b/doc/example.diff.diff new file mode 100644 index 0000000..8f35356 --- /dev/null +++ b/doc/example.diff.diff @@ -0,0 +1,20 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 30_default_charset_utf8.dpatch by <debacle@debian.org> +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Use UTF-8 as default charset + +@DPATCH@ + +diff -uraN trac-0.11.5.orig/trac/mimeview/api.py trac-0.11.5/trac/mimeview/api.py +--- trac-0.11.5.orig/trac/mimeview/api.py 2009-06-30 21:18:58.000000000 +0200 ++++ trac-0.11.5/trac/mimeview/api.py 2009-09-28 22:02:35.000000000 +0200 +@@ -579,7 +579,7 @@ + annotators = ExtensionPoint(IHTMLPreviewAnnotator) + converters = ExtensionPoint(IContentConverter) + +- default_charset = Option('trac', 'default_charset', 'iso-8859-15', ++ default_charset = Option('trac', 'default_charset', 'utf-8', + """Charset to be used when in doubt.""") + + tab_width = IntOption('mimeviewer', 'tab_width', 8, diff --git a/doc/example.hg.diff b/doc/example.hg.diff new file mode 100644 index 0000000..46462b6 --- /dev/null +++ b/doc/example.hg.diff @@ -0,0 +1,22 @@ +diff -r b2d9961ff1f5 TODO +--- a/TODO Sat Dec 26 16:36:37 2009 +0200 ++++ b/TODO Sun Dec 27 22:28:17 2009 +0200 +@@ -7,3 +7,7 @@ + - remove files
+ - svn diff
+ - hg diff
++
++Source and target file conflicts:
++- two same source files in the same patch
++- one source and later one target file with the same name (that exists)
+diff -r b2d9961ff1f5 test commit/review system test +--- a/test commit/review system test Sat Dec 26 16:36:37 2009 +0200 ++++ b/test commit/review system test Sun Dec 27 22:28:17 2009 +0200 +@@ -1,4 +1,4 @@ + something to
+ change in
+-this file
+-for review +\ No newline at end of file ++this file <-- this should be removed!!! ARRGH! BASTARD, HOW DARE YOU TO MESS WITH PROJECT HISTORY!
++for review
diff --git a/doc/example.python25.diff b/doc/example.python25.diff new file mode 100644 index 0000000..dc4cc6d --- /dev/null +++ b/doc/example.python25.diff @@ -0,0 +1,22 @@ +--- diff.py Sun Dec 27 16:08:28 2009
++++ trunk/diff.py Sun Dec 27 15:46:58 2009
+@@ -7,7 +7,7 @@
+
+ """
+
+-import sys, os, datetime, difflib, optparse
++import sys, os, time, difflib, optparse
+
+ def main():
+
+@@ -29,8 +29,8 @@
+ n = options.lines
+ fromfile, tofile = args
+
+- fromdate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ")
+- todate = datetime.datetime.fromtimestamp( os.stat(fromfile).st_mtime ).strftime(" ")
++ fromdate = time.ctime(os.stat(fromfile).st_mtime)
++ todate = time.ctime(os.stat(tofile).st_mtime)
+ fromlines = open(fromfile, 'U').readlines()
+ tolines = open(tofile, 'U').readlines()
+
diff --git a/doc/example.svn.diff b/doc/example.svn.diff new file mode 100644 index 0000000..4f447e5 --- /dev/null +++ b/doc/example.svn.diff @@ -0,0 +1,86 @@ +Index: trac/versioncontrol/svn_fs.py +=================================================================== +--- trac/versioncontrol/svn_fs.py (revision 8986) ++++ trac/versioncontrol/svn_fs.py (working copy) +@@ -289,7 +289,7 @@ + repos = fs_repos + else: + repos = CachedRepository(self.env.get_db_cnx, fs_repos, None, +- self.log) ++ self.log, self.env) + repos.has_linear_changesets = True + if authname: + authz = SubversionAuthorizer(self.env, weakref.proxy(repos), +Index: trac/versioncontrol/cache.py +=================================================================== +--- trac/versioncontrol/cache.py (revision 8986) ++++ trac/versioncontrol/cache.py (working copy) +@@ -18,7 +18,7 @@ + import os + import posixpath + +-from trac.core import TracError ++from trac.core import * + from trac.util.datefmt import utc, to_timestamp + from trac.util.translation import _ + from trac.versioncontrol import Changeset, Node, Repository, Authorizer, \ +@@ -36,19 +36,42 @@ + CACHE_METADATA_KEYS = (CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV) + + ++class ICacheChangesetListener(Interface): ++ """Cached changeset operations""" ++ ++ def edit_changeset(cset): ++ """Called when changeset is about to be cached. ++ Returns altered data to cache or None if unchanged. cset usually ++ contains cset.date, cset.author, cset.message and cset.rev ++ """ ++ ++class CacheManager(Component): ++ """Provide interface to plug-in into cache operations""" ++ ++ observers = ExtensionPoint(ICacheChangesetListener) ++ ++ def check_changeset(self, cset): ++ for observer in self.observers: ++ res = observer.edit_changeset(cset) ++ if res != None: ++ cset = res ++ return cset ++ ++ + class CachedRepository(Repository): + + has_linear_changesets = False + + scope = property(lambda self: self.repos.scope) + +- def __init__(self, getdb, repos, authz, log): ++ def __init__(self, getdb, repos, authz, log, env): + Repository.__init__(self, repos.name, authz, log) + if callable(getdb): + self.getdb = getdb + else: + self.getdb = lambda: getdb + self.repos = repos ++ self.cache_mgr = CacheManager(env) + + def close(self): + self.repos.close() +@@ -77,6 +100,7 @@ + + def sync_changeset(self, rev): + cset = self.repos.get_changeset(rev) ++ cset = self.cache_mgr.check_changeset(cset) + db = self.getdb() + cursor = db.cursor() + cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " +@@ -182,6 +206,7 @@ + self.log.info("Trying to sync revision [%s]" % + next_youngest) + cset = self.repos.get_changeset(next_youngest) ++ cset = self.cache_mgr.check_changeset(cset) + try: + cursor.execute("INSERT INTO revision " + " (rev,time,author,message) " diff --git a/doc/unified_diff_format.png b/doc/unified_diff_format.png Binary files differnew file mode 100644 index 0000000..2f7dadf --- /dev/null +++ b/doc/unified_diff_format.png diff --git a/doc/unified_diff_format.svg b/doc/unified_diff_format.svg new file mode 100644 index 0000000..97bd819 --- /dev/null +++ b/doc/unified_diff_format.svg @@ -0,0 +1,1453 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + width="744.09448" + height="1052.3622" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="unified_diff_format.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <metadata + id="metadata321"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + inkscape:window-height="977" + inkscape:window-width="1280" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="1" + inkscape:cx="326.14062" + inkscape:cy="773.5073" + inkscape:window-x="-4" + inkscape:window-y="-4" + inkscape:current-layer="g3096" /> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective323" /> + </defs> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect3986" + y="119.08554" + x="29.101612" + height="24.043991" + width="542.27417" /> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4079" + y="643.3783" + x="29.101664" + height="21.006182" + width="542.31421" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.49999997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4077" + y="530.40411" + x="29.101612" + height="112.92788" + width="542.34729" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.49999997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4075" + y="313.9101" + x="29.101612" + height="217.22546" + width="542.17633" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4073" + y="162.67993" + x="29.101612" + height="154.54366" + width="542.27075" /> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4071" + y="141.73454" + x="29.101612" + height="22.392969" + width="542.25354" /> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4419" + y="683.94727" + x="29.101612" + height="299.80609" + width="542.34729" /> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4421" + y="663.87445" + x="29.101664" + height="21.006186" + width="542.31421" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4536" + d="M 29.242421,164.12501 L 29.242421,174.27697 L 157.24347,174.27697 L 157.24347,164.12501 L 29.242421,164.12501 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4559" + d="M 29.242421,317.36739 L 29.242421,327.51935 L 157.24347,327.51935 L 157.24347,317.36739 L 29.242421,317.36739 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4561" + d="M 29.242421,531.36739 L 29.242421,541.51935 L 157.24347,541.51935 L 157.24347,531.36739 L 29.242421,531.36739 z" /> + <path + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4563" + d="M 29.242421,684.86231 L 29.242421,695.01427 L 157.24347,695.01427 L 157.24347,684.86231 L 29.242421,684.86231 z" /> + <text + style="font-size:40.86742401px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + xml:space="preserve" + id="text3113" + y="55.509674" + x="25.596079" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan3117" + y="55.509674" + x="25.596079">Unified Diff/Patch Format</tspan></text> + <g + id="g4529" + transform="matrix(1.0472513,0,0,1.0032667,-0.2576625,-0.3375146)"> + <rect + style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3125" + y="75.54467" + x="28.284271" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" + xml:space="preserve" + id="text3897" + y="91.36142" + x="41.263729"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3899" + y="91.36142" + x="41.263729">comments are ignored</tspan></text> + </g> + <g + id="g4520" + transform="matrix(1.0472513,0,0,1.0032667,-0.3960063,-0.3375146)"> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3908" + y="75.54467" + x="160.10918" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3910" + y="87.320808" + x="220.56581"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3912" + y="87.320808" + x="220.56581">the first file that</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3914" + y="97.320808" + x="220.56581">exists is used</tspan></text> + </g> + <g + id="g4504" + transform="matrix(1.0472513,0,0,1.0032667,-0.5343536,-0.3375146)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3926" + y="75.54467" + x="291.93408" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3928" + y="87.320808" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3960" + y="87.320808" + x="352.39072">may contain several</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3964" + y="97.320808" + x="352.39072">hunks for each file</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3958" + y="107.32081" + x="352.39072" /></text> + </g> + <g + id="g4486" + transform="matrix(1.0472513,0,0,1.0032667,-0.2576418,-0.3375146)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3970" + y="75.54467" + x="423.36264" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3972" + y="87.320808" + x="483.81927"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3976" + y="87.320808" + x="483.81927">-line_from,total_before</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3978" + y="97.320808" + x="483.81927">+line_after,total_after</tspan></text> + </g> + <g + id="g4567" + transform="matrix(1.0472513,0,0,1.0032667,423.10537,67.948198)"> + <rect + style="fill:#c9ffd5;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4569" + y="75.54467" + x="160.10918" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4571" + y="87.320808" + x="220.56581"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4583" + y="87.320808" + x="220.56581">--- filename \t comment</tspan></text> + </g> + <g + id="g4587" + transform="matrix(1.0472513,0,0,1.0032667,283.99712,828.76607)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4589" + y="75.54467" + x="291.93408" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4591" + y="91.348259" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4597" + y="91.348259" + x="352.39072">line ends may differ</tspan></text> + </g> + <g + id="g3056" + transform="translate(6.1391031e-6,-2.2888184e-5)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:1" + id="rect4606" + y="180.776" + x="590.77991" + height="41.044823" + width="128.00391" /> + <text + transform="scale(1.0216856,0.9787747)" + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4608" + y="197.62608" + x="640.2099"><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4622" + y="197.62608" + x="640.2099">for the format like</tspan><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4626" + y="207.87631" + x="640.2099">-line_from +line_after</tspan><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4616" + y="218.12654" + x="640.2099">total_xxx = 1</tspan></text> + </g> + <g + id="g4640" + transform="matrix(1.0472513,0,0,1.0032667,284.28523,864.45712)"> + <g + id="g4670" + transform="translate(-0.2751091,3.9869783)"> + <rect + style="fill:#faffc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect4642" + y="73.76046" + x="291.93408" + height="40.911179" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text4644" + y="87.320808" + x="352.39072"><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan4668" + y="87.320808" + x="354.79697">"\ No newline at end of </tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5786" + y="97.320808" + x="352.39072">file" marker is used if</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5800" + y="107.32081" + x="352.39072">file ends without newline</tspan><tspan + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan5784" + y="117.32081" + x="352.39072" /></text> + </g> + </g> + <text + sodipodi:linespacing="125%" + style="font-size:8.1734848px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" + xml:space="preserve" + id="text5802" + y="1030.8047" + x="37.980774" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan5804" + y="1030.8047" + x="37.980774">http://en.wikipedia.org/wiki/Diff#Unified_format</tspan></text> + <text + sodipodi:linespacing="125%" + style="font-size:8.1734848px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" + xml:space="preserve" + id="text5806" + y="1041.0216" + x="37.980774" + transform="scale(1.0216856,0.9787747)"><tspan + id="tspan5808" + y="1041.0216" + x="37.980774">http://techtonik.rainforce.org</tspan></text> + <text + sodipodi:linespacing="120%" + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + xml:space="preserve" + id="text4629" + transform="scale(0.960765,1.0408372)" + y="126.03973" + x="39.688015"><tspan + id="tspan4631" + y="126.03973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4633" + y="126.03973" + x="39.688015">Index: src/plugins/contrib/devpak_plugin/updatedlg.cpp</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4635" + dx="0" + y="126.03973" + x="305.19858" /></tspan><tspan + id="tspan4637" + y="135.84792" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4639" + y="135.84792" + x="39.688015">===================================================================</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4641" + dx="0" + y="135.84792" + x="369.11774" /></tspan><tspan + id="tspan4643" + y="145.65608" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4645" + y="145.65608" + x="39.688015">--- src/plugins/contrib/devpak_plugin/updatedlg.cpp (revision 5106)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4647" + dx="0" + y="145.65608" + x="369.11774" /></tspan><tspan + id="tspan4649" + y="155.46426" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4651" + y="155.46426" + x="39.688015">+++ src/plugins/contrib/devpak_plugin/updatedlg.cpp (working copy)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4653" + dx="0" + y="155.46426" + x="364.20087" /></tspan><tspan + id="tspan4655" + y="165.27246" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4657" + y="165.27246" + x="39.688015">@@ -94,11 +94,13 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4659" + dx="0" + y="165.27246" + x="133.10837" /></tspan><tspan + id="tspan4661" + y="175.08063" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4663" + y="175.08063" + x="39.688015"> lst->InsertColumn(1, _("Version"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4665" + dx="0" + y="175.08063" + x="236.36249" /></tspan><tspan + id="tspan4667" + y="184.88881" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4669" + y="184.88881" + x="39.688015"> lst->InsertColumn(2, _("Installed"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4671" + dx="0" + y="184.88881" + x="246.19618" /></tspan><tspan + id="tspan4673" + y="194.69701" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4675" + y="194.69701" + x="39.688015"> lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4677" + dx="0" + y="194.69701" + x="324.866" /></tspan><tspan + id="tspan4679" + y="204.5052" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4681" + y="204.5052" + x="39.688015">+ lst->InsertColumn(4, _("Rev"));</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4683" + dx="0" + y="204.5052" + x="216.69504" /></tspan><tspan + id="tspan4685" + y="214.31337" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4687" + y="214.31337" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4689" + dx="0" + y="214.31337" + x="44.604874" /></tspan><tspan + id="tspan4691" + y="224.12157" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4693" + y="224.12157" + x="39.688015">- lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4695" + dx="0" + y="224.12157" + x="536.29102" /></tspan><tspan + id="tspan4697" + y="233.92973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4699" + y="233.92973" + x="39.688015">+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4701" + dx="0" + y="233.92973" + x="565.79218" /></tspan><tspan + id="tspan4703" + y="243.73793" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4705" + y="243.73793" + x="39.688015"> lst->SetColumnWidth(1, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4707" + dx="0" + y="243.73793" + x="197.02759" /></tspan><tspan + id="tspan4709" + y="253.54611" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4711" + y="253.54611" + x="39.688015"> lst->SetColumnWidth(2, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4713" + dx="0" + y="253.54611" + x="197.02759" /></tspan><tspan + id="tspan4715" + y="263.35431" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4717" + y="263.35431" + x="39.688015"> lst->SetColumnWidth(3, 64);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4719" + dx="0" + y="263.35431" + x="197.02759" /></tspan><tspan + id="tspan4721" + y="273.16251" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4723" + y="273.16251" + x="39.688015">+ lst->SetColumnWidth(4, 40);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4725" + dx="0" + y="273.16251" + x="197.02759" /></tspan><tspan + id="tspan4727" + y="282.97067" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4729" + y="282.97067" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4731" + dx="0" + y="282.97067" + x="49.521736" /></tspan><tspan + id="tspan4733" + y="292.77884" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4735" + y="292.77884" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4737" + dx="0" + y="292.77884" + x="44.604874" /></tspan><tspan + id="tspan4739" + y="302.58707" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4741" + y="302.58707" + x="39.688015"> void UpdateDlg::AddRecordToList(UpdateRec* rec)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4743" + dx="0" + y="302.58707" + x="275.69739" /></tspan><tspan + id="tspan4745" + y="312.3952" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4747" + y="312.3952" + x="39.688015">@@ -111,8 +113,20 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4749" + dx="0" + y="312.3952" + x="138.02525" /></tspan><tspan + id="tspan4751" + y="322.2034" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4753" + y="322.2034" + x="39.688015"> lst->SetItem(idx, 1, rec->version);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4755" + dx="0" + y="322.2034" + x="236.36249" /></tspan><tspan + id="tspan4757" + y="332.01157" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4759" + y="332.01157" + x="39.688015"> lst->SetItem(idx, 2, rec->installed_version);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4761" + dx="0" + y="332.01157" + x="285.5311" /></tspan><tspan + id="tspan4763" + y="341.81973" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4765" + y="341.81973" + x="39.688015"> lst->SetItem(idx, 3, rec->size);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4767" + dx="0" + y="341.81973" + x="221.61188" /></tspan><tspan + id="tspan4769" + y="351.6279" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4771" + y="351.6279" + x="39.688015">+ lst->SetItem(idx, 4, rec->revision);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4773" + dx="0" + y="351.6279" + x="241.27933" /></tspan><tspan + id="tspan4775" + y="361.4361" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4777" + y="361.4361" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4779" + dx="0" + y="361.4361" + x="49.521736" /></tspan><tspan + id="tspan4781" + y="371.24423" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4783" + y="371.24423" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4785" + dx="0" + y="371.24423" + x="44.604874" /></tspan><tspan + id="tspan4787" + y="381.05243" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4789" + y="381.05243" + x="39.688015">+wxString UpdateDlg::GetListColumnText(int idx, int col) {</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4791" + dx="0" + y="381.05243" + x="324.866" /></tspan><tspan + id="tspan4793" + y="390.8606" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4795" + y="390.8606" + x="39.688015">+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4797" + dx="0" + y="390.8606" + x="339.61658" /></tspan><tspan + id="tspan4799" + y="400.66876" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4801" + y="400.66876" + x="39.688015">+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4803" + dx="0" + y="400.66876" + x="511.70673" /></tspan><tspan + id="tspan4805" + y="410.47693" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4807" + y="410.47693" + x="39.688015">+ wxListItem info;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4809" + dx="0" + y="410.47693" + x="142.94209" /></tspan><tspan + id="tspan4811" + y="420.28513" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4813" + y="420.28513" + x="39.688015">+ info.SetId(index);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4815" + dx="0" + y="420.28513" + x="152.77583" /></tspan><tspan + id="tspan4817" + y="430.09326" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4819" + y="430.09326" + x="39.688015">+ info.SetColumn(col);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4821" + dx="0" + y="430.09326" + x="162.60954" /></tspan><tspan + id="tspan4823" + y="439.90146" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4825" + y="439.90146" + x="39.688015">+ info.SetMask(wxLIST_MASK_TEXT);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4827" + dx="0" + y="439.90146" + x="216.69504" /></tspan><tspan + id="tspan4829" + y="449.70963" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4831" + y="449.70963" + x="39.688015">+ lst->GetItem(info);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4833" + dx="0" + y="449.70963" + x="157.6927" /></tspan><tspan + id="tspan4835" + y="459.51776" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4837" + y="459.51776" + x="39.688015">+ return info.GetText();</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4839" + dx="0" + y="459.51776" + x="172.44328" /></tspan><tspan + id="tspan4841" + y="469.32596" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4843" + y="469.32596" + x="39.688015">+}</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4845" + dx="0" + y="469.32596" + x="49.521736" /></tspan><tspan + id="tspan4847" + y="479.13412" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4849" + y="479.13412" + x="39.688015">+</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4851" + dx="0" + y="479.13412" + x="44.604874" /></tspan><tspan + id="tspan4853" + y="488.94229" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4855" + y="488.94229" + x="39.688015"> void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4857" + dx="0" + y="488.94229" + x="403.53577" /></tspan><tspan + id="tspan4859" + y="498.75046" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4861" + y="498.75046" + x="39.688015"> {</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4863" + dx="0" + y="498.75046" + x="49.521736" /></tspan><tspan + id="tspan4865" + y="508.55865" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4867" + y="508.55865" + x="39.688015"> wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4869" + dx="0" + y="508.55865" + x="339.61658" /></tspan><tspan + id="tspan4871" + y="518.36682" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4873" + y="518.36682" + x="39.688015">@@ -393,7 +407,9 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4875" + dx="0" + y="518.36682" + x="133.10837" /></tspan><tspan + id="tspan4877" + y="528.17499" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4879" + y="528.17499" + x="39.688015"> if (index == -1)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4881" + dx="0" + y="528.17499" + x="142.94209" /></tspan><tspan + id="tspan4883" + y="537.98315" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4885" + y="537.98315" + x="39.688015"> return 0;</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4887" + dx="0" + y="537.98315" + x="128.19151" /></tspan><tspan + id="tspan4889" + y="547.79132" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4891" + y="547.79132" + x="39.688015"> wxString title = lst->GetItemText(index);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4893" + dx="0" + y="547.79132" + x="265.86365" /></tspan><tspan + id="tspan4895" + y="557.59955" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4897" + y="557.59955" + x="39.688015">- return FindRecByTitle(title, m_Recs, m_RecsCount);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4899" + dx="0" + y="557.59955" + x="310.11542" /></tspan><tspan + id="tspan4901" + y="567.40765" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4903" + y="567.40765" + x="39.688015">+ wxString version = GetListColumnText(index, 1);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4905" + dx="0" + y="567.40765" + x="295.36481" /></tspan><tspan + id="tspan4907" + y="577.21588" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4909" + y="577.21588" + x="39.688015">+ wxString revision = GetListColumnText(index, 4);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4911" + dx="0" + y="577.21588" + x="300.28168" /></tspan><tspan + id="tspan4913" + y="587.02405" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4915" + y="587.02405" + x="39.688015">+ return FindRec(title, version, revision, m_Recs, m_RecsCount);</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4917" + dx="0" + y="587.02405" + x="369.11774" /></tspan><tspan + id="tspan4919" + y="596.83228" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4921" + y="596.83228" + x="39.688015"> }</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4923" + dx="0" + y="596.83228" + x="49.521736" /></tspan><tspan + id="tspan4925" + y="606.6405" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4927" + y="606.6405" + x="39.688015"> </tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4929" + dx="0" + y="606.6405" + x="44.604874" /></tspan><tspan + id="tspan4931" + y="616.44867" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4933" + y="616.44867" + x="39.688015"> void UpdateDlg::DownloadFile(bool dontInstall)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4935" + dx="0" + y="616.44867" + x="270.78052" /></tspan><tspan + id="tspan4937" + y="626.25684" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4939" + y="626.25684" + x="39.688015">Index: src/plugins/contrib/devpak_plugin/manifest.xml</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4941" + dx="0" + y="626.25684" + x="300.28168" /></tspan><tspan + id="tspan4943" + y="636.06506" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4945" + y="636.06506" + x="39.688015">===================================================================</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4947" + dx="0" + y="636.06506" + x="369.11774" /></tspan><tspan + id="tspan4949" + y="645.87329" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4951" + y="645.87329" + x="39.688015">--- src/plugins/contrib/devpak_plugin/manifest.xml (revision 5106)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4953" + dx="0" + y="645.87329" + x="364.20087" /></tspan><tspan + id="tspan4955" + y="655.68146" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4957" + y="655.68146" + x="39.688015">+++ src/plugins/contrib/devpak_plugin/manifest.xml (working copy)</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4959" + dx="0" + y="655.68146" + x="359.28403" /></tspan><tspan + id="tspan4961" + y="665.48969" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4963" + y="665.48969" + x="39.688015">@@ -2,18 +2,19 @@</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4965" + dx="0" + y="665.48969" + x="123.27467" /></tspan><tspan + id="tspan4967" + y="675.29791" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4969" + y="675.29791" + x="39.688015"> <CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4971" + dx="0" + y="675.29791" + x="206.86131" /></tspan><tspan + id="tspan4973" + y="685.10602" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4975" + y="685.10602" + x="39.688015"> <SdkVersion major="1" minor="10" release="0" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4977" + dx="0" + y="685.10602" + x="295.36481" /></tspan><tspan + id="tspan4979" + y="694.91425" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4981" + y="694.91425" + x="39.688015"> <Plugin name="DevPakUpdater"></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4983" + dx="0" + y="694.91425" + x="206.86131" /></tspan><tspan + id="tspan4985" + y="704.72247" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4987" + y="704.72247" + x="39.688015">- <Value title="Dev-C++ DevPak updater/installer" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4989" + dx="0" + y="704.72247" + x="329.78284" /></tspan><tspan + id="tspan4991" + y="714.5307" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4993" + y="714.5307" + x="39.688015">- <Value version="0.1" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4995" + dx="0" + y="714.5307" + x="197.02759" /></tspan><tspan + id="tspan4997" + y="724.33887" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan4999" + y="724.33887" + x="39.688015">+ <Value title="DevPak updater/installer" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5001" + dx="0" + y="724.33887" + x="290.44797" /></tspan><tspan + id="tspan5003" + y="734.14709" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5005" + y="734.14709" + x="39.688015">+ <Value version="0.2" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5007" + dx="0" + y="734.14709" + x="197.02759" /></tspan><tspan + id="tspan5009" + y="743.95526" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5011" + y="743.95526" + x="39.688015"> <Value description="Installs selected DevPaks from the Internet" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5013" + dx="0" + y="743.95526" + x="413.36951" /></tspan><tspan + id="tspan5015" + y="753.76343" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5017" + y="753.76343" + x="39.688015"> <Value author="Yiannis Mandravellos" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5019" + dx="0" + y="753.76343" + x="275.69739" /></tspan><tspan + id="tspan5021" + y="763.57166" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5023" + y="763.57166" + x="39.688015"> <Value authorEmail="info@codeblocks.org" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5025" + dx="0" + y="763.57166" + x="295.36481" /></tspan><tspan + id="tspan5027" + y="773.37988" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5029" + y="773.37988" + x="39.688015"> <Value authorWebsite="http://www.codeblocks.org/" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5031" + dx="0" + y="773.37988" + x="339.61658" /></tspan><tspan + id="tspan5033" + y="783.18805" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5035" + y="783.18805" + x="39.688015"> <Value thanksTo="Dev-C++ community.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5037" + dx="0" + y="783.18805" + x="256.02994" /></tspan><tspan + id="tspan5039" + y="792.99628" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5041" + y="792.99628" + x="39.688015">- Julian R Seward for libbzip2.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5043" + dx="0" + y="792.99628" + x="310.11542" /></tspan><tspan + id="tspan5045" + y="802.8045" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5047" + y="802.8045" + x="39.688015">- libbzip2 copyright notice:</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5049" + dx="0" + y="802.8045" + x="295.36481" /></tspan><tspan + id="tspan5051" + y="812.61267" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5053" + y="812.61267" + x="39.688015">- bzip2 and associated library libbzip2, are</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5055" + dx="0" + y="812.61267" + x="374.03461" /></tspan><tspan + id="tspan5057" + y="822.42084" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5059" + y="822.42084" + x="39.688015">- copyright (C) 1996-2000 Julian R Seward.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5061" + dx="0" + y="822.42084" + x="364.20087" /></tspan><tspan + id="tspan5063" + y="832.22906" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5065" + y="832.22906" + x="39.688015">- All rights reserved." /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5067" + dx="0" + y="832.22906" + x="285.5311" /></tspan><tspan + id="tspan5069" + y="842.03729" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5071" + y="842.03729" + x="39.688015">+ Julian R Seward for libbzip2.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5073" + dx="0" + y="842.03729" + x="226.52878" /></tspan><tspan + id="tspan5075" + y="851.84546" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5077" + y="851.84546" + x="39.688015">+</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5079" + dx="0" + y="851.84546" + x="44.604874" /></tspan><tspan + id="tspan5081" + y="861.65369" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5083" + y="861.65369" + x="39.688015">+ libbzip2 copyright notice:</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5085" + dx="0" + y="861.65369" + x="211.77817" /></tspan><tspan + id="tspan5087" + y="871.46185" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5089" + y="871.46185" + x="39.688015">+ bzip2 and associated library libbzip2, are</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5091" + dx="0" + y="871.46185" + x="290.44797" /></tspan><tspan + id="tspan5093" + y="881.27008" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5095" + y="881.27008" + x="39.688015">+ copyright (C) 1996-2000 Julian R Seward.</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5097" + dx="0" + y="881.27008" + x="280.61423" /></tspan><tspan + id="tspan5099" + y="891.07825" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5101" + y="891.07825" + x="39.688015">+ All rights reserved." /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5103" + dx="0" + y="891.07825" + x="201.94443" /></tspan><tspan + id="tspan5105" + y="900.88647" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5107" + y="900.88647" + x="39.688015"> <Value license="GPL" /></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5109" + dx="0" + y="900.88647" + x="197.02759" /></tspan><tspan + id="tspan5111" + y="910.6947" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5113" + y="910.6947" + x="39.688015"> </Plugin></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5115" + dx="0" + y="910.6947" + x="108.52407" /></tspan><tspan + id="tspan5117" + y="920.50287" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5119" + y="920.50287" + x="39.688015">-</CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5121" + dx="0" + y="920.50287" + x="211.77817" /></tspan><tspan + id="tspan5123" + y="930.31104" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5125" + y="930.31104" + x="39.688015">+</CodeBlocks_plugin_manifest_file></tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5127" + dx="0" + y="930.31104" + x="211.77817" /></tspan><tspan + id="tspan5129" + y="940.11926" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5131" + y="940.11926" + x="39.688015">\ No newline at end of file</tspan><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5133" + dx="0" + y="940.11926" + x="172.44328" /></tspan><tspan + id="tspan5135" + y="940.11926" + x="39.688015"><tspan + style="font-size:8.17348385px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:120.00000477%;writing-mode:lr-tb;text-anchor:start;font-family:Lucida Console;-inkscape-font-specification:Lucida Console" + id="tspan5137" + y="949.92749" + x="39.688015" /></tspan></text> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3015" + y="250.75372" + x="640.2099" + transform="scale(1.0216856,0.9787747)" /> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + y="250.75372" + x="640.2099" + transform="scale(1.0216856,0.9787747)" + id="text3035" /> + <text + sodipodi:linespacing="125%" + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3067" + y="248.54395" + x="640.2099" + transform="scale(1.0216856,0.9787747)"><tspan + style="font-size:8.20018482px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + id="tspan3069" + y="248.54395" + x="640.2099" /></text> + <g + transform="matrix(1.0472513,0,0,1.0032667,147.41284,155.66249)" + id="g3096"> + <g + id="g3106" + transform="translate(0,5.9804637)"> + <rect + style="fill:#ffcfc9;fill-opacity:1;stroke:#000000;stroke-width:0.97558779;stroke-opacity:1" + id="rect3098" + y="70.67984" + x="423.36264" + height="27.274118" + width="122.22845" /> + <text + sodipodi:linespacing="125%" + style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono" + xml:space="preserve" + id="text3100" + y="87.320808" + x="483.81927">line numbers start from 1</text> + </g> + </g> +</svg> diff --git a/patch.py b/patch.py new file mode 100644 index 0000000..195eec6 --- /dev/null +++ b/patch.py @@ -0,0 +1,658 @@ +""" Patch utility to apply unified diffs + + Brute-force line-by-line non-recursive parsing + + Copyright (c) 2008-2010 anatoly techtonik + Available under the terms of MIT license + + Project home: http://code.google.com/p/python-patch/ + + + $Id: patch.py 101 2010-08-29 08:57:30Z techtonik $ + $HeadURL: http://python-patch.googlecode.com/svn/trunk/patch.py $ +""" + +__author__ = "techtonik.rainforce.org" +__version__ = "10.08" + +import copy +import logging +import re +# cStringIO doesn't support unicode in 2.5 +from StringIO import StringIO + +from os.path import exists, isfile, abspath +from os import unlink + + +#------------------------------------------------ +# Logging is controlled by "python_patch" logger + +debugmode = False + +logger = logging.getLogger("python_patch") +loghandler = logging.StreamHandler() +logger.addHandler(loghandler) + +debug = logger.debug +info = logger.info +warning = logger.warning + +#: disable library logging by default +logger.setLevel(logging.CRITICAL) + +#------------------------------------------------ + +# constants for patch types + +DIFF = PLAIN = "plain" +HG = MERCURIAL = "mercurial" +SVN = SUBVERSION = "svn" + + +def fromfile(filename): + """ Parse patch file and return Patch() object + """ + debug("reading %s" % filename) + fp = open(filename, "rb") + patch = Patch(fp) + fp.close() + return patch + + +def fromstring(s): + """ Parse text string and return Patch() object + """ + return Patch( StringIO(s) ) + + + +class HunkInfo(object): + """ Parsed hunk data container (hunk starts with @@ -R +R @@) """ + + def __init__(self): + self.startsrc=None #: line count starts with 1 + self.linessrc=None + self.starttgt=None + self.linestgt=None + self.invalid=False + self.text=[] + + def copy(self): + return copy.copy(self) + +# def apply(self, estream): +# """ write hunk data into enumerable stream +# return strings one by one until hunk is +# over +# +# enumerable stream are tuples (lineno, line) +# where lineno starts with 0 +# """ +# pass + + + +class Patch(object): + + def __init__(self, stream=None): + + # define Patch data members + # table with a row for every source file + + #: list of source filenames + self.source=None + self.target=None + #: list of lists of hunks + self.hunks=None + #: file endings statistics for every hunk + self.hunkends=None + #: headers for each file + self.header=None + + #: patch type - one of constants + self.type = None + + if stream: + self.parse(stream) + + def copy(self): + return copy.copy(self) + + def parse(self, stream): + """ parse unified diff """ + self.header = [] + + self.source = [] + self.target = [] + self.hunks = [] + self.hunkends = [] + + # define states (possible file regions) that will direct the parser flow + headscan = False # scanning header before the patch body + filenames = False # lines starting with --- and +++ + + hunkhead = False # @@ -R +R @@ sequence + hunkbody = False # + hunkskip = False # skipping invalid hunk mode + + headscan = True + lineends = dict(lf=0, crlf=0, cr=0) + nextfileno = 0 + nexthunkno = 0 #: even if index starts with 0 user messages number hunks from 1 + + # hunkinfo holds parsed values, hunkactual - calculated + hunkinfo = HunkInfo() + hunkactual = dict(linessrc=None, linestgt=None) + + + class wrapumerate(enumerate): + """Enumerate wrapper that uses boolean end of stream status instead of + StopIteration exception, and properties to access line information. + """ + + def __init__(self, *args, **kwargs): + # we don't call parent, it is magically created by __new__ method + + self._exhausted = False + self._lineno = False # after end of stream equal to the num of lines + self._line = False # will be reset to False after end of stream + + def next(self): + """Try to read the next line and return True if it is available, + False if end of stream is reached.""" + if self._exhausted: + return False + + try: + self._lineno, self._line = super(wrapumerate, self).next() + except StopIteration: + self._exhausted = True + self._line = False + return False + return True + + @property + def is_empty(self): + return self._exhausted + + @property + def line(self): + return self._line + + @property + def lineno(self): + return self._lineno + + + # start of main cycle + # each parsing block already has line available in fe.line + fe = wrapumerate(stream) + while fe.next(): + + # read out header + if headscan: + header = '' + while not fe.is_empty and not fe.line.startswith("--- "): + header += fe.line + fe.next() + if fe.is_empty: + # this is actually a loop exit + warning("stream ended while scanning patch header at line %d" % fe.lineno) + continue + self.header.append(header) + + headscan = False + # switch to filenames state + filenames = True + + line = fe.line + lineno = fe.lineno + + + # hunkskip and hunkbody code skipped until definition of hunkhead is parsed + if hunkbody: + # process line first + if re.match(r"^[- \+\\]", line): + # gather stats about line endings + if line.endswith("\r\n"): + self.hunkends[nextfileno-1]["crlf"] += 1 + elif line.endswith("\n"): + self.hunkends[nextfileno-1]["lf"] += 1 + elif line.endswith("\r"): + self.hunkends[nextfileno-1]["cr"] += 1 + + if line.startswith("-"): + hunkactual["linessrc"] += 1 + elif line.startswith("+"): + hunkactual["linestgt"] += 1 + elif not line.startswith("\\"): + hunkactual["linessrc"] += 1 + hunkactual["linestgt"] += 1 + hunkinfo.text.append(line) + # todo: handle \ No newline cases + else: + warning("invalid hunk no.%d at %d for target file %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # check exit conditions + if hunkactual["linessrc"] > hunkinfo.linessrc or hunkactual["linestgt"] > hunkinfo.linestgt: + warning("extra hunk no.%d lines at %d for target %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + elif hunkinfo.linessrc == hunkactual["linessrc"] and hunkinfo.linestgt == hunkactual["linestgt"]: + self.hunks[nextfileno-1].append(hunkinfo.copy()) + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # detect mixed window/unix line ends + ends = self.hunkends[nextfileno-1] + if ((ends["cr"]!=0) + (ends["crlf"]!=0) + (ends["lf"]!=0)) > 1: + warning("inconsistent line ends in patch hunks for %s" % self.source[nextfileno-1]) + if debugmode: + debuglines = dict(ends) + debuglines.update(file=self.target[nextfileno-1], hunk=nexthunkno) + debug("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t - file: %(file)s hunk: %(hunk)d" % debuglines) + + if hunkskip: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if match: + # switch to hunkhead state + hunkskip = False + hunkhead = True + elif line.startswith("--- "): + # switch to filenames state + hunkskip = False + filenames = True + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + if filenames: + if line.startswith("--- "): + if nextfileno in self.source: + warning("skipping invalid patch for %s" % self.source[nextfileno]) + del self.source[nextfileno] + # double source filename line is encountered + # attempt to restart from this second line + re_filename = "^--- ([^\t]+)" + match = re.match(re_filename, line) + # todo: support spaces in filenames + if match: + self.source.append(match.group(1).strip()) + else: + warning("skipping invalid filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + elif not line.startswith("+++ "): + if nextfileno in self.source: + warning("skipping invalid patch with no target for %s" % self.source[nextfileno]) + del self.source[nextfileno] + else: + # this should be unreachable + warning("skipping invalid target patch") + filenames = False + headscan = True + else: + if nextfileno in self.target: + warning("skipping invalid patch - double target at line %d" % lineno) + del self.source[nextfileno] + del self.target[nextfileno] + nextfileno -= 1 + # double target filename line is encountered + # switch back to headscan state + filenames = False + headscan = True + else: + re_filename = "^\+\+\+ ([^\t]+)" + match = re.match(re_filename, line) + if not match: + warning("skipping invalid patch - no target filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + else: + self.target.append(match.group(1).strip()) + nextfileno += 1 + # switch to hunkhead state + filenames = False + hunkhead = True + nexthunkno = 0 + self.hunks.append([]) + self.hunkends.append(lineends.copy()) + continue + + if hunkhead: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if not match: + if nextfileno-1 not in self.hunks: + warning("skipping invalid patch with no hunks for file %s" % self.target[nextfileno-1]) + # switch to headscan state + hunkhead = False + headscan = True + continue + else: + # switch to headscan state + hunkhead = False + headscan = True + else: + hunkinfo.startsrc = int(match.group(1)) + hunkinfo.linessrc = 1 + if match.group(3): hunkinfo.linessrc = int(match.group(3)) + hunkinfo.starttgt = int(match.group(4)) + hunkinfo.linestgt = 1 + if match.group(6): hunkinfo.linestgt = int(match.group(6)) + hunkinfo.invalid = False + hunkinfo.text = [] + + hunkactual["linessrc"] = hunkactual["linestgt"] = 0 + + # switch to hunkbody state + hunkhead = False + hunkbody = True + nexthunkno += 1 + continue + + if not hunkskip: + warning("patch stream incomplete") + # sys.exit(?) + else: + # duplicated message when an eof is reached + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + debug("total files: %d total hunks: %d" % (len(self.source), sum(len(hset) for hset in self.hunks))) + + + def apply(self): + """ apply parsed patch """ + + total = len(self.source) + for fileno, filename in enumerate(self.source): + + f2patch = filename + if not exists(f2patch): + f2patch = self.target[fileno] + if not exists(f2patch): + warning("source/target file does not exist\n--- %s\n+++ %s" % (filename, f2patch)) + continue + if not isfile(f2patch): + warning("not a file - %s" % f2patch) + continue + filename = f2patch + + debug("processing %d/%d:\t %s" % (fileno+1, total, filename)) + + # validate before patching + f2fp = open(filename) + hunkno = 0 + hunk = self.hunks[fileno][hunkno] + hunkfind = [] + hunkreplace = [] + validhunks = 0 + canpatch = False + for lineno, line in enumerate(f2fp): + if lineno+1 < hunk.startsrc: + continue + elif lineno+1 == hunk.startsrc: + hunkfind = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " -"] + hunkreplace = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " +"] + #pprint(hunkreplace) + hunklineno = 0 + + # todo \ No newline at end of file + + # check hunks in source file + if lineno+1 < hunk.startsrc+len(hunkfind)-1: + if line.rstrip("\r\n") == hunkfind[hunklineno]: + hunklineno+=1 + else: + debug("hunk no.%d doesn't match source file %s" % (hunkno+1, filename)) + # file may be already patched, but we will check other hunks anyway + hunkno += 1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + continue + else: + break + + # check if processed line is the last line + if lineno+1 == hunk.startsrc+len(hunkfind)-1: + debug("file %s hunk no.%d -- is ready to be patched" % (filename, hunkno+1)) + hunkno+=1 + validhunks+=1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + else: + if validhunks == len(self.hunks[fileno]): + # patch file + canpatch = True + break + else: + if hunkno < len(self.hunks[fileno]): + warning("premature end of source file %s at hunk %d" % (filename, hunkno+1)) + + f2fp.close() + + if validhunks < len(self.hunks[fileno]): + if self._match_file_hunks(filename, self.hunks[fileno]): + warning("already patched %s" % filename) + else: + warning("source file is different - %s" % filename) + if canpatch: + backupname = filename+".orig" + if exists(backupname): + warning("can't backup original file to %s - aborting" % backupname) + else: + import shutil + shutil.move(filename, backupname) + if self.write_hunks(backupname, filename, self.hunks[fileno]): + info("successfully patched %d/%d:\t %s" % (fileno+1, total, filename)) + unlink(backupname) + else: + warning("error patching file %s" % filename) + shutil.copy(filename, filename+".invalid") + warning("invalid version is saved to %s" % filename+".invalid") + # todo: proper rejects + shutil.move(backupname, filename) + + # todo: check for premature eof + + + def can_patch(self, filename): + """ Check if specified filename can be patched. Returns None if file can + not be found among source filenames. False if patch can not be applied + clearly. True otherwise. + + :returns: True, False or None + """ + idx = self._get_file_idx(filename, source=True) + if idx == None: + return None + return self._match_file_hunks(filename, self.hunks[idx]) + + + def _match_file_hunks(self, filepath, hunks): + matched = True + fp = open(abspath(filepath)) + + class NoMatch(Exception): + pass + + lineno = 1 + line = fp.readline() + hno = None + try: + for hno, h in enumerate(hunks): + # skip to first line of the hunk + while lineno < h.starttgt: + if not len(line): # eof + debug("check failed - premature eof before hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + for hline in h.text: + if hline.startswith("-"): + continue + if not len(line): + debug("check failed - premature eof on hunk: %d" % (hno+1)) + # todo: \ No newline at the end of file + raise NoMatch + if line.rstrip("\r\n") != hline[1:].rstrip("\r\n"): + debug("file is not patched - failed hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + + except NoMatch: + matched = False + # todo: display failed hunk, i.e. expected/found + + fp.close() + return matched + + + def patch_stream(self, instream, hunks): + """ Generator that yields stream patched with hunks iterable + + Converts lineends in hunk lines to the best suitable format + autodetected from input + """ + + # todo: At the moment substituted lineends may not be the same + # at the start and at the end of patching. Also issue a + # warning/throw about mixed lineends (is it really needed?) + + hunks = iter(hunks) + + srclineno = 1 + + lineends = {'\n':0, '\r\n':0, '\r':0} + def get_line(): + """ + local utility function - return line from source stream + collecting line end statistics on the way + """ + line = instream.readline() + # 'U' mode works only with text files + if line.endswith("\r\n"): + lineends["\r\n"] += 1 + elif line.endswith("\n"): + lineends["\n"] += 1 + elif line.endswith("\r"): + lineends["\r"] += 1 + return line + + for hno, h in enumerate(hunks): + debug("hunk %d" % (hno+1)) + # skip to line just before hunk starts + while srclineno < h.startsrc: + yield get_line() + srclineno += 1 + + for hline in h.text: + # todo: check \ No newline at the end of file + if hline.startswith("-") or hline.startswith("\\"): + get_line() + srclineno += 1 + continue + else: + if not hline.startswith("+"): + get_line() + srclineno += 1 + line2write = hline[1:] + # detect if line ends are consistent in source file + if sum([bool(lineends[x]) for x in lineends]) == 1: + newline = [x for x in lineends if lineends[x] != 0][0] + yield line2write.rstrip("\r\n")+newline + else: # newlines are mixed + yield line2write + + for line in instream: + yield line + + + def write_hunks(self, srcname, tgtname, hunks): + src = open(srcname, "rb") + tgt = open(tgtname, "wb") + + debug("processing target file %s" % tgtname) + + tgt.writelines(self.patch_stream(src, hunks)) + + tgt.close() + src.close() + return True + + + def _get_file_idx(self, filename, source=None): + """ Detect index of given filename within patch. + + :param filename: + :param source: search filename among sources (True), + targets (False), or both (None) + :returns: int or None + """ + filename = abspath(filename) + if source == True or source == None: + for i,fnm in enumerate(self.source): + if filename == abspath(fnm): + return i + if source == False or source == None: + for i,fnm in enumerate(self.target): + if filename == abspath(fnm): + return i + + + + +if __name__ == "__main__": + from optparse import OptionParser + from os.path import exists + import sys + + opt = OptionParser(usage="%prog [options] unipatch-file", version="python-patch %s" % __version__) + opt.add_option("-q", "--quiet", action="store_const", dest="verbosity", + const=0, help="print only warnings and errors", default=1) + opt.add_option("-v", "--verbose", action="store_const", dest="verbosity", + const=2, help="be verbose") + opt.add_option("--debug", action="store_true", dest="debugmode", help="debug mode") + (options, args) = opt.parse_args() + + if not args: + opt.print_version() + opt.print_help() + sys.exit() + debugmode = options.debugmode + patchfile = args[0] + if not exists(patchfile) or not isfile(patchfile): + sys.exit("patch file does not exist - %s" % patchfile) + + + verbosity_levels = {0:logging.WARNING, 1:logging.INFO, 2:logging.DEBUG} + loglevel = verbosity_levels[options.verbosity] + logformat = "%(message)s" + if debugmode: + loglevel = logging.DEBUG + logformat = "%(levelname)8s %(message)s" + logger.setLevel(loglevel) + loghandler.setFormatter(logging.Formatter(logformat)) + + + + patch = fromfile(patchfile) + #pprint(patch) + patch.apply() + + # todo: document and test line ends handling logic - patch.py detects proper line-endings + # for inserted hunks and issues a warning if patched file has incosistent line ends diff --git a/python-patch-read-only/.svn/all-wcprops b/python-patch-read-only/.svn/all-wcprops new file mode 100644 index 0000000..39bee02 --- /dev/null +++ b/python-patch-read-only/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 23 +/svn/!svn/ver/101/trunk +END +patch.py +K 25 +svn:wc:ra_dav:version-url +V 32 +/svn/!svn/ver/101/trunk/patch.py +END diff --git a/python-patch-read-only/.svn/dir-prop-base b/python-patch-read-only/.svn/dir-prop-base new file mode 100644 index 0000000..8a02e8b --- /dev/null +++ b/python-patch-read-only/.svn/dir-prop-base @@ -0,0 +1,6 @@ +K 13 +svn:externals +V 57 +wiki https://python-patch.googlecode.com/svn/wiki + +END diff --git a/python-patch-read-only/.svn/entries b/python-patch-read-only/.svn/entries new file mode 100644 index 0000000..ef44c79 --- /dev/null +++ b/python-patch-read-only/.svn/entries @@ -0,0 +1,68 @@ +10 + +dir +101 +http://python-patch.googlecode.com/svn/trunk +http://python-patch.googlecode.com/svn + + + +2010-08-29T08:57:30.359404Z +101 +techtonik +has-props + + +svn:externals + + + + + + + + + + +7155d8b8-9951-0410-8dfa-c5fb0ae76a41 + +tests +dir + +doc +dir + +patch.py +file + + + + +2010-10-21T22:54:46.025691Z +765f79bfd9d298ce699c136d518f7565 +2010-08-29T08:57:30.359404Z +101 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +20874 + diff --git a/python-patch-read-only/.svn/prop-base/patch.py.svn-base b/python-patch-read-only/.svn/prop-base/patch.py.svn-base new file mode 100644 index 0000000..6a29c82 --- /dev/null +++ b/python-patch-read-only/.svn/prop-base/patch.py.svn-base @@ -0,0 +1,9 @@ +K 12 +svn:keywords +V 22 +Id Date HeadURL Author +K 13 +svn:mergeinfo +V 24 +/tags/8.06-2/patch.py:41 +END diff --git a/python-patch-read-only/.svn/text-base/patch.py.svn-base b/python-patch-read-only/.svn/text-base/patch.py.svn-base new file mode 100644 index 0000000..0f1cafd --- /dev/null +++ b/python-patch-read-only/.svn/text-base/patch.py.svn-base @@ -0,0 +1,658 @@ +""" Patch utility to apply unified diffs + + Brute-force line-by-line non-recursive parsing + + Copyright (c) 2008-2010 anatoly techtonik + Available under the terms of MIT license + + Project home: http://code.google.com/p/python-patch/ + + + $Id$ + $HeadURL$ +""" + +__author__ = "techtonik.rainforce.org" +__version__ = "10.08" + +import copy +import logging +import re +# cStringIO doesn't support unicode in 2.5 +from StringIO import StringIO + +from os.path import exists, isfile, abspath +from os import unlink + + +#------------------------------------------------ +# Logging is controlled by "python_patch" logger + +debugmode = False + +logger = logging.getLogger("python_patch") +loghandler = logging.StreamHandler() +logger.addHandler(loghandler) + +debug = logger.debug +info = logger.info +warning = logger.warning + +#: disable library logging by default +logger.setLevel(logging.CRITICAL) + +#------------------------------------------------ + +# constants for patch types + +DIFF = PLAIN = "plain" +HG = MERCURIAL = "mercurial" +SVN = SUBVERSION = "svn" + + +def fromfile(filename): + """ Parse patch file and return Patch() object + """ + debug("reading %s" % filename) + fp = open(filename, "rb") + patch = Patch(fp) + fp.close() + return patch + + +def fromstring(s): + """ Parse text string and return Patch() object + """ + return Patch( StringIO(s) ) + + + +class HunkInfo(object): + """ Parsed hunk data container (hunk starts with @@ -R +R @@) """ + + def __init__(self): + self.startsrc=None #: line count starts with 1 + self.linessrc=None + self.starttgt=None + self.linestgt=None + self.invalid=False + self.text=[] + + def copy(self): + return copy.copy(self) + +# def apply(self, estream): +# """ write hunk data into enumerable stream +# return strings one by one until hunk is +# over +# +# enumerable stream are tuples (lineno, line) +# where lineno starts with 0 +# """ +# pass + + + +class Patch(object): + + def __init__(self, stream=None): + + # define Patch data members + # table with a row for every source file + + #: list of source filenames + self.source=None + self.target=None + #: list of lists of hunks + self.hunks=None + #: file endings statistics for every hunk + self.hunkends=None + #: headers for each file + self.header=None + + #: patch type - one of constants + self.type = None + + if stream: + self.parse(stream) + + def copy(self): + return copy.copy(self) + + def parse(self, stream): + """ parse unified diff """ + self.header = [] + + self.source = [] + self.target = [] + self.hunks = [] + self.hunkends = [] + + # define states (possible file regions) that will direct the parser flow + headscan = False # scanning header before the patch body + filenames = False # lines starting with --- and +++ + + hunkhead = False # @@ -R +R @@ sequence + hunkbody = False # + hunkskip = False # skipping invalid hunk mode + + headscan = True + lineends = dict(lf=0, crlf=0, cr=0) + nextfileno = 0 + nexthunkno = 0 #: even if index starts with 0 user messages number hunks from 1 + + # hunkinfo holds parsed values, hunkactual - calculated + hunkinfo = HunkInfo() + hunkactual = dict(linessrc=None, linestgt=None) + + + class wrapumerate(enumerate): + """Enumerate wrapper that uses boolean end of stream status instead of + StopIteration exception, and properties to access line information. + """ + + def __init__(self, *args, **kwargs): + # we don't call parent, it is magically created by __new__ method + + self._exhausted = False + self._lineno = False # after end of stream equal to the num of lines + self._line = False # will be reset to False after end of stream + + def next(self): + """Try to read the next line and return True if it is available, + False if end of stream is reached.""" + if self._exhausted: + return False + + try: + self._lineno, self._line = super(wrapumerate, self).next() + except StopIteration: + self._exhausted = True + self._line = False + return False + return True + + @property + def is_empty(self): + return self._exhausted + + @property + def line(self): + return self._line + + @property + def lineno(self): + return self._lineno + + + # start of main cycle + # each parsing block already has line available in fe.line + fe = wrapumerate(stream) + while fe.next(): + + # read out header + if headscan: + header = '' + while not fe.is_empty and not fe.line.startswith("--- "): + header += fe.line + fe.next() + if fe.is_empty: + # this is actually a loop exit + warning("stream ended while scanning patch header at line %d" % fe.lineno) + continue + self.header.append(header) + + headscan = False + # switch to filenames state + filenames = True + + line = fe.line + lineno = fe.lineno + + + # hunkskip and hunkbody code skipped until definition of hunkhead is parsed + if hunkbody: + # process line first + if re.match(r"^[- \+\\]", line): + # gather stats about line endings + if line.endswith("\r\n"): + self.hunkends[nextfileno-1]["crlf"] += 1 + elif line.endswith("\n"): + self.hunkends[nextfileno-1]["lf"] += 1 + elif line.endswith("\r"): + self.hunkends[nextfileno-1]["cr"] += 1 + + if line.startswith("-"): + hunkactual["linessrc"] += 1 + elif line.startswith("+"): + hunkactual["linestgt"] += 1 + elif not line.startswith("\\"): + hunkactual["linessrc"] += 1 + hunkactual["linestgt"] += 1 + hunkinfo.text.append(line) + # todo: handle \ No newline cases + else: + warning("invalid hunk no.%d at %d for target file %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # check exit conditions + if hunkactual["linessrc"] > hunkinfo.linessrc or hunkactual["linestgt"] > hunkinfo.linestgt: + warning("extra hunk no.%d lines at %d for target %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + elif hunkinfo.linessrc == hunkactual["linessrc"] and hunkinfo.linestgt == hunkactual["linestgt"]: + self.hunks[nextfileno-1].append(hunkinfo.copy()) + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # detect mixed window/unix line ends + ends = self.hunkends[nextfileno-1] + if ((ends["cr"]!=0) + (ends["crlf"]!=0) + (ends["lf"]!=0)) > 1: + warning("inconsistent line ends in patch hunks for %s" % self.source[nextfileno-1]) + if debugmode: + debuglines = dict(ends) + debuglines.update(file=self.target[nextfileno-1], hunk=nexthunkno) + debug("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t - file: %(file)s hunk: %(hunk)d" % debuglines) + + if hunkskip: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if match: + # switch to hunkhead state + hunkskip = False + hunkhead = True + elif line.startswith("--- "): + # switch to filenames state + hunkskip = False + filenames = True + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + if filenames: + if line.startswith("--- "): + if nextfileno in self.source: + warning("skipping invalid patch for %s" % self.source[nextfileno]) + del self.source[nextfileno] + # double source filename line is encountered + # attempt to restart from this second line + re_filename = "^--- ([^\t]+)" + match = re.match(re_filename, line) + # todo: support spaces in filenames + if match: + self.source.append(match.group(1).strip()) + else: + warning("skipping invalid filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + elif not line.startswith("+++ "): + if nextfileno in self.source: + warning("skipping invalid patch with no target for %s" % self.source[nextfileno]) + del self.source[nextfileno] + else: + # this should be unreachable + warning("skipping invalid target patch") + filenames = False + headscan = True + else: + if nextfileno in self.target: + warning("skipping invalid patch - double target at line %d" % lineno) + del self.source[nextfileno] + del self.target[nextfileno] + nextfileno -= 1 + # double target filename line is encountered + # switch back to headscan state + filenames = False + headscan = True + else: + re_filename = "^\+\+\+ ([^\t]+)" + match = re.match(re_filename, line) + if not match: + warning("skipping invalid patch - no target filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + else: + self.target.append(match.group(1).strip()) + nextfileno += 1 + # switch to hunkhead state + filenames = False + hunkhead = True + nexthunkno = 0 + self.hunks.append([]) + self.hunkends.append(lineends.copy()) + continue + + if hunkhead: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if not match: + if nextfileno-1 not in self.hunks: + warning("skipping invalid patch with no hunks for file %s" % self.target[nextfileno-1]) + # switch to headscan state + hunkhead = False + headscan = True + continue + else: + # switch to headscan state + hunkhead = False + headscan = True + else: + hunkinfo.startsrc = int(match.group(1)) + hunkinfo.linessrc = 1 + if match.group(3): hunkinfo.linessrc = int(match.group(3)) + hunkinfo.starttgt = int(match.group(4)) + hunkinfo.linestgt = 1 + if match.group(6): hunkinfo.linestgt = int(match.group(6)) + hunkinfo.invalid = False + hunkinfo.text = [] + + hunkactual["linessrc"] = hunkactual["linestgt"] = 0 + + # switch to hunkbody state + hunkhead = False + hunkbody = True + nexthunkno += 1 + continue + + if not hunkskip: + warning("patch stream incomplete") + # sys.exit(?) + else: + # duplicated message when an eof is reached + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + debug("total files: %d total hunks: %d" % (len(self.source), sum(len(hset) for hset in self.hunks))) + + + def apply(self): + """ apply parsed patch """ + + total = len(self.source) + for fileno, filename in enumerate(self.source): + + f2patch = filename + if not exists(f2patch): + f2patch = self.target[fileno] + if not exists(f2patch): + warning("source/target file does not exist\n--- %s\n+++ %s" % (filename, f2patch)) + continue + if not isfile(f2patch): + warning("not a file - %s" % f2patch) + continue + filename = f2patch + + debug("processing %d/%d:\t %s" % (fileno+1, total, filename)) + + # validate before patching + f2fp = open(filename) + hunkno = 0 + hunk = self.hunks[fileno][hunkno] + hunkfind = [] + hunkreplace = [] + validhunks = 0 + canpatch = False + for lineno, line in enumerate(f2fp): + if lineno+1 < hunk.startsrc: + continue + elif lineno+1 == hunk.startsrc: + hunkfind = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " -"] + hunkreplace = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " +"] + #pprint(hunkreplace) + hunklineno = 0 + + # todo \ No newline at end of file + + # check hunks in source file + if lineno+1 < hunk.startsrc+len(hunkfind)-1: + if line.rstrip("\r\n") == hunkfind[hunklineno]: + hunklineno+=1 + else: + debug("hunk no.%d doesn't match source file %s" % (hunkno+1, filename)) + # file may be already patched, but we will check other hunks anyway + hunkno += 1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + continue + else: + break + + # check if processed line is the last line + if lineno+1 == hunk.startsrc+len(hunkfind)-1: + debug("file %s hunk no.%d -- is ready to be patched" % (filename, hunkno+1)) + hunkno+=1 + validhunks+=1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + else: + if validhunks == len(self.hunks[fileno]): + # patch file + canpatch = True + break + else: + if hunkno < len(self.hunks[fileno]): + warning("premature end of source file %s at hunk %d" % (filename, hunkno+1)) + + f2fp.close() + + if validhunks < len(self.hunks[fileno]): + if self._match_file_hunks(filename, self.hunks[fileno]): + warning("already patched %s" % filename) + else: + warning("source file is different - %s" % filename) + if canpatch: + backupname = filename+".orig" + if exists(backupname): + warning("can't backup original file to %s - aborting" % backupname) + else: + import shutil + shutil.move(filename, backupname) + if self.write_hunks(backupname, filename, self.hunks[fileno]): + info("successfully patched %d/%d:\t %s" % (fileno+1, total, filename)) + unlink(backupname) + else: + warning("error patching file %s" % filename) + shutil.copy(filename, filename+".invalid") + warning("invalid version is saved to %s" % filename+".invalid") + # todo: proper rejects + shutil.move(backupname, filename) + + # todo: check for premature eof + + + def can_patch(self, filename): + """ Check if specified filename can be patched. Returns None if file can + not be found among source filenames. False if patch can not be applied + clearly. True otherwise. + + :returns: True, False or None + """ + idx = self._get_file_idx(filename, source=True) + if idx == None: + return None + return self._match_file_hunks(filename, self.hunks[idx]) + + + def _match_file_hunks(self, filepath, hunks): + matched = True + fp = open(abspath(filepath)) + + class NoMatch(Exception): + pass + + lineno = 1 + line = fp.readline() + hno = None + try: + for hno, h in enumerate(hunks): + # skip to first line of the hunk + while lineno < h.starttgt: + if not len(line): # eof + debug("check failed - premature eof before hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + for hline in h.text: + if hline.startswith("-"): + continue + if not len(line): + debug("check failed - premature eof on hunk: %d" % (hno+1)) + # todo: \ No newline at the end of file + raise NoMatch + if line.rstrip("\r\n") != hline[1:].rstrip("\r\n"): + debug("file is not patched - failed hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + + except NoMatch: + matched = False + # todo: display failed hunk, i.e. expected/found + + fp.close() + return matched + + + def patch_stream(self, instream, hunks): + """ Generator that yields stream patched with hunks iterable + + Converts lineends in hunk lines to the best suitable format + autodetected from input + """ + + # todo: At the moment substituted lineends may not be the same + # at the start and at the end of patching. Also issue a + # warning/throw about mixed lineends (is it really needed?) + + hunks = iter(hunks) + + srclineno = 1 + + lineends = {'\n':0, '\r\n':0, '\r':0} + def get_line(): + """ + local utility function - return line from source stream + collecting line end statistics on the way + """ + line = instream.readline() + # 'U' mode works only with text files + if line.endswith("\r\n"): + lineends["\r\n"] += 1 + elif line.endswith("\n"): + lineends["\n"] += 1 + elif line.endswith("\r"): + lineends["\r"] += 1 + return line + + for hno, h in enumerate(hunks): + debug("hunk %d" % (hno+1)) + # skip to line just before hunk starts + while srclineno < h.startsrc: + yield get_line() + srclineno += 1 + + for hline in h.text: + # todo: check \ No newline at the end of file + if hline.startswith("-") or hline.startswith("\\"): + get_line() + srclineno += 1 + continue + else: + if not hline.startswith("+"): + get_line() + srclineno += 1 + line2write = hline[1:] + # detect if line ends are consistent in source file + if sum([bool(lineends[x]) for x in lineends]) == 1: + newline = [x for x in lineends if lineends[x] != 0][0] + yield line2write.rstrip("\r\n")+newline + else: # newlines are mixed + yield line2write + + for line in instream: + yield line + + + def write_hunks(self, srcname, tgtname, hunks): + src = open(srcname, "rb") + tgt = open(tgtname, "wb") + + debug("processing target file %s" % tgtname) + + tgt.writelines(self.patch_stream(src, hunks)) + + tgt.close() + src.close() + return True + + + def _get_file_idx(self, filename, source=None): + """ Detect index of given filename within patch. + + :param filename: + :param source: search filename among sources (True), + targets (False), or both (None) + :returns: int or None + """ + filename = abspath(filename) + if source == True or source == None: + for i,fnm in enumerate(self.source): + if filename == abspath(fnm): + return i + if source == False or source == None: + for i,fnm in enumerate(self.target): + if filename == abspath(fnm): + return i + + + + +if __name__ == "__main__": + from optparse import OptionParser + from os.path import exists + import sys + + opt = OptionParser(usage="%prog [options] unipatch-file", version="python-patch %s" % __version__) + opt.add_option("-q", "--quiet", action="store_const", dest="verbosity", + const=0, help="print only warnings and errors", default=1) + opt.add_option("-v", "--verbose", action="store_const", dest="verbosity", + const=2, help="be verbose") + opt.add_option("--debug", action="store_true", dest="debugmode", help="debug mode") + (options, args) = opt.parse_args() + + if not args: + opt.print_version() + opt.print_help() + sys.exit() + debugmode = options.debugmode + patchfile = args[0] + if not exists(patchfile) or not isfile(patchfile): + sys.exit("patch file does not exist - %s" % patchfile) + + + verbosity_levels = {0:logging.WARNING, 1:logging.INFO, 2:logging.DEBUG} + loglevel = verbosity_levels[options.verbosity] + logformat = "%(message)s" + if debugmode: + loglevel = logging.DEBUG + logformat = "%(levelname)8s %(message)s" + logger.setLevel(loglevel) + loghandler.setFormatter(logging.Formatter(logformat)) + + + + patch = fromfile(patchfile) + #pprint(patch) + patch.apply() + + # todo: document and test line ends handling logic - patch.py detects proper line-endings + # for inserted hunks and issues a warning if patched file has incosistent line ends diff --git a/tests/.svn/all-wcprops b/tests/.svn/all-wcprops new file mode 100644 index 0000000..776b60e --- /dev/null +++ b/tests/.svn/all-wcprops @@ -0,0 +1,77 @@ +K 25 +svn:wc:ra_dav:version-url +V 29 +/svn/!svn/ver/101/trunk/tests +END +02uni_newline.to +K 25 +svn:wc:ra_dav:version-url +V 45 +/svn/!svn/ver/18/trunk/tests/02uni_newline.to +END +02uni_newline.from +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/!svn/ver/18/trunk/tests/02uni_newline.from +END +Descript.ion +K 25 +svn:wc:ra_dav:version-url +V 41 +/svn/!svn/ver/46/trunk/tests/Descript.ion +END +04can_patch.patch +K 25 +svn:wc:ra_dav:version-url +V 46 +/svn/!svn/ver/84/trunk/tests/04can_patch.patch +END +03trail_fname.patch +K 25 +svn:wc:ra_dav:version-url +V 48 +/svn/!svn/ver/46/trunk/tests/03trail_fname.patch +END +01uni_multi.patch +K 25 +svn:wc:ra_dav:version-url +V 46 +/svn/!svn/ver/14/trunk/tests/01uni_multi.patch +END +04can_patch.to +K 25 +svn:wc:ra_dav:version-url +V 43 +/svn/!svn/ver/84/trunk/tests/04can_patch.to +END +03trail_fname.to +K 25 +svn:wc:ra_dav:version-url +V 45 +/svn/!svn/ver/46/trunk/tests/03trail_fname.to +END +04can_patch.from +K 25 +svn:wc:ra_dav:version-url +V 45 +/svn/!svn/ver/84/trunk/tests/04can_patch.from +END +03trail_fname.from +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/!svn/ver/46/trunk/tests/03trail_fname.from +END +02uni_newline.patch +K 25 +svn:wc:ra_dav:version-url +V 48 +/svn/!svn/ver/18/trunk/tests/02uni_newline.patch +END +run_tests.py +K 25 +svn:wc:ra_dav:version-url +V 42 +/svn/!svn/ver/101/trunk/tests/run_tests.py +END diff --git a/tests/.svn/entries b/tests/.svn/entries new file mode 100644 index 0000000..ae5ea06 --- /dev/null +++ b/tests/.svn/entries @@ -0,0 +1,442 @@ +10 + +dir +101 +http://python-patch.googlecode.com/svn/trunk/tests +http://python-patch.googlecode.com/svn + + + +2010-08-29T08:57:30.359404Z +101 +techtonik + + + + + + + + + + + + + + +7155d8b8-9951-0410-8dfa-c5fb0ae76a41 + +02uni_newline.to +file + + + + +2010-10-21T22:54:45.426691Z +4624cdc5b37827f46a3179026cced861 +2008-07-11T14:45:15.364747Z +18 +techtonik + + + + + + + + + + + + + + + + + + + + + +44 + +02uni_newline.from +file + + + + +2010-10-21T22:54:45.426691Z +369db37e6f851047287165063d2cdaf0 +2008-07-11T14:45:15.364747Z +18 +techtonik + + + + + + + + + + + + + + + + + + + + + +47 + +Descript.ion +file + + + + +2010-10-21T22:54:45.427691Z +075f78886ca61d9217571c37ccb90031 +2009-08-24T21:36:17.844291Z +46 +techtonik + + + + + + + + + + + + + + + + + + + + + +151 + +04can_patch.patch +file + + + + +2010-10-21T22:54:45.427691Z +e3413a75ca534731e3a2ca02ec1fb608 +2010-04-09T20:22:31.739775Z +84 +techtonik + + + + + + + + + + + + + + + + + + + + + +166 + +03trail_fname.patch +file + + + + +2010-10-21T22:54:45.427691Z +548008c05f5e21770243040e353946b0 +2009-08-24T21:36:17.844291Z +46 +techtonik + + + + + + + + + + + + + + + + + + + + + +226 + +01uni_multi.patch +file + + + + +2010-10-21T22:54:45.428691Z +9a5bd10e1ffed2d412dedea3ba5eb014 +2008-07-11T12:00:28.935567Z +14 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +6919 + +04can_patch.to +file + + + + +2010-10-21T22:54:45.428691Z +5343645f441111e5c8ef6101b4f5856f +2010-04-09T20:22:31.739775Z +84 +techtonik + + + + + + + + + + + + + + + + + + + + + +228 + +03trail_fname.to +file + + + + +2010-10-21T22:54:45.428691Z +46a42c8cd6e84505e2edf4fdec10fd98 +2009-08-24T21:36:17.844291Z +46 +techtonik + + + + + + + + + + + + + + + + + + + + + +117 + +04can_patch.from +file + + + + +2010-10-21T22:54:45.429691Z +c47c9d632e264c6996342a92d6a4a2ff +2010-04-09T20:22:31.739775Z +84 +techtonik + + + + + + + + + + + + + + + + + + + + + +240 + +03trail_fname.from +file + + + + +2010-10-21T22:54:45.429691Z +6ce4e277caa17924bf46d24b878a77e3 +2009-08-24T21:36:17.844291Z +46 +techtonik + + + + + + + + + + + + + + + + + + + + + +142 + +01uni_multi.to +dir + +01uni_multi.from +dir + +02uni_newline.patch +file + + + + +2010-10-21T22:54:45.429691Z +f3a5e24381fa6b709afc68a67a1a2f48 +2008-07-11T14:45:15.364747Z +18 +techtonik + + + + + + + + + + + + + + + + + + + + + +197 + +run_tests.py +file + + + + +2010-10-21T22:54:45.430691Z +e207832061e949d954a733db02fe9f5a +2010-08-29T08:57:30.359404Z +101 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +6565 + diff --git a/tests/.svn/prop-base/01uni_multi.patch.svn-base b/tests/.svn/prop-base/01uni_multi.patch.svn-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/tests/.svn/prop-base/01uni_multi.patch.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/tests/.svn/prop-base/run_tests.py.svn-base b/tests/.svn/prop-base/run_tests.py.svn-base new file mode 100644 index 0000000..6e84194 --- /dev/null +++ b/tests/.svn/prop-base/run_tests.py.svn-base @@ -0,0 +1,5 @@ +K 12 +svn:keywords +V 22 +Id Date HeadURL Author +END diff --git a/tests/.svn/text-base/01uni_multi.patch.svn-base b/tests/.svn/text-base/01uni_multi.patch.svn-base new file mode 100644 index 0000000..ba5939c --- /dev/null +++ b/tests/.svn/text-base/01uni_multi.patch.svn-base @@ -0,0 +1,180 @@ +Index: updatedlg.cpp
+===================================================================
+--- updatedlg.cpp (revision 5095)
++++ updatedlg.cpp (working copy)
+@@ -94,11 +94,13 @@
+ lst->InsertColumn(1, _("Version")); + lst->InsertColumn(2, _("Installed")); + lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT); ++ lst->InsertColumn(4, _("Rev")); + +- lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space ++ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space + lst->SetColumnWidth(1, 64); + lst->SetColumnWidth(2, 64); + lst->SetColumnWidth(3, 64); ++ lst->SetColumnWidth(4, 40); + } + + void UpdateDlg::AddRecordToList(UpdateRec* rec) +@@ -111,8 +113,20 @@
+ lst->SetItem(idx, 1, rec->version); + lst->SetItem(idx, 2, rec->installed_version); + lst->SetItem(idx, 3, rec->size); ++ lst->SetItem(idx, 4, rec->revision); + } + ++wxString UpdateDlg::GetListColumnText(int idx, int col) { ++ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); ++ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx; ++ wxListItem info; ++ info.SetId(index); ++ info.SetColumn(col); ++ info.SetMask(wxLIST_MASK_TEXT); ++ lst->GetItem(info); ++ return info.GetText(); ++} ++ + void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text) + { + wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); +@@ -393,7 +407,9 @@
+ if (index == -1) + return 0; + wxString title = lst->GetItemText(index); +- return FindRecByTitle(title, m_Recs, m_RecsCount); ++ wxString version = GetListColumnText(index, 1); ++ wxString revision = GetListColumnText(index, 4); ++ return FindRec(title, version, revision, m_Recs, m_RecsCount); + } + + void UpdateDlg::DownloadFile(bool dontInstall) +Index: updatedlg.h
+===================================================================
+--- updatedlg.h (revision 5095)
++++ updatedlg.h (working copy)
+@@ -49,6 +49,7 @@
+ UpdateRec* GetRecFromListView(); + void CreateListColumns(); + void AddRecordToList(UpdateRec* rec); ++ wxString GetListColumnText(int idx, int col); + void SetListColumnText(int idx, int col, const wxString& text); + + wxString GetConfFilename(); +Index: manifest.xml
+===================================================================
+--- manifest.xml (revision 5095)
++++ manifest.xml (working copy)
+@@ -2,18 +2,19 @@
+ <CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+- <Value title="Dev-C++ DevPak updater/installer" />
+- <Value version="0.1" />
++ <Value title="DevPak updater/installer" />
++ <Value version="0.2" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+- Julian R Seward for libbzip2.
+- libbzip2 copyright notice:
+- bzip2 and associated library libbzip2, are
+- copyright (C) 1996-2000 Julian R Seward.
+- All rights reserved." />
++ Julian R Seward for libbzip2.
++
++ libbzip2 copyright notice:
++ bzip2 and associated library libbzip2, are
++ copyright (C) 1996-2000 Julian R Seward.
++ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+ </CodeBlocks_plugin_manifest_file>
+Index: conf.cpp
+===================================================================
+--- conf.cpp (revision 5095)
++++ conf.cpp (working copy)
+@@ -46,10 +46,16 @@
+ // fix title + // devpaks.org has changed the title to contain some extra info + // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid] +- // we don't need this extra info, so if we find it we remove it +- int pos = rec.title.Find(_T("Library version:")); ++ int pos = rec.title.Lower().Find(_T("library version:")); + if (pos != -1) + { ++ int revpos = rec.title.Lower().Find(_T("devpak revision:")); ++ if (revpos != -1) { ++ rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false); ++ rec.revision.Replace(_T("\t"), _T(" ")); ++ rec.revision = rec.revision.BeforeFirst(_T(' ')); ++ } ++ + rec.title.Truncate(pos); + rec.title = rec.title.Trim(false); + rec.title = rec.title.Trim(true); +@@ -60,7 +66,7 @@
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename")); + rec.local_file = ini.GetKeyValue(i, _T("LocalFilename")); + rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(",")); +- rec.install = ini.GetKeyValue(i, _T("InstallPath")); ++ rec.install_path = ini.GetKeyValue(i, _T("InstallPath")); + rec.version = ini.GetKeyValue(i, _T("Version")); + ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes); + rec.date = ini.GetKeyValue(i, _T("Date")); +@@ -99,12 +105,17 @@
+ return list; + } + +-UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count) ++UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count) + { + for (int i = 0; i < count; ++i) + { +- if (list[i].title == title) +- return &list[i]; ++ if (list[i].title == title && list[i].version == version) { ++ if (revision.IsEmpty()) { ++ return &list[i]; ++ } else if (list[i].revision == revision) { ++ return &list[i]; ++ } ++ } + } + return 0; + } +Index: conf.h
+===================================================================
+--- conf.h (revision 5095)
++++ conf.h (working copy)
+@@ -7,7 +7,7 @@
+ + struct UpdateRec + { +- wxString entry; ++ wxString entry; //! .entry filename for installed + wxString title; + wxString name; + wxString desc; +@@ -15,8 +15,9 @@
+ wxString remote_file; + wxString local_file; + wxArrayString groups; +- wxString install; ++ wxString install_path; //! ignored + wxString version; ++ wxString revision; + wxString installed_version; + long int bytes; + float kilobytes; +@@ -31,7 +32,7 @@
+ extern wxString g_MasterPath; + + UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath); +-UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count); ++UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count); + // utility + wxString GetSizeString(int bytes); + diff --git a/tests/.svn/text-base/02uni_newline.from.svn-base b/tests/.svn/text-base/02uni_newline.from.svn-base new file mode 100644 index 0000000..cf5a315 --- /dev/null +++ b/tests/.svn/text-base/02uni_newline.from.svn-base @@ -0,0 +1,4 @@ +
+read_patch("fix_devpak_install.patch")
+
+asd
\ No newline at end of file diff --git a/tests/.svn/text-base/02uni_newline.patch.svn-base b/tests/.svn/text-base/02uni_newline.patch.svn-base new file mode 100644 index 0000000..072649c --- /dev/null +++ b/tests/.svn/text-base/02uni_newline.patch.svn-base @@ -0,0 +1,8 @@ +--- 02uni_newline.from 2008-07-02 18:34:04 +0000
++++ 02uni_newline.to 2008-07-02 18:34:08 +0000
+@@ -1,4 +1,3 @@
+
+ read_patch("fix_devpak_install.patch")
+
+-asd
+\ No newline at end of file
diff --git a/tests/.svn/text-base/02uni_newline.to.svn-base b/tests/.svn/text-base/02uni_newline.to.svn-base new file mode 100644 index 0000000..6c62b1a --- /dev/null +++ b/tests/.svn/text-base/02uni_newline.to.svn-base @@ -0,0 +1,3 @@ +
+read_patch("fix_devpak_install.patch")
+
diff --git a/tests/.svn/text-base/03trail_fname.from.svn-base b/tests/.svn/text-base/03trail_fname.from.svn-base new file mode 100644 index 0000000..01ff017 --- /dev/null +++ b/tests/.svn/text-base/03trail_fname.from.svn-base @@ -0,0 +1,8 @@ +Tests:
+- file not found
+- trailing spaces in patch filenames
+- already patched
+- create new files
+- remove files
+- svn diff
+- hg diff
diff --git a/tests/.svn/text-base/03trail_fname.patch.svn-base b/tests/.svn/text-base/03trail_fname.patch.svn-base new file mode 100644 index 0000000..ec29b30 --- /dev/null +++ b/tests/.svn/text-base/03trail_fname.patch.svn-base @@ -0,0 +1,12 @@ +--- 03trail_fname.from
++++ 03trail_fname.to
+@@ -1,7 +1,8 @@
+ Tests:
+ - file not found
+-- trailing spaces in patch filenames
+ - already patched
++
++Features:
+ - create new files
+ - remove files
+ - svn diff
diff --git a/tests/.svn/text-base/03trail_fname.to.svn-base b/tests/.svn/text-base/03trail_fname.to.svn-base new file mode 100644 index 0000000..758b097 --- /dev/null +++ b/tests/.svn/text-base/03trail_fname.to.svn-base @@ -0,0 +1,9 @@ +Tests:
+- file not found
+- already patched
+
+Features:
+- create new files
+- remove files
+- svn diff
+- hg diff
diff --git a/tests/.svn/text-base/04can_patch.from.svn-base b/tests/.svn/text-base/04can_patch.from.svn-base new file mode 100644 index 0000000..0380b9d --- /dev/null +++ b/tests/.svn/text-base/04can_patch.from.svn-base @@ -0,0 +1,40 @@ +beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
\ No newline at end of file diff --git a/tests/.svn/text-base/04can_patch.patch.svn-base b/tests/.svn/text-base/04can_patch.patch.svn-base new file mode 100644 index 0000000..356beb0 --- /dev/null +++ b/tests/.svn/text-base/04can_patch.patch.svn-base @@ -0,0 +1,11 @@ +--- 04can_patch.from Sun Dec 27 09:53:51 2009
++++ 04can_patch.to Sun Dec 27 09:54:06 2009
+@@ -6,8 +6,6 @@
+ beta
+ beta
+ alpha
+-beta
+-beta
+ beta
+ beta
+ beta
diff --git a/tests/.svn/text-base/04can_patch.to.svn-base b/tests/.svn/text-base/04can_patch.to.svn-base new file mode 100644 index 0000000..6e94e33 --- /dev/null +++ b/tests/.svn/text-base/04can_patch.to.svn-base @@ -0,0 +1,38 @@ +beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
\ No newline at end of file diff --git a/tests/.svn/text-base/Descript.ion.svn-base b/tests/.svn/text-base/Descript.ion.svn-base new file mode 100644 index 0000000..b57bb3e --- /dev/null +++ b/tests/.svn/text-base/Descript.ion.svn-base @@ -0,0 +1,3 @@ +01uni_multi.patch unified diff multiple files
+02uni_newline.patch newline at the end of file
+03trail_fname.patch trailing spaces in patch filenames
diff --git a/tests/.svn/text-base/run_tests.py.svn-base b/tests/.svn/text-base/run_tests.py.svn-base new file mode 100644 index 0000000..0a99af9 --- /dev/null +++ b/tests/.svn/text-base/run_tests.py.svn-base @@ -0,0 +1,221 @@ +""" +TestSuite + +Files/directories that comprise one test all have the same name, but a different extensions: +*.patch +*.from +*.to + +*.doctest - self contained doctest patch + +TODO: recheck input/output sources + +""" + +import os +import sys +import re +import shutil +import unittest +import copy +from os import listdir +from os.path import abspath, dirname, exists, join, isdir +from tempfile import mkdtemp + +verbose = False +if "-v" in sys.argv or "--verbose" in sys.argv: + verbose = True + + +#: full path for directory with tests +tests_dir = dirname(abspath(__file__)) + + +# import patch.py from parent directory +save_path = sys.path +sys.path.insert(0, dirname(tests_dir)) +import patch +sys.path = save_path + + +# ---------------------------------------------------------------------------- +class TestPatchFiles(unittest.TestCase): + """ + unittest hack - test* methods are generated by add_test_methods() function + below dynamicallt using information about *.patch files from tests directory + + """ + def _assert_files_equal(self, file1, file2): + f1 = f2 = None + try: + f1 = open(file1, "rb") + f2 = open(file2, "rb") + for line in f1: + self.assertEqual(line, f2.readline()) + + finally: + if f2: + f2.close() + if f1: + f1.close() + + def _assert_dirs_equal(self, dir1, dir2, ignore=[]): + """ compare dir1 with reference dir2 + .svn dirs are ignored + + """ + # recursion here + e2list = listdir(dir2) + for e1 in listdir(dir1): + if e1 == ".svn": + continue + e1path = join(dir1, e1) + e2path = join(dir2, e1) + self.assert_(exists(e1path)) + self.assert_(exists(e2path), "%s does not exist" % e2path) + self.assert_(isdir(e1path) == isdir(e2path)) + if not isdir(e1path): + self._assert_files_equal(e1path, e2path) + else: + self._assert_dirs_equal(e1path, e2path) + e2list.remove(e1) + for e2 in e2list: + if e2 == ".svn" or e2 in ignore: + continue + self.fail("extra file or directory: %s" % e2) + + + def _run_test(self, testname): + """ + boilerplate for running *.patch file tests + """ + + # 1. create temp test directory + # 2. copy files + # 3. execute file-based patch + # 4. compare results + # 5. cleanup on success + + tmpdir = mkdtemp(prefix="%s."%testname) + + patch_file = join(tmpdir, "%s.patch" % testname) + shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) + + from_src = join(tests_dir, "%s.from" % testname) + from_tgt = join(tmpdir, "%s.from" % testname) + + if not isdir(from_src): + shutil.copy(from_src, from_tgt) + else: + for e in listdir(from_src): + if e == ".svn": + continue + epath = join(from_src, e) + if not isdir(epath): + shutil.copy(epath, join(tmpdir, e)) + else: + shutil.copytree(epath, join(tmpdir, e)) + + + # 3. + # test utility as a whole + patch_tool = join(dirname(tests_dir), "patch.py") + save_cwd = os.getcwdu() + os.chdir(tmpdir) + if verbose: + ret = os.system('%s %s "%s"' % (sys.executable, patch_tool, patch_file)) + else: + ret = os.system('%s %s -q "%s"' % (sys.executable, patch_tool, patch_file)) + assert ret == 0, "Error %d running test %s" % (ret, testname) + os.chdir(save_cwd) + + + # 4. + # compare results + if not isdir(from_src): + self._assert_files_equal(join(tests_dir, "%s.to" % testname), from_tgt) + else: + # need recursive compare + self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) + + + + shutil.rmtree(tmpdir) + return 0 + + +def add_test_methods(cls): + """ + hack to generate test* methods in target class - one + for each *.patch file in tests directory + """ + + # list testcases - every test starts with number + # and add them as test* methods + testptn = re.compile(r"^(?P<name>\d{2,}.+)\.(?P<ext>[^\.]+)") + testset = sorted( set([testptn.match(e).group('name') for e in listdir(tests_dir) if testptn.match(e)]) ) + + for filename in testset: + methname = filename.replace(" ", "_") + def create_closure(): + name = filename + return lambda self: self._run_test(name) + setattr(cls, "test%s" % methname, create_closure()) + if verbose: + print "added test method %s to %s" % (methname, cls) +add_test_methods(TestPatchFiles) + +# ---------------------------------------------------------------------------- + +class TestCheckPatched(unittest.TestCase): + def setUp(self): + self.save_cwd = os.getcwdu() + os.chdir(tests_dir) + + def tearDown(self): + os.chdir(self.save_cwd) + + def test_patched_multiline(self): + pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) + os.chdir(join(tests_dir, "01uni_multi.to")) + self.assert_(pto.can_patch("updatedlg.cpp")) + + def test_can_patch_single_source(self): + pto2 = patch.fromfile(join(tests_dir, "02uni_newline.patch")) + self.assert_(pto2.can_patch("02uni_newline.from")) + + def test_can_patch_fails_on_target_file(self): + pto3 = patch.fromfile(join(tests_dir, "03trail_fname.patch")) + self.assertEqual(None, pto3.can_patch("03trail_fname.to")) + self.assertEqual(None, pto3.can_patch("not_in_source.also")) + + def test_multiline_false_on_other_file(self): + pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) + os.chdir(join(tests_dir, "01uni_multi.from")) + self.assertFalse(pto.can_patch("updatedlg.cpp")) + + def test_single_false_on_other_file(self): + pto3 = patch.fromfile(join(tests_dir, "03trail_fname.patch")) + self.assertFalse(pto3.can_patch("03trail_fname.from")) + + def test_can_patch_fails_even_if_file_in_targets_can_be_patched(self): + pto2 = patch.fromfile(join(tests_dir, "04can_patch.patch")) + self.assert_(not pto2.can_patch("04can_patch.to")) + +# ---------------------------------------------------------------------------- + +class TestPatchParse(unittest.TestCase): + def test_fromstring(self): + try: + f = open(join(tests_dir, "01uni_multi.patch"), "rb") + readstr = f.read() + finally: + f.close() + pto = patch.fromstring(readstr) + self.assertEqual(len(pto.source), 5) + +# ---------------------------------------------------------------------------- + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/01uni_multi.from/.svn/all-wcprops b/tests/01uni_multi.from/.svn/all-wcprops new file mode 100644 index 0000000..0c050b3 --- /dev/null +++ b/tests/01uni_multi.from/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 45 +/svn/!svn/ver/17/trunk/tests/01uni_multi.from +END +updatedlg.h +K 25 +svn:wc:ra_dav:version-url +V 57 +/svn/!svn/ver/17/trunk/tests/01uni_multi.from/updatedlg.h +END +manifest.xml +K 25 +svn:wc:ra_dav:version-url +V 58 +/svn/!svn/ver/17/trunk/tests/01uni_multi.from/manifest.xml +END +conf.cpp +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/!svn/ver/17/trunk/tests/01uni_multi.from/conf.cpp +END +updatedlg.cpp +K 25 +svn:wc:ra_dav:version-url +V 59 +/svn/!svn/ver/14/trunk/tests/01uni_multi.from/updatedlg.cpp +END +conf.h +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/!svn/ver/17/trunk/tests/01uni_multi.from/conf.h +END diff --git a/tests/01uni_multi.from/.svn/dir-prop-base b/tests/01uni_multi.from/.svn/dir-prop-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/tests/01uni_multi.from/.svn/dir-prop-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/tests/01uni_multi.from/.svn/entries b/tests/01uni_multi.from/.svn/entries new file mode 100644 index 0000000..f4757c2 --- /dev/null +++ b/tests/01uni_multi.from/.svn/entries @@ -0,0 +1,198 @@ +10 + +dir +101 +http://python-patch.googlecode.com/svn/trunk/tests/01uni_multi.from +http://python-patch.googlecode.com/svn + + + +2008-07-11T14:17:02.044387Z +17 +techtonik +has-props + + + + + + + + + + + + + +7155d8b8-9951-0410-8dfa-c5fb0ae76a41 + +updatedlg.h +file + + + + +2010-10-21T22:54:45.422691Z +85fed436ec9cccab10fe5eb54f613648 +2008-07-11T14:17:02.044387Z +17 +techtonik + + + + + + + + + + + + + + + + + + + + + +2680 + +manifest.xml +file + + + + +2010-10-21T22:54:45.422691Z +074e1f46a11de6483923c39a3a606624 +2008-07-11T14:17:02.044387Z +17 +techtonik + + + + + + + + + + + + + + + + + + + + + +939 + +conf.cpp +file + + + + +2010-10-21T22:54:45.423691Z +f3edb32005ecede51f0cf62bb316394c +2008-07-11T14:17:02.044387Z +17 +techtonik + + + + + + + + + + + + + + + + + + + + + +3744 + +updatedlg.cpp +file + + + + +2010-10-21T22:54:45.423691Z +8c6d7dcf7fc19feae9df112548ca1118 +2008-07-11T12:00:28.935567Z +14 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +22490 + +conf.h +file + + + + +2010-10-21T22:54:45.423691Z +100f3c788fbd7c6f1c0cf141ddbbabe6 +2008-07-11T14:17:02.044387Z +17 +techtonik + + + + + + + + + + + + + + + + + + + + + +875 + diff --git a/tests/01uni_multi.from/.svn/prop-base/updatedlg.cpp.svn-base b/tests/01uni_multi.from/.svn/prop-base/updatedlg.cpp.svn-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/tests/01uni_multi.from/.svn/prop-base/updatedlg.cpp.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/tests/01uni_multi.from/.svn/text-base/conf.cpp.svn-base b/tests/01uni_multi.from/.svn/text-base/conf.cpp.svn-base new file mode 100644 index 0000000..9962641 --- /dev/null +++ b/tests/01uni_multi.from/.svn/text-base/conf.cpp.svn-base @@ -0,0 +1,110 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $
+ */
+
+#include "conf.h"
+#include <wx/intl.h>
+#include <wx/url.h>
+#include <wx/filename.h>
+#include <globals.h>
+
+wxString g_MasterPath;
+
+wxString GetSizeString(int bytes)
+{
+ wxString ret;
+ float kilobytes = (float)bytes / 1024.0f;
+ float megabytes = kilobytes / 1024.0f;
+ if (megabytes >= 1.0f)
+ ret.Printf(_("%.2f MB"), megabytes);
+ else if (kilobytes >= 1.0f)
+ ret.Printf(_("%.2f KB"), kilobytes);
+ else
+ ret.Printf(_("%ld bytes"), bytes);
+ return ret;
+}
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath)
+{
+ *recCount = 0;
+ int groupsCount = ini.GetGroupsCount();
+ if (groupsCount == 0)
+ return 0;
+
+ UpdateRec* list = new UpdateRec[ini.GetGroupsCount()];
+ for (int i = 0; i < groupsCount; ++i)
+ {
+ UpdateRec& rec = list[i];
+
+ rec.title = ini.GetGroupName(i);
+
+ // fix title
+ // devpaks.org has changed the title to contain some extra info
+ // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid]
+ // we don't need this extra info, so if we find it we remove it
+ int pos = rec.title.Find(_T("Library version:"));
+ if (pos != -1)
+ {
+ rec.title.Truncate(pos);
+ rec.title = rec.title.Trim(false);
+ rec.title = rec.title.Trim(true);
+ }
+
+ rec.name = ini.GetKeyValue(i, _T("Name"));
+ rec.desc = ini.GetKeyValue(i, _T("Description"));
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename"));
+ rec.local_file = ini.GetKeyValue(i, _T("LocalFilename"));
+ rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(","));
+ rec.install = ini.GetKeyValue(i, _T("InstallPath"));
+ rec.version = ini.GetKeyValue(i, _T("Version"));
+ ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes);
+ rec.date = ini.GetKeyValue(i, _T("Date"));
+ rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1");
+
+ // read .entry file (if exists)
+ rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry");
+ IniParser p;
+ p.ParseFile(appPath + rec.entry);
+ rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion"));
+
+ rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file);
+ rec.installed = !rec.installed_version.IsEmpty();
+
+ // calculate size
+ rec.size = GetSizeString(rec.bytes);
+
+ // fix-up
+ if (rec.name.IsEmpty())
+ rec.name = rec.title;
+ rec.desc.Replace(_T("<CR>"), _T("\n"));
+ rec.desc.Replace(_T("<LF>"), _T("\r"));
+ wxURL url(rec.remote_file);
+ if (!url.GetServer().IsEmpty())
+ {
+ rec.remote_server = url.GetScheme() + _T("://") + url.GetServer();
+ int pos = rec.remote_file.Find(url.GetServer());
+ if (pos != wxNOT_FOUND)
+ rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1);
+ }
+ else
+ rec.remote_server = currentServer;
+ }
+
+ *recCount = groupsCount;
+ return list;
+}
+
+UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count)
+{
+ for (int i = 0; i < count; ++i)
+ {
+ if (list[i].title == title)
+ return &list[i];
+ }
+ return 0;
+}
diff --git a/tests/01uni_multi.from/.svn/text-base/conf.h.svn-base b/tests/01uni_multi.from/.svn/text-base/conf.h.svn-base new file mode 100644 index 0000000..03a0179 --- /dev/null +++ b/tests/01uni_multi.from/.svn/text-base/conf.h.svn-base @@ -0,0 +1,38 @@ +#ifndef CONF_H
+#define CONF_H
+
+#include <wx/string.h>
+#include <wx/dynarray.h>
+#include "cbiniparser.h"
+
+struct UpdateRec
+{
+ wxString entry;
+ wxString title;
+ wxString name;
+ wxString desc;
+ wxString remote_server;
+ wxString remote_file;
+ wxString local_file;
+ wxArrayString groups;
+ wxString install;
+ wxString version;
+ wxString installed_version;
+ long int bytes;
+ float kilobytes;
+ float megabytes;
+ wxString size;
+ wxString date;
+ bool installable;
+ bool downloaded;
+ bool installed;
+};
+
+extern wxString g_MasterPath;
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath);
+UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count);
+// utility
+wxString GetSizeString(int bytes);
+
+#endif // CONF_H
diff --git a/tests/01uni_multi.from/.svn/text-base/manifest.xml.svn-base b/tests/01uni_multi.from/.svn/text-base/manifest.xml.svn-base new file mode 100644 index 0000000..c7bc705 --- /dev/null +++ b/tests/01uni_multi.from/.svn/text-base/manifest.xml.svn-base @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+ <Value title="Dev-C++ DevPak updater/installer" />
+ <Value version="0.1" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+ Julian R Seward for libbzip2.
+ libbzip2 copyright notice:
+ bzip2 and associated library libbzip2, are
+ copyright (C) 1996-2000 Julian R Seward.
+ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+</CodeBlocks_plugin_manifest_file>
diff --git a/tests/01uni_multi.from/.svn/text-base/updatedlg.cpp.svn-base b/tests/01uni_multi.from/.svn/text-base/updatedlg.cpp.svn-base new file mode 100644 index 0000000..829b38a --- /dev/null +++ b/tests/01uni_multi.from/.svn/text-base/updatedlg.cpp.svn-base @@ -0,0 +1,726 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: updatedlg.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/updatedlg.cpp $
+ */
+
+#include "updatedlg.h"
+#include <wx/xrc/xmlres.h>
+#include <wx/msgdlg.h>
+#include <wx/button.h>
+#include <wx/gauge.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/combobox.h>
+#include <wx/checkbox.h>
+#include <wx/file.h>
+#include <wx/menu.h>
+#include "devpakinstaller.h"
+#include "crc32.h"
+
+#include "manager.h"
+#include "configmanager.h"
+#include "globals.h"
+
+int idNet = wxNewId();
+int idPopupInstall = wxNewId();
+int idPopupDownload = wxNewId();
+int idPopupDownloadAndInstall = wxNewId();
+int idPopupUninstall = wxNewId();
+
+BEGIN_EVENT_TABLE(UpdateDlg, wxDialog)
+ EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI)
+ EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged)
+ EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected)
+ EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected)
+ EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick)
+ EVT_MENU(idPopupDownload, UpdateDlg::OnDownload)
+ EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall)
+ EVT_MENU(idPopupInstall, UpdateDlg::OnInstall)
+ EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall)
+ EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange)
+ EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange)
+ EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange)
+ EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect)
+ EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect)
+ EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress)
+ EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted)
+ EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted)
+ EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded)
+END_EVENT_TABLE()
+
+UpdateDlg::UpdateDlg(wxWindow* parent)
+ : m_Recs(0),
+ m_RecsCount(0),
+ m_CurrFileSize(0),
+ m_LastBlockSize(0),
+ m_HasUpdated(false),
+ m_FirstTimeCheck(true),
+ m_Net(this, idNet, _T("http://devpaks.sourceforge.net/"))
+{
+ //ctor
+ wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame"));
+ CreateListColumns();
+ FillServers();
+ UpdateStatus(_("Ready"), 0);
+}
+
+UpdateDlg::~UpdateDlg()
+{
+ //dtor
+ delete[] m_Recs;
+ m_RecsCount = 0;
+}
+
+void UpdateDlg::EndModal(int retCode)
+{
+ if (!m_Net.IsConnected() || retCode != wxID_CANCEL)
+ {
+ wxDialog::EndModal(retCode);
+ return;
+ }
+
+ if (m_Net.IsConnected())
+ m_Net.Abort();
+}
+
+void UpdateDlg::CreateListColumns()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->InsertColumn(0, _("Title"));
+ lst->InsertColumn(1, _("Version"));
+ lst->InsertColumn(2, _("Installed"));
+ lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);
+
+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space
+ lst->SetColumnWidth(1, 64);
+ lst->SetColumnWidth(2, 64);
+ lst->SetColumnWidth(3, 64);
+}
+
+void UpdateDlg::AddRecordToList(UpdateRec* rec)
+{
+ if (!rec)
+ return;
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int idx = lst->GetItemCount();
+ lst->InsertItem(idx, rec->title);
+ lst->SetItem(idx, 1, rec->version);
+ lst->SetItem(idx, 2, rec->installed_version);
+ lst->SetItem(idx, 3, rec->size);
+}
+
+void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem it;
+ it.m_itemId = index;
+ it.m_col = col;
+ it.m_mask = wxLIST_MASK_TEXT;
+ it.m_text = text;
+ lst->SetItem(it);
+}
+
+void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress)
+{
+ wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText);
+ if (lbl->GetLabel() != status)
+ lbl->SetLabel(status);
+ if (curProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress);
+ if (maxProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress);
+}
+
+void UpdateDlg::EnableButtons(bool update, bool abort)
+{
+ wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton);
+
+ btnCl->Enable(abort);
+ // disable server list and cache checkbox while downloading
+ XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected());
+ XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected());
+
+ wxYield();
+}
+
+void UpdateDlg::FillGroups()
+{
+ UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1);
+
+ // get a list of unique group names
+ wxArrayString groups;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x)
+ {
+ if (m_Recs[i].groups[x].IsEmpty())
+ continue;
+ if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND)
+ {
+ if (FilterRec(&m_Recs[i]))
+ groups.Add(m_Recs[i].groups[x]);
+ }
+ }
+ }
+
+ // create the groups tree
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ tree->Freeze();
+ tree->DeleteAllItems();
+ wxTreeItemId root = tree->AddRoot(_("All categories"));
+ for (unsigned int i = 0; i < groups.GetCount(); ++i)
+ {
+ tree->AppendItem(root, groups[i]);
+ }
+ tree->SortChildren(root);
+ tree->Thaw();
+ tree->Expand(root);
+ tree->SelectItem(root); // this calls the event
+
+ UpdateStatus(_("Done parsing list of updates"), 0);
+}
+
+void UpdateDlg::FillFiles(const wxTreeItemId& id)
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->Freeze();
+ lst->ClearAll();
+ CreateListColumns();
+
+ wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id);
+
+ // add files belonging to group
+ int counter = 0;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND))
+ {
+ // filter
+ if (FilterRec(&m_Recs[i]))
+ {
+ AddRecordToList(&m_Recs[i]);
+ ++counter;
+ }
+ }
+ }
+ lst->Thaw();
+
+ // select first item
+ lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+}
+
+void UpdateDlg::FillFileDetails(const wxListItem& id)
+{
+ wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl);
+ txt->Clear();
+
+ UpdateRec* cur = GetRecFromListView();
+ if (!cur)
+ {
+ txt->Clear();
+ EnableButtons();
+ return;
+ }
+ txt->AppendText(_("Name: ") + cur->name + _T("\n"));
+// txt->AppendText(_("Server: ") + cur->remote_server + _T("\n"));
+// txt->AppendText(_("File: ") + cur->remote_file + _T("\n"));
+ txt->AppendText(_("Version: ") + cur->version + _T("\n"));
+ txt->AppendText(_("Size: ") + cur->size + _T("\n"));
+ txt->AppendText(_("Date: ") + cur->date + _T("\n\n"));
+ txt->AppendText(_("Description: \n"));
+ txt->AppendText(cur->desc);
+
+ txt->SetSelection(0, 0);
+ txt->SetInsertionPoint(0);
+}
+
+void UpdateDlg::InternetUpdate(bool forceDownload)
+{
+ UpdateStatus(_("Please wait..."));
+ m_HasUpdated = false;
+ m_Net.SetServer(GetCurrentServer());
+
+ EnableButtons(false);
+ forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue();
+
+ bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename());
+ if (forceDownloadMirrors)
+ {
+ if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename()))
+ {
+ UpdateStatus(_("Error downloading list of mirrors"), 0, 0);
+ return;
+ }
+ else
+ {
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ }
+ }
+
+ wxString config = GetConfFilename();
+ forceDownload = forceDownload || !wxFileExists(config);
+ if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config))
+ {
+ UpdateStatus(_("Error downloading list of updates"), 0, 0);
+ return;
+ }
+ else
+ {
+ IniParser ini;
+ if (!ini.ParseFile(config))
+ {
+ UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0);
+ return;
+ }
+ ini.Sort();
+
+ if (m_Recs)
+ delete[] m_Recs;
+
+ // remember to delete[] m_Recs when we 're done with it!!!
+ // it's our responsibility once given to us
+ m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath());
+
+ FillGroups();
+ }
+ EnableButtons();
+ UpdateStatus(_("Ready"), 0, 0);
+
+ m_HasUpdated = true;
+}
+
+void UpdateDlg::FillServers()
+{
+ wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox);
+ cmb->Clear();
+ m_Servers.Clear();
+
+ IniParser ini;
+ ini.ParseFile(GetMirrorsFilename());
+ int group = ini.FindGroupByName(_T("WebUpdate mirrors"));
+ for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i)
+ {
+ cmb->Append(ini.GetKeyName(group, i));
+ m_Servers.Add(ini.GetKeyValue(group, i));
+ }
+ if (cmb->GetCount() == 0)
+ {
+ cmb->Append(_("devpaks.org Community Devpaks"));
+ m_Servers.Add(_T("http://devpaks.sourceforge.net/"));
+ }
+ cmb->SetSelection(0);
+}
+
+wxString UpdateDlg::GetConfFilename()
+{
+ int server_hash = GetTextCRC32(GetCurrentServer().mb_str());
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH;
+ config.Printf(_T("%sdevpak_%x.conf"), config.c_str(), server_hash);
+ return config;
+}
+
+wxString UpdateDlg::GetMirrorsFilename() const
+{
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH + _T("devpak_mirrors.cfg");
+ return config;
+}
+
+wxString UpdateDlg::GetCurrentServer() const
+{
+ return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()];
+}
+
+wxString UpdateDlg::GetBasePath() const
+{
+ return g_MasterPath + wxFILE_SEP_PATH;
+}
+
+wxString UpdateDlg::GetPackagePath() const
+{
+ return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH;
+}
+
+bool UpdateDlg::FilterRec(UpdateRec* rec)
+{
+ if (!rec)
+ return false;
+ wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox);
+ switch (cmb->GetSelection())
+ {
+ case 0: // All
+ return true;
+
+ case 1: // Installed
+ return rec->installed;
+
+ case 2: // installed with update available
+ return rec->installed && rec->version != rec->installed_version;
+
+ case 3: // downloaded but not installed
+ return rec->downloaded && !rec->installed;
+
+ case 4: // not installed
+ return !rec->downloaded && !rec->installed;
+
+ default:
+ return false;
+ }
+ return false; // doesn't reach here
+}
+
+void UpdateDlg::ApplyFilter()
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+
+ FillGroups();
+ FillFiles(tree->GetSelection());
+ EnableButtons();
+}
+
+UpdateRec* UpdateDlg::GetRecFromListView()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (index == -1)
+ return 0;
+ wxString title = lst->GetItemText(index);
+ return FindRecByTitle(title, m_Recs, m_RecsCount);
+}
+
+void UpdateDlg::DownloadFile(bool dontInstall)
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (rec->version == rec->installed_version)
+ {
+ if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO)
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ if (wxFileExists(GetPackagePath() + rec->local_file))
+ {
+ if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO &&
+ rec->installable)
+ {
+ if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ return;
+ }
+ }
+
+ m_Net.SetServer(rec->remote_server);
+
+ EnableButtons(false);
+ if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file))
+ {
+ rec->downloaded = false;
+ UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0);
+ return;
+ }
+ else
+ rec->downloaded = true;
+ UpdateStatus(_("Ready"), 0, 0);
+ EnableButtons();
+}
+
+void UpdateDlg::InstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ if (rec->title == _T("WebUpdate Mirrors list"))
+ {
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ rec->installed = true;
+ ApplyFilter();
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ else if (!rec->installable)
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ wxArrayString files;
+ DevPakInstaller inst;
+ if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files))
+ {
+// wxFileName fname(GetPackagePath() + rec->local_file);
+// fname.SetExt("entry");
+// fname.SetName(rec->title);
+// CreateEntryFile(rec, fname.GetFullPath(), files);
+ CreateEntryFile(rec, GetPackagePath() + rec->entry, files);
+ wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed = true;
+ rec->installed_version = rec->version;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+ UpdateStatus(_("Ready"), 0, 0);
+}
+
+void UpdateDlg::InstallMirrors(const wxString& file)
+{
+ if (!wxCopyFile(file, GetMirrorsFilename(), true))
+ wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR);
+ else
+ {
+ wxRemoveFile(file);
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION);
+ }
+}
+
+void UpdateDlg::UninstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ DevPakInstaller inst;
+ if (inst.Uninstall(GetPackagePath() + rec->entry))
+ {
+ wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed_version.Clear();
+ rec->installed = false;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+}
+
+void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files)
+{
+ wxString entry;
+ entry << _T("[Setup]\n");
+ entry << _T("AppName=") << rec->name << _T("\n");
+ entry << _T("AppVersion=") << rec->version << _T("\n");
+
+ entry << _T("\n");
+ entry << _T("[Files]\n");
+ for (unsigned int i = 0; i < files.GetCount(); ++i)
+ {
+ entry << files[i] << _T("\n");
+ }
+
+ wxFile f(filename, wxFile::write);
+ if (f.IsOpened())
+ {
+ f.Write(entry.mb_str(wxConvUTF8),entry.Length());
+ }
+}
+
+void UpdateDlg::OnFileRightClick(wxListEvent& event)
+{
+// LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n';
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ return;
+
+ wxMenu popup;
+ popup.Append(idPopupDownloadAndInstall, _("Download && install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupDownload, _("Download"));
+ popup.Append(idPopupInstall, _("Install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupUninstall, _("Uninstall"));
+
+ bool canDl = !rec->downloaded || rec->version != rec->installed_version;
+ bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version);
+
+ popup.Enable(idPopupDownload, canDl);
+ popup.Enable(idPopupInstall, canInst);
+ popup.Enable(idPopupDownloadAndInstall, canInst || canDl);
+ popup.Enable(idPopupUninstall, rec->installed);
+
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->PopupMenu(&popup, event.GetPoint());
+}
+
+void UpdateDlg::OnFileDeSelected(wxListEvent& event)
+{
+ wxListItem id;
+ FillFileDetails(id);
+ EnableButtons();
+}
+
+void UpdateDlg::OnFileSelected(wxListEvent& event)
+{
+ FillFileDetails(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event)
+{
+ FillFiles(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnDownload(wxCommandEvent& event)
+{
+ DownloadFile(true);
+}
+
+void UpdateDlg::OnInstall(wxCommandEvent& event)
+{
+ InstallFile();
+}
+
+void UpdateDlg::OnUninstall(wxCommandEvent& event)
+{
+ UninstallFile();
+}
+
+void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event)
+{
+ DownloadFile();
+}
+
+void UpdateDlg::OnServerChange(wxCommandEvent& event)
+{
+ InternetUpdate();
+}
+
+void UpdateDlg::OnFilterChange(wxCommandEvent& event)
+{
+ ApplyFilter();
+}
+
+void UpdateDlg::OnConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnDisConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnProgress(wxCommandEvent& event)
+{
+ int prg = -1;
+ if (m_CurrFileSize != 0)
+ prg = event.GetInt() * 100 / m_CurrFileSize;
+ UpdateStatus(_("Downloading: ") + event.GetString(), prg);
+
+ wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText);
+
+ wxString msg;
+ msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str());
+ lbl->SetLabel(msg);
+}
+
+void UpdateDlg::OnAborted(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadStarted(wxCommandEvent& event)
+{
+ m_CurrFileSize = event.GetInt();
+ UpdateStatus(_("Download started: ") + event.GetString(), 0, 100);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadEnded(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download finished: ") + event.GetString());
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+
+ if (m_HasUpdated && event.GetInt() == 0)
+ {
+ UpdateRec* rec = GetRecFromListView();
+ if (rec)
+ {
+ if (rec->bytes != m_CurrFileSize)
+ wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n"
+ "This, usually, means one of three things:\n"
+ "1) The reported size in the update list is wrong. The DevPak might still be valid.\n"
+ "2) The file's location returned a web error-page. Invalid DevPak...\n"
+ "3) The file is corrupt...\n\n"
+ "You can try to install it anyway. If it is not a valid DevPak, the operation will fail."),
+ _("Warning"), wxICON_WARNING);
+ }
+ if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ else if (rec && rec->title == _T("WebUpdate Mirrors list"))
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ }
+ m_CurrFileSize = 0;
+}
+
+void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event)
+{
+ // hack to display the download message *after* the dialog has been shown...
+ if (m_FirstTimeCheck)
+ {
+ m_FirstTimeCheck = false; // no more, just once
+ wxString config = GetConfFilename();
+ if (wxFileExists(config))
+ InternetUpdate();
+ else
+ {
+ if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InternetUpdate(true);
+ }
+ }
+}
diff --git a/tests/01uni_multi.from/.svn/text-base/updatedlg.h.svn-base b/tests/01uni_multi.from/.svn/text-base/updatedlg.h.svn-base new file mode 100644 index 0000000..6e317ca --- /dev/null +++ b/tests/01uni_multi.from/.svn/text-base/updatedlg.h.svn-base @@ -0,0 +1,73 @@ +#ifndef UPDATEDLG_H
+#define UPDATEDLG_H
+
+#include <wx/dialog.h>
+#include <wx/listctrl.h>
+#include <wx/treectrl.h>
+#include "cbnetwork.h"
+#include "conf.h"
+
+class UpdateDlg : public wxDialog
+{
+ public:
+ UpdateDlg(wxWindow* parent);
+ virtual ~UpdateDlg();
+
+ void EndModal(int retCode);
+ protected:
+ void OnFileSelected(wxListEvent& event);
+ void OnFileDeSelected(wxListEvent& event);
+ void OnFileRightClick(wxListEvent& event);
+ void OnTreeSelChanged(wxTreeEvent& event);
+ void OnDownload(wxCommandEvent& event);
+ void OnInstall(wxCommandEvent& event);
+ void OnUninstall(wxCommandEvent& event);
+ void OnDownloadAndInstall(wxCommandEvent& event);
+ void OnUpdate(wxCommandEvent& event);
+ void OnServerChange(wxCommandEvent& event);
+ void OnFilterChange(wxCommandEvent& event);
+ void OnConnect(wxCommandEvent& event);
+ void OnDisConnect(wxCommandEvent& event);
+ void OnProgress(wxCommandEvent& event);
+ void OnAborted(wxCommandEvent& event);
+ void OnDownloadStarted(wxCommandEvent& event);
+ void OnDownloadEnded(wxCommandEvent& event);
+ void OnUpdateUI(wxUpdateUIEvent& event);
+ private:
+ void InternetUpdate(bool forceDownload = false);
+ void DownloadFile(bool dontInstall = false);
+ void InstallFile();
+ void UninstallFile();
+ void InstallMirrors(const wxString& file);
+ void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files);
+ void EnableButtons(bool update = true, bool abort = true);
+ void FillServers();
+ void FillGroups();
+ void FillFiles(const wxTreeItemId& id);
+ void FillFileDetails(const wxListItem& id);
+ void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1);
+ UpdateRec* GetRecFromListView();
+ void CreateListColumns();
+ void AddRecordToList(UpdateRec* rec);
+ void SetListColumnText(int idx, int col, const wxString& text);
+
+ wxString GetConfFilename();
+ wxString GetMirrorsFilename() const;
+ wxString GetCurrentServer() const;
+ wxString GetBasePath() const;
+ wxString GetPackagePath() const;
+ bool FilterRec(UpdateRec* rec);
+ void ApplyFilter();
+
+ UpdateRec* m_Recs;
+ wxArrayString m_Servers;
+ int m_RecsCount;
+ int m_CurrFileSize;
+ int m_LastBlockSize; // for bps
+ bool m_HasUpdated;
+ bool m_FirstTimeCheck;
+ cbNetwork m_Net;
+ DECLARE_EVENT_TABLE();
+};
+
+#endif // UPDATEDLG_H
diff --git a/tests/01uni_multi.from/conf.cpp b/tests/01uni_multi.from/conf.cpp new file mode 100644 index 0000000..9962641 --- /dev/null +++ b/tests/01uni_multi.from/conf.cpp @@ -0,0 +1,110 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $
+ */
+
+#include "conf.h"
+#include <wx/intl.h>
+#include <wx/url.h>
+#include <wx/filename.h>
+#include <globals.h>
+
+wxString g_MasterPath;
+
+wxString GetSizeString(int bytes)
+{
+ wxString ret;
+ float kilobytes = (float)bytes / 1024.0f;
+ float megabytes = kilobytes / 1024.0f;
+ if (megabytes >= 1.0f)
+ ret.Printf(_("%.2f MB"), megabytes);
+ else if (kilobytes >= 1.0f)
+ ret.Printf(_("%.2f KB"), kilobytes);
+ else
+ ret.Printf(_("%ld bytes"), bytes);
+ return ret;
+}
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath)
+{
+ *recCount = 0;
+ int groupsCount = ini.GetGroupsCount();
+ if (groupsCount == 0)
+ return 0;
+
+ UpdateRec* list = new UpdateRec[ini.GetGroupsCount()];
+ for (int i = 0; i < groupsCount; ++i)
+ {
+ UpdateRec& rec = list[i];
+
+ rec.title = ini.GetGroupName(i);
+
+ // fix title
+ // devpaks.org has changed the title to contain some extra info
+ // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid]
+ // we don't need this extra info, so if we find it we remove it
+ int pos = rec.title.Find(_T("Library version:"));
+ if (pos != -1)
+ {
+ rec.title.Truncate(pos);
+ rec.title = rec.title.Trim(false);
+ rec.title = rec.title.Trim(true);
+ }
+
+ rec.name = ini.GetKeyValue(i, _T("Name"));
+ rec.desc = ini.GetKeyValue(i, _T("Description"));
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename"));
+ rec.local_file = ini.GetKeyValue(i, _T("LocalFilename"));
+ rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(","));
+ rec.install = ini.GetKeyValue(i, _T("InstallPath"));
+ rec.version = ini.GetKeyValue(i, _T("Version"));
+ ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes);
+ rec.date = ini.GetKeyValue(i, _T("Date"));
+ rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1");
+
+ // read .entry file (if exists)
+ rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry");
+ IniParser p;
+ p.ParseFile(appPath + rec.entry);
+ rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion"));
+
+ rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file);
+ rec.installed = !rec.installed_version.IsEmpty();
+
+ // calculate size
+ rec.size = GetSizeString(rec.bytes);
+
+ // fix-up
+ if (rec.name.IsEmpty())
+ rec.name = rec.title;
+ rec.desc.Replace(_T("<CR>"), _T("\n"));
+ rec.desc.Replace(_T("<LF>"), _T("\r"));
+ wxURL url(rec.remote_file);
+ if (!url.GetServer().IsEmpty())
+ {
+ rec.remote_server = url.GetScheme() + _T("://") + url.GetServer();
+ int pos = rec.remote_file.Find(url.GetServer());
+ if (pos != wxNOT_FOUND)
+ rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1);
+ }
+ else
+ rec.remote_server = currentServer;
+ }
+
+ *recCount = groupsCount;
+ return list;
+}
+
+UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count)
+{
+ for (int i = 0; i < count; ++i)
+ {
+ if (list[i].title == title)
+ return &list[i];
+ }
+ return 0;
+}
diff --git a/tests/01uni_multi.from/conf.h b/tests/01uni_multi.from/conf.h new file mode 100644 index 0000000..03a0179 --- /dev/null +++ b/tests/01uni_multi.from/conf.h @@ -0,0 +1,38 @@ +#ifndef CONF_H
+#define CONF_H
+
+#include <wx/string.h>
+#include <wx/dynarray.h>
+#include "cbiniparser.h"
+
+struct UpdateRec
+{
+ wxString entry;
+ wxString title;
+ wxString name;
+ wxString desc;
+ wxString remote_server;
+ wxString remote_file;
+ wxString local_file;
+ wxArrayString groups;
+ wxString install;
+ wxString version;
+ wxString installed_version;
+ long int bytes;
+ float kilobytes;
+ float megabytes;
+ wxString size;
+ wxString date;
+ bool installable;
+ bool downloaded;
+ bool installed;
+};
+
+extern wxString g_MasterPath;
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath);
+UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count);
+// utility
+wxString GetSizeString(int bytes);
+
+#endif // CONF_H
diff --git a/tests/01uni_multi.from/manifest.xml b/tests/01uni_multi.from/manifest.xml new file mode 100644 index 0000000..c7bc705 --- /dev/null +++ b/tests/01uni_multi.from/manifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+ <Value title="Dev-C++ DevPak updater/installer" />
+ <Value version="0.1" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+ Julian R Seward for libbzip2.
+ libbzip2 copyright notice:
+ bzip2 and associated library libbzip2, are
+ copyright (C) 1996-2000 Julian R Seward.
+ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+</CodeBlocks_plugin_manifest_file>
diff --git a/tests/01uni_multi.from/updatedlg.cpp b/tests/01uni_multi.from/updatedlg.cpp new file mode 100644 index 0000000..829b38a --- /dev/null +++ b/tests/01uni_multi.from/updatedlg.cpp @@ -0,0 +1,726 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: updatedlg.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/updatedlg.cpp $
+ */
+
+#include "updatedlg.h"
+#include <wx/xrc/xmlres.h>
+#include <wx/msgdlg.h>
+#include <wx/button.h>
+#include <wx/gauge.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/combobox.h>
+#include <wx/checkbox.h>
+#include <wx/file.h>
+#include <wx/menu.h>
+#include "devpakinstaller.h"
+#include "crc32.h"
+
+#include "manager.h"
+#include "configmanager.h"
+#include "globals.h"
+
+int idNet = wxNewId();
+int idPopupInstall = wxNewId();
+int idPopupDownload = wxNewId();
+int idPopupDownloadAndInstall = wxNewId();
+int idPopupUninstall = wxNewId();
+
+BEGIN_EVENT_TABLE(UpdateDlg, wxDialog)
+ EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI)
+ EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged)
+ EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected)
+ EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected)
+ EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick)
+ EVT_MENU(idPopupDownload, UpdateDlg::OnDownload)
+ EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall)
+ EVT_MENU(idPopupInstall, UpdateDlg::OnInstall)
+ EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall)
+ EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange)
+ EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange)
+ EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange)
+ EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect)
+ EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect)
+ EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress)
+ EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted)
+ EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted)
+ EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded)
+END_EVENT_TABLE()
+
+UpdateDlg::UpdateDlg(wxWindow* parent)
+ : m_Recs(0),
+ m_RecsCount(0),
+ m_CurrFileSize(0),
+ m_LastBlockSize(0),
+ m_HasUpdated(false),
+ m_FirstTimeCheck(true),
+ m_Net(this, idNet, _T("http://devpaks.sourceforge.net/"))
+{
+ //ctor
+ wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame"));
+ CreateListColumns();
+ FillServers();
+ UpdateStatus(_("Ready"), 0);
+}
+
+UpdateDlg::~UpdateDlg()
+{
+ //dtor
+ delete[] m_Recs;
+ m_RecsCount = 0;
+}
+
+void UpdateDlg::EndModal(int retCode)
+{
+ if (!m_Net.IsConnected() || retCode != wxID_CANCEL)
+ {
+ wxDialog::EndModal(retCode);
+ return;
+ }
+
+ if (m_Net.IsConnected())
+ m_Net.Abort();
+}
+
+void UpdateDlg::CreateListColumns()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->InsertColumn(0, _("Title"));
+ lst->InsertColumn(1, _("Version"));
+ lst->InsertColumn(2, _("Installed"));
+ lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);
+
+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space
+ lst->SetColumnWidth(1, 64);
+ lst->SetColumnWidth(2, 64);
+ lst->SetColumnWidth(3, 64);
+}
+
+void UpdateDlg::AddRecordToList(UpdateRec* rec)
+{
+ if (!rec)
+ return;
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int idx = lst->GetItemCount();
+ lst->InsertItem(idx, rec->title);
+ lst->SetItem(idx, 1, rec->version);
+ lst->SetItem(idx, 2, rec->installed_version);
+ lst->SetItem(idx, 3, rec->size);
+}
+
+void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem it;
+ it.m_itemId = index;
+ it.m_col = col;
+ it.m_mask = wxLIST_MASK_TEXT;
+ it.m_text = text;
+ lst->SetItem(it);
+}
+
+void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress)
+{
+ wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText);
+ if (lbl->GetLabel() != status)
+ lbl->SetLabel(status);
+ if (curProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress);
+ if (maxProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress);
+}
+
+void UpdateDlg::EnableButtons(bool update, bool abort)
+{
+ wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton);
+
+ btnCl->Enable(abort);
+ // disable server list and cache checkbox while downloading
+ XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected());
+ XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected());
+
+ wxYield();
+}
+
+void UpdateDlg::FillGroups()
+{
+ UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1);
+
+ // get a list of unique group names
+ wxArrayString groups;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x)
+ {
+ if (m_Recs[i].groups[x].IsEmpty())
+ continue;
+ if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND)
+ {
+ if (FilterRec(&m_Recs[i]))
+ groups.Add(m_Recs[i].groups[x]);
+ }
+ }
+ }
+
+ // create the groups tree
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ tree->Freeze();
+ tree->DeleteAllItems();
+ wxTreeItemId root = tree->AddRoot(_("All categories"));
+ for (unsigned int i = 0; i < groups.GetCount(); ++i)
+ {
+ tree->AppendItem(root, groups[i]);
+ }
+ tree->SortChildren(root);
+ tree->Thaw();
+ tree->Expand(root);
+ tree->SelectItem(root); // this calls the event
+
+ UpdateStatus(_("Done parsing list of updates"), 0);
+}
+
+void UpdateDlg::FillFiles(const wxTreeItemId& id)
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->Freeze();
+ lst->ClearAll();
+ CreateListColumns();
+
+ wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id);
+
+ // add files belonging to group
+ int counter = 0;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND))
+ {
+ // filter
+ if (FilterRec(&m_Recs[i]))
+ {
+ AddRecordToList(&m_Recs[i]);
+ ++counter;
+ }
+ }
+ }
+ lst->Thaw();
+
+ // select first item
+ lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+}
+
+void UpdateDlg::FillFileDetails(const wxListItem& id)
+{
+ wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl);
+ txt->Clear();
+
+ UpdateRec* cur = GetRecFromListView();
+ if (!cur)
+ {
+ txt->Clear();
+ EnableButtons();
+ return;
+ }
+ txt->AppendText(_("Name: ") + cur->name + _T("\n"));
+// txt->AppendText(_("Server: ") + cur->remote_server + _T("\n"));
+// txt->AppendText(_("File: ") + cur->remote_file + _T("\n"));
+ txt->AppendText(_("Version: ") + cur->version + _T("\n"));
+ txt->AppendText(_("Size: ") + cur->size + _T("\n"));
+ txt->AppendText(_("Date: ") + cur->date + _T("\n\n"));
+ txt->AppendText(_("Description: \n"));
+ txt->AppendText(cur->desc);
+
+ txt->SetSelection(0, 0);
+ txt->SetInsertionPoint(0);
+}
+
+void UpdateDlg::InternetUpdate(bool forceDownload)
+{
+ UpdateStatus(_("Please wait..."));
+ m_HasUpdated = false;
+ m_Net.SetServer(GetCurrentServer());
+
+ EnableButtons(false);
+ forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue();
+
+ bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename());
+ if (forceDownloadMirrors)
+ {
+ if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename()))
+ {
+ UpdateStatus(_("Error downloading list of mirrors"), 0, 0);
+ return;
+ }
+ else
+ {
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ }
+ }
+
+ wxString config = GetConfFilename();
+ forceDownload = forceDownload || !wxFileExists(config);
+ if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config))
+ {
+ UpdateStatus(_("Error downloading list of updates"), 0, 0);
+ return;
+ }
+ else
+ {
+ IniParser ini;
+ if (!ini.ParseFile(config))
+ {
+ UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0);
+ return;
+ }
+ ini.Sort();
+
+ if (m_Recs)
+ delete[] m_Recs;
+
+ // remember to delete[] m_Recs when we 're done with it!!!
+ // it's our responsibility once given to us
+ m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath());
+
+ FillGroups();
+ }
+ EnableButtons();
+ UpdateStatus(_("Ready"), 0, 0);
+
+ m_HasUpdated = true;
+}
+
+void UpdateDlg::FillServers()
+{
+ wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox);
+ cmb->Clear();
+ m_Servers.Clear();
+
+ IniParser ini;
+ ini.ParseFile(GetMirrorsFilename());
+ int group = ini.FindGroupByName(_T("WebUpdate mirrors"));
+ for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i)
+ {
+ cmb->Append(ini.GetKeyName(group, i));
+ m_Servers.Add(ini.GetKeyValue(group, i));
+ }
+ if (cmb->GetCount() == 0)
+ {
+ cmb->Append(_("devpaks.org Community Devpaks"));
+ m_Servers.Add(_T("http://devpaks.sourceforge.net/"));
+ }
+ cmb->SetSelection(0);
+}
+
+wxString UpdateDlg::GetConfFilename()
+{
+ int server_hash = GetTextCRC32(GetCurrentServer().mb_str());
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH;
+ config.Printf(_T("%sdevpak_%x.conf"), config.c_str(), server_hash);
+ return config;
+}
+
+wxString UpdateDlg::GetMirrorsFilename() const
+{
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH + _T("devpak_mirrors.cfg");
+ return config;
+}
+
+wxString UpdateDlg::GetCurrentServer() const
+{
+ return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()];
+}
+
+wxString UpdateDlg::GetBasePath() const
+{
+ return g_MasterPath + wxFILE_SEP_PATH;
+}
+
+wxString UpdateDlg::GetPackagePath() const
+{
+ return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH;
+}
+
+bool UpdateDlg::FilterRec(UpdateRec* rec)
+{
+ if (!rec)
+ return false;
+ wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox);
+ switch (cmb->GetSelection())
+ {
+ case 0: // All
+ return true;
+
+ case 1: // Installed
+ return rec->installed;
+
+ case 2: // installed with update available
+ return rec->installed && rec->version != rec->installed_version;
+
+ case 3: // downloaded but not installed
+ return rec->downloaded && !rec->installed;
+
+ case 4: // not installed
+ return !rec->downloaded && !rec->installed;
+
+ default:
+ return false;
+ }
+ return false; // doesn't reach here
+}
+
+void UpdateDlg::ApplyFilter()
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+
+ FillGroups();
+ FillFiles(tree->GetSelection());
+ EnableButtons();
+}
+
+UpdateRec* UpdateDlg::GetRecFromListView()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (index == -1)
+ return 0;
+ wxString title = lst->GetItemText(index);
+ return FindRecByTitle(title, m_Recs, m_RecsCount);
+}
+
+void UpdateDlg::DownloadFile(bool dontInstall)
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (rec->version == rec->installed_version)
+ {
+ if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO)
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ if (wxFileExists(GetPackagePath() + rec->local_file))
+ {
+ if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO &&
+ rec->installable)
+ {
+ if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ return;
+ }
+ }
+
+ m_Net.SetServer(rec->remote_server);
+
+ EnableButtons(false);
+ if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file))
+ {
+ rec->downloaded = false;
+ UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0);
+ return;
+ }
+ else
+ rec->downloaded = true;
+ UpdateStatus(_("Ready"), 0, 0);
+ EnableButtons();
+}
+
+void UpdateDlg::InstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ if (rec->title == _T("WebUpdate Mirrors list"))
+ {
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ rec->installed = true;
+ ApplyFilter();
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ else if (!rec->installable)
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ wxArrayString files;
+ DevPakInstaller inst;
+ if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files))
+ {
+// wxFileName fname(GetPackagePath() + rec->local_file);
+// fname.SetExt("entry");
+// fname.SetName(rec->title);
+// CreateEntryFile(rec, fname.GetFullPath(), files);
+ CreateEntryFile(rec, GetPackagePath() + rec->entry, files);
+ wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed = true;
+ rec->installed_version = rec->version;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+ UpdateStatus(_("Ready"), 0, 0);
+}
+
+void UpdateDlg::InstallMirrors(const wxString& file)
+{
+ if (!wxCopyFile(file, GetMirrorsFilename(), true))
+ wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR);
+ else
+ {
+ wxRemoveFile(file);
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION);
+ }
+}
+
+void UpdateDlg::UninstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ DevPakInstaller inst;
+ if (inst.Uninstall(GetPackagePath() + rec->entry))
+ {
+ wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed_version.Clear();
+ rec->installed = false;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+}
+
+void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files)
+{
+ wxString entry;
+ entry << _T("[Setup]\n");
+ entry << _T("AppName=") << rec->name << _T("\n");
+ entry << _T("AppVersion=") << rec->version << _T("\n");
+
+ entry << _T("\n");
+ entry << _T("[Files]\n");
+ for (unsigned int i = 0; i < files.GetCount(); ++i)
+ {
+ entry << files[i] << _T("\n");
+ }
+
+ wxFile f(filename, wxFile::write);
+ if (f.IsOpened())
+ {
+ f.Write(entry.mb_str(wxConvUTF8),entry.Length());
+ }
+}
+
+void UpdateDlg::OnFileRightClick(wxListEvent& event)
+{
+// LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n';
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ return;
+
+ wxMenu popup;
+ popup.Append(idPopupDownloadAndInstall, _("Download && install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupDownload, _("Download"));
+ popup.Append(idPopupInstall, _("Install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupUninstall, _("Uninstall"));
+
+ bool canDl = !rec->downloaded || rec->version != rec->installed_version;
+ bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version);
+
+ popup.Enable(idPopupDownload, canDl);
+ popup.Enable(idPopupInstall, canInst);
+ popup.Enable(idPopupDownloadAndInstall, canInst || canDl);
+ popup.Enable(idPopupUninstall, rec->installed);
+
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->PopupMenu(&popup, event.GetPoint());
+}
+
+void UpdateDlg::OnFileDeSelected(wxListEvent& event)
+{
+ wxListItem id;
+ FillFileDetails(id);
+ EnableButtons();
+}
+
+void UpdateDlg::OnFileSelected(wxListEvent& event)
+{
+ FillFileDetails(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event)
+{
+ FillFiles(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnDownload(wxCommandEvent& event)
+{
+ DownloadFile(true);
+}
+
+void UpdateDlg::OnInstall(wxCommandEvent& event)
+{
+ InstallFile();
+}
+
+void UpdateDlg::OnUninstall(wxCommandEvent& event)
+{
+ UninstallFile();
+}
+
+void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event)
+{
+ DownloadFile();
+}
+
+void UpdateDlg::OnServerChange(wxCommandEvent& event)
+{
+ InternetUpdate();
+}
+
+void UpdateDlg::OnFilterChange(wxCommandEvent& event)
+{
+ ApplyFilter();
+}
+
+void UpdateDlg::OnConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnDisConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnProgress(wxCommandEvent& event)
+{
+ int prg = -1;
+ if (m_CurrFileSize != 0)
+ prg = event.GetInt() * 100 / m_CurrFileSize;
+ UpdateStatus(_("Downloading: ") + event.GetString(), prg);
+
+ wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText);
+
+ wxString msg;
+ msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str());
+ lbl->SetLabel(msg);
+}
+
+void UpdateDlg::OnAborted(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadStarted(wxCommandEvent& event)
+{
+ m_CurrFileSize = event.GetInt();
+ UpdateStatus(_("Download started: ") + event.GetString(), 0, 100);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadEnded(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download finished: ") + event.GetString());
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+
+ if (m_HasUpdated && event.GetInt() == 0)
+ {
+ UpdateRec* rec = GetRecFromListView();
+ if (rec)
+ {
+ if (rec->bytes != m_CurrFileSize)
+ wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n"
+ "This, usually, means one of three things:\n"
+ "1) The reported size in the update list is wrong. The DevPak might still be valid.\n"
+ "2) The file's location returned a web error-page. Invalid DevPak...\n"
+ "3) The file is corrupt...\n\n"
+ "You can try to install it anyway. If it is not a valid DevPak, the operation will fail."),
+ _("Warning"), wxICON_WARNING);
+ }
+ if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ else if (rec && rec->title == _T("WebUpdate Mirrors list"))
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ }
+ m_CurrFileSize = 0;
+}
+
+void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event)
+{
+ // hack to display the download message *after* the dialog has been shown...
+ if (m_FirstTimeCheck)
+ {
+ m_FirstTimeCheck = false; // no more, just once
+ wxString config = GetConfFilename();
+ if (wxFileExists(config))
+ InternetUpdate();
+ else
+ {
+ if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InternetUpdate(true);
+ }
+ }
+}
diff --git a/tests/01uni_multi.from/updatedlg.h b/tests/01uni_multi.from/updatedlg.h new file mode 100644 index 0000000..6e317ca --- /dev/null +++ b/tests/01uni_multi.from/updatedlg.h @@ -0,0 +1,73 @@ +#ifndef UPDATEDLG_H
+#define UPDATEDLG_H
+
+#include <wx/dialog.h>
+#include <wx/listctrl.h>
+#include <wx/treectrl.h>
+#include "cbnetwork.h"
+#include "conf.h"
+
+class UpdateDlg : public wxDialog
+{
+ public:
+ UpdateDlg(wxWindow* parent);
+ virtual ~UpdateDlg();
+
+ void EndModal(int retCode);
+ protected:
+ void OnFileSelected(wxListEvent& event);
+ void OnFileDeSelected(wxListEvent& event);
+ void OnFileRightClick(wxListEvent& event);
+ void OnTreeSelChanged(wxTreeEvent& event);
+ void OnDownload(wxCommandEvent& event);
+ void OnInstall(wxCommandEvent& event);
+ void OnUninstall(wxCommandEvent& event);
+ void OnDownloadAndInstall(wxCommandEvent& event);
+ void OnUpdate(wxCommandEvent& event);
+ void OnServerChange(wxCommandEvent& event);
+ void OnFilterChange(wxCommandEvent& event);
+ void OnConnect(wxCommandEvent& event);
+ void OnDisConnect(wxCommandEvent& event);
+ void OnProgress(wxCommandEvent& event);
+ void OnAborted(wxCommandEvent& event);
+ void OnDownloadStarted(wxCommandEvent& event);
+ void OnDownloadEnded(wxCommandEvent& event);
+ void OnUpdateUI(wxUpdateUIEvent& event);
+ private:
+ void InternetUpdate(bool forceDownload = false);
+ void DownloadFile(bool dontInstall = false);
+ void InstallFile();
+ void UninstallFile();
+ void InstallMirrors(const wxString& file);
+ void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files);
+ void EnableButtons(bool update = true, bool abort = true);
+ void FillServers();
+ void FillGroups();
+ void FillFiles(const wxTreeItemId& id);
+ void FillFileDetails(const wxListItem& id);
+ void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1);
+ UpdateRec* GetRecFromListView();
+ void CreateListColumns();
+ void AddRecordToList(UpdateRec* rec);
+ void SetListColumnText(int idx, int col, const wxString& text);
+
+ wxString GetConfFilename();
+ wxString GetMirrorsFilename() const;
+ wxString GetCurrentServer() const;
+ wxString GetBasePath() const;
+ wxString GetPackagePath() const;
+ bool FilterRec(UpdateRec* rec);
+ void ApplyFilter();
+
+ UpdateRec* m_Recs;
+ wxArrayString m_Servers;
+ int m_RecsCount;
+ int m_CurrFileSize;
+ int m_LastBlockSize; // for bps
+ bool m_HasUpdated;
+ bool m_FirstTimeCheck;
+ cbNetwork m_Net;
+ DECLARE_EVENT_TABLE();
+};
+
+#endif // UPDATEDLG_H
diff --git a/tests/01uni_multi.patch b/tests/01uni_multi.patch new file mode 100644 index 0000000..ba5939c --- /dev/null +++ b/tests/01uni_multi.patch @@ -0,0 +1,180 @@ +Index: updatedlg.cpp
+===================================================================
+--- updatedlg.cpp (revision 5095)
++++ updatedlg.cpp (working copy)
+@@ -94,11 +94,13 @@
+ lst->InsertColumn(1, _("Version")); + lst->InsertColumn(2, _("Installed")); + lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT); ++ lst->InsertColumn(4, _("Rev")); + +- lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3) - 2); // 1st column takes all remaining space ++ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space + lst->SetColumnWidth(1, 64); + lst->SetColumnWidth(2, 64); + lst->SetColumnWidth(3, 64); ++ lst->SetColumnWidth(4, 40); + } + + void UpdateDlg::AddRecordToList(UpdateRec* rec) +@@ -111,8 +113,20 @@
+ lst->SetItem(idx, 1, rec->version); + lst->SetItem(idx, 2, rec->installed_version); + lst->SetItem(idx, 3, rec->size); ++ lst->SetItem(idx, 4, rec->revision); + } + ++wxString UpdateDlg::GetListColumnText(int idx, int col) { ++ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); ++ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx; ++ wxListItem info; ++ info.SetId(index); ++ info.SetColumn(col); ++ info.SetMask(wxLIST_MASK_TEXT); ++ lst->GetItem(info); ++ return info.GetText(); ++} ++ + void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text) + { + wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl); +@@ -393,7 +407,9 @@
+ if (index == -1) + return 0; + wxString title = lst->GetItemText(index); +- return FindRecByTitle(title, m_Recs, m_RecsCount); ++ wxString version = GetListColumnText(index, 1); ++ wxString revision = GetListColumnText(index, 4); ++ return FindRec(title, version, revision, m_Recs, m_RecsCount); + } + + void UpdateDlg::DownloadFile(bool dontInstall) +Index: updatedlg.h
+===================================================================
+--- updatedlg.h (revision 5095)
++++ updatedlg.h (working copy)
+@@ -49,6 +49,7 @@
+ UpdateRec* GetRecFromListView(); + void CreateListColumns(); + void AddRecordToList(UpdateRec* rec); ++ wxString GetListColumnText(int idx, int col); + void SetListColumnText(int idx, int col, const wxString& text); + + wxString GetConfFilename(); +Index: manifest.xml
+===================================================================
+--- manifest.xml (revision 5095)
++++ manifest.xml (working copy)
+@@ -2,18 +2,19 @@
+ <CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+- <Value title="Dev-C++ DevPak updater/installer" />
+- <Value version="0.1" />
++ <Value title="DevPak updater/installer" />
++ <Value version="0.2" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+- Julian R Seward for libbzip2.
+- libbzip2 copyright notice:
+- bzip2 and associated library libbzip2, are
+- copyright (C) 1996-2000 Julian R Seward.
+- All rights reserved." />
++ Julian R Seward for libbzip2.
++
++ libbzip2 copyright notice:
++ bzip2 and associated library libbzip2, are
++ copyright (C) 1996-2000 Julian R Seward.
++ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+ </CodeBlocks_plugin_manifest_file>
+Index: conf.cpp
+===================================================================
+--- conf.cpp (revision 5095)
++++ conf.cpp (working copy)
+@@ -46,10 +46,16 @@
+ // fix title + // devpaks.org has changed the title to contain some extra info + // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid] +- // we don't need this extra info, so if we find it we remove it +- int pos = rec.title.Find(_T("Library version:")); ++ int pos = rec.title.Lower().Find(_T("library version:")); + if (pos != -1) + { ++ int revpos = rec.title.Lower().Find(_T("devpak revision:")); ++ if (revpos != -1) { ++ rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false); ++ rec.revision.Replace(_T("\t"), _T(" ")); ++ rec.revision = rec.revision.BeforeFirst(_T(' ')); ++ } ++ + rec.title.Truncate(pos); + rec.title = rec.title.Trim(false); + rec.title = rec.title.Trim(true); +@@ -60,7 +66,7 @@
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename")); + rec.local_file = ini.GetKeyValue(i, _T("LocalFilename")); + rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(",")); +- rec.install = ini.GetKeyValue(i, _T("InstallPath")); ++ rec.install_path = ini.GetKeyValue(i, _T("InstallPath")); + rec.version = ini.GetKeyValue(i, _T("Version")); + ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes); + rec.date = ini.GetKeyValue(i, _T("Date")); +@@ -99,12 +105,17 @@
+ return list; + } + +-UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count) ++UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count) + { + for (int i = 0; i < count; ++i) + { +- if (list[i].title == title) +- return &list[i]; ++ if (list[i].title == title && list[i].version == version) { ++ if (revision.IsEmpty()) { ++ return &list[i]; ++ } else if (list[i].revision == revision) { ++ return &list[i]; ++ } ++ } + } + return 0; + } +Index: conf.h
+===================================================================
+--- conf.h (revision 5095)
++++ conf.h (working copy)
+@@ -7,7 +7,7 @@
+ + struct UpdateRec + { +- wxString entry; ++ wxString entry; //! .entry filename for installed + wxString title; + wxString name; + wxString desc; +@@ -15,8 +15,9 @@
+ wxString remote_file; + wxString local_file; + wxArrayString groups; +- wxString install; ++ wxString install_path; //! ignored + wxString version; ++ wxString revision; + wxString installed_version; + long int bytes; + float kilobytes; +@@ -31,7 +32,7 @@
+ extern wxString g_MasterPath; + + UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath); +-UpdateRec* FindRecByTitle(const wxString& title, UpdateRec* list, int count); ++UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count); + // utility + wxString GetSizeString(int bytes); + diff --git a/tests/01uni_multi.to/.svn/all-wcprops b/tests/01uni_multi.to/.svn/all-wcprops new file mode 100644 index 0000000..fac5824 --- /dev/null +++ b/tests/01uni_multi.to/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 43 +/svn/!svn/ver/38/trunk/tests/01uni_multi.to +END +updatedlg.h +K 25 +svn:wc:ra_dav:version-url +V 55 +/svn/!svn/ver/38/trunk/tests/01uni_multi.to/updatedlg.h +END +manifest.xml +K 25 +svn:wc:ra_dav:version-url +V 56 +/svn/!svn/ver/38/trunk/tests/01uni_multi.to/manifest.xml +END +conf.cpp +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/!svn/ver/38/trunk/tests/01uni_multi.to/conf.cpp +END +updatedlg.cpp +K 25 +svn:wc:ra_dav:version-url +V 57 +/svn/!svn/ver/14/trunk/tests/01uni_multi.to/updatedlg.cpp +END +conf.h +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/!svn/ver/38/trunk/tests/01uni_multi.to/conf.h +END diff --git a/tests/01uni_multi.to/.svn/dir-prop-base b/tests/01uni_multi.to/.svn/dir-prop-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/tests/01uni_multi.to/.svn/dir-prop-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/tests/01uni_multi.to/.svn/entries b/tests/01uni_multi.to/.svn/entries new file mode 100644 index 0000000..1013095 --- /dev/null +++ b/tests/01uni_multi.to/.svn/entries @@ -0,0 +1,198 @@ +10 + +dir +101 +http://python-patch.googlecode.com/svn/trunk/tests/01uni_multi.to +http://python-patch.googlecode.com/svn + + + +2008-12-31T11:24:21.549045Z +38 +techtonik +has-props + + + + + + + + + + + + + +7155d8b8-9951-0410-8dfa-c5fb0ae76a41 + +updatedlg.h +file + + + + +2010-10-21T22:54:45.411691Z +e64dcbcaefb553a2f38ac94ac266a6db +2008-12-31T11:24:21.549045Z +38 +techtonik + + + + + + + + + + + + + + + + + + + + + +2735 + +manifest.xml +file + + + + +2010-10-21T22:54:45.411691Z +1d72b0cffaaac3276061b95304d859a8 +2008-12-31T11:24:21.549045Z +38 +techtonik + + + + + + + + + + + + + + + + + + + + + +848 + +conf.cpp +file + + + + +2010-10-21T22:54:45.411691Z +c61e1a55442abb2767c7993b700a961e +2008-12-31T11:24:21.549045Z +38 +techtonik + + + + + + + + + + + + + + + + + + + + + +4262 + +updatedlg.cpp +file + + + + +2010-10-21T22:54:45.412691Z +9dc113de05c0c7fe9dec26a0fa5003c9 +2008-07-11T12:00:28.935567Z +14 +techtonik +has-props + + + + + + + + + + + + + + + + + + + + +23112 + +conf.h +file + + + + +2010-10-21T22:54:45.412691Z +e51ac9a67934ebe32a050641edd535f3 +2008-12-31T11:24:21.549045Z +38 +techtonik + + + + + + + + + + + + + + + + + + + + + +1003 + diff --git a/tests/01uni_multi.to/.svn/prop-base/updatedlg.cpp.svn-base b/tests/01uni_multi.to/.svn/prop-base/updatedlg.cpp.svn-base new file mode 100644 index 0000000..3160658 --- /dev/null +++ b/tests/01uni_multi.to/.svn/prop-base/updatedlg.cpp.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mergeinfo +V 0 + +END diff --git a/tests/01uni_multi.to/.svn/text-base/conf.cpp.svn-base b/tests/01uni_multi.to/.svn/text-base/conf.cpp.svn-base new file mode 100644 index 0000000..bdd5349 --- /dev/null +++ b/tests/01uni_multi.to/.svn/text-base/conf.cpp.svn-base @@ -0,0 +1,121 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $
+ */
+
+#include "conf.h"
+#include <wx/intl.h>
+#include <wx/url.h>
+#include <wx/filename.h>
+#include <globals.h>
+
+wxString g_MasterPath;
+
+wxString GetSizeString(int bytes)
+{
+ wxString ret;
+ float kilobytes = (float)bytes / 1024.0f;
+ float megabytes = kilobytes / 1024.0f;
+ if (megabytes >= 1.0f)
+ ret.Printf(_("%.2f MB"), megabytes);
+ else if (kilobytes >= 1.0f)
+ ret.Printf(_("%.2f KB"), kilobytes);
+ else
+ ret.Printf(_("%ld bytes"), bytes);
+ return ret;
+}
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath)
+{
+ *recCount = 0;
+ int groupsCount = ini.GetGroupsCount();
+ if (groupsCount == 0)
+ return 0;
+
+ UpdateRec* list = new UpdateRec[ini.GetGroupsCount()];
+ for (int i = 0; i < groupsCount; ++i)
+ {
+ UpdateRec& rec = list[i];
+
+ rec.title = ini.GetGroupName(i);
+
+ // fix title
+ // devpaks.org has changed the title to contain some extra info
+ // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid]
+ int pos = rec.title.Lower().Find(_T("library version:"));
+ if (pos != -1)
+ {
+ int revpos = rec.title.Lower().Find(_T("devpak revision:"));
+ if (revpos != -1) {
+ rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false);
+ rec.revision.Replace(_T("\t"), _T(" "));
+ rec.revision = rec.revision.BeforeFirst(_T(' '));
+ }
+
+ rec.title.Truncate(pos);
+ rec.title = rec.title.Trim(false);
+ rec.title = rec.title.Trim(true);
+ }
+
+ rec.name = ini.GetKeyValue(i, _T("Name"));
+ rec.desc = ini.GetKeyValue(i, _T("Description"));
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename"));
+ rec.local_file = ini.GetKeyValue(i, _T("LocalFilename"));
+ rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(","));
+ rec.install_path = ini.GetKeyValue(i, _T("InstallPath"));
+ rec.version = ini.GetKeyValue(i, _T("Version"));
+ ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes);
+ rec.date = ini.GetKeyValue(i, _T("Date"));
+ rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1");
+
+ // read .entry file (if exists)
+ rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry");
+ IniParser p;
+ p.ParseFile(appPath + rec.entry);
+ rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion"));
+
+ rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file);
+ rec.installed = !rec.installed_version.IsEmpty();
+
+ // calculate size
+ rec.size = GetSizeString(rec.bytes);
+
+ // fix-up
+ if (rec.name.IsEmpty())
+ rec.name = rec.title;
+ rec.desc.Replace(_T("<CR>"), _T("\n"));
+ rec.desc.Replace(_T("<LF>"), _T("\r"));
+ wxURL url(rec.remote_file);
+ if (!url.GetServer().IsEmpty())
+ {
+ rec.remote_server = url.GetScheme() + _T("://") + url.GetServer();
+ int pos = rec.remote_file.Find(url.GetServer());
+ if (pos != wxNOT_FOUND)
+ rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1);
+ }
+ else
+ rec.remote_server = currentServer;
+ }
+
+ *recCount = groupsCount;
+ return list;
+}
+
+UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count)
+{
+ for (int i = 0; i < count; ++i)
+ {
+ if (list[i].title == title && list[i].version == version) {
+ if (revision.IsEmpty()) {
+ return &list[i];
+ } else if (list[i].revision == revision) {
+ return &list[i];
+ }
+ }
+ }
+ return 0;
+}
diff --git a/tests/01uni_multi.to/.svn/text-base/conf.h.svn-base b/tests/01uni_multi.to/.svn/text-base/conf.h.svn-base new file mode 100644 index 0000000..6788ba5 --- /dev/null +++ b/tests/01uni_multi.to/.svn/text-base/conf.h.svn-base @@ -0,0 +1,39 @@ +#ifndef CONF_H
+#define CONF_H
+
+#include <wx/string.h>
+#include <wx/dynarray.h>
+#include "cbiniparser.h"
+
+struct UpdateRec
+{
+ wxString entry; //! .entry filename for installed
+ wxString title;
+ wxString name;
+ wxString desc;
+ wxString remote_server;
+ wxString remote_file;
+ wxString local_file;
+ wxArrayString groups;
+ wxString install_path; //! ignored
+ wxString version;
+ wxString revision;
+ wxString installed_version;
+ long int bytes;
+ float kilobytes;
+ float megabytes;
+ wxString size;
+ wxString date;
+ bool installable;
+ bool downloaded;
+ bool installed;
+};
+
+extern wxString g_MasterPath;
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath);
+UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count);
+// utility
+wxString GetSizeString(int bytes);
+
+#endif // CONF_H
diff --git a/tests/01uni_multi.to/.svn/text-base/manifest.xml.svn-base b/tests/01uni_multi.to/.svn/text-base/manifest.xml.svn-base new file mode 100644 index 0000000..578085c --- /dev/null +++ b/tests/01uni_multi.to/.svn/text-base/manifest.xml.svn-base @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+ <Value title="DevPak updater/installer" />
+ <Value version="0.2" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+ Julian R Seward for libbzip2.
+
+ libbzip2 copyright notice:
+ bzip2 and associated library libbzip2, are
+ copyright (C) 1996-2000 Julian R Seward.
+ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+</CodeBlocks_plugin_manifest_file>
diff --git a/tests/01uni_multi.to/.svn/text-base/updatedlg.cpp.svn-base b/tests/01uni_multi.to/.svn/text-base/updatedlg.cpp.svn-base new file mode 100644 index 0000000..99e01e6 --- /dev/null +++ b/tests/01uni_multi.to/.svn/text-base/updatedlg.cpp.svn-base @@ -0,0 +1,742 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: updatedlg.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/updatedlg.cpp $
+ */
+
+#include "updatedlg.h"
+#include <wx/xrc/xmlres.h>
+#include <wx/msgdlg.h>
+#include <wx/button.h>
+#include <wx/gauge.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/combobox.h>
+#include <wx/checkbox.h>
+#include <wx/file.h>
+#include <wx/menu.h>
+#include "devpakinstaller.h"
+#include "crc32.h"
+
+#include "manager.h"
+#include "configmanager.h"
+#include "globals.h"
+
+int idNet = wxNewId();
+int idPopupInstall = wxNewId();
+int idPopupDownload = wxNewId();
+int idPopupDownloadAndInstall = wxNewId();
+int idPopupUninstall = wxNewId();
+
+BEGIN_EVENT_TABLE(UpdateDlg, wxDialog)
+ EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI)
+ EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged)
+ EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected)
+ EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected)
+ EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick)
+ EVT_MENU(idPopupDownload, UpdateDlg::OnDownload)
+ EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall)
+ EVT_MENU(idPopupInstall, UpdateDlg::OnInstall)
+ EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall)
+ EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange)
+ EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange)
+ EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange)
+ EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect)
+ EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect)
+ EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress)
+ EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted)
+ EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted)
+ EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded)
+END_EVENT_TABLE()
+
+UpdateDlg::UpdateDlg(wxWindow* parent)
+ : m_Recs(0),
+ m_RecsCount(0),
+ m_CurrFileSize(0),
+ m_LastBlockSize(0),
+ m_HasUpdated(false),
+ m_FirstTimeCheck(true),
+ m_Net(this, idNet, _T("http://devpaks.sourceforge.net/"))
+{
+ //ctor
+ wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame"));
+ CreateListColumns();
+ FillServers();
+ UpdateStatus(_("Ready"), 0);
+}
+
+UpdateDlg::~UpdateDlg()
+{
+ //dtor
+ delete[] m_Recs;
+ m_RecsCount = 0;
+}
+
+void UpdateDlg::EndModal(int retCode)
+{
+ if (!m_Net.IsConnected() || retCode != wxID_CANCEL)
+ {
+ wxDialog::EndModal(retCode);
+ return;
+ }
+
+ if (m_Net.IsConnected())
+ m_Net.Abort();
+}
+
+void UpdateDlg::CreateListColumns()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->InsertColumn(0, _("Title"));
+ lst->InsertColumn(1, _("Version"));
+ lst->InsertColumn(2, _("Installed"));
+ lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);
+ lst->InsertColumn(4, _("Rev"));
+
+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space
+ lst->SetColumnWidth(1, 64);
+ lst->SetColumnWidth(2, 64);
+ lst->SetColumnWidth(3, 64);
+ lst->SetColumnWidth(4, 40);
+}
+
+void UpdateDlg::AddRecordToList(UpdateRec* rec)
+{
+ if (!rec)
+ return;
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int idx = lst->GetItemCount();
+ lst->InsertItem(idx, rec->title);
+ lst->SetItem(idx, 1, rec->version);
+ lst->SetItem(idx, 2, rec->installed_version);
+ lst->SetItem(idx, 3, rec->size);
+ lst->SetItem(idx, 4, rec->revision);
+}
+
+wxString UpdateDlg::GetListColumnText(int idx, int col) {
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem info;
+ info.SetId(index);
+ info.SetColumn(col);
+ info.SetMask(wxLIST_MASK_TEXT);
+ lst->GetItem(info);
+ return info.GetText();
+}
+
+void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem it;
+ it.m_itemId = index;
+ it.m_col = col;
+ it.m_mask = wxLIST_MASK_TEXT;
+ it.m_text = text;
+ lst->SetItem(it);
+}
+
+void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress)
+{
+ wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText);
+ if (lbl->GetLabel() != status)
+ lbl->SetLabel(status);
+ if (curProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress);
+ if (maxProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress);
+}
+
+void UpdateDlg::EnableButtons(bool update, bool abort)
+{
+ wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton);
+
+ btnCl->Enable(abort);
+ // disable server list and cache checkbox while downloading
+ XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected());
+ XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected());
+
+ wxYield();
+}
+
+void UpdateDlg::FillGroups()
+{
+ UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1);
+
+ // get a list of unique group names
+ wxArrayString groups;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x)
+ {
+ if (m_Recs[i].groups[x].IsEmpty())
+ continue;
+ if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND)
+ {
+ if (FilterRec(&m_Recs[i]))
+ groups.Add(m_Recs[i].groups[x]);
+ }
+ }
+ }
+
+ // create the groups tree
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ tree->Freeze();
+ tree->DeleteAllItems();
+ wxTreeItemId root = tree->AddRoot(_("All categories"));
+ for (unsigned int i = 0; i < groups.GetCount(); ++i)
+ {
+ tree->AppendItem(root, groups[i]);
+ }
+ tree->SortChildren(root);
+ tree->Thaw();
+ tree->Expand(root);
+ tree->SelectItem(root); // this calls the event
+
+ UpdateStatus(_("Done parsing list of updates"), 0);
+}
+
+void UpdateDlg::FillFiles(const wxTreeItemId& id)
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->Freeze();
+ lst->ClearAll();
+ CreateListColumns();
+
+ wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id);
+
+ // add files belonging to group
+ int counter = 0;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND))
+ {
+ // filter
+ if (FilterRec(&m_Recs[i]))
+ {
+ AddRecordToList(&m_Recs[i]);
+ ++counter;
+ }
+ }
+ }
+ lst->Thaw();
+
+ // select first item
+ lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+}
+
+void UpdateDlg::FillFileDetails(const wxListItem& id)
+{
+ wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl);
+ txt->Clear();
+
+ UpdateRec* cur = GetRecFromListView();
+ if (!cur)
+ {
+ txt->Clear();
+ EnableButtons();
+ return;
+ }
+ txt->AppendText(_("Name: ") + cur->name + _T("\n"));
+// txt->AppendText(_("Server: ") + cur->remote_server + _T("\n"));
+// txt->AppendText(_("File: ") + cur->remote_file + _T("\n"));
+ txt->AppendText(_("Version: ") + cur->version + _T("\n"));
+ txt->AppendText(_("Size: ") + cur->size + _T("\n"));
+ txt->AppendText(_("Date: ") + cur->date + _T("\n\n"));
+ txt->AppendText(_("Description: \n"));
+ txt->AppendText(cur->desc);
+
+ txt->SetSelection(0, 0);
+ txt->SetInsertionPoint(0);
+}
+
+void UpdateDlg::InternetUpdate(bool forceDownload)
+{
+ UpdateStatus(_("Please wait..."));
+ m_HasUpdated = false;
+ m_Net.SetServer(GetCurrentServer());
+
+ EnableButtons(false);
+ forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue();
+
+ bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename());
+ if (forceDownloadMirrors)
+ {
+ if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename()))
+ {
+ UpdateStatus(_("Error downloading list of mirrors"), 0, 0);
+ return;
+ }
+ else
+ {
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ }
+ }
+
+ wxString config = GetConfFilename();
+ forceDownload = forceDownload || !wxFileExists(config);
+ if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config))
+ {
+ UpdateStatus(_("Error downloading list of updates"), 0, 0);
+ return;
+ }
+ else
+ {
+ IniParser ini;
+ if (!ini.ParseFile(config))
+ {
+ UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0);
+ return;
+ }
+ ini.Sort();
+
+ if (m_Recs)
+ delete[] m_Recs;
+
+ // remember to delete[] m_Recs when we 're done with it!!!
+ // it's our responsibility once given to us
+ m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath());
+
+ FillGroups();
+ }
+ EnableButtons();
+ UpdateStatus(_("Ready"), 0, 0);
+
+ m_HasUpdated = true;
+}
+
+void UpdateDlg::FillServers()
+{
+ wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox);
+ cmb->Clear();
+ m_Servers.Clear();
+
+ IniParser ini;
+ ini.ParseFile(GetMirrorsFilename());
+ int group = ini.FindGroupByName(_T("WebUpdate mirrors"));
+ for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i)
+ {
+ cmb->Append(ini.GetKeyName(group, i));
+ m_Servers.Add(ini.GetKeyValue(group, i));
+ }
+ if (cmb->GetCount() == 0)
+ {
+ cmb->Append(_("devpaks.org Community Devpaks"));
+ m_Servers.Add(_T("http://devpaks.sourceforge.net/"));
+ }
+ cmb->SetSelection(0);
+}
+
+wxString UpdateDlg::GetConfFilename()
+{
+ int server_hash = GetTextCRC32(GetCurrentServer().mb_str());
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH;
+ config.Printf(_T("%sdevpak_%x.conf"), config.c_str(), server_hash);
+ return config;
+}
+
+wxString UpdateDlg::GetMirrorsFilename() const
+{
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH + _T("devpak_mirrors.cfg");
+ return config;
+}
+
+wxString UpdateDlg::GetCurrentServer() const
+{
+ return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()];
+}
+
+wxString UpdateDlg::GetBasePath() const
+{
+ return g_MasterPath + wxFILE_SEP_PATH;
+}
+
+wxString UpdateDlg::GetPackagePath() const
+{
+ return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH;
+}
+
+bool UpdateDlg::FilterRec(UpdateRec* rec)
+{
+ if (!rec)
+ return false;
+ wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox);
+ switch (cmb->GetSelection())
+ {
+ case 0: // All
+ return true;
+
+ case 1: // Installed
+ return rec->installed;
+
+ case 2: // installed with update available
+ return rec->installed && rec->version != rec->installed_version;
+
+ case 3: // downloaded but not installed
+ return rec->downloaded && !rec->installed;
+
+ case 4: // not installed
+ return !rec->downloaded && !rec->installed;
+
+ default:
+ return false;
+ }
+ return false; // doesn't reach here
+}
+
+void UpdateDlg::ApplyFilter()
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+
+ FillGroups();
+ FillFiles(tree->GetSelection());
+ EnableButtons();
+}
+
+UpdateRec* UpdateDlg::GetRecFromListView()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (index == -1)
+ return 0;
+ wxString title = lst->GetItemText(index);
+ wxString version = GetListColumnText(index, 1);
+ wxString revision = GetListColumnText(index, 4);
+ return FindRec(title, version, revision, m_Recs, m_RecsCount);
+}
+
+void UpdateDlg::DownloadFile(bool dontInstall)
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (rec->version == rec->installed_version)
+ {
+ if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO)
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ if (wxFileExists(GetPackagePath() + rec->local_file))
+ {
+ if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO &&
+ rec->installable)
+ {
+ if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ return;
+ }
+ }
+
+ m_Net.SetServer(rec->remote_server);
+
+ EnableButtons(false);
+ if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file))
+ {
+ rec->downloaded = false;
+ UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0);
+ return;
+ }
+ else
+ rec->downloaded = true;
+ UpdateStatus(_("Ready"), 0, 0);
+ EnableButtons();
+}
+
+void UpdateDlg::InstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ if (rec->title == _T("WebUpdate Mirrors list"))
+ {
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ rec->installed = true;
+ ApplyFilter();
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ else if (!rec->installable)
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ wxArrayString files;
+ DevPakInstaller inst;
+ if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files))
+ {
+// wxFileName fname(GetPackagePath() + rec->local_file);
+// fname.SetExt("entry");
+// fname.SetName(rec->title);
+// CreateEntryFile(rec, fname.GetFullPath(), files);
+ CreateEntryFile(rec, GetPackagePath() + rec->entry, files);
+ wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed = true;
+ rec->installed_version = rec->version;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+ UpdateStatus(_("Ready"), 0, 0);
+}
+
+void UpdateDlg::InstallMirrors(const wxString& file)
+{
+ if (!wxCopyFile(file, GetMirrorsFilename(), true))
+ wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR);
+ else
+ {
+ wxRemoveFile(file);
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION);
+ }
+}
+
+void UpdateDlg::UninstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ DevPakInstaller inst;
+ if (inst.Uninstall(GetPackagePath() + rec->entry))
+ {
+ wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed_version.Clear();
+ rec->installed = false;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+}
+
+void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files)
+{
+ wxString entry;
+ entry << _T("[Setup]\n");
+ entry << _T("AppName=") << rec->name << _T("\n");
+ entry << _T("AppVersion=") << rec->version << _T("\n");
+
+ entry << _T("\n");
+ entry << _T("[Files]\n");
+ for (unsigned int i = 0; i < files.GetCount(); ++i)
+ {
+ entry << files[i] << _T("\n");
+ }
+
+ wxFile f(filename, wxFile::write);
+ if (f.IsOpened())
+ {
+ f.Write(entry.mb_str(wxConvUTF8),entry.Length());
+ }
+}
+
+void UpdateDlg::OnFileRightClick(wxListEvent& event)
+{
+// LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n';
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ return;
+
+ wxMenu popup;
+ popup.Append(idPopupDownloadAndInstall, _("Download && install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupDownload, _("Download"));
+ popup.Append(idPopupInstall, _("Install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupUninstall, _("Uninstall"));
+
+ bool canDl = !rec->downloaded || rec->version != rec->installed_version;
+ bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version);
+
+ popup.Enable(idPopupDownload, canDl);
+ popup.Enable(idPopupInstall, canInst);
+ popup.Enable(idPopupDownloadAndInstall, canInst || canDl);
+ popup.Enable(idPopupUninstall, rec->installed);
+
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->PopupMenu(&popup, event.GetPoint());
+}
+
+void UpdateDlg::OnFileDeSelected(wxListEvent& event)
+{
+ wxListItem id;
+ FillFileDetails(id);
+ EnableButtons();
+}
+
+void UpdateDlg::OnFileSelected(wxListEvent& event)
+{
+ FillFileDetails(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event)
+{
+ FillFiles(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnDownload(wxCommandEvent& event)
+{
+ DownloadFile(true);
+}
+
+void UpdateDlg::OnInstall(wxCommandEvent& event)
+{
+ InstallFile();
+}
+
+void UpdateDlg::OnUninstall(wxCommandEvent& event)
+{
+ UninstallFile();
+}
+
+void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event)
+{
+ DownloadFile();
+}
+
+void UpdateDlg::OnServerChange(wxCommandEvent& event)
+{
+ InternetUpdate();
+}
+
+void UpdateDlg::OnFilterChange(wxCommandEvent& event)
+{
+ ApplyFilter();
+}
+
+void UpdateDlg::OnConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnDisConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnProgress(wxCommandEvent& event)
+{
+ int prg = -1;
+ if (m_CurrFileSize != 0)
+ prg = event.GetInt() * 100 / m_CurrFileSize;
+ UpdateStatus(_("Downloading: ") + event.GetString(), prg);
+
+ wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText);
+
+ wxString msg;
+ msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str());
+ lbl->SetLabel(msg);
+}
+
+void UpdateDlg::OnAborted(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadStarted(wxCommandEvent& event)
+{
+ m_CurrFileSize = event.GetInt();
+ UpdateStatus(_("Download started: ") + event.GetString(), 0, 100);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadEnded(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download finished: ") + event.GetString());
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+
+ if (m_HasUpdated && event.GetInt() == 0)
+ {
+ UpdateRec* rec = GetRecFromListView();
+ if (rec)
+ {
+ if (rec->bytes != m_CurrFileSize)
+ wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n"
+ "This, usually, means one of three things:\n"
+ "1) The reported size in the update list is wrong. The DevPak might still be valid.\n"
+ "2) The file's location returned a web error-page. Invalid DevPak...\n"
+ "3) The file is corrupt...\n\n"
+ "You can try to install it anyway. If it is not a valid DevPak, the operation will fail."),
+ _("Warning"), wxICON_WARNING);
+ }
+ if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ else if (rec && rec->title == _T("WebUpdate Mirrors list"))
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ }
+ m_CurrFileSize = 0;
+}
+
+void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event)
+{
+ // hack to display the download message *after* the dialog has been shown...
+ if (m_FirstTimeCheck)
+ {
+ m_FirstTimeCheck = false; // no more, just once
+ wxString config = GetConfFilename();
+ if (wxFileExists(config))
+ InternetUpdate();
+ else
+ {
+ if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InternetUpdate(true);
+ }
+ }
+}
diff --git a/tests/01uni_multi.to/.svn/text-base/updatedlg.h.svn-base b/tests/01uni_multi.to/.svn/text-base/updatedlg.h.svn-base new file mode 100644 index 0000000..596b9c6 --- /dev/null +++ b/tests/01uni_multi.to/.svn/text-base/updatedlg.h.svn-base @@ -0,0 +1,74 @@ +#ifndef UPDATEDLG_H
+#define UPDATEDLG_H
+
+#include <wx/dialog.h>
+#include <wx/listctrl.h>
+#include <wx/treectrl.h>
+#include "cbnetwork.h"
+#include "conf.h"
+
+class UpdateDlg : public wxDialog
+{
+ public:
+ UpdateDlg(wxWindow* parent);
+ virtual ~UpdateDlg();
+
+ void EndModal(int retCode);
+ protected:
+ void OnFileSelected(wxListEvent& event);
+ void OnFileDeSelected(wxListEvent& event);
+ void OnFileRightClick(wxListEvent& event);
+ void OnTreeSelChanged(wxTreeEvent& event);
+ void OnDownload(wxCommandEvent& event);
+ void OnInstall(wxCommandEvent& event);
+ void OnUninstall(wxCommandEvent& event);
+ void OnDownloadAndInstall(wxCommandEvent& event);
+ void OnUpdate(wxCommandEvent& event);
+ void OnServerChange(wxCommandEvent& event);
+ void OnFilterChange(wxCommandEvent& event);
+ void OnConnect(wxCommandEvent& event);
+ void OnDisConnect(wxCommandEvent& event);
+ void OnProgress(wxCommandEvent& event);
+ void OnAborted(wxCommandEvent& event);
+ void OnDownloadStarted(wxCommandEvent& event);
+ void OnDownloadEnded(wxCommandEvent& event);
+ void OnUpdateUI(wxUpdateUIEvent& event);
+ private:
+ void InternetUpdate(bool forceDownload = false);
+ void DownloadFile(bool dontInstall = false);
+ void InstallFile();
+ void UninstallFile();
+ void InstallMirrors(const wxString& file);
+ void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files);
+ void EnableButtons(bool update = true, bool abort = true);
+ void FillServers();
+ void FillGroups();
+ void FillFiles(const wxTreeItemId& id);
+ void FillFileDetails(const wxListItem& id);
+ void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1);
+ UpdateRec* GetRecFromListView();
+ void CreateListColumns();
+ void AddRecordToList(UpdateRec* rec);
+ wxString GetListColumnText(int idx, int col);
+ void SetListColumnText(int idx, int col, const wxString& text);
+
+ wxString GetConfFilename();
+ wxString GetMirrorsFilename() const;
+ wxString GetCurrentServer() const;
+ wxString GetBasePath() const;
+ wxString GetPackagePath() const;
+ bool FilterRec(UpdateRec* rec);
+ void ApplyFilter();
+
+ UpdateRec* m_Recs;
+ wxArrayString m_Servers;
+ int m_RecsCount;
+ int m_CurrFileSize;
+ int m_LastBlockSize; // for bps
+ bool m_HasUpdated;
+ bool m_FirstTimeCheck;
+ cbNetwork m_Net;
+ DECLARE_EVENT_TABLE();
+};
+
+#endif // UPDATEDLG_H
diff --git a/tests/01uni_multi.to/conf.cpp b/tests/01uni_multi.to/conf.cpp new file mode 100644 index 0000000..bdd5349 --- /dev/null +++ b/tests/01uni_multi.to/conf.cpp @@ -0,0 +1,121 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: conf.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/conf.cpp $
+ */
+
+#include "conf.h"
+#include <wx/intl.h>
+#include <wx/url.h>
+#include <wx/filename.h>
+#include <globals.h>
+
+wxString g_MasterPath;
+
+wxString GetSizeString(int bytes)
+{
+ wxString ret;
+ float kilobytes = (float)bytes / 1024.0f;
+ float megabytes = kilobytes / 1024.0f;
+ if (megabytes >= 1.0f)
+ ret.Printf(_("%.2f MB"), megabytes);
+ else if (kilobytes >= 1.0f)
+ ret.Printf(_("%.2f KB"), kilobytes);
+ else
+ ret.Printf(_("%ld bytes"), bytes);
+ return ret;
+}
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath)
+{
+ *recCount = 0;
+ int groupsCount = ini.GetGroupsCount();
+ if (groupsCount == 0)
+ return 0;
+
+ UpdateRec* list = new UpdateRec[ini.GetGroupsCount()];
+ for (int i = 0; i < groupsCount; ++i)
+ {
+ UpdateRec& rec = list[i];
+
+ rec.title = ini.GetGroupName(i);
+
+ // fix title
+ // devpaks.org has changed the title to contain some extra info
+ // e.g.: [libunicows Library version: 1.1.1 Devpak revision: 1sid]
+ int pos = rec.title.Lower().Find(_T("library version:"));
+ if (pos != -1)
+ {
+ int revpos = rec.title.Lower().Find(_T("devpak revision:"));
+ if (revpos != -1) {
+ rec.revision = rec.title.Mid(revpos).AfterFirst(_T(':')).Trim(false);
+ rec.revision.Replace(_T("\t"), _T(" "));
+ rec.revision = rec.revision.BeforeFirst(_T(' '));
+ }
+
+ rec.title.Truncate(pos);
+ rec.title = rec.title.Trim(false);
+ rec.title = rec.title.Trim(true);
+ }
+
+ rec.name = ini.GetKeyValue(i, _T("Name"));
+ rec.desc = ini.GetKeyValue(i, _T("Description"));
+ rec.remote_file = ini.GetKeyValue(i, _T("RemoteFilename"));
+ rec.local_file = ini.GetKeyValue(i, _T("LocalFilename"));
+ rec.groups = GetArrayFromString(ini.GetKeyValue(i, _T("Group")), _T(","));
+ rec.install_path = ini.GetKeyValue(i, _T("InstallPath"));
+ rec.version = ini.GetKeyValue(i, _T("Version"));
+ ini.GetKeyValue(i, _T("Size")).ToLong(&rec.bytes);
+ rec.date = ini.GetKeyValue(i, _T("Date"));
+ rec.installable = ini.GetKeyValue(i, _T("Execute")) == _T("1");
+
+ // read .entry file (if exists)
+ rec.entry = (!rec.name.IsEmpty() ? rec.name : wxFileName(rec.local_file).GetName()) + _T(".entry");
+ IniParser p;
+ p.ParseFile(appPath + rec.entry);
+ rec.installed_version = p.GetValue(_T("Setup"), _T("AppVersion"));
+
+ rec.downloaded = wxFileExists(appPath + _T("/") + rec.local_file);
+ rec.installed = !rec.installed_version.IsEmpty();
+
+ // calculate size
+ rec.size = GetSizeString(rec.bytes);
+
+ // fix-up
+ if (rec.name.IsEmpty())
+ rec.name = rec.title;
+ rec.desc.Replace(_T("<CR>"), _T("\n"));
+ rec.desc.Replace(_T("<LF>"), _T("\r"));
+ wxURL url(rec.remote_file);
+ if (!url.GetServer().IsEmpty())
+ {
+ rec.remote_server = url.GetScheme() + _T("://") + url.GetServer();
+ int pos = rec.remote_file.Find(url.GetServer());
+ if (pos != wxNOT_FOUND)
+ rec.remote_file.Remove(0, pos + url.GetServer().Length() + 1);
+ }
+ else
+ rec.remote_server = currentServer;
+ }
+
+ *recCount = groupsCount;
+ return list;
+}
+
+UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count)
+{
+ for (int i = 0; i < count; ++i)
+ {
+ if (list[i].title == title && list[i].version == version) {
+ if (revision.IsEmpty()) {
+ return &list[i];
+ } else if (list[i].revision == revision) {
+ return &list[i];
+ }
+ }
+ }
+ return 0;
+}
diff --git a/tests/01uni_multi.to/conf.h b/tests/01uni_multi.to/conf.h new file mode 100644 index 0000000..6788ba5 --- /dev/null +++ b/tests/01uni_multi.to/conf.h @@ -0,0 +1,39 @@ +#ifndef CONF_H
+#define CONF_H
+
+#include <wx/string.h>
+#include <wx/dynarray.h>
+#include "cbiniparser.h"
+
+struct UpdateRec
+{
+ wxString entry; //! .entry filename for installed
+ wxString title;
+ wxString name;
+ wxString desc;
+ wxString remote_server;
+ wxString remote_file;
+ wxString local_file;
+ wxArrayString groups;
+ wxString install_path; //! ignored
+ wxString version;
+ wxString revision;
+ wxString installed_version;
+ long int bytes;
+ float kilobytes;
+ float megabytes;
+ wxString size;
+ wxString date;
+ bool installable;
+ bool downloaded;
+ bool installed;
+};
+
+extern wxString g_MasterPath;
+
+UpdateRec* ReadConf(const IniParser& ini, int* recCount, const wxString& currentServer, const wxString& appPath);
+UpdateRec* FindRec(const wxString& title, const wxString& version, const wxString& revision, UpdateRec* list, int count);
+// utility
+wxString GetSizeString(int bytes);
+
+#endif // CONF_H
diff --git a/tests/01uni_multi.to/manifest.xml b/tests/01uni_multi.to/manifest.xml new file mode 100644 index 0000000..578085c --- /dev/null +++ b/tests/01uni_multi.to/manifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_plugin_manifest_file>
+ <SdkVersion major="1" minor="10" release="0" />
+ <Plugin name="DevPakUpdater">
+ <Value title="DevPak updater/installer" />
+ <Value version="0.2" />
+ <Value description="Installs selected DevPaks from the Internet" />
+ <Value author="Yiannis Mandravellos" />
+ <Value authorEmail="info@codeblocks.org" />
+ <Value authorWebsite="http://www.codeblocks.org/" />
+ <Value thanksTo="Dev-C++ community.
+ Julian R Seward for libbzip2.
+
+ libbzip2 copyright notice:
+ bzip2 and associated library libbzip2, are
+ copyright (C) 1996-2000 Julian R Seward.
+ All rights reserved." />
+ <Value license="GPL" />
+ </Plugin>
+</CodeBlocks_plugin_manifest_file>
diff --git a/tests/01uni_multi.to/updatedlg.cpp b/tests/01uni_multi.to/updatedlg.cpp new file mode 100644 index 0000000..99e01e6 --- /dev/null +++ b/tests/01uni_multi.to/updatedlg.cpp @@ -0,0 +1,742 @@ +/*
+ * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
+ * http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * $Revision: 4909 $
+ * $Id: updatedlg.cpp 4909 2008-02-27 13:15:26Z mortenmacfly $
+ * $HeadURL: http://svn.berlios.de/svnroot/repos/codeblocks/tags/8.02/src/plugins/contrib/devpak_plugin/updatedlg.cpp $
+ */
+
+#include "updatedlg.h"
+#include <wx/xrc/xmlres.h>
+#include <wx/msgdlg.h>
+#include <wx/button.h>
+#include <wx/gauge.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/combobox.h>
+#include <wx/checkbox.h>
+#include <wx/file.h>
+#include <wx/menu.h>
+#include "devpakinstaller.h"
+#include "crc32.h"
+
+#include "manager.h"
+#include "configmanager.h"
+#include "globals.h"
+
+int idNet = wxNewId();
+int idPopupInstall = wxNewId();
+int idPopupDownload = wxNewId();
+int idPopupDownloadAndInstall = wxNewId();
+int idPopupUninstall = wxNewId();
+
+BEGIN_EVENT_TABLE(UpdateDlg, wxDialog)
+ EVT_UPDATE_UI(-1, UpdateDlg::OnUpdateUI)
+ EVT_TREE_SEL_CHANGED(XRCID("tvCategories"), UpdateDlg::OnTreeSelChanged)
+ EVT_LIST_ITEM_SELECTED(XRCID("lvFiles"), UpdateDlg::OnFileSelected)
+ EVT_LIST_ITEM_DESELECTED(XRCID("lvFiles"), UpdateDlg::OnFileDeSelected)
+ EVT_LIST_ITEM_RIGHT_CLICK(XRCID("lvFiles"), UpdateDlg::OnFileRightClick)
+ EVT_MENU(idPopupDownload, UpdateDlg::OnDownload)
+ EVT_MENU(idPopupDownloadAndInstall, UpdateDlg::OnDownloadAndInstall)
+ EVT_MENU(idPopupInstall, UpdateDlg::OnInstall)
+ EVT_MENU(idPopupUninstall, UpdateDlg::OnUninstall)
+ EVT_COMBOBOX(XRCID("cmbServer"), UpdateDlg::OnServerChange)
+ EVT_COMBOBOX(XRCID("cmbFilter"), UpdateDlg::OnFilterChange)
+ EVT_CHECKBOX(XRCID("chkCache"), UpdateDlg::OnServerChange)
+ EVT_CBNET_CONNECT(idNet, UpdateDlg::OnConnect)
+ EVT_CBNET_DISCONNECT(idNet, UpdateDlg::OnDisConnect)
+ EVT_CBNET_PROGRESS(idNet, UpdateDlg::OnProgress)
+ EVT_CBNET_ABORTED(idNet, UpdateDlg::OnAborted)
+ EVT_CBNET_START_DOWNLOAD(idNet, UpdateDlg::OnDownloadStarted)
+ EVT_CBNET_END_DOWNLOAD(idNet, UpdateDlg::OnDownloadEnded)
+END_EVENT_TABLE()
+
+UpdateDlg::UpdateDlg(wxWindow* parent)
+ : m_Recs(0),
+ m_RecsCount(0),
+ m_CurrFileSize(0),
+ m_LastBlockSize(0),
+ m_HasUpdated(false),
+ m_FirstTimeCheck(true),
+ m_Net(this, idNet, _T("http://devpaks.sourceforge.net/"))
+{
+ //ctor
+ wxXmlResource::Get()->LoadDialog(this, parent, _T("MainFrame"));
+ CreateListColumns();
+ FillServers();
+ UpdateStatus(_("Ready"), 0);
+}
+
+UpdateDlg::~UpdateDlg()
+{
+ //dtor
+ delete[] m_Recs;
+ m_RecsCount = 0;
+}
+
+void UpdateDlg::EndModal(int retCode)
+{
+ if (!m_Net.IsConnected() || retCode != wxID_CANCEL)
+ {
+ wxDialog::EndModal(retCode);
+ return;
+ }
+
+ if (m_Net.IsConnected())
+ m_Net.Abort();
+}
+
+void UpdateDlg::CreateListColumns()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->InsertColumn(0, _("Title"));
+ lst->InsertColumn(1, _("Version"));
+ lst->InsertColumn(2, _("Installed"));
+ lst->InsertColumn(3, _("Size"), wxLIST_FORMAT_RIGHT);
+ lst->InsertColumn(4, _("Rev"));
+
+ lst->SetColumnWidth(0, lst->GetSize().x - (64 * 3 + 40) - 6 ); // 1st column takes all remaining space
+ lst->SetColumnWidth(1, 64);
+ lst->SetColumnWidth(2, 64);
+ lst->SetColumnWidth(3, 64);
+ lst->SetColumnWidth(4, 40);
+}
+
+void UpdateDlg::AddRecordToList(UpdateRec* rec)
+{
+ if (!rec)
+ return;
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int idx = lst->GetItemCount();
+ lst->InsertItem(idx, rec->title);
+ lst->SetItem(idx, 1, rec->version);
+ lst->SetItem(idx, 2, rec->installed_version);
+ lst->SetItem(idx, 3, rec->size);
+ lst->SetItem(idx, 4, rec->revision);
+}
+
+wxString UpdateDlg::GetListColumnText(int idx, int col) {
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem info;
+ info.SetId(index);
+ info.SetColumn(col);
+ info.SetMask(wxLIST_MASK_TEXT);
+ lst->GetItem(info);
+ return info.GetText();
+}
+
+void UpdateDlg::SetListColumnText(int idx, int col, const wxString& text)
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = idx == -1 ? lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) : idx;
+ wxListItem it;
+ it.m_itemId = index;
+ it.m_col = col;
+ it.m_mask = wxLIST_MASK_TEXT;
+ it.m_text = text;
+ lst->SetItem(it);
+}
+
+void UpdateDlg::UpdateStatus(const wxString& status, int curProgress, int maxProgress)
+{
+ wxStaticText* lbl = XRCCTRL(*this, "lblStatus", wxStaticText);
+ if (lbl->GetLabel() != status)
+ lbl->SetLabel(status);
+ if (curProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetValue(curProgress);
+ if (maxProgress != -1)
+ XRCCTRL(*this, "gauProgress", wxGauge)->SetRange(maxProgress);
+}
+
+void UpdateDlg::EnableButtons(bool update, bool abort)
+{
+ wxButton* btnCl = XRCCTRL(*this, "wxID_CANCEL", wxButton);
+
+ btnCl->Enable(abort);
+ // disable server list and cache checkbox while downloading
+ XRCCTRL(*this, "cmbServer", wxComboBox)->Enable(!m_Net.IsConnected());
+ XRCCTRL(*this, "chkCache", wxCheckBox)->Enable(!m_Net.IsConnected());
+
+ wxYield();
+}
+
+void UpdateDlg::FillGroups()
+{
+ UpdateStatus(_("Parsing list of updates"), 0, m_RecsCount - 1);
+
+ // get a list of unique group names
+ wxArrayString groups;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ for (unsigned int x = 0; x < m_Recs[i].groups.GetCount(); ++x)
+ {
+ if (m_Recs[i].groups[x].IsEmpty())
+ continue;
+ if (groups.Index(m_Recs[i].groups[x]) == wxNOT_FOUND)
+ {
+ if (FilterRec(&m_Recs[i]))
+ groups.Add(m_Recs[i].groups[x]);
+ }
+ }
+ }
+
+ // create the groups tree
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ tree->Freeze();
+ tree->DeleteAllItems();
+ wxTreeItemId root = tree->AddRoot(_("All categories"));
+ for (unsigned int i = 0; i < groups.GetCount(); ++i)
+ {
+ tree->AppendItem(root, groups[i]);
+ }
+ tree->SortChildren(root);
+ tree->Thaw();
+ tree->Expand(root);
+ tree->SelectItem(root); // this calls the event
+
+ UpdateStatus(_("Done parsing list of updates"), 0);
+}
+
+void UpdateDlg::FillFiles(const wxTreeItemId& id)
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->Freeze();
+ lst->ClearAll();
+ CreateListColumns();
+
+ wxString group = id == tree->GetRootItem() ? _T("") : tree->GetItemText(id);
+
+ // add files belonging to group
+ int counter = 0;
+ for (int i = 0; i < m_RecsCount; ++i)
+ {
+ if (group.IsEmpty() || (!m_Recs[i].groups.IsEmpty() && m_Recs[i].groups.Index(group) != wxNOT_FOUND))
+ {
+ // filter
+ if (FilterRec(&m_Recs[i]))
+ {
+ AddRecordToList(&m_Recs[i]);
+ ++counter;
+ }
+ }
+ }
+ lst->Thaw();
+
+ // select first item
+ lst->SetItemState(0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
+}
+
+void UpdateDlg::FillFileDetails(const wxListItem& id)
+{
+ wxTextCtrl* txt = XRCCTRL(*this, "txtInfo", wxTextCtrl);
+ txt->Clear();
+
+ UpdateRec* cur = GetRecFromListView();
+ if (!cur)
+ {
+ txt->Clear();
+ EnableButtons();
+ return;
+ }
+ txt->AppendText(_("Name: ") + cur->name + _T("\n"));
+// txt->AppendText(_("Server: ") + cur->remote_server + _T("\n"));
+// txt->AppendText(_("File: ") + cur->remote_file + _T("\n"));
+ txt->AppendText(_("Version: ") + cur->version + _T("\n"));
+ txt->AppendText(_("Size: ") + cur->size + _T("\n"));
+ txt->AppendText(_("Date: ") + cur->date + _T("\n\n"));
+ txt->AppendText(_("Description: \n"));
+ txt->AppendText(cur->desc);
+
+ txt->SetSelection(0, 0);
+ txt->SetInsertionPoint(0);
+}
+
+void UpdateDlg::InternetUpdate(bool forceDownload)
+{
+ UpdateStatus(_("Please wait..."));
+ m_HasUpdated = false;
+ m_Net.SetServer(GetCurrentServer());
+
+ EnableButtons(false);
+ forceDownload = forceDownload || !XRCCTRL(*this, "chkCache", wxCheckBox)->GetValue();
+
+ bool forceDownloadMirrors = forceDownload || !wxFileExists(GetMirrorsFilename());
+ if (forceDownloadMirrors)
+ {
+ if (!m_Net.DownloadFile(_T("mirrors.cfg"), GetMirrorsFilename()))
+ {
+ UpdateStatus(_("Error downloading list of mirrors"), 0, 0);
+ return;
+ }
+ else
+ {
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ }
+ }
+
+ wxString config = GetConfFilename();
+ forceDownload = forceDownload || !wxFileExists(config);
+ if (forceDownload && !m_Net.DownloadFile(_T("webupdate.conf"), config))
+ {
+ UpdateStatus(_("Error downloading list of updates"), 0, 0);
+ return;
+ }
+ else
+ {
+ IniParser ini;
+ if (!ini.ParseFile(config))
+ {
+ UpdateStatus(_("Failed to retrieve the list of updates"), 0, 0);
+ return;
+ }
+ ini.Sort();
+
+ if (m_Recs)
+ delete[] m_Recs;
+
+ // remember to delete[] m_Recs when we 're done with it!!!
+ // it's our responsibility once given to us
+ m_Recs = ReadConf(ini, &m_RecsCount, GetCurrentServer(), GetPackagePath());
+
+ FillGroups();
+ }
+ EnableButtons();
+ UpdateStatus(_("Ready"), 0, 0);
+
+ m_HasUpdated = true;
+}
+
+void UpdateDlg::FillServers()
+{
+ wxComboBox* cmb = XRCCTRL(*this, "cmbServer", wxComboBox);
+ cmb->Clear();
+ m_Servers.Clear();
+
+ IniParser ini;
+ ini.ParseFile(GetMirrorsFilename());
+ int group = ini.FindGroupByName(_T("WebUpdate mirrors"));
+ for (int i = 0; group != -1 && i < ini.GetKeysCount(group); ++i)
+ {
+ cmb->Append(ini.GetKeyName(group, i));
+ m_Servers.Add(ini.GetKeyValue(group, i));
+ }
+ if (cmb->GetCount() == 0)
+ {
+ cmb->Append(_("devpaks.org Community Devpaks"));
+ m_Servers.Add(_T("http://devpaks.sourceforge.net/"));
+ }
+ cmb->SetSelection(0);
+}
+
+wxString UpdateDlg::GetConfFilename()
+{
+ int server_hash = GetTextCRC32(GetCurrentServer().mb_str());
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH;
+ config.Printf(_T("%sdevpak_%x.conf"), config.c_str(), server_hash);
+ return config;
+}
+
+wxString UpdateDlg::GetMirrorsFilename() const
+{
+ wxString config;
+ config = ConfigManager::GetConfigFolder() + wxFILE_SEP_PATH + _T("devpak_mirrors.cfg");
+ return config;
+}
+
+wxString UpdateDlg::GetCurrentServer() const
+{
+ return m_Servers[XRCCTRL(*this, "cmbServer", wxComboBox)->GetSelection()];
+}
+
+wxString UpdateDlg::GetBasePath() const
+{
+ return g_MasterPath + wxFILE_SEP_PATH;
+}
+
+wxString UpdateDlg::GetPackagePath() const
+{
+ return GetBasePath() + _T("Packages") + wxFILE_SEP_PATH;
+}
+
+bool UpdateDlg::FilterRec(UpdateRec* rec)
+{
+ if (!rec)
+ return false;
+ wxComboBox* cmb = XRCCTRL(*this, "cmbFilter", wxComboBox);
+ switch (cmb->GetSelection())
+ {
+ case 0: // All
+ return true;
+
+ case 1: // Installed
+ return rec->installed;
+
+ case 2: // installed with update available
+ return rec->installed && rec->version != rec->installed_version;
+
+ case 3: // downloaded but not installed
+ return rec->downloaded && !rec->installed;
+
+ case 4: // not installed
+ return !rec->downloaded && !rec->installed;
+
+ default:
+ return false;
+ }
+ return false; // doesn't reach here
+}
+
+void UpdateDlg::ApplyFilter()
+{
+ wxTreeCtrl* tree = XRCCTRL(*this, "tvCategories", wxTreeCtrl);
+
+ FillGroups();
+ FillFiles(tree->GetSelection());
+ EnableButtons();
+}
+
+UpdateRec* UpdateDlg::GetRecFromListView()
+{
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ int index = lst->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (index == -1)
+ return 0;
+ wxString title = lst->GetItemText(index);
+ wxString version = GetListColumnText(index, 1);
+ wxString revision = GetListColumnText(index, 4);
+ return FindRec(title, version, revision, m_Recs, m_RecsCount);
+}
+
+void UpdateDlg::DownloadFile(bool dontInstall)
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (rec->version == rec->installed_version)
+ {
+ if (wxMessageBox(_("You seem to have installed the latest version.\nAre you sure you want to proceed?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO)
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ if (wxFileExists(GetPackagePath() + rec->local_file))
+ {
+ if (wxMessageBox(_("This file already exists!\nAre you sure you want to download it again?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxNO &&
+ rec->installable)
+ {
+ if (!dontInstall && wxMessageBox(_("Do you want to force-install it?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ return;
+ }
+ }
+
+ m_Net.SetServer(rec->remote_server);
+
+ EnableButtons(false);
+ if (!m_Net.DownloadFile(rec->remote_file, GetPackagePath() + rec->local_file))
+ {
+ rec->downloaded = false;
+ UpdateStatus(_("Error downloading file: ") + rec->remote_server + _T(" > ") + rec->remote_file, 0, 0);
+ return;
+ }
+ else
+ rec->downloaded = true;
+ UpdateStatus(_("Ready"), 0, 0);
+ EnableButtons();
+}
+
+void UpdateDlg::InstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ if (rec->title == _T("WebUpdate Mirrors list"))
+ {
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ rec->installed = true;
+ ApplyFilter();
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ else if (!rec->installable)
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+
+ if (!CreateDirRecursively(GetPackagePath()))
+ {
+ UpdateStatus(_("Ready"), 0, 0);
+ wxMessageBox(_("Can't create directory ") + GetPackagePath(), _("Error"), wxICON_ERROR);
+ return;
+ }
+
+ wxArrayString files;
+ DevPakInstaller inst;
+ if (inst.Install(rec->name, GetPackagePath() + rec->local_file, GetBasePath(), &files))
+ {
+// wxFileName fname(GetPackagePath() + rec->local_file);
+// fname.SetExt("entry");
+// fname.SetName(rec->title);
+// CreateEntryFile(rec, fname.GetFullPath(), files);
+ CreateEntryFile(rec, GetPackagePath() + rec->entry, files);
+ wxMessageBox(_("DevPak installed"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed = true;
+ rec->installed_version = rec->version;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not installed.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+ UpdateStatus(_("Ready"), 0, 0);
+}
+
+void UpdateDlg::InstallMirrors(const wxString& file)
+{
+ if (!wxCopyFile(file, GetMirrorsFilename(), true))
+ wxMessageBox(_("Can't install mirrors file: ") + file, _("Error"), wxICON_ERROR);
+ else
+ {
+ wxRemoveFile(file);
+ FillServers();
+ m_Net.SetServer(GetCurrentServer()); // update server based on mirrors
+ wxMessageBox(_("Mirrors installed"), _("Information"), wxICON_INFORMATION);
+ }
+}
+
+void UpdateDlg::UninstallFile()
+{
+ UpdateStatus(_("Please wait..."));
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ {
+ wxMessageBox(_("No file selected!"), _("Error"), wxICON_ERROR);
+ UpdateStatus(_("Ready"), 0, 0);
+ return;
+ }
+ wxYield();
+
+ DevPakInstaller inst;
+ if (inst.Uninstall(GetPackagePath() + rec->entry))
+ {
+ wxMessageBox(_("DevPak uninstalled"), _("Message"), wxICON_INFORMATION);
+
+ // refresh installed_version
+ rec->installed_version.Clear();
+ rec->installed = false;
+ SetListColumnText(-1, 2, rec->installed_version);
+ }
+ else
+ {
+ wxMessageBox(_("DevPak was not uninstalled.\nStatus:\n") + inst.GetStatus(), _("Error"), wxICON_ERROR);
+ }
+}
+
+void UpdateDlg::CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files)
+{
+ wxString entry;
+ entry << _T("[Setup]\n");
+ entry << _T("AppName=") << rec->name << _T("\n");
+ entry << _T("AppVersion=") << rec->version << _T("\n");
+
+ entry << _T("\n");
+ entry << _T("[Files]\n");
+ for (unsigned int i = 0; i < files.GetCount(); ++i)
+ {
+ entry << files[i] << _T("\n");
+ }
+
+ wxFile f(filename, wxFile::write);
+ if (f.IsOpened())
+ {
+ f.Write(entry.mb_str(wxConvUTF8),entry.Length());
+ }
+}
+
+void UpdateDlg::OnFileRightClick(wxListEvent& event)
+{
+// LOGSTREAM << "pt.x=" << event.GetPoint().x << ", pt.y=" << event.GetPoint().y << '\n';
+ UpdateRec* rec = GetRecFromListView();
+ if (!rec)
+ return;
+
+ wxMenu popup;
+ popup.Append(idPopupDownloadAndInstall, _("Download && install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupDownload, _("Download"));
+ popup.Append(idPopupInstall, _("Install"));
+ popup.AppendSeparator();
+ popup.Append(idPopupUninstall, _("Uninstall"));
+
+ bool canDl = !rec->downloaded || rec->version != rec->installed_version;
+ bool canInst = rec->downloaded && (!rec->installed || rec->version != rec->installed_version);
+
+ popup.Enable(idPopupDownload, canDl);
+ popup.Enable(idPopupInstall, canInst);
+ popup.Enable(idPopupDownloadAndInstall, canInst || canDl);
+ popup.Enable(idPopupUninstall, rec->installed);
+
+ wxListCtrl* lst = XRCCTRL(*this, "lvFiles", wxListCtrl);
+ lst->PopupMenu(&popup, event.GetPoint());
+}
+
+void UpdateDlg::OnFileDeSelected(wxListEvent& event)
+{
+ wxListItem id;
+ FillFileDetails(id);
+ EnableButtons();
+}
+
+void UpdateDlg::OnFileSelected(wxListEvent& event)
+{
+ FillFileDetails(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnTreeSelChanged(wxTreeEvent& event)
+{
+ FillFiles(event.GetItem());
+ EnableButtons();
+}
+
+void UpdateDlg::OnDownload(wxCommandEvent& event)
+{
+ DownloadFile(true);
+}
+
+void UpdateDlg::OnInstall(wxCommandEvent& event)
+{
+ InstallFile();
+}
+
+void UpdateDlg::OnUninstall(wxCommandEvent& event)
+{
+ UninstallFile();
+}
+
+void UpdateDlg::OnDownloadAndInstall(wxCommandEvent& event)
+{
+ DownloadFile();
+}
+
+void UpdateDlg::OnServerChange(wxCommandEvent& event)
+{
+ InternetUpdate();
+}
+
+void UpdateDlg::OnFilterChange(wxCommandEvent& event)
+{
+ ApplyFilter();
+}
+
+void UpdateDlg::OnConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Abort"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnDisConnect(wxCommandEvent& event)
+{
+ XRCCTRL(*this, "wxID_CANCEL", wxButton)->SetLabel(_("Close"));
+ EnableButtons();
+}
+
+void UpdateDlg::OnProgress(wxCommandEvent& event)
+{
+ int prg = -1;
+ if (m_CurrFileSize != 0)
+ prg = event.GetInt() * 100 / m_CurrFileSize;
+ UpdateStatus(_("Downloading: ") + event.GetString(), prg);
+
+ wxStaticText* lbl = XRCCTRL(*this, "lblProgress", wxStaticText);
+
+ wxString msg;
+ msg.Printf(_("%s of %s"), GetSizeString(event.GetInt()).c_str(), GetSizeString(m_CurrFileSize).c_str());
+ lbl->SetLabel(msg);
+}
+
+void UpdateDlg::OnAborted(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download aborted: ") + event.GetString(), 0, 0);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadStarted(wxCommandEvent& event)
+{
+ m_CurrFileSize = event.GetInt();
+ UpdateStatus(_("Download started: ") + event.GetString(), 0, 100);
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+}
+
+void UpdateDlg::OnDownloadEnded(wxCommandEvent& event)
+{
+ UpdateStatus(_("Download finished: ") + event.GetString());
+ XRCCTRL(*this, "lblProgress", wxStaticText)->SetLabel(_T(""));
+ m_LastBlockSize = 0;
+
+ if (m_HasUpdated && event.GetInt() == 0)
+ {
+ UpdateRec* rec = GetRecFromListView();
+ if (rec)
+ {
+ if (rec->bytes != m_CurrFileSize)
+ wxMessageBox(_("File size mismatch for ") + event.GetString() + _("!\n\n"
+ "This, usually, means one of three things:\n"
+ "1) The reported size in the update list is wrong. The DevPak might still be valid.\n"
+ "2) The file's location returned a web error-page. Invalid DevPak...\n"
+ "3) The file is corrupt...\n\n"
+ "You can try to install it anyway. If it is not a valid DevPak, the operation will fail."),
+ _("Warning"), wxICON_WARNING);
+ }
+ if (rec && rec->installable && wxMessageBox(_("Do you want to install ") + event.GetString() + _(" now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InstallFile();
+ else if (rec && rec->title == _T("WebUpdate Mirrors list"))
+ InstallMirrors(GetPackagePath() + rec->local_file);
+ }
+ m_CurrFileSize = 0;
+}
+
+void UpdateDlg::OnUpdateUI(wxUpdateUIEvent& event)
+{
+ // hack to display the download message *after* the dialog has been shown...
+ if (m_FirstTimeCheck)
+ {
+ m_FirstTimeCheck = false; // no more, just once
+ wxString config = GetConfFilename();
+ if (wxFileExists(config))
+ InternetUpdate();
+ else
+ {
+ if (wxMessageBox(_("A list of updates needs to be downloaded.\nDo you want to do this now?"), _("Confirmation"), wxICON_QUESTION | wxYES_NO) == wxYES)
+ InternetUpdate(true);
+ }
+ }
+}
diff --git a/tests/01uni_multi.to/updatedlg.h b/tests/01uni_multi.to/updatedlg.h new file mode 100644 index 0000000..596b9c6 --- /dev/null +++ b/tests/01uni_multi.to/updatedlg.h @@ -0,0 +1,74 @@ +#ifndef UPDATEDLG_H
+#define UPDATEDLG_H
+
+#include <wx/dialog.h>
+#include <wx/listctrl.h>
+#include <wx/treectrl.h>
+#include "cbnetwork.h"
+#include "conf.h"
+
+class UpdateDlg : public wxDialog
+{
+ public:
+ UpdateDlg(wxWindow* parent);
+ virtual ~UpdateDlg();
+
+ void EndModal(int retCode);
+ protected:
+ void OnFileSelected(wxListEvent& event);
+ void OnFileDeSelected(wxListEvent& event);
+ void OnFileRightClick(wxListEvent& event);
+ void OnTreeSelChanged(wxTreeEvent& event);
+ void OnDownload(wxCommandEvent& event);
+ void OnInstall(wxCommandEvent& event);
+ void OnUninstall(wxCommandEvent& event);
+ void OnDownloadAndInstall(wxCommandEvent& event);
+ void OnUpdate(wxCommandEvent& event);
+ void OnServerChange(wxCommandEvent& event);
+ void OnFilterChange(wxCommandEvent& event);
+ void OnConnect(wxCommandEvent& event);
+ void OnDisConnect(wxCommandEvent& event);
+ void OnProgress(wxCommandEvent& event);
+ void OnAborted(wxCommandEvent& event);
+ void OnDownloadStarted(wxCommandEvent& event);
+ void OnDownloadEnded(wxCommandEvent& event);
+ void OnUpdateUI(wxUpdateUIEvent& event);
+ private:
+ void InternetUpdate(bool forceDownload = false);
+ void DownloadFile(bool dontInstall = false);
+ void InstallFile();
+ void UninstallFile();
+ void InstallMirrors(const wxString& file);
+ void CreateEntryFile(UpdateRec* rec, const wxString& filename, const wxArrayString& files);
+ void EnableButtons(bool update = true, bool abort = true);
+ void FillServers();
+ void FillGroups();
+ void FillFiles(const wxTreeItemId& id);
+ void FillFileDetails(const wxListItem& id);
+ void UpdateStatus(const wxString& status, int curProgress = -1, int maxProgress = -1);
+ UpdateRec* GetRecFromListView();
+ void CreateListColumns();
+ void AddRecordToList(UpdateRec* rec);
+ wxString GetListColumnText(int idx, int col);
+ void SetListColumnText(int idx, int col, const wxString& text);
+
+ wxString GetConfFilename();
+ wxString GetMirrorsFilename() const;
+ wxString GetCurrentServer() const;
+ wxString GetBasePath() const;
+ wxString GetPackagePath() const;
+ bool FilterRec(UpdateRec* rec);
+ void ApplyFilter();
+
+ UpdateRec* m_Recs;
+ wxArrayString m_Servers;
+ int m_RecsCount;
+ int m_CurrFileSize;
+ int m_LastBlockSize; // for bps
+ bool m_HasUpdated;
+ bool m_FirstTimeCheck;
+ cbNetwork m_Net;
+ DECLARE_EVENT_TABLE();
+};
+
+#endif // UPDATEDLG_H
diff --git a/tests/02uni_newline.from b/tests/02uni_newline.from new file mode 100644 index 0000000..cf5a315 --- /dev/null +++ b/tests/02uni_newline.from @@ -0,0 +1,4 @@ +
+read_patch("fix_devpak_install.patch")
+
+asd
\ No newline at end of file diff --git a/tests/02uni_newline.patch b/tests/02uni_newline.patch new file mode 100644 index 0000000..072649c --- /dev/null +++ b/tests/02uni_newline.patch @@ -0,0 +1,8 @@ +--- 02uni_newline.from 2008-07-02 18:34:04 +0000
++++ 02uni_newline.to 2008-07-02 18:34:08 +0000
+@@ -1,4 +1,3 @@
+
+ read_patch("fix_devpak_install.patch")
+
+-asd
+\ No newline at end of file
diff --git a/tests/02uni_newline.to b/tests/02uni_newline.to new file mode 100644 index 0000000..6c62b1a --- /dev/null +++ b/tests/02uni_newline.to @@ -0,0 +1,3 @@ +
+read_patch("fix_devpak_install.patch")
+
diff --git a/tests/03trail_fname.from b/tests/03trail_fname.from new file mode 100644 index 0000000..01ff017 --- /dev/null +++ b/tests/03trail_fname.from @@ -0,0 +1,8 @@ +Tests:
+- file not found
+- trailing spaces in patch filenames
+- already patched
+- create new files
+- remove files
+- svn diff
+- hg diff
diff --git a/tests/03trail_fname.patch b/tests/03trail_fname.patch new file mode 100644 index 0000000..ec29b30 --- /dev/null +++ b/tests/03trail_fname.patch @@ -0,0 +1,12 @@ +--- 03trail_fname.from
++++ 03trail_fname.to
+@@ -1,7 +1,8 @@
+ Tests:
+ - file not found
+-- trailing spaces in patch filenames
+ - already patched
++
++Features:
+ - create new files
+ - remove files
+ - svn diff
diff --git a/tests/03trail_fname.to b/tests/03trail_fname.to new file mode 100644 index 0000000..758b097 --- /dev/null +++ b/tests/03trail_fname.to @@ -0,0 +1,9 @@ +Tests:
+- file not found
+- already patched
+
+Features:
+- create new files
+- remove files
+- svn diff
+- hg diff
diff --git a/tests/04can_patch.from b/tests/04can_patch.from new file mode 100644 index 0000000..0380b9d --- /dev/null +++ b/tests/04can_patch.from @@ -0,0 +1,40 @@ +beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
\ No newline at end of file diff --git a/tests/04can_patch.patch b/tests/04can_patch.patch new file mode 100644 index 0000000..356beb0 --- /dev/null +++ b/tests/04can_patch.patch @@ -0,0 +1,11 @@ +--- 04can_patch.from Sun Dec 27 09:53:51 2009
++++ 04can_patch.to Sun Dec 27 09:54:06 2009
+@@ -6,8 +6,6 @@
+ beta
+ beta
+ alpha
+-beta
+-beta
+ beta
+ beta
+ beta
diff --git a/tests/04can_patch.to b/tests/04can_patch.to new file mode 100644 index 0000000..6e94e33 --- /dev/null +++ b/tests/04can_patch.to @@ -0,0 +1,38 @@ +beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+alpha
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
+beta
\ No newline at end of file diff --git a/tests/Descript.ion b/tests/Descript.ion new file mode 100644 index 0000000..b57bb3e --- /dev/null +++ b/tests/Descript.ion @@ -0,0 +1,3 @@ +01uni_multi.patch unified diff multiple files
+02uni_newline.patch newline at the end of file
+03trail_fname.patch trailing spaces in patch filenames
diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..0a99af9 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,221 @@ +""" +TestSuite + +Files/directories that comprise one test all have the same name, but a different extensions: +*.patch +*.from +*.to + +*.doctest - self contained doctest patch + +TODO: recheck input/output sources + +""" + +import os +import sys +import re +import shutil +import unittest +import copy +from os import listdir +from os.path import abspath, dirname, exists, join, isdir +from tempfile import mkdtemp + +verbose = False +if "-v" in sys.argv or "--verbose" in sys.argv: + verbose = True + + +#: full path for directory with tests +tests_dir = dirname(abspath(__file__)) + + +# import patch.py from parent directory +save_path = sys.path +sys.path.insert(0, dirname(tests_dir)) +import patch +sys.path = save_path + + +# ---------------------------------------------------------------------------- +class TestPatchFiles(unittest.TestCase): + """ + unittest hack - test* methods are generated by add_test_methods() function + below dynamicallt using information about *.patch files from tests directory + + """ + def _assert_files_equal(self, file1, file2): + f1 = f2 = None + try: + f1 = open(file1, "rb") + f2 = open(file2, "rb") + for line in f1: + self.assertEqual(line, f2.readline()) + + finally: + if f2: + f2.close() + if f1: + f1.close() + + def _assert_dirs_equal(self, dir1, dir2, ignore=[]): + """ compare dir1 with reference dir2 + .svn dirs are ignored + + """ + # recursion here + e2list = listdir(dir2) + for e1 in listdir(dir1): + if e1 == ".svn": + continue + e1path = join(dir1, e1) + e2path = join(dir2, e1) + self.assert_(exists(e1path)) + self.assert_(exists(e2path), "%s does not exist" % e2path) + self.assert_(isdir(e1path) == isdir(e2path)) + if not isdir(e1path): + self._assert_files_equal(e1path, e2path) + else: + self._assert_dirs_equal(e1path, e2path) + e2list.remove(e1) + for e2 in e2list: + if e2 == ".svn" or e2 in ignore: + continue + self.fail("extra file or directory: %s" % e2) + + + def _run_test(self, testname): + """ + boilerplate for running *.patch file tests + """ + + # 1. create temp test directory + # 2. copy files + # 3. execute file-based patch + # 4. compare results + # 5. cleanup on success + + tmpdir = mkdtemp(prefix="%s."%testname) + + patch_file = join(tmpdir, "%s.patch" % testname) + shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) + + from_src = join(tests_dir, "%s.from" % testname) + from_tgt = join(tmpdir, "%s.from" % testname) + + if not isdir(from_src): + shutil.copy(from_src, from_tgt) + else: + for e in listdir(from_src): + if e == ".svn": + continue + epath = join(from_src, e) + if not isdir(epath): + shutil.copy(epath, join(tmpdir, e)) + else: + shutil.copytree(epath, join(tmpdir, e)) + + + # 3. + # test utility as a whole + patch_tool = join(dirname(tests_dir), "patch.py") + save_cwd = os.getcwdu() + os.chdir(tmpdir) + if verbose: + ret = os.system('%s %s "%s"' % (sys.executable, patch_tool, patch_file)) + else: + ret = os.system('%s %s -q "%s"' % (sys.executable, patch_tool, patch_file)) + assert ret == 0, "Error %d running test %s" % (ret, testname) + os.chdir(save_cwd) + + + # 4. + # compare results + if not isdir(from_src): + self._assert_files_equal(join(tests_dir, "%s.to" % testname), from_tgt) + else: + # need recursive compare + self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) + + + + shutil.rmtree(tmpdir) + return 0 + + +def add_test_methods(cls): + """ + hack to generate test* methods in target class - one + for each *.patch file in tests directory + """ + + # list testcases - every test starts with number + # and add them as test* methods + testptn = re.compile(r"^(?P<name>\d{2,}.+)\.(?P<ext>[^\.]+)") + testset = sorted( set([testptn.match(e).group('name') for e in listdir(tests_dir) if testptn.match(e)]) ) + + for filename in testset: + methname = filename.replace(" ", "_") + def create_closure(): + name = filename + return lambda self: self._run_test(name) + setattr(cls, "test%s" % methname, create_closure()) + if verbose: + print "added test method %s to %s" % (methname, cls) +add_test_methods(TestPatchFiles) + +# ---------------------------------------------------------------------------- + +class TestCheckPatched(unittest.TestCase): + def setUp(self): + self.save_cwd = os.getcwdu() + os.chdir(tests_dir) + + def tearDown(self): + os.chdir(self.save_cwd) + + def test_patched_multiline(self): + pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) + os.chdir(join(tests_dir, "01uni_multi.to")) + self.assert_(pto.can_patch("updatedlg.cpp")) + + def test_can_patch_single_source(self): + pto2 = patch.fromfile(join(tests_dir, "02uni_newline.patch")) + self.assert_(pto2.can_patch("02uni_newline.from")) + + def test_can_patch_fails_on_target_file(self): + pto3 = patch.fromfile(join(tests_dir, "03trail_fname.patch")) + self.assertEqual(None, pto3.can_patch("03trail_fname.to")) + self.assertEqual(None, pto3.can_patch("not_in_source.also")) + + def test_multiline_false_on_other_file(self): + pto = patch.fromfile(join(tests_dir, "01uni_multi.patch")) + os.chdir(join(tests_dir, "01uni_multi.from")) + self.assertFalse(pto.can_patch("updatedlg.cpp")) + + def test_single_false_on_other_file(self): + pto3 = patch.fromfile(join(tests_dir, "03trail_fname.patch")) + self.assertFalse(pto3.can_patch("03trail_fname.from")) + + def test_can_patch_fails_even_if_file_in_targets_can_be_patched(self): + pto2 = patch.fromfile(join(tests_dir, "04can_patch.patch")) + self.assert_(not pto2.can_patch("04can_patch.to")) + +# ---------------------------------------------------------------------------- + +class TestPatchParse(unittest.TestCase): + def test_fromstring(self): + try: + f = open(join(tests_dir, "01uni_multi.patch"), "rb") + readstr = f.read() + finally: + f.close() + pto = patch.fromstring(readstr) + self.assertEqual(len(pto.source), 5) + +# ---------------------------------------------------------------------------- + + +if __name__ == '__main__': + unittest.main() |