diff --git a/CouchPotato.py b/CouchPotato.py old mode 100755 new mode 100644 index f4083f6..a07f235 --- a/CouchPotato.py +++ b/CouchPotato.py @@ -1,63 +1,167 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -'''Wrapper for the command line interface.''' - -from os.path import dirname +from threading import Thread +from wx.lib.softwareupdate import SoftwareUpdate import os import sys -import subprocess -import time +import webbrowser +import wx -# Root path -base_path = dirname(os.path.abspath(__file__)) +# Include proper dirs +if hasattr(sys, 'frozen'): + import libs + base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__))) + print base_path +else: + base_path = os.path.dirname(os.path.abspath(__file__)) -# Insert local directories into path -sys.path.insert(0, os.path.join(base_path, 'libs')) +lib_dir = os.path.join(base_path, 'libs') -from couchpotato.core.logger import CPLog -log = CPLog(__name__) +sys.path.insert(0, base_path) +sys.path.insert(0, lib_dir) # Get options via arg from couchpotato.runner import getOptions -from couchpotato.core.helpers.variable import getDataDir -options = getOptions(base_path, sys.argv[1:]) -data_dir = getDataDir() - -def start(): - try: - args = [sys.executable] + sys.argv - new_environ = os.environ.copy() - new_environ['cp_main'] = 'true' - - if os.name == 'nt': - for key, value in new_environ.iteritems(): - if isinstance(value, unicode): - new_environ[key] = value.encode('iso-8859-1') - - subprocess.call(args, env = new_environ) - return os.path.isfile(os.path.join(data_dir, 'restart')) - except KeyboardInterrupt, e: - pass - except Exception, e: - log.critical(e) - return 0 - from couchpotato.runner import runCouchPotato -if __name__ == '__main__': - if os.environ.get('cp_main', 'false') == 'true': + +class TaskBarIcon(wx.TaskBarIcon): + + TBMENU_OPEN = wx.NewId() + TBMENU_SETTINGS = wx.NewId() + TBMENU_ABOUT = wx.ID_ABOUT + TBMENU_EXIT = wx.ID_EXIT + + def __init__(self, frame): + wx.TaskBarIcon.__init__(self) + self.frame = frame + + icon = wx.Icon('icon.ico', wx.BITMAP_TYPE_ANY) + self.SetIcon(icon) + + self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.onTaskBarActivate) + + self.Bind(wx.EVT_MENU, self.onOpen, id = self.TBMENU_OPEN) + self.Bind(wx.EVT_MENU, self.onSettings, id = self.TBMENU_SETTINGS) + self.Bind(wx.EVT_MENU, self.onAbout, id = self.TBMENU_ABOUT) + self.Bind(wx.EVT_MENU, self.onTaskBarClose, id = self.TBMENU_EXIT) + + + def CreatePopupMenu(self): + menu = wx.Menu() + menu.Append(self.TBMENU_OPEN, "Open") + menu.Append(self.TBMENU_SETTINGS, "Settings") + menu.Append(self.TBMENU_ABOUT, "About") + menu.Append(self.TBMENU_EXIT, "Close") + return menu + + def onOpen(self, event): + url = self.frame.parent.getSetting('base_url') + webbrowser.open(url) + + def onSettings(self, event): + url = self.frame.parent.getSetting('base_url') + '/settings/' + webbrowser.open(url) + + def onAbout(self, event): + print 'onAbout' + + def onTaskBarActivate(self, evt): + if not self.frame.IsShown(): + self.frame.Show(True) + self.frame.Raise() + + def onTaskBarClose(self, evt): + wx.CallAfter(self.frame.Close) + + def makeIcon(self, img): + if "wxMSW" in wx.PlatformInfo: + img = img.Scale(16, 16) + elif "wxGTK" in wx.PlatformInfo: + img = img.Scale(22, 22) + + icon = wx.IconFromBitmap(img.CopyFromBitmap()) + return icon + + +class MainFrame(wx.Frame): + + def __init__(self, parent): + wx.Frame.__init__(self, None) + + self.parent = parent + self.tbicon = TaskBarIcon(self) + + +class WorkerThread(Thread): + + def __init__(self, desktop): + Thread.__init__(self) + self._desktop = desktop + + self.start() + + def run(self): + + args = ['--nogit', '--console_log']#, '--quiet'] + options = getOptions(base_path, args) + try: - runCouchPotato(options, base_path, sys.argv[1:]) + runCouchPotato(options, base_path, args, desktop = self._desktop) + except KeyboardInterrupt, e: + raise except Exception, e: - log.critical(e) - else: - while 1: - restart = start() - if not restart: - break - - from couchpotato.core.event import fireEvent - fireEvent('app.crappy_shutdown', single = True) - time.sleep(1) - sys.exit() + raise + finally: + pass + + +class CouchPotatoApp(wx.App, SoftwareUpdate): + + settings = {} + events = {} + restart = False + + def OnInit(self): + + # Updater + base_url = 'http://couchpotatoapp.com/updates/' + self.InitUpdates(base_url, base_url + 'changelog.txt', + icon = wx.Icon('icon.ico')) + + self.frame = MainFrame(self) + self.frame.Bind(wx.EVT_CLOSE, self.onClose) + + # CouchPotato thread + self.worker = WorkerThread(self) + + return True + + def setSettings(self, settings = {}): + self.settings = settings + + def getSetting(self, name): + return self.settings.get(name) + + def addEvents(self, events = {}): + for name in events.iterkeys(): + self.events[name] = events[name] + + def onClose(self, event): + onClose = self.events.get('onClose') + if self.events.get('onClose'): + onClose(event) + else: + self.afterShutdown() + + def afterShutdown(self, restart = False): + self.frame.Destroy() + self.restart = restart + + +if __name__ == '__main__': + app = CouchPotatoApp(redirect = False) + app.MainLoop() + + #path = os.path.join(sys.path[0].decode(sys.getfilesystemencoding()), sys.argv[0]) + #if app.restart: + # wx.Process.Open(sys.executable + ' ' + path) diff --git a/README.md b/README.md index 4f70d09..8226558 100644 --- a/README.md +++ b/README.md @@ -1,5 +1 @@ -CouchPotato Server -===== - -CouchPotato (CP) is an automatic NZB and torrent downloader. You can keep a "movies I want"-list and it will search for NZBs/torrents of these movies every X hours. -Once a movie is found, it will send it to SABnzbd or download the torrent to a specified directory. \ No newline at end of file +CouchPotato Desktop \ No newline at end of file diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index e9b4bdf..4c7e75b 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -1,4 +1,3 @@ -from couchpotato import app from couchpotato.api import addApiView from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.helpers.request import jsonified @@ -7,7 +6,6 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env from flask import request -from flask.helpers import url_for import os import time import traceback @@ -29,6 +27,7 @@ class Core(Plugin): addEvent('app.crappy_restart', self.crappyRestart) addEvent('app.load', self.launchBrowser, priority = 1) addEvent('app.base_url', self.createBaseUrl) + addEvent('app.api_url', self.createApiUrl) addEvent('setting.save.core.password', self.md5Password) @@ -43,16 +42,10 @@ class Core(Plugin): }) def crappyShutdown(self): - ctx = app.test_request_context() - ctx.push() - self.urlopen('%s%sapp.shutdown' % (fireEvent('app.base_url', single = True), url_for('api.index'))) - ctx.pop() + self.urlopen('%sapp.shutdown' % self.createApiUrl()) def crappyRestart(self): - ctx = app.test_request_context() - ctx.push() - self.urlopen('%s%sapp.restart' % (fireEvent('app.base_url', single = True), url_for('api.index'))) - ctx.pop() + self.urlopen('%sapp.restart' % self.createApiUrl()) def shutdown(self): self.initShutdown() @@ -63,6 +56,7 @@ class Core(Plugin): return 'restarting' def initShutdown(self, restart = False): + log.info('Shutting down' if not restart else 'Restarting') fireEvent('app.shutdown') @@ -122,3 +116,7 @@ class Core(Plugin): port = Env.setting('port') return '%s:%d' % (cleanHost(host).rstrip('/'), int(port)) + + def createApiUrl(self): + + return '%s/%s/' % (self.createBaseUrl(), Env.setting('api_key')) diff --git a/couchpotato/core/_base/desktop/main.py b/couchpotato/core/_base/desktop/main.py index bd52dcd..ce1ff28 100644 --- a/couchpotato/core/_base/desktop/main.py +++ b/couchpotato/core/_base/desktop/main.py @@ -7,25 +7,28 @@ log = CPLog(__name__) if Env.get('desktop'): - #import os - #import sys - import wx - class Desktop(Plugin): def __init__(self): desktop = Env.get('desktop') desktop.setSettings({ - 'url': fireEvent('app.base_url', single = True) + 'base_url': fireEvent('app.base_url', single = True), + 'api_url': fireEvent('app.api_url', single = True), + 'api': Env.setting('api'), }) - def onClose(event): - return fireEvent('app.crappy_shutdown') - desktop.close_handler = onClose + # Events from desktop + desktop.addEvents({ + 'onClose': self.onClose, + }) + # Events to desktop addEvent('app.after_shutdown', desktop.afterShutdown) + def onClose(self, event): + return fireEvent('app.crappy_shutdown', single = True) + else: class Desktop(Plugin): diff --git a/couchpotato/environment.py b/couchpotato/environment.py index c6b427e..39adb3d 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -14,7 +14,7 @@ class Env(object): _args = None _quiet = False _deamonize = False - _version = 0.5 + _desktop = None ''' Data paths and directories ''' _app_dir = "" diff --git a/icon.icns b/icon.icns new file mode 100644 index 0000000..04cbc86 Binary files /dev/null and b/icon.icns differ diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..f93f9fb Binary files /dev/null and b/icon.ico differ diff --git a/libs/guessit/fileutils.py b/libs/guessit/fileutils.py index 7c07af7..1c263b8 100644 --- a/libs/guessit/fileutils.py +++ b/libs/guessit/fileutils.py @@ -18,7 +18,6 @@ # along with this program. If not, see . # -import ntpath import os.path @@ -45,7 +44,7 @@ def split_path(path): """ result = [] while True: - head, tail = ntpath.split(path) + head, tail = os.path.split(path) # on Unix systems, the root folder is '/' if head == '/' and tail == '': diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c505c6b --- /dev/null +++ b/setup.py @@ -0,0 +1,86 @@ +from esky import bdist_esky +from setuptools import setup +import sys +import version +import os + + +# Include proper dirs +base_path = os.path.dirname(os.path.abspath(__file__)) +lib_dir = os.path.join(base_path, 'libs') + +sys.path.insert(0, base_path) +sys.path.insert(0, lib_dir) + + + +# Windows +if sys.platform == "win32": + import py2exe + + FREEZER = 'py2exe' + FREEZER_OPTIONS = dict( + compressed = 0, + optimize = 0, + bundle_files = 3, + dll_excludes = [ + 'MSVCP90.dll', + 'mswsock.dll', + 'powrprof.dll', + 'USP10.dll', + ], + packages = ['couchpotato', 'libs'], + includes = [ + 'telnetlib', + 'xml.etree.ElementTree', + 'xml.etree.cElementTree', + 'xml.dom', + 'xml.dom.minidom', + ], + ) + exeICON = 'icon.ico' + + +# OSX +elif sys.platform == "darwin": + import py2app + + FREEZER = 'py2app' + FREEZER_OPTIONS = dict( + argv_emulation = False, + iconfile = 'icon.icns', + plist = dict( + LSUIElement = True, + ), + packages = ['couchpotato', 'libs'], + includes = [ + 'telnetlib', + 'xml.etree.ElementTree', + 'xml.etree.cElementTree', + 'xml.dom', + 'xml.dom.minidom', + ], + ) + exeICON = None + +# Common +NAME = "CouchPotato" +APP = [bdist_esky.Executable("CouchPotato.py", gui_only = True, icon = exeICON,)] +DATA_FILES = ['icon.ico'] +ESKY_OPTIONS = dict( + freezer_module = FREEZER, + freezer_options = FREEZER_OPTIONS, + bundle_msvcrt = True, +) + + +# Build the app and the esky bundle +setup( + name = NAME, + scripts = APP, + version = version.VERSION, + data_files = DATA_FILES, + options = dict(bdist_esky = ESKY_OPTIONS), +) + + diff --git a/version.py b/version.py new file mode 100644 index 0000000..f424186 --- /dev/null +++ b/version.py @@ -0,0 +1 @@ +VERSION = '0.5'