From bce5cfd226d4c6f6cab4aa0352b7f10c9fcc1e55 Mon Sep 17 00:00:00 2001 From: Luo Jinghua Date: Fri, 22 Oct 2010 06:56:49 +0800 Subject: Initial import of patch.py --- doc/.svn/all-wcprops | 47 + doc/.svn/entries | 266 ++++ doc/.svn/prop-base/LICENSE.svn-base | 5 + .../prop-base/unified_diff_format.png.svn-base | 5 + .../prop-base/unified_diff_format.svg.svn-base | 5 + doc/.svn/text-base/LICENSE.svn-base | 22 + doc/.svn/text-base/example.diff.diff.svn-base | 20 + doc/.svn/text-base/example.hg.diff.svn-base | 22 + doc/.svn/text-base/example.python25.diff.svn-base | 22 + doc/.svn/text-base/example.svn.diff.svn-base | 86 ++ .../text-base/unified_diff_format.png.svn-base | Bin 0 -> 72047 bytes .../text-base/unified_diff_format.svg.svn-base | 1453 ++++++++++++++++++++ doc/LICENSE | 22 + doc/example.diff.diff | 20 + doc/example.hg.diff | 22 + doc/example.python25.diff | 22 + doc/example.svn.diff | 86 ++ doc/unified_diff_format.png | Bin 0 -> 72047 bytes doc/unified_diff_format.svg | 1453 ++++++++++++++++++++ patch.py | 658 +++++++++ python-patch-read-only/.svn/all-wcprops | 11 + python-patch-read-only/.svn/dir-prop-base | 6 + python-patch-read-only/.svn/entries | 68 + .../.svn/prop-base/patch.py.svn-base | 9 + .../.svn/text-base/patch.py.svn-base | 658 +++++++++ tests/.svn/all-wcprops | 77 ++ tests/.svn/entries | 442 ++++++ tests/.svn/prop-base/01uni_multi.patch.svn-base | 5 + tests/.svn/prop-base/run_tests.py.svn-base | 5 + tests/.svn/text-base/01uni_multi.patch.svn-base | 180 +++ tests/.svn/text-base/02uni_newline.from.svn-base | 4 + tests/.svn/text-base/02uni_newline.patch.svn-base | 8 + tests/.svn/text-base/02uni_newline.to.svn-base | 3 + tests/.svn/text-base/03trail_fname.from.svn-base | 8 + tests/.svn/text-base/03trail_fname.patch.svn-base | 12 + tests/.svn/text-base/03trail_fname.to.svn-base | 9 + tests/.svn/text-base/04can_patch.from.svn-base | 40 + tests/.svn/text-base/04can_patch.patch.svn-base | 11 + tests/.svn/text-base/04can_patch.to.svn-base | 38 + tests/.svn/text-base/Descript.ion.svn-base | 3 + tests/.svn/text-base/run_tests.py.svn-base | 221 +++ tests/01uni_multi.from/.svn/all-wcprops | 35 + tests/01uni_multi.from/.svn/dir-prop-base | 5 + tests/01uni_multi.from/.svn/entries | 198 +++ .../.svn/prop-base/updatedlg.cpp.svn-base | 5 + .../.svn/text-base/conf.cpp.svn-base | 110 ++ .../.svn/text-base/conf.h.svn-base | 38 + .../.svn/text-base/manifest.xml.svn-base | 19 + .../.svn/text-base/updatedlg.cpp.svn-base | 726 ++++++++++ .../.svn/text-base/updatedlg.h.svn-base | 73 + tests/01uni_multi.from/conf.cpp | 110 ++ tests/01uni_multi.from/conf.h | 38 + tests/01uni_multi.from/manifest.xml | 19 + tests/01uni_multi.from/updatedlg.cpp | 726 ++++++++++ tests/01uni_multi.from/updatedlg.h | 73 + tests/01uni_multi.patch | 180 +++ tests/01uni_multi.to/.svn/all-wcprops | 35 + tests/01uni_multi.to/.svn/dir-prop-base | 5 + tests/01uni_multi.to/.svn/entries | 198 +++ .../.svn/prop-base/updatedlg.cpp.svn-base | 5 + .../.svn/text-base/conf.cpp.svn-base | 121 ++ .../01uni_multi.to/.svn/text-base/conf.h.svn-base | 39 + .../.svn/text-base/manifest.xml.svn-base | 20 + .../.svn/text-base/updatedlg.cpp.svn-base | 742 ++++++++++ .../.svn/text-base/updatedlg.h.svn-base | 74 + tests/01uni_multi.to/conf.cpp | 121 ++ tests/01uni_multi.to/conf.h | 39 + tests/01uni_multi.to/manifest.xml | 20 + tests/01uni_multi.to/updatedlg.cpp | 742 ++++++++++ tests/01uni_multi.to/updatedlg.h | 74 + tests/02uni_newline.from | 4 + tests/02uni_newline.patch | 8 + tests/02uni_newline.to | 3 + tests/03trail_fname.from | 8 + tests/03trail_fname.patch | 12 + tests/03trail_fname.to | 9 + tests/04can_patch.from | 40 + tests/04can_patch.patch | 11 + tests/04can_patch.to | 38 + tests/Descript.ion | 3 + tests/run_tests.py | 221 +++ 81 files changed, 11001 insertions(+) create mode 100644 doc/.svn/all-wcprops create mode 100644 doc/.svn/entries create mode 100644 doc/.svn/prop-base/LICENSE.svn-base create mode 100644 doc/.svn/prop-base/unified_diff_format.png.svn-base create mode 100644 doc/.svn/prop-base/unified_diff_format.svg.svn-base create mode 100644 doc/.svn/text-base/LICENSE.svn-base create mode 100644 doc/.svn/text-base/example.diff.diff.svn-base create mode 100644 doc/.svn/text-base/example.hg.diff.svn-base create mode 100644 doc/.svn/text-base/example.python25.diff.svn-base create mode 100644 doc/.svn/text-base/example.svn.diff.svn-base create mode 100644 doc/.svn/text-base/unified_diff_format.png.svn-base create mode 100644 doc/.svn/text-base/unified_diff_format.svg.svn-base create mode 100644 doc/LICENSE create mode 100644 doc/example.diff.diff create mode 100644 doc/example.hg.diff create mode 100644 doc/example.python25.diff create mode 100644 doc/example.svn.diff create mode 100644 doc/unified_diff_format.png create mode 100644 doc/unified_diff_format.svg create mode 100644 patch.py create mode 100644 python-patch-read-only/.svn/all-wcprops create mode 100644 python-patch-read-only/.svn/dir-prop-base create mode 100644 python-patch-read-only/.svn/entries create mode 100644 python-patch-read-only/.svn/prop-base/patch.py.svn-base create mode 100644 python-patch-read-only/.svn/text-base/patch.py.svn-base create mode 100644 tests/.svn/all-wcprops create mode 100644 tests/.svn/entries create mode 100644 tests/.svn/prop-base/01uni_multi.patch.svn-base create mode 100644 tests/.svn/prop-base/run_tests.py.svn-base create mode 100644 tests/.svn/text-base/01uni_multi.patch.svn-base create mode 100644 tests/.svn/text-base/02uni_newline.from.svn-base create mode 100644 tests/.svn/text-base/02uni_newline.patch.svn-base create mode 100644 tests/.svn/text-base/02uni_newline.to.svn-base create mode 100644 tests/.svn/text-base/03trail_fname.from.svn-base create mode 100644 tests/.svn/text-base/03trail_fname.patch.svn-base create mode 100644 tests/.svn/text-base/03trail_fname.to.svn-base create mode 100644 tests/.svn/text-base/04can_patch.from.svn-base create mode 100644 tests/.svn/text-base/04can_patch.patch.svn-base create mode 100644 tests/.svn/text-base/04can_patch.to.svn-base create mode 100644 tests/.svn/text-base/Descript.ion.svn-base create mode 100644 tests/.svn/text-base/run_tests.py.svn-base create mode 100644 tests/01uni_multi.from/.svn/all-wcprops create mode 100644 tests/01uni_multi.from/.svn/dir-prop-base create mode 100644 tests/01uni_multi.from/.svn/entries create mode 100644 tests/01uni_multi.from/.svn/prop-base/updatedlg.cpp.svn-base create mode 100644 tests/01uni_multi.from/.svn/text-base/conf.cpp.svn-base create mode 100644 tests/01uni_multi.from/.svn/text-base/conf.h.svn-base create mode 100644 tests/01uni_multi.from/.svn/text-base/manifest.xml.svn-base create mode 100644 tests/01uni_multi.from/.svn/text-base/updatedlg.cpp.svn-base create mode 100644 tests/01uni_multi.from/.svn/text-base/updatedlg.h.svn-base create mode 100644 tests/01uni_multi.from/conf.cpp create mode 100644 tests/01uni_multi.from/conf.h create mode 100644 tests/01uni_multi.from/manifest.xml create mode 100644 tests/01uni_multi.from/updatedlg.cpp create mode 100644 tests/01uni_multi.from/updatedlg.h create mode 100644 tests/01uni_multi.patch create mode 100644 tests/01uni_multi.to/.svn/all-wcprops create mode 100644 tests/01uni_multi.to/.svn/dir-prop-base create mode 100644 tests/01uni_multi.to/.svn/entries create mode 100644 tests/01uni_multi.to/.svn/prop-base/updatedlg.cpp.svn-base create mode 100644 tests/01uni_multi.to/.svn/text-base/conf.cpp.svn-base create mode 100644 tests/01uni_multi.to/.svn/text-base/conf.h.svn-base create mode 100644 tests/01uni_multi.to/.svn/text-base/manifest.xml.svn-base create mode 100644 tests/01uni_multi.to/.svn/text-base/updatedlg.cpp.svn-base create mode 100644 tests/01uni_multi.to/.svn/text-base/updatedlg.h.svn-base create mode 100644 tests/01uni_multi.to/conf.cpp create mode 100644 tests/01uni_multi.to/conf.h create mode 100644 tests/01uni_multi.to/manifest.xml create mode 100644 tests/01uni_multi.to/updatedlg.cpp create mode 100644 tests/01uni_multi.to/updatedlg.h create mode 100644 tests/02uni_newline.from create mode 100644 tests/02uni_newline.patch create mode 100644 tests/02uni_newline.to create mode 100644 tests/03trail_fname.from create mode 100644 tests/03trail_fname.patch create mode 100644 tests/03trail_fname.to create mode 100644 tests/04can_patch.from create mode 100644 tests/04can_patch.patch create mode 100644 tests/04can_patch.to create mode 100644 tests/Descript.ion create mode 100644 tests/run_tests.py 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 +## +## 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 Binary files /dev/null and b/doc/.svn/text-base/unified_diff_format.png.svn-base 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 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + Unified Diff/Patch Format + + + comments are ignored + + + + the first file thatexists is used + + + + may contain severalhunks for each file + + + + -line_from,total_before+line_after,total_after + + + + --- filename \t comment + + + + line ends may differ + + + + for the format like-line_from +line_aftertotal_xxx = 1 + + + + + "\ No newline at end of file" marker is used iffile ends without newline + + + http://en.wikipedia.org/wiki/Diff#Unified_format + http://techtonik.rainforce.org + Index: src/plugins/contrib/devpak_plugin/updatedlg.cpp===================================================================--- src/plugins/contrib/devpak_plugin/updatedlg.cpp (revision 5106)+++ src/plugins/contrib/devpak_plugin/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: src/plugins/contrib/devpak_plugin/manifest.xml===================================================================--- src/plugins/contrib/devpak_plugin/manifest.xml (revision 5106)+++ src/plugins/contrib/devpak_plugin/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>+</CodeBlocks_plugin_manifest_file>\ No newline at end of file + + + + + + + line numbers start from 1 + + + 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 +## +## 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 Binary files /dev/null and b/doc/unified_diff_format.png 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 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + Unified Diff/Patch Format + + + comments are ignored + + + + the first file thatexists is used + + + + may contain severalhunks for each file + + + + -line_from,total_before+line_after,total_after + + + + --- filename \t comment + + + + line ends may differ + + + + for the format like-line_from +line_aftertotal_xxx = 1 + + + + + "\ No newline at end of file" marker is used iffile ends without newline + + + http://en.wikipedia.org/wiki/Diff#Unified_format + http://techtonik.rainforce.org + Index: src/plugins/contrib/devpak_plugin/updatedlg.cpp===================================================================--- src/plugins/contrib/devpak_plugin/updatedlg.cpp (revision 5106)+++ src/plugins/contrib/devpak_plugin/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: src/plugins/contrib/devpak_plugin/manifest.xml===================================================================--- src/plugins/contrib/devpak_plugin/manifest.xml (revision 5106)+++ src/plugins/contrib/devpak_plugin/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>+</CodeBlocks_plugin_manifest_file>\ No newline at end of file + + + + + + + line numbers start from 1 + + + 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 @@ + + + +- +- ++ ++ + + + + + ++ Julian R Seward for libbzip2. ++ ++ libbzip2 copyright notice: ++ bzip2 and associated library libbzip2, are ++ copyright (C) 1996-2000 Julian R Seward. ++ All rights reserved." /> + + + +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\d{2,}.+)\.(?P[^\.]+)") + 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 +#include +#include +#include + +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(""), _T("\n")); + rec.desc.Replace(_T(""), _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 +#include +#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 @@ + + + + + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include + +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(""), _T("\n")); + rec.desc.Replace(_T(""), _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 +#include +#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 @@ + + + + + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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 @@ + + + +- +- ++ ++ + + + + + ++ Julian R Seward for libbzip2. ++ ++ libbzip2 copyright notice: ++ bzip2 and associated library libbzip2, are ++ copyright (C) 1996-2000 Julian R Seward. ++ All rights reserved." /> + + + +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 +#include +#include +#include + +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(""), _T("\n")); + rec.desc.Replace(_T(""), _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 +#include +#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 @@ + + + + + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#include + +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(""), _T("\n")); + rec.desc.Replace(_T(""), _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 +#include +#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 @@ + + + + + + + + + + + + + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#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\d{2,}.+)\.(?P[^\.]+)") + 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() -- cgit v1.2.3