summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuo Jinghua <sunmoon1997@gmail.com>2010-10-22 06:56:49 +0800
committerLuo Jinghua <sunmoon1997@gmail.com>2010-10-22 06:56:49 +0800
commitbce5cfd226d4c6f6cab4aa0352b7f10c9fcc1e55 (patch)
treeee2a5c554b356ab98b272e7ac3bf9c1e008fc7b5
Initial import of patch.py
-rw-r--r--doc/.svn/all-wcprops47
-rw-r--r--doc/.svn/entries266
-rw-r--r--doc/.svn/prop-base/LICENSE.svn-base5
-rw-r--r--doc/.svn/prop-base/unified_diff_format.png.svn-base5
-rw-r--r--doc/.svn/prop-base/unified_diff_format.svg.svn-base5
-rw-r--r--doc/.svn/text-base/LICENSE.svn-base22
-rw-r--r--doc/.svn/text-base/example.diff.diff.svn-base20
-rw-r--r--doc/.svn/text-base/example.hg.diff.svn-base22
-rw-r--r--doc/.svn/text-base/example.python25.diff.svn-base22
-rw-r--r--doc/.svn/text-base/example.svn.diff.svn-base86
-rw-r--r--doc/.svn/text-base/unified_diff_format.png.svn-basebin0 -> 72047 bytes
-rw-r--r--doc/.svn/text-base/unified_diff_format.svg.svn-base1453
-rw-r--r--doc/LICENSE22
-rw-r--r--doc/example.diff.diff20
-rw-r--r--doc/example.hg.diff22
-rw-r--r--doc/example.python25.diff22
-rw-r--r--doc/example.svn.diff86
-rw-r--r--doc/unified_diff_format.pngbin0 -> 72047 bytes
-rw-r--r--doc/unified_diff_format.svg1453
-rw-r--r--patch.py658
-rw-r--r--python-patch-read-only/.svn/all-wcprops11
-rw-r--r--python-patch-read-only/.svn/dir-prop-base6
-rw-r--r--python-patch-read-only/.svn/entries68
-rw-r--r--python-patch-read-only/.svn/prop-base/patch.py.svn-base9
-rw-r--r--python-patch-read-only/.svn/text-base/patch.py.svn-base658
-rw-r--r--tests/.svn/all-wcprops77
-rw-r--r--tests/.svn/entries442
-rw-r--r--tests/.svn/prop-base/01uni_multi.patch.svn-base5
-rw-r--r--tests/.svn/prop-base/run_tests.py.svn-base5
-rw-r--r--tests/.svn/text-base/01uni_multi.patch.svn-base180
-rw-r--r--tests/.svn/text-base/02uni_newline.from.svn-base4
-rw-r--r--tests/.svn/text-base/02uni_newline.patch.svn-base8
-rw-r--r--tests/.svn/text-base/02uni_newline.to.svn-base3
-rw-r--r--tests/.svn/text-base/03trail_fname.from.svn-base8
-rw-r--r--tests/.svn/text-base/03trail_fname.patch.svn-base12
-rw-r--r--tests/.svn/text-base/03trail_fname.to.svn-base9
-rw-r--r--tests/.svn/text-base/04can_patch.from.svn-base40
-rw-r--r--tests/.svn/text-base/04can_patch.patch.svn-base11
-rw-r--r--tests/.svn/text-base/04can_patch.to.svn-base38
-rw-r--r--tests/.svn/text-base/Descript.ion.svn-base3
-rw-r--r--tests/.svn/text-base/run_tests.py.svn-base221
-rw-r--r--tests/01uni_multi.from/.svn/all-wcprops35
-rw-r--r--tests/01uni_multi.from/.svn/dir-prop-base5
-rw-r--r--tests/01uni_multi.from/.svn/entries198
-rw-r--r--tests/01uni_multi.from/.svn/prop-base/updatedlg.cpp.svn-base5
-rw-r--r--tests/01uni_multi.from/.svn/text-base/conf.cpp.svn-base110
-rw-r--r--tests/01uni_multi.from/.svn/text-base/conf.h.svn-base38
-rw-r--r--tests/01uni_multi.from/.svn/text-base/manifest.xml.svn-base19
-rw-r--r--tests/01uni_multi.from/.svn/text-base/updatedlg.cpp.svn-base726
-rw-r--r--tests/01uni_multi.from/.svn/text-base/updatedlg.h.svn-base73
-rw-r--r--tests/01uni_multi.from/conf.cpp110
-rw-r--r--tests/01uni_multi.from/conf.h38
-rw-r--r--tests/01uni_multi.from/manifest.xml19
-rw-r--r--tests/01uni_multi.from/updatedlg.cpp726
-rw-r--r--tests/01uni_multi.from/updatedlg.h73
-rw-r--r--tests/01uni_multi.patch180
-rw-r--r--tests/01uni_multi.to/.svn/all-wcprops35
-rw-r--r--tests/01uni_multi.to/.svn/dir-prop-base5
-rw-r--r--tests/01uni_multi.to/.svn/entries198
-rw-r--r--tests/01uni_multi.to/.svn/prop-base/updatedlg.cpp.svn-base5
-rw-r--r--tests/01uni_multi.to/.svn/text-base/conf.cpp.svn-base121
-rw-r--r--tests/01uni_multi.to/.svn/text-base/conf.h.svn-base39
-rw-r--r--tests/01uni_multi.to/.svn/text-base/manifest.xml.svn-base20
-rw-r--r--tests/01uni_multi.to/.svn/text-base/updatedlg.cpp.svn-base742
-rw-r--r--tests/01uni_multi.to/.svn/text-base/updatedlg.h.svn-base74
-rw-r--r--tests/01uni_multi.to/conf.cpp121
-rw-r--r--tests/01uni_multi.to/conf.h39
-rw-r--r--tests/01uni_multi.to/manifest.xml20
-rw-r--r--tests/01uni_multi.to/updatedlg.cpp742
-rw-r--r--tests/01uni_multi.to/updatedlg.h74
-rw-r--r--tests/02uni_newline.from4
-rw-r--r--tests/02uni_newline.patch8
-rw-r--r--tests/02uni_newline.to3
-rw-r--r--tests/03trail_fname.from8
-rw-r--r--tests/03trail_fname.patch12
-rw-r--r--tests/03trail_fname.to9
-rw-r--r--tests/04can_patch.from40
-rw-r--r--tests/04can_patch.patch11
-rw-r--r--tests/04can_patch.to38
-rw-r--r--tests/Descript.ion3
-rw-r--r--tests/run_tests.py221
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
new file mode 100644
index 0000000..2f7dadf
--- /dev/null
+++ b/doc/.svn/text-base/unified_diff_format.png.svn-base
Binary files differ
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">&quot;\ 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&quot; 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-&gt;InsertColumn(1, _(&quot;Version&quot;));</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-&gt;InsertColumn(2, _(&quot;Installed&quot;));</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-&gt;InsertColumn(3, _(&quot;Size&quot;), 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-&gt;InsertColumn(4, _(&quot;Rev&quot;));</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-&gt;SetColumnWidth(0, lst-&gt;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-&gt;SetColumnWidth(0, lst-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;SetItem(idx, 1, rec-&gt;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-&gt;SetItem(idx, 2, rec-&gt;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-&gt;SetItem(idx, 3, rec-&gt;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-&gt;SetItem(idx, 4, rec-&gt;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, &quot;lvFiles&quot;, 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-&gt;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-&gt;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&amp; 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, &quot;lvFiles&quot;, 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-&gt;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"> &lt;CodeBlocks_plugin_manifest_file&gt;</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"> &lt;SdkVersion major=&quot;1&quot; minor=&quot;10&quot; release=&quot;0&quot; /&gt;</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"> &lt;Plugin name=&quot;DevPakUpdater&quot;&gt;</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">- &lt;Value title=&quot;Dev-C++ DevPak updater/installer&quot; /&gt;</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">- &lt;Value version=&quot;0.1&quot; /&gt;</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">+ &lt;Value title=&quot;DevPak updater/installer&quot; /&gt;</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">+ &lt;Value version=&quot;0.2&quot; /&gt;</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"> &lt;Value description=&quot;Installs selected DevPaks from the Internet&quot; /&gt;</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"> &lt;Value author=&quot;Yiannis Mandravellos&quot; /&gt;</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"> &lt;Value authorEmail=&quot;info@codeblocks.org&quot; /&gt;</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"> &lt;Value authorWebsite=&quot;http://www.codeblocks.org/&quot; /&gt;</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"> &lt;Value thanksTo=&quot;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.&quot; /&gt;</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.&quot; /&gt;</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"> &lt;Value license=&quot;GPL&quot; /&gt;</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"> &lt;/Plugin&gt;</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">-&lt;/CodeBlocks_plugin_manifest_file&gt;</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">+&lt;/CodeBlocks_plugin_manifest_file&gt;</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
new file mode 100644
index 0000000..2f7dadf
--- /dev/null
+++ b/doc/unified_diff_format.png
Binary files differ
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">&quot;\ 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&quot; 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-&gt;InsertColumn(1, _(&quot;Version&quot;));</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-&gt;InsertColumn(2, _(&quot;Installed&quot;));</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-&gt;InsertColumn(3, _(&quot;Size&quot;), 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-&gt;InsertColumn(4, _(&quot;Rev&quot;));</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-&gt;SetColumnWidth(0, lst-&gt;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-&gt;SetColumnWidth(0, lst-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;SetItem(idx, 1, rec-&gt;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-&gt;SetItem(idx, 2, rec-&gt;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-&gt;SetItem(idx, 3, rec-&gt;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-&gt;SetItem(idx, 4, rec-&gt;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, &quot;lvFiles&quot;, 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-&gt;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-&gt;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&amp; 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, &quot;lvFiles&quot;, 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-&gt;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"> &lt;CodeBlocks_plugin_manifest_file&gt;</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"> &lt;SdkVersion major=&quot;1&quot; minor=&quot;10&quot; release=&quot;0&quot; /&gt;</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"> &lt;Plugin name=&quot;DevPakUpdater&quot;&gt;</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">- &lt;Value title=&quot;Dev-C++ DevPak updater/installer&quot; /&gt;</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">- &lt;Value version=&quot;0.1&quot; /&gt;</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">+ &lt;Value title=&quot;DevPak updater/installer&quot; /&gt;</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">+ &lt;Value version=&quot;0.2&quot; /&gt;</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"> &lt;Value description=&quot;Installs selected DevPaks from the Internet&quot; /&gt;</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"> &lt;Value author=&quot;Yiannis Mandravellos&quot; /&gt;</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"> &lt;Value authorEmail=&quot;info@codeblocks.org&quot; /&gt;</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"> &lt;Value authorWebsite=&quot;http://www.codeblocks.org/&quot; /&gt;</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"> &lt;Value thanksTo=&quot;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.&quot; /&gt;</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.&quot; /&gt;</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"> &lt;Value license=&quot;GPL&quot; /&gt;</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"> &lt;/Plugin&gt;</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">-&lt;/CodeBlocks_plugin_manifest_file&gt;</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">+&lt;/CodeBlocks_plugin_manifest_file&gt;</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()