diff --git a/Desktop.py b/Desktop.py new file mode 100644 index 0000000..2d523e1 --- /dev/null +++ b/Desktop.py @@ -0,0 +1,241 @@ +from esky.util import appdir_from_executable #@UnresolvedImport +from threading import Thread +from version import VERSION +from wx.lib.softwareupdate import SoftwareUpdate +import os +import sys +import time +import webbrowser +import wx + +# Include proper dirs +if hasattr(sys, 'frozen'): + import libs + base_path = os.path.dirname(os.path.dirname(os.path.abspath(libs.__file__))) +else: + base_path = os.path.dirname(os.path.abspath(__file__)) + +def icon(): + icon = 'icon_windows.png' + if os.path.isfile('icon_mac.png'): + icon = 'icon_mac.png' + + return wx.Icon(icon, wx.BITMAP_TYPE_PNG) + +lib_dir = os.path.join(base_path, 'libs') + +sys.path.insert(0, base_path) +sys.path.insert(0, lib_dir) + +from couchpotato.environment import Env + +class TaskBarIcon(wx.TaskBarIcon): + + TBMENU_OPEN = wx.NewId() + TBMENU_SETTINGS = wx.NewId() + TBMENU_EXIT = wx.ID_EXIT + + closed = False + menu = False + enabled = False + + def __init__(self, frame): + wx.TaskBarIcon.__init__(self) + self.frame = frame + + self.SetIcon(icon()) + + self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTaskBarClick) + self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarClick) + + 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.onTaskBarClose, id = self.TBMENU_EXIT) + + def OnTaskBarClick(self, evt): + menu = self.CreatePopupMenu() + self.PopupMenu(menu) + menu.Destroy() + + def enable(self): + self.enabled = True + + if self.menu: + self.open_menu.Enable(True) + self.setting_menu.Enable(True) + + self.open_menu.SetText('Open') + + def CreatePopupMenu(self): + + if not self.menu: + self.menu = wx.Menu() + self.open_menu = self.menu.Append(self.TBMENU_OPEN, 'Open') + self.setting_menu = self.menu.Append(self.TBMENU_SETTINGS, 'About') + self.exit_menu = self.menu.Append(self.TBMENU_EXIT, 'Quit') + + if not self.enabled: + self.open_menu.Enable(False) + self.setting_menu.Enable(False) + + self.open_menu.SetText('Loading...') + + return self.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/about/' + webbrowser.open(url) + + def onTaskBarClose(self, evt): + if self.closed: + return + + self.closed = True + + self.RemoveIcon() + 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, style = wx.FRAME_NO_TASKBAR) + + self.parent = parent + self.tbicon = TaskBarIcon(self) + + +class WorkerThread(Thread): + + def __init__(self, desktop): + Thread.__init__(self) + self.daemon = True + self._desktop = desktop + + self.start() + + def run(self): + + # Get options via arg + from couchpotato.runner import getOptions + args = ['--quiet'] + self.options = getOptions(args) + + # Load settings + settings = Env.get('settings') + settings.setFile(self.options.config_file) + + # Create data dir if needed + self.data_dir = os.path.expanduser(Env.setting('data_dir')) + if self.data_dir == '': + from couchpotato.core.helpers.variable import getDataDir + self.data_dir = getDataDir() + + if not os.path.isdir(self.data_dir): + os.makedirs(self.data_dir) + + # Create logging dir + self.log_dir = os.path.join(self.data_dir, 'logs'); + if not os.path.isdir(self.log_dir): + os.mkdir(self.log_dir) + + try: + from couchpotato.runner import runCouchPotato + runCouchPotato(self.options, base_path, args, data_dir = self.data_dir, log_dir = self.log_dir, Env = Env, desktop = self._desktop) + except: + pass + + self._desktop.frame.Close() + self._desktop.ExitMainLoop() + + +class CouchPotatoApp(wx.App, SoftwareUpdate): + + settings = {} + events = {} + restart = False + closing = False + triggered_onClose = False + + def OnInit(self): + + # Updater + base_url = 'https://api.couchpota.to/updates/%s' + self.InitUpdates(base_url % VERSION + '/', 'https://couchpota.to/updates/%s' % 'changelog.html', + icon = icon()) + + self.frame = MainFrame(self) + self.frame.Bind(wx.EVT_CLOSE, self.onClose) + + # CouchPotato thread + self.worker = WorkerThread(self) + + return True + + def onAppLoad(self): + self.frame.tbicon.enable() + + 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): + + if not self.closing: + self.closing = True + self.frame.tbicon.onTaskBarClose(event) + + onClose = self.events.get('onClose') + if onClose and not self.triggered_onClose: + self.triggered_onClose = True + onClose(event) + + def afterShutdown(self, restart = False): + self.frame.Destroy() + self.restart = restart + self.ExitMainLoop() + + +if __name__ == '__main__': + + app = CouchPotatoApp(redirect = False) + app.MainLoop() + + time.sleep(1) + + if app.restart: + + def appexe_from_executable(exepath): + appdir = appdir_from_executable(exepath) + exename = os.path.basename(exepath) + + if sys.platform == "darwin": + if os.path.isdir(os.path.join(appdir, "Contents", "MacOS")): + return os.path.join(appdir, "Contents", "MacOS", exename) + + return os.path.join(appdir, exename) + + exe = appexe_from_executable(sys.executable) + os.chdir(os.path.dirname(exe)) + + os.execv(exe, [exe] + sys.argv[1:]) diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index a2482f8..f36f7e4 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -117,7 +117,7 @@ var AboutSettingTab = new Class({ var self = this; var date = new Date(json.version.date * 1000); self.version_text.set('text', json.version.hash + (json.version.date ? ' ('+date.toLocaleString()+')' : '')); - self.updater_type.set('text', json.version.type + ', ' + json.branch); + self.updater_type.set('text', (json.version.type != json.branch) ? (json.version.type + ', ' + json.branch) : json.branch); } }); diff --git a/icon.icns b/icon.icns new file mode 100644 index 0000000..3be592a Binary files /dev/null and b/icon.icns differ diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..224b9c1 Binary files /dev/null and b/icon.ico differ diff --git a/icon_mac.png b/icon_mac.png new file mode 100644 index 0000000..6cdda5c Binary files /dev/null and b/icon_mac.png differ diff --git a/icon_windows.png b/icon_windows.png new file mode 100644 index 0000000..d45baab Binary files /dev/null and b/icon_windows.png differ diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..47990ae --- /dev/null +++ b/installer.iss @@ -0,0 +1,52 @@ +#define MyAppName "CouchPotato" +#define MyAppVer "2.6.1" +#define MyAppBit "win32" +//#define MyAppBit "win-amd64" + +[Setup] +AppName={#MyAppName} +AppVersion=2 +AppVerName={#MyAppName} +DefaultDirName={userappdata}\{#MyAppName}\application +DisableProgramGroupPage=yes +DisableDirPage=yes +UninstallDisplayIcon=./icon.ico +SetupIconFile=./icon.ico +OutputDir=./dist +OutputBaseFilename={#MyAppName}-{#MyAppVer}.{#MyAppBit}.installer +AppPublisher=Your Mom +AppPublisherURL=http://couchpota.to +PrivilegesRequired=none +WizardSmallImageFile=installer_icon.bmp +WizardImageFile=installer_banner.bmp +UsePreviousAppDir=no + +[Messages] +WelcomeLabel1=Installing [name]! +WelcomeLabel2=This wizard will install [name] to your AppData folder. It does this so it can use the build in updater without needing admin rights. + +[CustomMessages] +LaunchProgram=Launch {#MyAppName} right now. + +[Files] +Source: "./dist/{#MyAppName}-{#MyAppVer}.{#MyAppBit}/*"; Flags: recursesubdirs; DestDir: "{app}" + +[Icons] +Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppName}.exe" +Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppName}.exe"; Tasks: startup + +[Tasks] +Name: "startup"; Description: "Run {#MyAppName} at startup"; Flags: unchecked + +[Run] +Filename: {app}\{#MyAppName}.exe; Description: {cm:LaunchProgram,{#MyAppName}}; Flags: nowait postinstall skipifsilent + + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\appdata" +Type: filesandordirs; Name: "{app}\Microsoft.VC90.CRT" +Type: filesandordirs; Name: "{app}\updates" +Type: filesandordirs; Name: "{app}\CouchPotato*" +Type: filesandordirs; Name: "{app}\python27.dll" +Type: filesandordirs; Name: "{app}\unins000.dat" +Type: filesandordirs; Name: "{app}\unins000.exe" diff --git a/installer_banner.bmp b/installer_banner.bmp new file mode 100644 index 0000000..834bb11 Binary files /dev/null and b/installer_banner.bmp differ diff --git a/installer_icon.bmp b/installer_icon.bmp new file mode 100644 index 0000000..05d9d38 Binary files /dev/null and b/installer_icon.bmp differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..10e100e --- /dev/null +++ b/setup.py @@ -0,0 +1,133 @@ +from esky import bdist_esky +from setuptools import setup +import os +import sys +import version + + +# 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) + +def getDataFiles(dirs): + data_files = [] + for directory in dirs: + for root, dirs, files in os.walk(directory): + if files: + for filename in files: + if filename[:-4] is not '.pyc': + data_files.append((root, [os.path.join(root, filename)])) + + return data_files + +includes = [ + 'telnetlib', + 'xml.etree.ElementTree', + 'xml.etree.cElementTree', + 'xml.dom', + 'xml.dom.minidom', + 'netrc', + 'csv', + 'HTMLParser', + 'version', + 'distutils', + 'lxml', 'lxml.etree', 'lxml._elementpath', 'gzip', +] + +excludes = [ + 'doctest', + 'pdb', + 'unittest', + 'difflib', + 'bsddb', + 'pywin.debugger', 'pywin.debugger.dbgcon', 'pywin.dialogs', + 'Tkconstants', 'Tkinter', + 'curses', + '_gtkagg', '_tkagg', +] + +# Windows +if sys.platform == "win32": + import py2exe + + sys.path.append('C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.21022.8_none_bcb86ed6ac711f91') + + FREEZER = 'py2exe' + FREEZER_OPTIONS = dict( + compressed = 0, + bundle_files = 3, + dll_excludes = [ + 'msvcp90.dll', + 'msvcr90.dll', + 'msvcr71.dll', + 'mswsock.dll', + 'powrprof.dll', + 'USP10.dll', + 'libgdk-win32-2.0-0.dll', + 'libgobject-2.0-0.dll', + 'tcl84.dll', + 'tk84.dll' + ], + packages = ['couchpotato', 'libs'], + includes = includes, + excludes = excludes, + skip_archive = 1, + ) + exeICON = os.path.join(base_path, 'icon.ico') + DATA_FILES = getDataFiles([r'.\\couchpotato', r'.\\libs']) + DATA_FILES.append('icon_windows.png') + file_ext = 'win32.zip' + + +# OSX +elif sys.platform == "darwin": + import py2app + + FREEZER = 'py2app' + FREEZER_OPTIONS = dict( + arch = 'intel', + optimize = 2, + strip = True, + argv_emulation = False, + site_packages = False, + iconfile = 'icon.icns', + plist = dict( + LSUIElement = True, + ), + packages = ['couchpotato', 'libs'], + includes = includes, + excludes = excludes, + ) + exeICON = None + DATA_FILES = ['icon_mac.png'] + + file_ext = 'macosx-10_6-intel.zip' + +# Common +NAME = "CouchPotato" +APP = [bdist_esky.Executable("Desktop.py", name = NAME, icon = exeICON, gui_only = True,)] +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, + author = "Ruud", + author_email = "info@couchpota.to", + description = 'CouchPotato %s' % version.VERSION, + data_files = DATA_FILES, + options = dict(bdist_esky = ESKY_OPTIONS), +) + +#distpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist') +#zipfilename = os.path.join(distpath, '%s-%s.%s' % (NAME, version.VERSION, file_ext)) +#zfile = zipfile.ZipFile(zipfilename, "r") +#zfile.extractall(distpath) diff --git a/version.py b/version.py index 12e96b5..96780f9 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -VERSION = None -BRANCH = 'master' +VERSION = '2.6.1' +BRANCH = 'desktop'