Commit 32f589ea authored by Matt Harbison's avatar Matt Harbison

about: migrate to `latest.dat` on for upgrade checks

This represents both a file format and host change.  The file on the server was
copied from a source used to advertise core Mercurial installers, so we're
reusing existing conventions.  It has a distinct pattern for WOW64 User-Agents
vs 64bit ones.  (Currently, both patterns point to the same file.)

Yuya expressed concern about blindly using regex patterns from the server, so
there's a check to filter out anything that's not trivial.  I toyed with the
idea of just checking substrings in each pattern, but it got hard to cover all
of the cases that the pattern matching provides.  For example x86 Windows needs
to know about x64 Windows in order to actively ignore x64 packages.  With
pattern matching, x86 Windows only needs to know about itself when emulating the
User-Agent.  I also toyed with the idea that matching any OS substring was OK
(e.g. any pattern containing "Windows").  But that won't work because I can
eventually see x86 Windows being frozen in time at the last version, and it
would be annoying if the x86 builds reported an update anyway.

Either of those rejected options could mean extra coordination with any changes
to the file on the server.  Overall, it seems better to have a single, unified
method of checking for upgrades from web browsers and the application,
especially because there's enough I forget about doing the package uploads once
per month already.

There is near-identical code in the bugreporter module, but I don't see an
obvious, shareable place to store this code.  The existing version modules seem
minimal by design, and various other util modules seem even less related.

branch : stable
parent 7e572dbf5cb7
......@@ -13,6 +13,8 @@ TortoiseHg About dialog - PyQt4 version
from __future__ import absolute_import
import platform
import re
import sys
from .qtcore import (
......@@ -159,7 +161,7 @@ class AboutDialog(QDialog):
def getUpdateInfo(self):
verurl = ''
verurl = ''
# If we use QNetworkAcessManager elsewhere, it should be shared
# through the application.
self._netmanager = QNetworkAccessManager(self)
......@@ -169,28 +171,73 @@ class AboutDialog(QDialog):
def uFinished(self):
newver = (0,0,0)
newverstr = '0.0.0'
upgradeurl = ''
f = pycompat.sysstr(self._newverreply.readAll().data()).splitlines()
data = self._newverreply.readAll().data()
self._newverreply = None
newverstr = f[0]
newver = tuple([int(p) for p in newverstr.split('.')])
upgradeurl = f[1] # generic download URL
platform = sys.platform
if platform == 'win32':
from win32process import IsWow64Process as IsX64 # pytype: disable=import-error
platform = IsX64() and 'x64' or 'x86'
# linux2 for Linux, darwin for OSX
for line in f[2:]:
p, _url = line.split(':', 1)
if platform == p:
upgradeurl = _url.strip()
except (IndexError, ImportError, ValueError):
# Simulate a User-Agent string for platforms with an entry in the file.
useragent = "unknown"
if sys.platform == 'win32':
from win32process import \
IsWow64Process as IsX64 # pytype: disable=import-error
if IsX64():
useragent = "Windows WOW64"
import struct
width = struct.calcsize("P") * 8
useragent = width == 64 and 'Windows x64' or 'Windows'
elif sys.platform == 'darwin':
# Mac packages will be universal binaries, but try to include the
# architecture so that older architectures can eventually be
# dropped from the current installer, and the last of the older
# architecture offered independently.
useragent = "Macintosh"
arch = platform.processor()
if arch == 'i386':
useragent += "; Intel"
candidates = {}
# Extra check to ensure the regex returned from the server is safe
# before using it.
def _check_regex(p):
# Clear any simple groups. Example: ``.*Windows.*(WOW|x)64.*``
p = re.sub(r"\([0-9A-Za-z|]+\)", "", p)
# Now the regex should be a simple alphanumeric or wildcards
return re.match("^[A-Za-z0-9.*]+$", p)
# priority \t version \t user-agent-regex \t download url \t desc
for line in pycompat.sysstr(data).splitlines():
parts = line.strip().split("\t")
pattern = parts[2]
if not _check_regex(pattern):
print("Ignoring upgrade pattern %s" % pattern)
if not re.match(pattern, useragent):
prio = int(parts[0].strip())
candidates[prio] = (parts[1].strip(), parts[3].strip())
except (IndexError, ValueError):
if not candidates:
prio = [p for p in sorted(candidates.keys())][0]
newverstr, upgradeurl = candidates[prio]
newver = tuple([int(p) for p in newverstr.split('.')])
thgv = version.package_version()
curver = tuple([int(p) for p in thgv.split('.')])
except ValueError:
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment