File: updates.py

package info (click to toggle)
psychopy 2020.2.10%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 44,056 kB
  • sloc: python: 119,649; javascript: 3,022; makefile: 148; sh: 125; xml: 9
file content (640 lines) | stat: -rw-r--r-- 27,548 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Part of the PsychoPy library
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2020 Open Science Tools Ltd.
# Distributed under the terms of the GNU General Public License (GPL).

from __future__ import absolute_import, division, print_function

from builtins import object
import sys
import re
import glob
import zipfile
import os
from pkg_resources import parse_version
import wx
import wx.lib.filebrowsebutton
try:
    import wx.lib.agw.hyperlink as wxhl  # 4.0+
except ImportError:
    import wx.lib.hyperlink as wxhl # <3.0.2

import psychopy
from .. import dialogs
from psychopy.localization import _translate
from psychopy import logging
from psychopy import web
from psychopy import constants
if constants.PY3:
    import io
else:
    import StringIO as io
urllib = web.urllib

versionURL = "http://www.psychopy.org/version.txt"

"""The Updater class checks for updates and suggests that an update is carried
out if a new version is found. The actual updating is handled by
InstallUpdateDialog (via Updater.doUpdate() ).
"""


def getLatestVersionInfo(app=None):
    """
    Fetch info about the latest available version.
    Returns -1 if fails to make a connection
    """
    try:
        page = urllib.request.urlopen(versionURL)
    except urllib.error.URLError:
        return -1
    # parse update file as a dictionary
    latest = {}
    for line in page.readlines():
        # in some odd circumstances (wifi hotspots) you can fetch a
        # page that is not the correct URL but a redirect
        if constants.PY3:
            line = line.decode()  # convert from a byte to a str
        if line.find(':') == -1:
            return -1
            # this will succeed if every line has a key
        key, keyInfo = line.split(':')
        latest[key] = keyInfo.replace('\n', '').replace('\r', '')
    if app:
        app._latestAvailableVersion = latest
    return latest


class Updater(object):

    def __init__(self, app=None, runningVersion=None):
        """The updater will check for updates and download/install as needed.
        Several dialogs may be created as needed during the process.

        Usage::

            if app.prefs['AutoUpdate']:
                app.updates = Updater(app)
                app.updater.checkForUpdates()
                # if updates are found further dialogs will prompt
        """
        super(Updater, self).__init__()
        self.app = app
        if runningVersion is None:
            self.runningVersion = psychopy.__version__
        else:
            self.runningVersion = runningVersion

        # self.headers = {'User-Agent': psychopy.constants.PSYCHOPY_USERAGENT}
        self.latest = None
        if web.proxies is None:
            web.setupProxy()

    def getLatestInfo(self, warnMsg=False):
        # open page
        latest = getLatestVersionInfo()
        if latest == -1:
            m1 = _translate("Couldn't connect to psychopy.org to check for "
                            "updates. \n")
            m2 = _translate("Check internet settings (and proxy setting in "
                            "PsychoPy Preferences).")
            confirmDlg = dialogs.MessageDialog(
                parent=None, message=m1 + m2, type='Info',
                title=_translate('PsychoPy updates'))
            confirmDlg.ShowModal()
        return latest

    def suggestUpdate(self, confirmationDlg=False):
        """Query user about whether to update (if it's possible to update)
        """
        if self.latest is None:  # we haven't checked for updates yet
            self.latest = self.getLatestInfo()

        if self.latest == -1:
            return -1  # failed to find out about updates
        # have found 'latest'. Is it newer than running version?
        try:
            newer = self.latest['version'] > self.runningVersion
        except KeyError as err:
            print(self.latest)
            raise(err)
        skip = self.app.prefs.appData['skipVersion'] == self.latest['version']
        if newer and not skip:
            if (parse_version(self.latest['lastUpdatable'])
                    <= parse_version(self.runningVersion)):
                # go to the updating window
                confirmDlg = SuggestUpdateDialog(
                    self.latest, self.runningVersion)
                resp = confirmDlg.ShowModal()
                confirmDlg.Destroy()
                # what did the user ask us to do?
                if resp == wx.ID_CANCEL:
                    return 0  # do nothing
                if resp == wx.ID_NO:
                    self.app.prefs.appData[
                        'skipVersion'] = self.latest['version']
                    self.app.prefs.saveAppData()
                    return 0  # do nothing
                if resp == wx.ID_YES:
                    self.doUpdate()
            else:
                # the latest version needs a full install, not autoupdate
                msg = _translate("PsychoPy v%(latest)s is available (you are"
                                 " running %(running)s).\n\n")
                msg = msg % {'latest': self.latest['version'],
                             'running': self.runningVersion}
                msg += _translate("This version is too big an update to be "
                                  "handled automatically.\n")
                msg += _translate("Please fetch the latest version from "
                                  "www.psychopy.org and install manually.")
                confirmDlg = dialogs.MessageDialog(
                    parent=None, message=msg, type='Warning',
                    title=_translate('PsychoPy updates'))
                confirmDlg.cancelBtn.SetLabel(_translate('Go to downloads'))
                confirmDlg.cancelBtn.SetDefault()
                confirmDlg.noBtn.SetLabel(_translate('Go to changelog'))
                confirmDlg.yesBtn.SetLabel(_translate('Later'))
                resp = confirmDlg.ShowModal()
                confirmDlg.Destroy()
                if resp == wx.ID_CANCEL:
                    self.app.followLink(url=self.app.urls['downloads'])
                if resp == wx.ID_NO:
                    self.app.followLink(url=self.app.urls['changelog'])
        elif not confirmationDlg:  # do nothing
            return 0
        else:
            txt = _translate(
                "You are running the latest version of PsychoPy (%s). ")
            msg = txt % self.runningVersion
            confirmDlg = dialogs.MessageDialog(
                parent=None, message=msg, type='Info',
                title=_translate('PsychoPy updates'))
            confirmDlg.ShowModal()
            return -1

    def doUpdate(self):
        """Should be called from suggestUpdate
        (separate dialog to ask user whether they want to)
        """
        # app contains a reciprocal pointer to this Updater object
        dlg = InstallUpdateDialog(None, -1, app=self.app)


class SuggestUpdateDialog(wx.Dialog):
    """A dialog explaining that a new version is available
    with a link to the changelog
    """

    def __init__(self, latest, runningVersion):
        wx.Dialog.__init__(self, None, -1, title='PsychoPy3 Updates')
        sizer = wx.BoxSizer(wx.VERTICAL)

        # info about current version
        txt = _translate("PsychoPy v%(latest)s is available (you are running "
                         "%(running)s).\n\n"
                         "(To disable this check, see Preferences > "
                         "connections > checkForUpdates)")
        label = txt % {'latest': latest['version'], 'running': runningVersion}
        msg1 = wx.StaticText(self, -1, style=wx.ALIGN_CENTRE, label=label)
        if latest['lastCompatible'] > runningVersion:
            label = _translate("This version MAY require you to modify your\n"
                               "scripts/exps slightly. Read the changelog "
                               "carefully.")
            msg2 = wx.StaticText(self, -1, style=wx.ALIGN_CENTRE, label=label)
            msg2.SetForegroundColour([200, 0, 0])
        else:
            label = _translate("There are no known compatibility\nissues "
                               "with your current version.")
            msg2 = wx.StaticText(self, -1, style=wx.ALIGN_CENTRE, label=label)
        changelog = wxhl.HyperLinkCtrl(self, wx.ID_ANY,
                                       _translate("View complete Changelog"),
                                       URL="https://www.psychopy.org/changelog.html")

        if sys.platform.startswith('linux'):
            msg = _translate("You can update PsychoPy with your package "
                             "manager")
            msg3 = wx.StaticText(self, -1, msg)
        else:
            msg = _translate("Should PsychoPy update itself?")
            msg3 = wx.StaticText(self, -1, msg)

        sizer.Add(msg1, flag=wx.ALL | wx.CENTER, border=15)
        sizer.Add(msg2, flag=wx.RIGHT | wx.LEFT | wx.CENTER, border=15)
        sizer.Add(changelog, flag=wx.RIGHT | wx.LEFT | wx.CENTER, border=5)
        sizer.Add(msg3, flag=wx.ALL | wx.CENTER, border=15)

        # add buttons
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        # for linux there should be no 'update' option
        if sys.platform.startswith('linux'):
            self.cancelBtn = wx.Button(
                self, wx.ID_CANCEL, _translate('Keep warning me'))
            self.cancelBtn.SetDefault()
            self.noBtn = wx.Button(
                self, wx.ID_NO, _translate('Stop warning me'))
        else:
            self.yesBtn = wx.Button(self, wx.ID_YES, _translate('Yes'))
            self.Bind(wx.EVT_BUTTON, self.onButton, id=wx.ID_YES)
            self.cancelBtn = wx.Button(
                self, wx.ID_CANCEL, _translate('Not now'))
            self.noBtn = wx.Button(
                self, wx.ID_NO, _translate('Skip this version'))
        self.Bind(wx.EVT_BUTTON, self.onButton, id=wx.ID_CANCEL)
        self.Bind(wx.EVT_BUTTON, self.onButton, id=wx.ID_NO)
        btnSizer.Add(self.noBtn, wx.ALIGN_LEFT)
        btnSizer.Add((60, 20), 0, wx.EXPAND)
        btnSizer.Add(self.cancelBtn)

        if not sys.platform.startswith('linux'):
            self.yesBtn.SetDefault()
            btnSizer.Add((5, 20), 0)
            btnSizer.Add(self.yesBtn)

        # configure sizers and fit
        sizer.Add(btnSizer, flag= wx.ALL, border=5)
        self.Center()
        self.SetSizerAndFit(sizer)

    def onButton(self, event):
        self.EndModal(event.GetId())


class InstallUpdateDialog(wx.Dialog):

    def __init__(self, parent, ID, app):
        """Latest is optional extra. If not given it will be fetched.
        """
        self.app = app
        # get latest version info if poss
        if app.updater in [False, None]:
            # user has turned off check for updates in prefs so check now
            app.updater = updater = Updater(app=self.app)
            # don't need a warning - we'll provide one ourselves
            self.latest = updater.getLatestInfo(warnMsg=False)
        else:
            self.latest = app.updater.latest
        self.runningVersion = app.updater.runningVersion
        wx.Dialog.__init__(self, parent, ID,
                           title=_translate('PsychoPy Updates'),
                           size=(100, 200))

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        # set the actual content of status msg later in self.updateStatus()
        self.statusMessage = wx.StaticText(
            self, -1, "msg", style=wx.ALIGN_CENTER)
        mainSizer.Add(self.statusMessage, flag=wx.EXPAND | wx.ALL, border=5)
        # ctrls for auto-update from web
        msg = _translate(" Auto-update (will fetch latest version)")
        self.useLatestBtn = wx.RadioButton(self, -1, msg,
                                           style=wx.RB_GROUP)
        self.Bind(wx.EVT_RADIOBUTTON, self.onRadioSelect, self.useLatestBtn)
        self.progressBar = wx.Gauge(self, -1, 100, size=(250, 25))
        mainSizer.Add(self.useLatestBtn,
                      flag=wx.ALIGN_LEFT | wx.ALL, border=5)
        mainSizer.Add(self.progressBar, flag=wx.EXPAND | wx.ALL, border=5)
        # ctrls for updating from specific zip file
        msg = _translate(" Use zip file below (download a PsychoPy release "
                         "file ending .zip)")
        self.useZipBtn = wx.RadioButton(self, -1, msg)
        self.Bind(wx.EVT_RADIOBUTTON, self.onRadioSelect, self.useZipBtn)
        self.fileBrowseCtrl = wx.lib.filebrowsebutton.FileBrowseButton(
            self, -1, size=(450, -1), changeCallback=self.onFileBrowse,
            fileMask='*.zip')
        mainSizer.Add(self.useZipBtn, flag=wx.ALIGN_LEFT | wx.ALL, border=5)
        mainSizer.Add(self.fileBrowseCtrl,
                      flag=wx.ALIGN_LEFT | wx.ALL, border=5)
        # ctrls for buttons (install/cancel)
        self.installBtn = wx.Button(self, -1, _translate('Install'))
        self.Bind(wx.EVT_BUTTON, self.onInstall, self.installBtn)
        self.installBtn.SetDefault()
        self.cancelBtn = wx.Button(self, -1, _translate('Close'))
        self.Bind(wx.EVT_BUTTON, self.onCancel, self.cancelBtn)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer.Add(self.installBtn)
        btnSizer.Add(self.cancelBtn, flag= wx.LEFT, border=5)
        mainSizer.Add(btnSizer, flag= wx.ALL, border=5)

        self.SetSizerAndFit(mainSizer)
        self.SetAutoLayout(True)

        # positioning and sizing
        self.updateStatus()
        self.Center()
        self.ShowModal()

    def updateStatus(self):
        """Check the current version and most recent version and update ctrls
        """
        if self.latest == -1:
            msg = _translate(
                "You are running PsychoPy v%s.\n ") % self.runningVersion
            msg += _translate("PsychoPy could not connect to the \n internet"
                              " to check for more recent versions.\n")
            msg += _translate("Check proxy settings in preferences.")
        elif (parse_version(self.latest['version'])
                  < parse_version(self.runningVersion)):
            msg = _translate(
                "You are running PsychoPy (%(running)s), which is ahead of "
                "the latest official version (%(latest)s)") % {
                'running':self.runningVersion, 'latest':self.latest['version']}
        elif self.latest['version'] == self.runningVersion:
            msg = _translate(
                "You are running the latest version of PsychoPy (%s)\n ") % self.runningVersion
            msg += _translate("You can revert to a previous version by "
                              "selecting a specific .zip source installation file")
        else:
            txt = _translate(
                "PsychoPy v%(latest)s is available\nYou are running v%(running)s")
            msg = txt % {'latest': self.latest['version'],
                         'running': self.runningVersion}
            if (parse_version(self.latest['lastUpdatable'])
                                  <= parse_version(self.runningVersion)):
                msg += _translate("\nYou can update to the latest version automatically")
            else:
                msg += _translate("\nYou cannot update to the latest version "
                                  "automatically.\nPlease fetch the latest "
                                  "Standalone package from www.psychopy.org")
        self.statusMessage.SetLabel(msg)
        areRunningLatest = self.latest['version'] == self.runningVersion
        notUpdateable = self.latest['lastUpdatable'] > self.runningVersion
        if self.latest == -1 or areRunningLatest or notUpdateable:
            # can't auto-update
            self.currentSelection = self.useZipBtn
            self.useZipBtn.SetValue(True)
            self.useLatestBtn.Disable()
        else:
            self.currentSelection = self.useLatestBtn
            self.useLatestBtn.SetValue(True)
        self.Fit()
        # this will enable/disable additional controls for the above:
        self.onRadioSelect()

    def onRadioSelect(self, event=None):
        """Set the controls of the appropriate selection to disabled/enabled
        """
        # if receive no event then just set everything to previous state
        if event != None:
            self.currentSelection = event.GetEventObject()
        else:
            pass
        if self.currentSelection == self.useLatestBtn:
            self.fileBrowseCtrl.Disable()
            self.progressBar.Enable()
        elif self.currentSelection == self.useZipBtn:
            self.fileBrowseCtrl.Enable()
            self.progressBar.Disable()
            # if this has been disabled by the fact that we couldn't connect
            self.installBtn.Enable()

    def onCancel(self, event):
        self.app.updater = None
        self.Close()

    def onFileBrowse(self, event):
        self.filename = event.GetString()

    def onInstall(self, event):
        if self.currentSelection == self.useLatestBtn:
            info = self.doAutoInstall()
        else:
            info = self.installZipFile(self.filename)
        self.statusMessage.SetLabel(str(info))
        self.Fit()

    def fetchPsychoPy(self, v='latest'):
        msg = _translate("Attempting to fetch PsychoPy %s...")
        self.statusMessage.SetLabel(msg % self.latest['version'])
        info = ""
        if v == 'latest':
            v = self.latest['version']

        # open page
        URL = "http://github.com/psychopy/psychopy/releases/download/%s/PsychoPy-%s.zip"
        page = urllib.request.urlopen(URL % v)
        # download in chunks so that we can monitor progress and abort mid-way
        chunk = 4096
        read = 0
        fileSize = int(page.info()['Content-Length'])
        buffer = io.StringIO()
        self.progressBar.SetRange(fileSize)
        while read < fileSize:
            ch = page.read(chunk)
            buffer.write(ch)
            read += chunk
            self.progressBar.SetValue(read)
            txt = _translate(
                "Fetched %(done)i of %(total)i kb of PsychoPy-%(version)s.zip")
            msg = txt % {'done': read // 1000,
                         'total': fileSize // 1000, 'version': v}
            self.statusMessage.SetLabel(msg)
            self.Update()
        info += _translate('Successfully downloaded PsychoPy-%s.zip') % v
        page.close()
        zfile = zipfile.ZipFile(buffer)
        # buffer.close()
        return zfile, info

    def installZipFile(self, zfile, v=None):
        """If v is provided this will be used as new version number;
        otherwise try and retrieve a version number from zip file name
        """
        info = ""  # return this at the end
        if constants.PY3:
            zfileIsName = type(zfile) == str
        else:
            zfileIsName = type(zfile) in (str, unicode)
        if os.path.isfile(zfile) and zfileIsName:
            # zfile is filename not an actual file
            if v is None:  # try and deduce it
                zFilename = os.path.split(zfile)[-1]
                searchName = re.search('[0-9]*\.[0-9]*\.[0-9]*.', zFilename)
                if searchName != None:
                    v = searchName.group(0)[:-1]
                else:
                    msg = "Couldn't deduce version from zip file: %s"
                    logging.warning(msg % zFilename)
            f = open(zfile, 'rb')
            zfile = zipfile.ZipFile(f)
        else:  # assume here that zfile is a ZipFile
            pass  # todo: error checking - is it a zipfile?

        currPath = self.app.prefs.paths['psychopy']
        currVer = psychopy.__version__
        # any commands that are successfully executed may need to be undone if
        # a later one fails
        undoStr = ""
        # depending on install method, needs diff handling
        # if path ends with 'psychopy' then move it to 'psychopy-version' and
        # create a new 'psychopy' folder for new version
        # does the path contain any version number?
        versionLabelsInPath = re.findall('PsychoPy-.*/', currPath)
        # e.g. the mac standalone app, no need to refer to new version number
        onWin32 = bool(sys.platform == 'win32' and
                       int(sys.getwindowsversion()[1]) > 5)
        if len(versionLabelsInPath) == 0:
            unzipTarget = currPath
            try:  # to move existing PsychoPy
                os.rename(currPath, "%s-%s" % (currPath, currVer))
                undoStr += 'os.rename("%s-%s" %(currPath, currVer),currPath)\n'
            except Exception:
                if onWin32:
                    msg = _translate("To upgrade you need to restart the app"
                                     " as admin (Right-click the app and "
                                     "'Run as admin')")
                else:
                    msg = _translate("Could not move existing PsychoPy "
                                     "installation (permissions error?)")
                return msg
        else:  # setuptools-style installation
            # generate new target path
            unzipTarget = currPath
            for thisVersionLabel in versionLabelsInPath:
                # remove final slash from the re.findall
                pathVersion = thisVersionLabel[:-1]
                unzipTarget = unzipTarget.replace(pathVersion,
                                                  "PsychoPy-%s" % v)
                # find the .pth file that specifies the python dir
                # create the new installation directory BEFORE changing pth
                # file
                nUpdates, newInfo = self.updatePthFile(pathVersion,
                                                       "PsychoPy-%s" % v)
                if nUpdates == -1:  # there was an error (likely permissions)
                    undoStr += 'self.updatePthFile(unzipTarget, currPath)\n'
                    exec(undoStr)  # undo previous changes
                    return newInfo

        try:
            # create the new installation dir AFTER renaming existing dir
            os.makedirs(unzipTarget)
            undoStr += 'os.remove(%s)\n' % unzipTarget
        except Exception:  # revert path rename and inform user
            exec(undoStr)  # undo previous changes
            if onWin32:
                msg = _translate(
                    "Right-click the app and 'Run as admin'):\n%s")
            else:
                msg = _translate("Failed to create directory for new version"
                                 " (permissions error?):\n%s")
            return msg % unzipTarget

        # do the actual extraction
        for name in zfile.namelist():  # for each file within the zip
            # check that this file is part of psychopy (not metadata or docs)
            if name.count('/psychopy/') < 1:
                continue
            try:
                targetFile = os.path.join(unzipTarget,
                                          name.split('/psychopy/')[1])
                targetContainer = os.path.split(targetFile)[0]
                if not os.path.isdir(targetContainer):
                    os.makedirs(targetContainer)  # make the containing folder
                if targetFile.endswith('/'):
                    os.makedirs(targetFile)  # it's a folder
                else:
                    outfile = open(targetFile, 'wb')
                    outfile.write(zfile.read(name))
                    outfile.close()
            except Exception:
                exec(undoStr)  # undo previous changes
                logging.error('failed to unzip file: ' + name)
                logging.error(sys.exc_info()[0])
        info += _translate('Success. \nChanges to PsychoPy will be completed'
                           ' when the application is next run')
        self.cancelBtn.SetDefault()
        self.installBtn.Disable()
        return info

    def doAutoInstall(self, v='latest'):
        if v == 'latest':
            v = self.latest['version']
        msg = _translate("Downloading PsychoPy v%s") % v
        self.statusMessage.SetLabel(msg)
        try:
            zipFile, info = self.fetchPsychoPy(v)
        except Exception:
            msg = _translate('Failed to fetch PsychoPy release.\n'
                             'Check proxy setting in preferences')
            self.statusMessage.SetLabel(msg)
            return -1
        self.statusMessage.SetLabel(info)
        self.Fit()
        # got a download - try to install it
        info = self.installZipFile(zipFile, v)
        return info

    def updatePthFile(self, oldName, newName):
        """Searches site-packages for .pth files and replaces any instance of
        `oldName` with `newName`, expect names like PsychoPy-1.60.04
        """
        from distutils.sysconfig import get_python_lib
        siteDir = get_python_lib()
        pthFiles = glob.glob(os.path.join(siteDir, '*.pth'))
        # sometimes the site-packages dir isn't where the pth files are kept?
        enclosingSiteDir = os.path.split(siteDir)[0]
        pthFiles.extend(glob.glob(os.path.join(enclosingSiteDir, '*.pth')))
        nUpdates = 0  # no paths updated
        info = ""
        for filename in pthFiles:
            lines = open(filename, 'r').readlines()
            needSave = False
            for lineN, line in enumerate(lines):
                if oldName in line:
                    lines[lineN] = line.replace(oldName, newName)
                    needSave = True
            if needSave:
                try:
                    f = open(filename, 'w')
                    f.writelines(lines)
                    f.close()
                    nUpdates += 1
                    logging.info('Updated PsychoPy path in %s' % filename)
                except Exception:
                    info += 'Failed to update PsychoPy path in %s' % filename
                    return -1, info
        return nUpdates, info


def sendUsageStats():
    """Sends anonymous, very basic usage stats to psychopy server:
      the version of PsychoPy
      the system used (platform and version)
      the date
    """

    v = psychopy.__version__
    dateNow = time.strftime("%Y-%m-%d_%H:%M")
    miscInfo = ''

    # urllib.install_opener(opener)
    # check for proxies
    if web.proxies is None:
        web.setupProxy()

    # get platform-specific info
    if sys.platform == 'darwin':
        OSXver, junk, architecture = platform.mac_ver()
        systemInfo = "OSX_%s_%s" % (OSXver, architecture)
    elif sys.platform.startswith('linux'):
        from distro import linux_distribution
        systemInfo = '%s_%s_%s' % (
            'Linux',
            ':'.join([x for x in linux_distribution() if x != '']),
            platform.release())
        if len(systemInfo) > 30:  # if it's too long PHP/SQL fails to store!?
            systemInfo = systemInfo[0:30]
    elif sys.platform == 'win32':
        systemInfo = "win32_v" + platform.version()
    else:
        systemInfo = platform.system() + platform.release()
    u = "http://www.psychopy.org/usage.php?date=%s&sys=%s&version=%s&misc=%s"
    URL = u % (dateNow, systemInfo, v, miscInfo)
    try:
        req = urllib.request.Request(URL)
        page = urllib.request.urlopen(req)  # proxies
    except Exception:
        logging.warning("Couldn't connect to psychopy.org\n"
                        "Check internet settings (and proxy "
                        "setting in PsychoPy Preferences.")