|
|
|
from couchpotato.api import addApiView
|
|
|
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
|
|
|
from couchpotato.core.helpers.request import jsonified
|
|
|
|
from couchpotato.core.logger import CPLog
|
|
|
|
from couchpotato.core.plugins.base import Plugin
|
|
|
|
from couchpotato.environment import Env
|
|
|
|
from git.repository import LocalRepository
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
log = CPLog(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class Updater(Plugin):
|
|
|
|
|
|
|
|
repo_name = 'RuudBurger/CouchPotatoServer'
|
|
|
|
|
|
|
|
version = None
|
|
|
|
update_failed = False
|
|
|
|
update_version = None
|
|
|
|
last_check = 0
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
self.repo = LocalRepository(Env.get('app_dir'), command = self.conf('git_command', default = 'git'))
|
|
|
|
|
|
|
|
fireEvent('schedule.interval', 'updater.check', self.check, hours = 6)
|
|
|
|
|
|
|
|
addEvent('app.load', self.check)
|
|
|
|
|
|
|
|
addApiView('updater.info', self.getInfo, docs = {
|
|
|
|
'desc': 'Get updater information',
|
|
|
|
'return': {
|
|
|
|
'type': 'object',
|
|
|
|
'example': """
|
|
|
|
{
|
|
|
|
'repo_name': "Name of used repository",
|
|
|
|
'last_check': "last checked for update",
|
|
|
|
'update_version': "available update version or empty",
|
|
|
|
'version': current_cp_version
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
}
|
|
|
|
})
|
|
|
|
addApiView('updater.update', self.doUpdateView)
|
|
|
|
addApiView('updater.check', self.checkView, docs = {
|
|
|
|
'desc': 'Check for available update',
|
|
|
|
'return': {'type': 'see updater.info'}
|
|
|
|
})
|
|
|
|
|
|
|
|
def getInfo(self):
|
|
|
|
|
|
|
|
return jsonified({
|
|
|
|
'repo_name': self.repo_name,
|
|
|
|
'last_check': self.last_check,
|
|
|
|
'update_version': self.update_version,
|
|
|
|
'version': self.getVersion()
|
|
|
|
})
|
|
|
|
|
|
|
|
def getVersion(self):
|
|
|
|
|
|
|
|
if not self.version:
|
|
|
|
try:
|
|
|
|
output = self.repo.getHead() # Yes, please
|
|
|
|
log.debug('Git version output: %s' % output.hash)
|
|
|
|
self.version = {
|
|
|
|
'hash': output.hash[:8],
|
|
|
|
'date': output.getDate(),
|
|
|
|
}
|
|
|
|
except Exception, e:
|
|
|
|
log.error('Failed using GIT updater, running from source, you need to have GIT installed. %s' % e)
|
|
|
|
return 'No GIT'
|
|
|
|
|
|
|
|
return self.version
|
|
|
|
|
|
|
|
def check(self):
|
|
|
|
|
|
|
|
if self.update_version or self.isDisabled():
|
|
|
|
return
|
|
|
|
|
|
|
|
log.info('Checking for new version on github for %s' % self.repo_name)
|
|
|
|
if not Env.get('dev'):
|
|
|
|
self.repo.fetch()
|
|
|
|
|
|
|
|
current_branch = self.repo.getCurrentBranch().name
|
|
|
|
|
|
|
|
for branch in self.repo.getRemoteByName('origin').getBranches():
|
|
|
|
if current_branch == branch.name:
|
|
|
|
|
|
|
|
local = self.repo.getHead()
|
|
|
|
remote = branch.getHead()
|
|
|
|
|
|
|
|
log.info('Versions, local:%s, remote:%s' % (local.hash[:8], remote.hash[:8]))
|
|
|
|
|
|
|
|
if local.getDate() < remote.getDate():
|
|
|
|
if self.conf('automatic') and not self.update_failed:
|
|
|
|
if self.doUpdate():
|
|
|
|
fireEventAsync('app.crappy_restart')
|
|
|
|
else:
|
|
|
|
self.update_version = {
|
|
|
|
'hash': remote.hash[:8],
|
|
|
|
'date': remote.getDate(),
|
|
|
|
}
|
|
|
|
if self.conf('notification'):
|
|
|
|
fireEvent('updater.available', message = 'A new update is available', data = self.getVersion())
|
|
|
|
|
|
|
|
self.last_check = time.time()
|
|
|
|
|
|
|
|
def checkView(self):
|
|
|
|
self.check()
|
|
|
|
return self.getInfo()
|
|
|
|
|
|
|
|
def doUpdateView(self):
|
|
|
|
return jsonified({
|
|
|
|
'success': self.doUpdate()
|
|
|
|
})
|
|
|
|
|
|
|
|
def doUpdate(self):
|
|
|
|
try:
|
|
|
|
log.debug('Stashing local changes')
|
|
|
|
self.repo.saveStash()
|
|
|
|
|
|
|
|
log.info('Updating to latest version')
|
|
|
|
self.repo.pull()
|
|
|
|
|
|
|
|
# Delete leftover .pyc files
|
|
|
|
self.deletePyc()
|
|
|
|
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
log.error('Failed updating via GIT: %s' % traceback.format_exc())
|
|
|
|
|
|
|
|
self.update_failed = True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def deletePyc(self):
|
|
|
|
|
|
|
|
for root, dirs, files in os.walk(Env.get('app_dir')):
|
|
|
|
|
|
|
|
pyc_files = filter(lambda filename: filename.endswith('.pyc'), files)
|
|
|
|
py_files = set(filter(lambda filename: filename.endswith('.py'), files))
|
|
|
|
excess_pyc_files = filter(lambda pyc_filename: pyc_filename[:-1] not in py_files, pyc_files)
|
|
|
|
|
|
|
|
for excess_pyc_file in excess_pyc_files:
|
|
|
|
full_path = os.path.join(root, excess_pyc_file)
|
|
|
|
log.debug('Removing old PYC file: %s' % full_path)
|
|
|
|
try:
|
|
|
|
os.remove(full_path)
|
|
|
|
except:
|
|
|
|
log.error('Couldn\'t remove %s: %s' % (full_path, traceback.format_exc()))
|
|
|
|
|
|
|
|
for dir_name in dirs:
|
|
|
|
full_path = os.path.join(root, dir_name)
|
|
|
|
if len(os.listdir(full_path)) == 0:
|
|
|
|
try:
|
|
|
|
os.rmdir(full_path)
|
|
|
|
except:
|
|
|
|
log.error('Couldn\'t remove empty directory %s: %s' % (full_path, traceback.format_exc()))
|
|
|
|
|
|
|
|
def isEnabled(self):
|
|
|
|
return super(Updater, self).isEnabled() and Env.get('uses_git')
|