version_updater.py 26.8 KB
Newer Older
1
# Author: echel0n <[email protected]>
echel0n's avatar
echel0n committed
2
# URL: https://sickrage.ca
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#
# This file is part of SickRage.
#
# SickRage is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickRage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickRage.  If not, see <http://www.gnu.org/licenses/>.

19
20
from __future__ import unicode_literals

21
import io
22
23
24
import os
import platform
import re
25
import shutil
echel0n's avatar
echel0n committed
26
import stat
27
import subprocess
28
import sys
echel0n's avatar
echel0n committed
29
import tarfile
30
import threading
31
32
import traceback

33
import sickrage
34
from sickrage.core.helpers import backupSR
35
from sickrage.notifiers import Notifiers
36

37

38
class VersionUpdater(object):
39
40
41
42
    """
    Version check class meant to run as a thread object with the sr scheduler.
    """

43
    def __init__(self):
echel0n's avatar
echel0n committed
44
        self.name = "VERSIONUPDATER"
Fernando's avatar
Fernando committed
45
        self.amActive = False
46
47
48
49

    @property
    def updater(self):
        return self.find_install_type()
50
51

    def run(self, force=False):
echel0n's avatar
echel0n committed
52
        if self.amActive:
53
            return
54

Fernando's avatar
Fernando committed
55
        self.amActive = True
56

57
58
59
        # set thread name
        threading.currentThread().setName(self.name)

60
        try:
61
            if self.check_for_new_version(force) and sickrage.app.config.auto_update:
62
                if sickrage.app.show_updater.amActive:
63
                    sickrage.app.log.debug("We can't proceed with auto-updating. Shows are being updated")
64
65
                    return

66
                sickrage.app.log.info("New update found for SiCKRAGE, starting auto-updater ...")
67
                sickrage.app.alerts.message(_('New update found for SiCKRAGE, starting auto-updater'))
echel0n's avatar
echel0n committed
68
                if self.update():
69
                    sickrage.app.log.info("Update was successful!")
70
                    sickrage.app.alerts.message(_('Update was successful'))
71
                    sickrage.app.shutdown(restart=True)
echel0n's avatar
echel0n committed
72
                else:
73
                    sickrage.app.log.info("Update failed!")
74
                    sickrage.app.alerts.message(_('Update failed!'))
echel0n's avatar
echel0n committed
75
76
        finally:
            self.amActive = False
77

78
79
80
    def backup(self):
        if self.safe_to_update():
            # Do a system backup before update
81
            sickrage.app.log.info("Config backup in progress...")
82
            sickrage.app.alerts.message(_('Backup'), _('Config backup in progress...'))
83
            try:
84
                backupDir = os.path.join(sickrage.app.data_dir, 'backup')
85
86
                if not os.path.isdir(backupDir):
                    os.mkdir(backupDir)
Dustyn Gibson's avatar
Dustyn Gibson committed
87

echel0n's avatar
echel0n committed
88
                if backupSR(backupDir, keep_latest=True):
89
                    sickrage.app.log.info("Config backup successful, updating...")
90
                    sickrage.app.alerts.message(_('Backup'), _('Config backup successful, updating...'))
91
92
                    return True
                else:
93
                    sickrage.app.log.error("Config backup failed, aborting update")
94
                    sickrage.app.alerts.message(_('Backup'), _('Config backup failed, aborting update'))
95
96
                    return False
            except Exception as e:
97
                sickrage.app.log.error('Update: Config backup failed. Error: %s' % e)
98
                sickrage.app.alerts.message(_('Backup'), _('Config backup failed, aborting update'))
99
100
                return False

101
102
    @staticmethod
    def safe_to_update():
103
        if sickrage.app.config.developer:
echel0n's avatar
echel0n committed
104
105
            return False

106
        if not sickrage.app.started:
107
            return True
108
        if not sickrage.app.auto_postprocessor.amActive:
109
            return True
110

111
        sickrage.app.log.debug("We can't proceed with the update. Post-Processor is running")
112

113
114
    @staticmethod
    def find_install_type():
115
116
117
118
        """
        Determines how this copy of sr was installed.

        returns: type of installation. Possible values are:
119

120
            'git': running from source using git
121
            'pip': running from source using pip
122
123
124
            'source': running from source without git
        """

125
126
127
        # default to source install type
        install_type = SourceUpdateManager()

128
        if os.path.isdir(os.path.join(os.path.dirname(sickrage.PROG_DIR), '.git')):
129
130
            # GIT install type
            install_type = GitUpdateManager()
131
        elif PipUpdateManager().version:
132
133
134
135
            # PIP install type
            install_type = PipUpdateManager()

        return install_type
136
137
138
139
140
141

    def check_for_new_version(self, force=False):
        """
        Checks the internet for a newer version.

        returns: bool, True for new version or False for no new version.
142
        :param force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced
143
144
        """

145
        if self.updater and self.updater.need_update():
146
            if force: self.updater.set_newest_text()
echel0n's avatar
echel0n committed
147
            return True
148

149
    def update(self):
echel0n's avatar
echel0n committed
150
        if self.updater and self.backup():
151
152
            # check for updates
            if self.updater.need_update():
153
                if self.updater.update():
154
                    # Clean up after update
155
                    to_clean = os.path.join(sickrage.app.cache_dir, 'mako')
156

157
                    for root, dirs, files in os.walk(to_clean, topdown=False):
158
                        [os.remove(os.path.join(root, name)) for name in files]
159
                        [shutil.rmtree(os.path.join(root, name)) for name in dirs]
160

161
                    return True
162

163
    @property
164
    def version(self):
165
        if self.updater:
echel0n's avatar
echel0n committed
166
            return self.updater.version
167

echel0n's avatar
echel0n committed
168
169
170
171
172
    @property
    def branch(self):
        if self.updater:
            return self.updater.current_branch
        return "master"
echel0n's avatar
echel0n committed
173

174
class UpdateManager(object):
175
    @property
176
    def _git_path(self):
177
        test_cmd = '--version'
178

179
        main_git = sickrage.app.config.git_path or 'git'
180

181
        sickrage.app.log.debug("Checking if we can use git commands: " + main_git + ' ' + test_cmd)
182
        __, __, exit_status = self._git_cmd(main_git, test_cmd)
183
184

        if exit_status == 0:
185
            sickrage.app.log.debug("Using: " + main_git)
186
187
            return main_git
        else:
188
            sickrage.app.log.debug("Not using: " + main_git)
189
190
191
192
193
194
195
196
197
198
199
200
201

        # trying alternatives
        alternative_git = []

        # osx people who start sr from launchd have a broken path, so try a hail-mary attempt for them
        if platform.system().lower() == 'darwin':
            alternative_git.append('/usr/local/git/bin/git')

        if platform.system().lower() == 'windows':
            if main_git != main_git.lower():
                alternative_git.append(main_git.lower())

        if alternative_git:
202
            sickrage.app.log.debug("Trying known alternative git locations")
203
204

            for cur_git in alternative_git:
205
                sickrage.app.log.debug("Checking if we can use git commands: " + cur_git + ' ' + test_cmd)
206
                __, __, exit_status = self._git_cmd(cur_git, test_cmd)
207
208

                if exit_status == 0:
209
                    sickrage.app.log.debug("Using: " + cur_git)
210
211
                    return cur_git
                else:
212
                    sickrage.app.log.debug("Not using: " + cur_git)
213
214

        # Still haven't found a working git
echel0n's avatar
echel0n committed
215
216
        error_message = _('Unable to find your git executable - Set your git path from Settings->General->Advanced OR '
                          'delete your .git folder and run from source to enable updates.')
echel0n's avatar
echel0n committed
217

218
        sickrage.app.newest_version_string = error_message
219
220
221

        return None

222
223
224
225
    @property
    def _pip_path(self):
        test_cmd = '-V'

226
        main_pip = sickrage.app.config.pip_path or 'pip'
227

228
        sickrage.app.log.debug("Checking if we can use pip commands: " + main_pip + ' ' + test_cmd)
229
        __, __, exit_status = self._pip_cmd(main_pip, test_cmd)
230
231

        if exit_status == 0:
232
            sickrage.app.log.debug("Using: " + main_pip)
233
234
            return main_pip
        else:
235
            sickrage.app.log.debug("Not using: " + main_pip)
236
237
238
239
240
241
242
243
244
245
246

        # trying alternatives
        alternative_pip = []

        # osx people who start sr from launchd have a broken path, so try a hail-mary attempt for them
        if platform.system().lower() == 'darwin':
            alternative_pip.append('/usr/local/python2.7/bin/pip')

        if platform.system().lower() == 'windows':
            if main_pip != main_pip.lower():
                alternative_pip.append(main_pip.lower())
247

248
        if alternative_pip:
249
            sickrage.app.log.debug("Trying known alternative pip locations")
250
251

            for cur_pip in alternative_pip:
252
                sickrage.app.log.debug("Checking if we can use pip commands: " + cur_pip + ' ' + test_cmd)
253
                __, __, exit_status = self._pip_cmd(cur_pip, test_cmd)
254
255

                if exit_status == 0:
256
                    sickrage.app.log.debug("Using: " + cur_pip)
257
258
                    return cur_pip
                else:
259
                    sickrage.app.log.debug("Not using: " + cur_pip)
260
261

        # Still haven't found a working git
echel0n's avatar
echel0n committed
262
        error_message = _('Unable to find your pip executable - Set your pip path from Settings->General->Advanced')
263
        sickrage.app.newest_version_string = error_message
264
265
266
267
268

        return None

    @staticmethod
    def _git_cmd(git_path, args):
269
        output = err = None
270
271

        if not git_path:
272
            sickrage.app.log.warning("No path to git specified, can't use git commands")
273
            exit_status = 1
274
            return output, err, exit_status
275

276
        cmd = [git_path] + args.split()
277
278

        try:
279
            sickrage.app.log.debug("Executing " + ' '.join(cmd) + " with your shell in " + sickrage.PROG_DIR)
280
281
            p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                                 shell=(sys.platform == 'win32'), cwd=sickrage.PROG_DIR)
282
283
284
285
286
287
288
            output, err = p.communicate()
            exit_status = p.returncode

            if output:
                output = output.strip()

        except OSError:
289
            sickrage.app.log.info("Command " + ' '.join(cmd) + " didn't work")
290
291
292
            exit_status = 1

        if exit_status == 0:
293
            sickrage.app.log.debug(' '.join(cmd) + " : returned successful")
294
295
296
            exit_status = 0
        elif exit_status == 1:
            if 'stash' in output:
297
                sickrage.app.log.warning(
echel0n's avatar
echel0n committed
298
                    "Please enable 'git reset' in settings or stash your changes in local files")
299
            else:
300
                sickrage.app.log.debug(' '.join(cmd) + " returned : " + str(output))
301
302
            exit_status = 1
        elif exit_status == 128 or 'fatal:' in output or err:
303
            sickrage.app.log.debug(' '.join(cmd) + " returned : " + str(output))
304
305
            exit_status = 128
        else:
306
            sickrage.app.log.debug(' '.join(cmd) + " returned : " + str(output) + ", treat as error for now")
307
308
            exit_status = 1

309
        return output, err, exit_status
310

311
312
313
314
315
    @staticmethod
    def _pip_cmd(pip_path, args):
        output = err = None

        if not pip_path:
316
            sickrage.app.log.warning("No path to pip specified, can't use pip commands")
317
318
319
            exit_status = 1
            return output, err, exit_status

320
        cmd = [pip_path] + args.split()
321
322

        try:
323
            sickrage.app.log.debug("Executing " + ' '.join(cmd) + " with your shell in " + sickrage.PROG_DIR)
324
325
            p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                                 shell=(sys.platform == 'win32'), cwd=sickrage.PROG_DIR)
326
327
328
329
330
331
            output, err = p.communicate()
            exit_status = p.returncode

            if output:
                output = output.strip()
        except OSError:
332
            sickrage.app.log.info("Command " + ' '.join(cmd) + " didn't work")
333
334
335
            exit_status = 1

        if exit_status == 0:
336
            sickrage.app.log.debug(' '.join(cmd) + " : returned successful")
337
338
            exit_status = 0
        else:
339
            sickrage.app.log.debug(' '.join(cmd) + " returned : " + str(output) + ", treat as error for now")
340
341
342
343
344
345
            exit_status = 1

        return output, err, exit_status

    @staticmethod
    def get_update_url():
346
        return "{}/home/update/?pid={}".format(sickrage.app.config.web_root, sickrage.app.pid)
347
348

    def install_requirements(self):
349
        __, __, exit_status = self._pip_cmd(self._pip_path,
350
351
                                            'install --no-cache-dir --user -r {}'.format(sickrage.REQS_FILE))
        return (False, True)[exit_status == 0]
352
353
354
355
356
357
358
359
360
361
362
363
364
365


class GitUpdateManager(UpdateManager):
    def __init__(self):
        self.type = "git"

    @property
    def version(self):
        return self._find_installed_version()

    @property
    def get_newest_version(self):
        return self._check_for_new_version() or self.version

echel0n's avatar
echel0n committed
366
367
368
369
370
371
372
373
374
375
376
377
378
379
    @property
    def current_branch(self):
        branch, __, exit_status = self._git_cmd(self._git_path, 'rev-parse --abbrev-ref HEAD')
        return ("", branch)[exit_status == 0 and branch is not None]

    @property
    def remote_branches(self):
        branches, __, exit_status = self._git_cmd(self._git_path,
                                                  'ls-remote --heads {}'.format(sickrage.app.config.git_remote))
        if exit_status == 0 and branches:
            return re.findall(r'refs/heads/(.*)', branches)

        return []

echel0n's avatar
echel0n committed
380
    def _find_installed_version(self):
381
        """
382
        Attempts to find the currently installed version of SiCKRAGE.
383
384
385
386
387
388

        Uses git show to get commit version.

        Returns: True for success or False for failure
        """

389
        output, __, exit_status = self._git_cmd(self._git_path, 'rev-parse HEAD')
390
391
392
        if exit_status == 0 and output:
            cur_commit_hash = output.strip()
            if not re.match('^[a-z0-9]+$', cur_commit_hash):
393
                sickrage.app.log.error("Output doesn't look like a hash, not using it")
394
                return False
echel0n's avatar
echel0n committed
395
            return cur_commit_hash
396

397
    def _check_for_new_version(self):
398
399
400
401
402
        """
        Uses git commands to check if there is a newer version that the provided
        commit hash. If there is a newer version it sets _num_commits_behind.
        """

403
        # get all new info from server
404
        output, __, exit_status = self._git_cmd(self._git_path, 'remote update')
405
        if not exit_status == 0:
406
            sickrage.app.log.warning("Unable to contact server, can't check for update")
407
408
409
            return

        # get latest commit_hash from remote
410
411
        output, __, exit_status = self._git_cmd(self._git_path,
                                                'rev-parse --verify --quiet origin/{}'.format(self.current_branch))
412
        if exit_status == 0 and output:
echel0n's avatar
echel0n committed
413
            return output.strip()
414
415
416

    def set_newest_text(self):
        # if we're up to date then don't set this
417
        sickrage.app.newest_version_string = None
418

echel0n's avatar
echel0n committed
419
        if self.version != self.get_newest_version:
echel0n's avatar
echel0n committed
420
421
422
423
            newest_text = _(
                'There is a newer version available, version {} &mdash; <a href=\"{}\">Update Now</a>').format(
                self.get_newest_version, self.get_update_url())

424
            sickrage.app.newest_version_string = newest_text
425
426

    def need_update(self):
echel0n's avatar
echel0n committed
427
        try:
echel0n's avatar
echel0n committed
428
            return (False, True)[self.version != self.get_newest_version]
echel0n's avatar
echel0n committed
429
        except Exception as e:
430
            sickrage.app.log.warning("Unable to contact server, can't check for update: " + repr(e))
echel0n's avatar
echel0n committed
431
            return False
432
433
434

    def update(self):
        """
435
        Calls git pull origin <branch> in order to update SiCKRAGE. Returns a bool depending
436
437
438
439
        on the call's success.
        """

        # remove untracked files and performs a hard reset on git branch to avoid update issues
440
        if sickrage.app.config.git_reset:
labrys's avatar
labrys committed
441
            # self.clean() # This is removing user data and backups
442
443
            self.reset()

444
        __, __, exit_status = self._git_cmd(self._git_path, 'pull -f {} {}'.format(sickrage.app.config.git_remote,
445
                                                                                   self.current_branch))
446
        if exit_status == 0:
447
            sickrage.app.log.info("Updating SiCKRAGE from GIT servers")
448
            Notifiers.notify_version_update(self.get_newest_version)
449
            self.install_requirements()
450
            return True
451

452
        return False
453
454
455
456
457
458

    def clean(self):
        """
        Calls git clean to remove all untracked files. Returns a bool depending
        on the call's success.
        """
459
        __, __, exit_status = self._git_cmd(self._git_path, 'clean -df ""')
460
        return (False, True)[exit_status == 0]
461
462
463
464
465
466

    def reset(self):
        """
        Calls git reset --hard to perform a hard reset. Returns a bool depending
        on the call's success.
        """
467
        __, __, exit_status = self._git_cmd(self._git_path, 'reset --hard')
468
469
        return (False, True)[exit_status == 0]

470
471
    def fetch(self):
        """
472
        Calls git fetch to fetch all remote branches
473
474
        on the call's success.
        """
475
        __, __, exit_status = self._git_cmd(self._git_path,
476
                                            'config remote.origin.fetch %s' % '+refs/heads/*:refs/remotes/origin/*')
477
        if exit_status == 0:
478
            __, __, exit_status = self._git_cmd(self._git_path, 'fetch --all')
479
480
        return (False, True)[exit_status == 0]

481
482
    def checkout_branch(self, branch):
        if branch in self.remote_branches:
483
            sickrage.app.log.debug("Branch checkout: " + self._find_installed_version() + "->" + branch)
484
485

            # remove untracked files and performs a hard reset on git branch to avoid update issues
486
            if sickrage.app.config.git_reset:
487
488
                self.reset()

489
490
491
            # fetch all branches
            self.fetch()

492
            __, __, exit_status = self._git_cmd(self._git_path, 'checkout -f ' + branch)
493
            if exit_status == 0:
494
                self.install_requirements()
495
496
497
498
                return True

        return False

499
    def get_remote_url(self):
500
        url, __, exit_status = self._git_cmd(self._git_path,
501
                                             'remote get-url {}'.format(sickrage.app.config.git_remote))
502
503
504
        return ("", url)[exit_status == 0 and url is not None]

    def set_remote_url(self):
505
506
507
        if not sickrage.app.config.developer:
            self._git_cmd(self._git_path, 'remote set-url {} {}'.format(sickrage.app.config.git_remote,
                                                                        sickrage.app.config.git_remote_url))
508

echel0n's avatar
echel0n committed
509

510
511
class SourceUpdateManager(UpdateManager):
    def __init__(self):
512
        self.type = "source"
513

echel0n's avatar
echel0n committed
514
515
516
    @property
    def version(self):
        return self._find_installed_version()
517

echel0n's avatar
echel0n committed
518
519
    @property
    def get_newest_version(self):
520
        return self._check_for_new_version() or self.version
521

echel0n's avatar
echel0n committed
522
523
524
525
    @property
    def current_branch(self):
        return 'master'

526
527
    @staticmethod
    def _find_installed_version():
528
        with io.open(os.path.join(sickrage.PROG_DIR, 'version.txt')) as f:
echel0n's avatar
echel0n committed
529
            return f.read().strip() or ""
530

echel0n's avatar
echel0n committed
531
532
    def need_update(self):
        try:
echel0n's avatar
echel0n committed
533
            return (False, True)[self.version != self.get_newest_version]
echel0n's avatar
echel0n committed
534
        except Exception as e:
535
            sickrage.app.log.warning("Unable to contact server, can't check for update: " + repr(e))
echel0n's avatar
echel0n committed
536
            return False
537

echel0n's avatar
echel0n committed
538
    def _check_for_new_version(self):
539
        git_version_url = "https://git.sickrage.ca/SiCKRAGE/sickrage/raw/master/sickrage/version.txt"
echel0n's avatar
echel0n committed
540

541
        try:
542
            return sickrage.app.wsession.get(git_version_url).text
543
544
        except Exception:
            return self._find_installed_version()
echel0n's avatar
echel0n committed
545

546
    def set_newest_text(self):
echel0n's avatar
echel0n committed
547
        # if we're up to date then don't set this
548
        sickrage.app.newest_version_string = None
echel0n's avatar
echel0n committed
549
550

        if not self.version:
551
            sickrage.app.log.debug("Unknown current version number, don't know if we should update or not")
echel0n's avatar
echel0n committed
552

echel0n's avatar
echel0n committed
553
554
555
            newest_text = _("Unknown current version number: If yo've never used the SiCKRAGE upgrade system before "
                            "then current version is not set. &mdash; "
                            "<a href=\"{}\">Update Now</a>").format(self.get_update_url())
echel0n's avatar
echel0n committed
556
        else:
echel0n's avatar
echel0n committed
557
558
            newest_text = _("There is a newer version available, version {} &mdash; "
                            "<a href=\"{}\">Update Now</a>").format(self.get_newest_version, self.get_update_url())
echel0n's avatar
echel0n committed
559

560
        sickrage.app.newest_version_string = newest_text
echel0n's avatar
echel0n committed
561

562
    def update(self):
echel0n's avatar
echel0n committed
563
        """
564
        Downloads the latest source tarball from server and installs it over the existing version.
echel0n's avatar
echel0n committed
565
566
        """

567
        tar_download_url = 'https://git.sickrage.ca/SiCKRAGE/sickrage/repository/archive.tar?ref=master'
echel0n's avatar
echel0n committed
568
569
570

        try:
            # prepare the update dir
571
            sr_update_dir = os.path.join(sickrage.PROG_DIR, 'sr-update')
echel0n's avatar
echel0n committed
572
573

            if os.path.isdir(sr_update_dir):
574
                sickrage.app.log.info("Clearing out update folder " + sr_update_dir + " before extracting")
575
                shutil.rmtree(sr_update_dir)
echel0n's avatar
echel0n committed
576

577
            sickrage.app.log.info("Creating update folder " + sr_update_dir + " before extracting")
echel0n's avatar
echel0n committed
578
579
580
            os.makedirs(sr_update_dir)

            # retrieve file
581
            sickrage.app.log.info("Downloading update from " + repr(tar_download_url))
echel0n's avatar
echel0n committed
582
            tar_download_path = os.path.join(sr_update_dir, 'sr-update.tar')
583
            sickrage.app.wsession.download(tar_download_url, tar_download_path)
echel0n's avatar
echel0n committed
584
585

            if not os.path.isfile(tar_download_path):
586
                sickrage.app.log.warning(
echel0n's avatar
echel0n committed
587
588
589
590
                    "Unable to retrieve new version from " + tar_download_url + ", can't update")
                return False

            if not tarfile.is_tarfile(tar_download_path):
591
                sickrage.app.log.error(
592
                    "Retrieved version from " + tar_download_url + " is corrupt, can't update")
echel0n's avatar
echel0n committed
593
594
595
                return False

            # extract to sr-update dir
596
            sickrage.app.log.info("Extracting file " + tar_download_path)
echel0n's avatar
echel0n committed
597
598
599
600
601
            tar = tarfile.open(tar_download_path)
            tar.extractall(sr_update_dir)
            tar.close()

            # delete .tar.gz
602
            sickrage.app.log.info("Deleting file " + tar_download_path)
echel0n's avatar
echel0n committed
603
604
605
606
607
608
            os.remove(tar_download_path)

            # find update dir name
            update_dir_contents = [x for x in os.listdir(sr_update_dir) if
                                   os.path.isdir(os.path.join(sr_update_dir, x))]
            if len(update_dir_contents) != 1:
609
                sickrage.app.log.error("Invalid update data, update failed: " + str(update_dir_contents))
echel0n's avatar
echel0n committed
610
611
612
613
                return False
            content_dir = os.path.join(sr_update_dir, update_dir_contents[0])

            # walk temp folder and move files to main folder
614
            sickrage.app.log.info("Moving files from " + content_dir + " to " + sickrage.PROG_DIR)
615
            for dirname, __, filenames in os.walk(content_dir):
echel0n's avatar
echel0n committed
616
617
618
                dirname = dirname[len(content_dir) + 1:]
                for curfile in filenames:
                    old_path = os.path.join(content_dir, dirname, curfile)
619
                    new_path = os.path.join(sickrage.PROG_DIR, dirname, curfile)
echel0n's avatar
echel0n committed
620
621
622
623
624
625
626
627
628
629

                    # Avoid DLL access problem on WIN32/64
                    # These files needing to be updated manually
                    # or find a way to kill the access from memory
                    if curfile in ('unrar.dll', 'unrar64.dll'):
                        try:
                            os.chmod(new_path, stat.S_IWRITE)
                            os.remove(new_path)
                            os.renames(old_path, new_path)
                        except Exception as e:
630
                            sickrage.app.log.debug("Unable to update " + new_path + ': ' + e.message)
echel0n's avatar
echel0n committed
631
632
633
634
635
636
637
638
                            os.remove(old_path)  # Trash the updated file without moving in new path
                        continue

                    if os.path.isfile(new_path):
                        os.remove(new_path)
                    os.renames(old_path, new_path)

        except Exception as e:
639
640
            sickrage.app.log.error("Error while trying to update: {}".format(e.message))
            sickrage.app.log.debug("Traceback: " + traceback.format_exc())
echel0n's avatar
echel0n committed
641
642
643
            return False

        # Notify update successful
644
        Notifiers.notify_version_update(self.get_newest_version)
echel0n's avatar
echel0n committed
645

646
647
648
        # install requirements
        self.install_requirements()

echel0n's avatar
echel0n committed
649
650
651
652
653
654
        return True


class PipUpdateManager(UpdateManager):
    def __init__(self):
        self.type = "pip"
655

656
    @property
echel0n's avatar
echel0n committed
657
    def version(self):
658
        return self._find_installed_version()
659

660
661
    @property
    def get_newest_version(self):
662
        return self._check_for_new_version() or self.version
echel0n's avatar
echel0n committed
663

echel0n's avatar
echel0n committed
664
665
666
667
    @property
    def current_branch(self):
        return 'master'

668
    def _find_installed_version(self):
669
        out, __, exit_status = self._pip_cmd(self._pip_path, 'show sickrage')
670
671
672
        if exit_status == 0:
            return out.split('\n')[1].split()[1]
        return ""
echel0n's avatar
echel0n committed
673
674
675
676
677

    def need_update(self):
        # need this to run first to set self._newest_commit_hash
        try:
            pypi_version = self.get_newest_version
678
            if self.version != pypi_version:
679
                sickrage.app.log.debug(
680
                    "Version upgrade: " + self._find_installed_version() + " -> " + pypi_version)
echel0n's avatar
echel0n committed
681
682
                return True
        except Exception as e:
683
            sickrage.app.log.warning("Unable to contact PyPi, can't check for update: " + repr(e))
echel0n's avatar
echel0n committed
684
685
686
            return False

    def _check_for_new_version(self):
687
688
        from distutils.version import StrictVersion
        url = "https://pypi.python.org/pypi/{}/json".format('sickrage')
689
        resp = sickrage.app.wsession.get(url)
690
691
        versions = resp.json()["releases"].keys()
        versions.sort(key=StrictVersion, reverse=True)
692

693
694
        try:
            return versions[0]
echel0n's avatar
echel0n committed
695
        except Exception:
696
            return self._find_installed_version()
697

698
699
700
    def set_newest_text(self):

        # if we're up to date then don't set this
701
        sickrage.app.newest_version_string = None
702

echel0n's avatar
echel0n committed
703
        if not self.version:
704
            sickrage.app.log.debug("Unknown current version number, don't know if we should update or not")
705
706
            return
        else:
echel0n's avatar
echel0n committed
707
708
            newest_text = _("New SiCKRAGE update found on PyPy servers, version {} &mdash; "
                            "<a href=\"{}\">Update Now</a>").format(self.get_newest_version, self.get_update_url())
709

710
        sickrage.app.newest_version_string = newest_text
711
712
713

    def update(self):
        """
714
        Performs pip upgrade
715
        """
716
        __, __, exit_status = self._pip_cmd(self._pip_path, 'install -U --no-cache-dir sickrage')
717
        if exit_status == 0:
718
            sickrage.app.log.info("Updating SiCKRAGE from PyPi servers")
719
            Notifiers.notify_version_update(self.get_newest_version)
720
            return True
721

722
        return False