'''
Created on Jun 22, 2013

@author: wpanther
'''
#from PyObjCTools import AppHelper
from AppKit import NSDate, NSAlert, NSInformationalAlertStyle, NSApp, NSWindowController, \
    NSImage, NSStatusBar, NSVariableStatusItemLength, NSTimer, NSRunLoop, NSDefaultRunLoopMode, \
    NSURL, NSWorkspace, NSObject, NSApplication, NSFilenamesPboardType, NSPasteboard, NSArray, \
    NSUpdateDynamicServices, NSMenu, NSMenuItem, NSBundle, NSTask

import Foundation, objc    

from serverscan import ServerConnection

import os
import sys
import traceback
from syncer import CloudikeSyncher, SyncerStateHolder

from SystemConfiguration import CFRunLoopGetCurrent, kCFRunLoopCommonModes, \
    SCNetworkReachabilityCreateWithAddress, SCNetworkReachabilitySetCallback, \
    SCNetworkReachabilityGetFlags, SCNetworkReachabilityScheduleWithRunLoop, \
    SCNetworkReachabilityUnscheduleFromRunLoop, kSCNetworkFlagsTransientConnection, \
    kSCNetworkFlagsReachable, kSCNetworkFlagsConnectionRequired, kSCNetworkFlagsConnectionAutomatic, kSCNetworkFlagsInterventionRequired, \
    kSCNetworkFlagsIsLocalAddress, kSCNetworkFlagsIsDirect, objc

from ServiceManagement import SMLoginItemSetEnabled    
    
import socket
import select
import Queue
from threading import Thread
import shutil
import subprocess
import unicodedata

import wx
from wx.lib.wordwrap import wordwrap

from cloudbitforms import *
from webauth import *
from netwatchdog import *
from threading import Lock

from singleinstance import SingleInstance

from updater import CloudikeUpdater
import collections

#import esky

from raven.handlers.logging import SentryHandler
from raven.conf import setup_logging

handler = SentryHandler('http://ebefc7c382e4468ebe96aff8d70a1d1c:93c5834020b54b9e96bcd037340e5898@sentry.cloudike.com/28')
setup_logging(handler)

import logging
import logging.handlers
logger = logging.getLogger('cloudbit')

syncLock = Lock()

import gettext
'''
if __name__ == '__main__':
    t = gettext.translation('messages',os.path.join(os.getcwdu(),'strings'), ['ru'])
    t.install(True) 
'''    

if __name__ == '__main__':
    t = gettext.NullTranslations()
    t.install(True) 

syncer = None
connection = None
networkWatchdog = None
appPath = None

link = None
logon = None

settings = None
problems = None
problemReport = None

webView = None
offerView = None
slider = None
slide2 = None
slide_fse = None

about = None

rerunOnExit = False        
wxapp = None
proxyLogin = None

selectLogin = None
selectServer = None

from config import *
import fileops

configFile = None
syncState = None

prefs = None

import utils

class PrefExtSettings:

    def showSettings(self):
        url = NSURL.fileURLWithPath_("/System/Library/PreferencePanes/Extensions.prefPane")
        NSWorkspace.sharedWorkspace().openURL_(url)

        

class OIProcessThread(Thread):
    def __init__(self):
        super(OIProcessThread, self).__init__()
        self.running = True
        self.q = Queue.Queue()
 
    def add(self, data):
        self.q.put(data)
 
    def stop(self):
        self.running = False
 
    def run(self):
        q = self.q
        while self.running:
            try:
                client = q.get(block=True, timeout=10)
                d = client.recv(8096)
                #print data
                global connection,syncLock, syncer
                d = d.decode('unicode_escape')
                d = unicodedata.normalize('NFC', d)
                data = d.split("|")
                #logger.info("Pipe data: %s" % data)
                if data[0] == "0":
                    #wx.CallAfter(openWebFolder, data[1])
                    #WriteFile(pipeHandle, "ok")
                    pass #opening web folder
                elif data[0] == "1":
                    #wx.CallAfter(showLinkWindow, connection, data[1])
                    #WriteFile(pipeHandle, "ok")
                    pass # showing link window
                elif data[0] == "1001":

                    syncLock.acquire()
                    res = "2"
                    if (syncer):
                        try:
                            #data = str(data)                        
                            #data = data.decode('utf8')
                            #logger.info('pipe data: %s' % data)
                            
                            #data = data[:-1]
                            path = utils.xplatformPath(connection.getServerPathFromLocal(data[1]))
                            #logger.info("Pipe data: %s\nPath data: %s" % (data,path))
                            if path:
                                res = syncer.isInSync(path)
                                #logger.info("Result from syncher: %s" % res)
                            else:
                                res = "2"
                        except:
                            #logger.info('exception for path %s' % data)
                            logger.warn("data error : %s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )
                            res = "2"
                    else:
                        res = "2"
                    syncLock.release()
                    #logger.info("Result for path: %s" % res)
                    '''
                    if res=="0":
                        client.send(os.path.abspath("oi_success.png").encode('utf8'))
                    elif res=="1":
                        client.send(os.path.abspath("oi_sync.png").encode('utf8'))
                    elif res=="3":
                        client.send(os.path.abspath("oi_error.png").encode('utf8'))
                    elif res=="4":
                        client.send(os.path.abspath("oi_exclude.png").encode('utf8'))
                    else:
                        client.send("|")
                    '''
                    if res=="0":
                        client.send("success")
                    elif res=="1":
                        client.send("sync")
                    elif res=="3":
                        client.send("error")
                    elif res=="4":
                        client.send("exclude")
                    else:
                        client.send("*")

            except Queue.Empty:
                pass
        #
        if not q.empty():
            while not q.empty():
                q.get()

class OIListenerThread(Thread):
    def __init__(self):
        super(OIListenerThread, self).__init__()
        self.running = False

    def run(self):
        t = OIProcessThread()
        t.start()

        self.running = True


        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(3)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        host = '127.0.0.1'
        port = macSyncPort
        self.sock.bind((host, port))

        '''
        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sockname = "/Users/raventhemad/Downloads/Cloudike_locsock"
        try:
            os.remove(sockname)
        except OSError:
            pass
        self.sock.bind(sockname)
        '''
        
        while self.running:
            try:
                #print "Listening on port {p}...".format(p=port)
                self.sock.listen(10)                 # Now wait for client connection.
                while True:
                    client, addr = self.sock.accept()
                    ready = select.select([client,],[], [],2)
                    if ready[0]:
                        t.add(client)
            except socket.error, msg:
                #logger.info( "Local socket error! %s" % msg )
                #break
                pass
            except: pass
        self.sock.close()        
            
    def close(self):
        logger.info('Closing badges server...')
        self.running = False
        while self.isAlive(): pass
        self.sock.close()

def popupsPossible():
    global syncer
    try:
        return syncer and (not syncer.shouldTerminate)
    except:
        return False        
                    

class MountainLionNotification(Foundation.NSObject):

    def notify(self, title, subtitle, text, folder=None, sound=False):
        global configFile
        if not configFile.localSettings['commonUsePopups'] == 1 and not popupsPossible():
            return
        NSUserNotification = objc.lookUpClass('NSUserNotification')
        NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
        if NSUserNotification and NSUserNotificationCenter:
            # If these objects are avaliable - setup the notification (10.8 and above)
            notification = NSUserNotification.alloc().init()
            '''
            notification.setTitle_(str(title))
            notification.setSubtitle_(str(subtitle))
            notification.setInformativeText_(str(text))
            '''
            notification.setTitle_(title)
            notification.setSubtitle_(subtitle)
            notification.setInformativeText_(text)
            if sound:
                    notification.setSoundName_("NSUserNotificationDefaultSoundName")
            if folder:
                notification.setHasActionButton_(True)
            notification.setOtherButtonTitle_("View")
            if folder:
                notification.setUserInfo_({"action":"open_folder", "value": folder})
            NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
            NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)

    def userNotificationCenter_didActivateNotification_(self, center, notification):
        userInfo = notification.userInfo()
        if userInfo and userInfo["action"] == "open_folder":
            print "Will try to open folder ", userInfo["value"]
            openLocalFolder(userInfo["value"])
            
    def userNotificationCenter_shouldPresentNotification_(self, center, notification):
        return True

start_time = NSDate.date()

class CloudbitMenu(NSWindowController):
    
    iconIdleImage = NSImage.imageNamed_('synced')
    iconNonetImage = NSImage.imageNamed_('no_internet')
    iconLogoutImage = NSImage.imageNamed_('logged_out')
    iconErrorImage = NSImage.imageNamed_('error')
    iconPauseImage = NSImage.imageNamed_('pause')
    iconSyncImages = [NSImage.imageNamed_('sync_frame1'), NSImage.imageNamed_('sync_frame2')]
    currentImage = iconLogoutImage
    currentLabel = _("Not logged in")
    iconSyncFrame = 0
    fileJobsToShow = 0
    idleCycles = 0
    prevSyncAlive = False
    prevErrorCount = 0
    prevQuotaStatus = False
    prevStorageSize = 0
    prevUsedSize = 0
    logonStatus = False
    refList = []
    lastTransBytes = 0
    miList = []
    transHistory = collections.deque(maxlen=75)
    prevMovingSyncFolder = False
    wasPaused = False
    prevEnableSpeedLimit = False
    prevSpeedLimit = 0
    prevNetworkStatus = True
    
    statusBarMenu = objc.IBOutlet()
    progressItem = objc.IBOutlet()
    storageSize = objc.IBOutlet()
    logoutItem = objc.IBOutlet()
    pauseItem = objc.IBOutlet()
    recents = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('Recent files'), '', '')
    
    def windowDidLoad(self):
        self.statusBarMenu.setAutoenablesItems_(False)
        statusbar = NSStatusBar.systemStatusBar()
        self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
        self.statusitem.setImage_(self.currentImage)
        self.statusitem.setHighlightMode_(1)
        self.statusitem.setMenu_(self.statusBarMenu)
        self.progressItem.setTitle_(self.currentLabel)
        self.recents.setEnabled_(False)
        self.pauseItem.setEnabled_(False)
        self.statusBarMenu.insertItem_atIndex_(self.recents,4)
        
        submenu = NSMenu.alloc().init()
        
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 1'), 'openFile00:', ''))
        submenu.addItem_(self.miList[0])
        self.miList[0].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 2'), 'openFile01:', ''))
        submenu.addItem_(self.miList[1])
        self.miList[1].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 3'), 'openFile02:', ''))
        submenu.addItem_(self.miList[2])
        self.miList[2].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 4'), 'openFile03:', ''))
        submenu.addItem_(self.miList[3])
        self.miList[3].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 5'), 'openFile04:', ''))
        submenu.addItem_(self.miList[4])
        self.miList[4].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 6'), 'openFile05:', ''))
        submenu.addItem_(self.miList[5])
        self.miList[5].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 7'), 'openFile06:', ''))
        submenu.addItem_(self.miList[6])
        self.miList[6].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 8'), 'openFile07:', ''))
        submenu.addItem_(self.miList[7])
        self.miList[7].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 9'), 'openFile08:', ''))
        submenu.addItem_(self.miList[8])                                                                
        self.miList[8].setTarget_(self)
        self.miList.append(NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(_('file 0'), 'openFile09:', ''))
        submenu.addItem_(self.miList[9])
        self.miList[9].setTarget_(self)

        self.recents.setSubmenu_(submenu)
        
        # Get the icon update timer going

        self.timer = NSTimer.alloc().initWithFireDate_interval_target_selector_userInfo_repeats_(start_time, 0.5, self, 'tick:', None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSDefaultRunLoopMode)
        # init push notifications for 10.8 or higher
        try:
            if isMacMLion():
                self.notify = MountainLionNotification.alloc().init()
            else:
                self.notify = None
        except:
            self.notify = None            
        self.timer.fire()

    def updateRecents(self):
        global syncer
        if syncer.isRecentListChanged() == False: return 
        rawList = syncer.getRecentObjects()
        del self.refList[:]
        cnt = 0
        for p in rawList:
            if os.path.exists(p) and not p in self.refList: 
                self.refList.append(p)
                cnt += 1
            if cnt>=10: break
        if len(self.refList)==0:
            self.recents.setEnabled_(False)
            return
        for i,mi in enumerate(self.miList):
            if (i<len(self.refList)): 
                mi.setHidden_(False)
                mi.setTitle_(os.path.basename(self.refList[i]))
            else: mi.setHidden_(True)
        self.recents.setEnabled_(True)

    def openFile(self, num):
        path = self.refList[num]
        path = path.replace('/', os.sep)
        path = path.replace('\\', os.sep)
        path = os.path.dirname(path)
        openLocalFolder(path)

    @objc.IBAction        
    def openFile00_(self, notification): self.openFile(0)

    @objc.IBAction
    def openFile01_(self, notification): self.openFile(1)

    @objc.IBAction
    def openFile02_(self, notification): self.openFile(2)

    @objc.IBAction
    def openFile03_(self, notification): self.openFile(3)

    @objc.IBAction
    def openFile04_(self, notification): self.openFile(4)

    @objc.IBAction
    def openFile05_(self, notification): self.openFile(5)

    @objc.IBAction
    def openFile06_(self, notification): self.openFile(6)

    @objc.IBAction
    def openFile07_(self, notification): self.openFile(7)

    @objc.IBAction
    def openFile08_(self, notification): self.openFile(8)

    @objc.IBAction
    def openFile09_(self, notification): self.openFile(9)

    @objc.IBAction
    def terminate_(self, sender):
        logger.info('terminating main app')
        wxapp.ExitMainLoop()
    
        
    @objc.IBAction
    def showFolder_(self, notification):
        global configFile
        openLocalFolder(configFile.localSettings['syncRootDesired'])

    @objc.IBAction
    def showWeb_(self, notification):
        global webSyncUrl, connection
        if connection.token:
            key = connection.getLoginKey()
            path = '/'
            #urlInt = webSyncUrl+"/account/loginByKey?key="+key+'&action=files&path='+urllib.quote(path.encode('utf-8'))
            urlInt = connection.getWebUrl()+"/signin?key="+key+'&state=drive&path='+urllib.quote(path.encode('utf-8'))
            url = NSURL.URLWithString_(urlInt)
        else:
            url = NSURL.URLWithString_(connection.getWebUrl())
        NSWorkspace.sharedWorkspace().openURL_(url)
            
    @objc.IBAction
    def logout_(self, notification):
        global syncer, networkWatchdog, syncLock, configFile

        configFile.paused = False
        self.pauseItem.setEnabled_(False)
        self.pauseItem.setTitle_(_("Pause"))
        
        syncLock.acquire()
        if syncer:
            syncer.terminate(1)
            syncer = None
            connection.logout()
            #networkWatchdog.teardown()
            #networkWatchdog = None
        syncLock.release()

        utils.activateApp()
        logger.info('trying to logout')
        #wx.CallAfter(startLogon)
        startLogon()
        #thread.start_new_thread(wx.CallAfter, (webView.startLogon,))
            
    @objc.IBAction
    def about_(self, sender):
        if (about):
            utils.activateApp()
            wx.CallAfter(about.Show)    

    @objc.IBAction
    def settings_(self, sender):
        if (settings):
            utils.activateApp()
            wx.CallAfter(settings.showSettings)

    @objc.IBAction
    def progress_(self, sender):
        if (problems and syncer):
            utils.activateApp()
            wx.CallAfter(problems.updateGrid, syncer.getErrors())
            wx.CallAfter(problems.Show)
            wx.CallAfter(problems.Raise)

    def problems_(self, sender):
        if (problemReport):
            utils.activateApp()
            wx.CallAfter(problemReport.beginShow)

    def finishPause(self):
        launchSyncForPause()    
        self.pauseItem.setEnabled_(True)
        
    def finishContinue(self):
        logoutForPause()
        self.pauseItem.setEnabled_(True)
        
    @objc.IBAction            
    def pauseSync_(self, sender):
        global configFile
        self.pauseItem.setEnabled_(False)
        if configFile.paused == False:
            logger.info('*** Paused sync')
            configFile.paused = True
            self.pauseItem.setTitle_(_("Continue"))
            wx.CallAfter(self.finishContinue)
        else:
            logger.info('*** Sync continued')
            configFile.paused = False
            self.pauseItem.setTitle_(_("Pause"))
            wx.CallAfter(self.finishPause)            

    def tick_(self, notification):
        #Method is update system tray icon
        global syncer, networkWatchdog, configFile
        
        try:
            newImage = self.currentImage
            newLabel = ""
            
            if syncer == None:
                #Not logged in yet
                self.prevSyncAlive = False
                if configFile.paused:
                    logonStatus =True
                    newImage = self.iconPauseImage
                    newLabel = _("Paused")
                    self.wasPaused = True
                else:
                    logonStatus =False
                    if networkWatchdog and networkWatchdog.isReachable == False:
                        #It seems there's no network connection
                        newImage = self.iconNonetImage
                        newLabel = _("No network connection")
                        if (self.notify) and self.prevNetworkStatus == True:
                            #self.sysTray.showBalloon(_("No network connection"), _('Cloudike'), 200, None )
                            self.notify.notify(_('Cloudike'), '', _("No network connection"), configFile.localSettings['syncRootCurrent'])
                            #logger.info('No network connection balloon!!!')
                        self.prevNetworkStatus = False
                    else:
                        newImage = self.iconLogoutImage
                        newLabel = _("Not logged in")
                        self.prevNetworkStatus = True
                    #self.pauseItem.setEnabled_(False)
            else:
                logonStatus = True

                if configFile.localSettings['syncRootCurrent']==configFile.localSettings['syncRootDesired'] and not os.path.exists(configFile.localSettings['syncRootCurrent']):
                    syncer.needRelogin = True

                if syncer.needRelogin:
                    self.logout_(notification)
                    return
                    
                '''
                if (rerunOnExit == True):
                    wxapp.ExitMainLoop()
                    return
                '''
                    
                if networkWatchdog and networkWatchdog.getIsReachable() == False:
                        #It seems there's no network connection
                        newImage = self.iconNonetImage
                        newLabel = _("No network connection")
                else:
                    if (problems and problems.IsShown() and syncer and syncer.hasErrors):
                        wx.CallAfter(problems.updateGrid, syncer.getErrors())    
                        
                    if syncer.isAlive() != self.prevSyncAlive and not self.wasPaused:
                        self.prevSyncAlive = syncer.isAlive()
                        if syncer.isAlive():
                            if (self.notify):
                                self.notify.notify(_('Cloudike'), '', _("Welcome!\nCloudike folder successfully connected to the cloud storage."), configFile.localSettings['syncRootCurrent'])

                    self.updateRecents()

                    if syncer.quotaExceeded:
                        if syncer.quotaExceeded and not self.prevQuotaStatus:
                            self.prevQuotaStatus = True
                            if (self.notify):
                                self.notify.notify(_('Cloudike'), '', _("No more room in storage!"), configFile.localSettings['syncRootCurrent'])
                        newLabel = _("Storage exceeded")
                        newImage = self.iconIdleImage
                    elif not syncer.networkError and syncer.errorCount > 3 and len(syncer.errors)>0 and not syncer.movingSyncFolder:
                        # Persistent error (except for network connectivity errors)
                        self.prevQuotaStatus = False
                        newImage = self.iconErrorImage
                        newLabel = _("Unable to sync")
                        if syncer.errorCount == 4 and self.prevErrorCount!=syncer.errorCount:
                            if (self.notify):
                                self.notify.notify(_('Cloudike'), '', _("Unable to sync"), configFile.localSettings['syncRootCurrent'])
                        self.prevErrorCount = syncer.errorCount
                        self.progressItem.setEnabled_(True)
                    elif not syncer.networkError and syncer.movingSyncFolder and len(syncer.errors)>0:
                        # sync folder move error
                        newImage = self.iconErrorImage
                        newLabel = _("Unable to relocate sync folder")
                        if (self.notify):
                                self.notify.notify(_('Cloudike'), '', _("Unable to relocate sync folder"), configFile.localSettings['syncRootCurrent'])
                        self.prevErrorCount = syncer.errorCount
                        sysTrayHandler.sysTray.enableItem(syncProgress)
                    else:
                        self.prevQuotaStatus = False
                        self.progressItem.setEnabled_(False)
                        if (self.prevStorageSize != syncer.storageSize or self.prevUsedSize != syncer.usedSize):
                            if syncer.storageSize != 0:
                                self.prevStorageSize = syncer.storageSize
                            self.prevUsedSize = syncer.usedSize
                            if syncer.storageSize>0:
                                self.storageSize.setTitle_(_("Used %s of %s") % (utils.sizeof_fmt(syncer.usedSize), utils.sizeof_fmt(syncer.storageSize)))
                            elif syncer.storageSize==0:
                                self.storageSize.setTitle_(_("Used %s") % utils.sizeof_fmt(syncer.usedSize))
                            else:
                                self.storageSize.setTitle_(_("Storage size undefined"))

                        fileJobs = syncer.getJobsCount()
                        if (fileJobs > 0):
                            self.idleCycles = 0
                            self.fileJobsToShow += fileJobs
                            print "file jobs to show: %s" % self.fileJobsToShow
                        if self.prevMovingSyncFolder != syncer.movingSyncFolder and not syncer.movingSyncFolder:
                            self.prevMovingSyncFolder = False
                            if (self.notify):
                                self.notify.notify(_('Cloudike'), '', _("Successfully relocated sync folder."), configFile.localSettings['syncRootCurrent'])
                        if syncer.getTotalJobsCount() == 0 and not syncer.serverWatcher.loadingFileList and not syncer.movingSyncFolder:
                            # Idle mode
                            if (rerunOnExit == True):
                                wxapp.ExitMainLoop()
                            newImage = self.iconIdleImage
                            newLabel = _("All synced")
                            self.lastTransBytes = 0
                            self.transHistory.clear()
                            if (self.fileJobsToShow != 0):
                                self.idleCycles += 1
                                if (self.idleCycles > 2):
                                    if (self.notify):
                                        self.notify.notify(_('Cloudike'), '', _("Successfully synchronized %s file(s).") % self.fileJobsToShow, configFile.localSettings['syncRootCurrent'])
                                    self.fileJobsToShow = 0
                        else:
                            # Progress
                            if not syncer.isSyncing() and not syncer.movingSyncFolder:
                                if syncer.serverWatcher.fileListReady >=0:
                                    newLabel = _("Loading file list...(%s%%)" % syncer.serverWatcher.fileListReady)
                                else:
                                    newLabel = _("Loading file list...")
                            else:
                                if not syncer.movingSyncFolder:
                                    self.idleCycles = 0
                                    #if (tTotal>0 and tCurrent>0):
                                    if (syncer.fileProcessing()):

                                        if (self.prevEnableSpeedLimit != configFile.localSettings['enableSpeedLimit'] or self.prevSpeedLimit != configFile.localSettings['speedLimit']):
                                            #logger.info('******************************** speed limit settings changed')
                                            self.transHistory.clear()

                                        self.prevEnableSpeedLimit = configFile.localSettings['enableSpeedLimit']
                                        self.prevSpeedLimit = configFile.localSettings['speedLimit']

                                        tCurrent, tTotal = syncer.calcTransfers()
                                        #logger.info('file trans speed c/t: %s %s' % (tCurrent, tTotal))
                                        BPS = (tCurrent - self.lastTransBytes)*2
                                        self.lastTransBytes = tCurrent
                                        self.transHistory.append(BPS)
                                        sumbps = 0
                                        for i in self.transHistory:
                                            sumbps += i
                                        s = sumbps/len(self.transHistory)
                                        if (s<=0):
                                            s = 1
                                        t = (tTotal-tCurrent)/s
                                        if t<0: t=0
                                        #newLabel = _("Syncing %s files (%s of %s)") % (syncer.pendingJobs, utils.sizeof_fmt(tCurrent), utils.sizeof_fmt(tTotal))
                                        newLabel = _("Syncing %s files (%s/sec, %s left)") % (syncer.getTotalJobsCount(), utils.sizeof_fmt(s), utils.time_fmt(t))
                                    else:
                                        newLabel = _("Syncing %s files...") % syncer.getTotalJobsCount()
                                else:
                                    self.prevMovingSyncFolder = True
                                    newLabel = _("Relocating sync folder...")    

                            newImage = self.iconSyncImages[self.iconSyncFrame]
                            self.iconSyncFrame += 1
                            if self.iconSyncFrame > 1: self.iconSyncFrame = 0

            if newImage != self.currentImage:
                self.currentImage = newImage                       
                self.statusitem.setImage_(self.currentImage)
            if newLabel != self.currentLabel:
                self.currentLabel = newLabel
                self.progressItem.setTitle_(self.currentLabel)

            if logonStatus != self.logonStatus:
                self.logonStatus = logonStatus
                if logonStatus:
                    self.logoutItem.setTitle_(_("Logout"))
                    self.pauseItem.setEnabled_(True)
                else:
                    self.logoutItem.setTitle_(_("Enter account"))            
        except:                    
            logger.warn("Tray ticker error: %s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )

    
class Cloudbit(NSObject):
    
    def applicationDidFinishLaunching_(self, notification):
        self.statusController = CloudbitMenu.alloc().initWithWindowNibName_("StatusBarMenu")
        self.statusController.window() # Try loading the window
        
        serviceProvider = CloudbitService.alloc().init()
        NSApplication.sharedApplication().setServicesProvider_(serviceProvider)


def serviceSelector(fn):
    # this is the signature of service selectors
    return objc.selector(fn, signature="v@:@@o^@")
    
class CloudbitService(NSObject):
    
    @serviceSelector
    def doOpenWebService_userData_error_(self, pboard, data, error):
        global connection
        
        logger.info("Open web request")
        types = pboard.types()
        pboardString = None
        if NSFilenamesPboardType in types:
            pboardString = pboard.propertyListForType_(NSFilenamesPboardType)
            logger.info("Got string form pboard: %s", pboardString[0])
            if connection and connection.token:
                path = pboardString[0]
                if os.path.isfile(path):  path = os.path.dirname(path) # If file was selected we will show it's parent folder instead
                path = connection.getServerPathFromLocal(path) # Convert to relative server path
                logger.info("Will try to get weblink for path: %s", path)
                if path:
                    if path:
                        key = connection.getLoginKey()
                        #urlInt = webSyncUrl+"/account/loginByKey?key="+key+'&action=files&path='+urllib.quote(path.encode('utf-8'))
                        urlInt = connection.getWebUrl()+"/signin?key="+key+'&state=drive&path='+urllib.quote(path.encode('utf-8'))
                        url = NSURL.URLWithString_(urlInt)
                        NSWorkspace.sharedWorkspace().openURL_(url)
                else:
                    logger.info("Path %s is not inside the sync path. Ignoring open request...", path)
                    showAlert(_("Can't open web page!"), _("Selected file or folder is not in folder synchronized by Cloudike."))
            else:
                showAlert(_("Can't open web page!"), _("Not logged in to Cloudike, please try later."))

                
        return None
        
    @serviceSelector
    def doGetShareLinkService_userData_error_(self, pboard, data, error):
        global connection, linkController
        
        logger.info("Get shared link")
        types = pboard.types()
        pboardString = None
        if NSFilenamesPboardType in types:
            pboardString = pboard.propertyListForType_(NSFilenamesPboardType)
            logger.info("Got string form pboard: %s", pboardString[0])
            wx.CallAfter(showLinkWindow, connection, pboardString[0])
            '''
            if connection and connection.token:
                path = connection.getServerPathFromLocal(pboardString[0]) # Convert to relative server path
                if path:
                    #hash = connection.getSharedLink(path)
                    if hash:
                        #link = webSyncUrl + '/public/' + hash + '/'
                        wx.CallAfter(showLinkWindow, path)
                        #linkController.setLink(link)
                        #linkController.showWindow_(linkController)
                        #NSApp.activateIgnoringOtherApps_(True)
                        logger.info("Public link: %s", link)
                else:
                    logger.info("Path %s is not inside the sync path. Ignoring link request...", path)
                    showAlert(_("Can't obtain shared link!"), _("Selected file or folder is not in folder synchronized by Cloudike."))
            else:
                showAlert(_("Can't obtain shared link!"), _("Not logged in to Cloudike, please try later."))
            '''
                    
        return None

def launchSyncForPause():        
    global syncLock, syncer, networkWatchdog, appPath, syncState
    syncLock.acquire()
    syncer = CloudikeSyncher(connection)
    syncer.daemon = True
    syncer.start()
    syncLock.release()
    #networkWatchdog = NetworkWatchdog(connection, syncer.serverWatcher)
    #networkWatchdog.start()
    syncState = None        

def logoutForPause():
        global syncLock, syncer, networkWatchdog, syncState
        syncLock.acquire()
        if syncer:
            #networkWatchdog.teardown()
            #networkWatchdog = None
            syncer.terminate(1)
            logger.info('*********************** logout for pause 1')
            syncState = SyncerStateHolder()
            logger.info('*********************** logout for pause 2')
            syncer.copyState(syncState)
            logger.info('*********************** logout for pause 3')
            syncer = None
        syncLock.release()                

def showSyncFolder():
    global configFile
    openLocalFolder(configFile.localSettings['syncRootCurrent'])

def showSyncFolderOnLogon():
    global configFile
    openLocalFolder(configFile.localSettings['syncRootDesired'])    

def launchSyncOnLogon():            
    showSyncFolderOnLogon()
    launchSync()    

def launchSync():        
    global syncer, networkWatchdog, appPath, syncLock, configFile, updater, connection, updateUrl
    
    if BIZ_VERSION:
        logger.info("Recreating updater")
        if updater:
            updater.stop()
        try:        
            updater = CloudikeUpdater(quitFromUpdater, connection.getUpdateUrl(), connection)
            updater.start()
        except:
            pass

    showSlide_fse()

    configFile.paused = False

    logger.info("Entering launchSync.")
    syncLock.acquire()
    syncer = CloudikeSyncher(connection)
    syncer.start()
    syncLock.release()
    #networkWatchdog = NetworkWatchdog(connection, syncer.serverWatcher)
    #networkWatchdog.start()

    logger.info('Server URL: %s' % connection.getServerUrl())
    logger.info('Web URL: %s' % connection.getWebUrl())
    logger.info('Updater URL: %s' % connection.getUpdateUrl())

    if BIZ_VERSION:
        about.termsPage = connection.getWebUrl()+'/terms/'

    try:
        #appPath = NSBundle.mainBundle().bundlePath()+'/Contents/MacOS/'+commonName
        appPath = NSBundle.mainBundle().executablePath()
        logger.info('autorun path: %s' % appPath)
        if appPath: setAutoRun(appPath, configFile.localSettings['commonAutostart'])
    except: 
        logger.info('error in adding to autostart')
        logger.warn("%s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )
    logger.info("Leaving launchSync.")

def startLogon():    
    global connection, webView, offerView
    if (webView):
        webView.Close()
    slide2.Close()
    slider.Close()
    if (offerView):
        wx.CallAfter(offerView.Close)
    #del webView
    #webView = CloudikeWebView(None, connection, showSlide1, startOffer)
    #wx.CallAfter(webView.startLogon)
    if BIZ_VERSION:
        if selectServer:
            selectServer.Close()
        if (offerView):
            wx.CallAfter(offerView.Close)
        if selectLogin:
            selectLogin.Show()
            selectLogin.Raise()
    else:            
        #wx.CallAfter(webView.startLogon)
        thread.start_new_thread(wx.CallAfter, (webView.startLogon,))
    #wx.CallAfter(webView.startLogon)
    #thread.start_new_thread(wx.CallAfter, (webView.startLogon,))

def serverSelection(login):
    global connection
    lst = connection.getURLsForLogin(login)
    
    '''
    srv = {}
    srv['frontend'] = 'https://saas.cloudike.com'
    srv['backend'] = 'https://be-saas.cloudike.com'
    srv['update-server'] = 'https://updates.cloudike.com/'
    srv['name'] = 'Home'
    lst.append(srv)
    '''
                        
    if lst:
        if len(lst)==1:
            srv = lst[0]
            web = srv['frontend']
            server = srv['backend']
            update = srv['update-server']
            name = srv['name']
            connection.storeURLs(web, server, update)
            webView.startLogon()    
        else:
            selectServer.setServers(lst)
            selectServer.Show()
            selectServer.Raise()
    else:
        showAlert(_("Login not found!"),_("Can not find login on any server!\nPlease select another login."))
        wx.CallAfter(startLogon)
    

def serverSelected(srv):
    web = srv['frontend']
    server = srv['backend']
    update = srv['update-server']
    name = srv['name']
    connection.storeURLs(web, server, update)            
    webView.startLogon()                
    
def startOffer():    
    global offerView, connection
    #wx.CallAfter(webView.startLogon)
    if (not offerView):
        offerView = CloudbitOffer( None, connection, connection.offer_url, launchSync )
    if (offerView):
        thread.start_new_thread(wx.CallAfter, (offerView.showOffer,))        
    
        
def quitFromUpdater():
    global rerunOnExit, wxapp
    logger.info('Exit on updater request')
    rerunOnExit = True
    #wxapp.ExitMainLoop()
    logger.info('Quit message posted on updater request')    

def showSlide1():
    slider.showSlide('slide1', showSlide2)

def showSlide2():
    global settings
    slide2.showSlide('slide2', launchSyncOnLogon, settings)

def dummy_func():    
    pass

def showSlide_fse():
    global prefs, configFile
    if (macVersion() >= versiontuple('10.10')) and (not fileops.sendPath(configFile.localSettings['syncRootCurrent'])):
        utils.activateApp()
        slide_fse.showSlide('slide_fse', dummy_func, prefs)    
        configFile.localSettings['shownExtPrefs']=1
        configFile.writeSettings()

def getSync(n):
    global syncLock, syncer
    if syncer:
        if n==1: syncLock.acquire()
        elif n==2: syncLock.release()
        return syncer
    else:
        return None    

def runFinderExtension():
    logger.info('Preparing finder extension...')
    try: 
        os.mkdir(os.path.join(localAppData,'CloudikePlugins'))
    except: pass
    try: 
        os.mkdir(os.path.join(localAppData,'CloudikePlugins','Plugins'))
    except: pass
    ###
    # check plugin bundle version here
    ###
    if os.path.exists("../../../../CloudbitFinderIcons.bundle"):
        baseRelPath = "../../../../"
    else:
        baseRelPath = "../../../../../"
    newBundlePath = baseRelPath+"CloudbitFinderIcons.bundle"
    oldBundlePath = os.path.join(localAppData,'CloudikePlugins','Plugins','CloudbitFinderIcons.bundle')
    if os.path.exists(oldBundlePath):
        oldBundleVer = NSBundle.bundleWithPath_(os.path.abspath(oldBundlePath)).objectForInfoDictionaryKey_("CFBundleShortVersionString");
    else:
        oldBundleVer = '0.0'
    logger.info('Old plugin ver: %s' % oldBundleVer)
    if os.path.exists(newBundlePath):
        newBundleVer = NSBundle.bundleWithPath_(os.path.abspath(newBundlePath)).objectForInfoDictionaryKey_("CFBundleShortVersionString");
    else:
        newBundleVer = '0.0'
    logger.info('New plugin ver: %s' % newBundleVer)    

    # lets kill old agent
    try:
        shutil.rmtree(baseRelPath+'CloudikeSIMBL.app')
    except: pass
    try:
        sp = subprocess.Popen(['killall', '-KILL', "Cloudike SIMBL Agent"])
        sp. communicate()
    except: pass

    logger.info('Old plugin path: %s' % oldBundlePath)
    logger.info('New plugin path: %s' % newBundlePath)

    if utils.versiontuple(oldBundleVer) < utils.versiontuple(newBundleVer):
        try:
            logger.info('removing old plugin...')
            shutil.rmtree(oldBundlePath)
        except:
            logger.warn("Finder plugin error: %s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )
        try:
            logger.info('coping new plugin...')
            shutil.copytree(newBundlePath, oldBundlePath)
        except:
            logger.warn("Finder plugin error: %s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )
        if oldBundleVer!='0.0':
            if askQuestion('Due to badge plugin update we need to restart Finder, restart?'):
                sp = subprocess.Popen(['killall', '-KILL', "Finder"])
                sp. communicate()
    os.system('open "%sCloudike SIMBL Agent.app"' % baseRelPath)

if __name__ == '__main__':

    if (sys.argv[1:] and '-psn' not in sys.argv[1:][0]):
        #globals localSyncRoot, serverSyncUrl, webSyncUrl
        parser = argparse.ArgumentParser(description='Sync folder with cloud server.')
        parser.add_argument('localPath', nargs=1, help='local path to be synchronized')
        parser.add_argument('serverUrl', nargs='?', help='cloud server backend URL', default=serverSyncUrl)
        parser.add_argument('webUrl', nargs='?', help='cloud server frontend URL', default=webSyncUrl)
        parser.add_argument('updateUrl', nargs='?', help='cloud server update URL', default=updateSyncUrl)
        args = parser.parse_args()
        localSyncRoot = args.localPath[0]
        serverSyncUrl = args.serverUrl
        webSyncUrl    = args.webUrl
        updateUrl     = args.updateUrl
        print "Local sync path: %s" % localSyncRoot
        print "Backend server URL: %s" % serverSyncUrl
        print "Frontend server URL: %s" % webSyncUrl
        print "Update server URL: %s" % updateUrl


    if macVersion() < versiontuple('10.7'):
        showAlert(_("Error"),_("Cloudike needs least 10.7 MacOS version to run."))
        os._exit(0)

    print "Loading local settings"        
        
    configFile = LocalSettings()
    
    print "Creating local dirs"
    # Make sure sync and app folders exists
    res = fileops.createDataFolders(configFile.localSettings['syncRootCurrent'], dataFolder, localAppData)
    if res != 0:
        if (res == errno.EACCESS):
            showAlert(_("Error"),_("You don't have enough access rights in Your own folder!\nPlease fix access rights or run program as admin."))
            os._exit(0)

    print "Redirect stdio to files"            

    #redirect std out/err to files
    sys.stdout = open(os.path.join(localAppData,commonName+"/app-out.txt"), "w")
    sys.stderr = open(os.path.join(localAppData,commonName+"/app-err.txt"), "w")
    # Put log into AppData folder, because on Windows 7 and above Program's own folder is read-only
    h = logging.handlers.RotatingFileHandler(os.path.join(localAppData,commonName+'/app-log.txt'), maxBytes=config.LOG_SIZE, backupCount=config.LOG_FILE_COUNT)
    h.setFormatter(logging.Formatter('%(asctime)s %(thread)d %(message)s'))
    logger.addHandler(h)
    logger.setLevel(logging.INFO)
    logger.addHandler(logging.StreamHandler())
    handler.setLevel(logging.ERROR)
    logger.addHandler(handler)
    
    logger.info("Config settings file: %s" % configFile.settingsPath)
    logger.info(configFile.localSettings['syncRootCurrent'])

    si = SingleInstance(configFile)
    try:
        if si.aleradyrunning():
            logger.info("another instance detected - exitting")
            os._exit(0)
    except:
        pass

    logger.info('Cloudbit v.%s' % VERSION)        
        

    regPath = NSBundle.mainBundle().bundlePath()
    logger.info("Host app path: %s" % regPath)
    cmd = 'open -a "%s" -n' % regPath
    logger.info("Open app cmd: %s" % cmd)
    res = os.system(cmd)
    logger.info("Open cmd result: %s" % res)

    '''
    task = NSTask.alloc().init()
    args = ["-c", 'sleep 20; open "%s"' % NSBundle.mainBundle().bundlePath()]
    task.setLaunchPath_("/bin/sh")
    task.setArguments_(args)
    task.launch()
    '''
        
    logger.info(configFile.localSettings['syncRootCurrent'])
        
    # cleanup downloads
    fileops.deleteDownloads(configFile) 
    fileops.addToFavorites(configFile.localSettings['syncRootCurrent'], _("Cloudbit sync folder"))

    syncFolderIsEmpty = isFolderEmpty(configFile.localSettings['syncRootCurrent'])

    # StartSSL doesn't seem to be validated from a main bundle, downloaded separate bundle from their site
    #caBundle = os.path.join(os.getcwdu(),'ca-bundle.crt')
    caBundle = None
    # Requests cannot access it CA sertificated in usial place when bundled, so we have to provide path to file manually
    caBundleMain = os.path.join(os.getcwdu(),'cacert.pem')
    logger.info('Using certificate authority bundle: %s ; %s', caBundle, caBundleMain)
    
    appPath = os.path.join(os.path.dirname(os.getcwdu()),'MacOS',commonName)
    logger.info('Application path is: %s', appPath)

    localSettingsPath = os.path.join(localAppData,commonName+"/settings.ini")

    lt = OIListenerThread()
    lt.start()
    
    wxapp = wx.App()

    proxyLogin = SaasProxyLogin(None)
    connection = ServerConnection(caBundle, caBundleMain, serverSyncUrl, configFile, proxyLogin)

    if BIZ_VERSION:
        logger.info('Creating login selection')
        selectLogin = CloudbitSelectLogin(None, configFile, serverSelection)
        logger.info('Creating server selection')
        selectServer = CloudbitSelectServer(None, startLogon, serverSelected)

    networkWatchdog = NetworkWatchdog(connection, None)
    networkWatchdog.start()

    prefs = PrefExtSettings()
    

    webView = CloudikeWebView(None, connection, showSlide1, startOffer)
    slider = CloudbitSlider(None, 'images/slides.cfg')
    slide2 = CloudbitSettingsSlider(None, 'images/slides.cfg')
    slide_fse = CloudbitSettingsSlider(None, 'images/slides.cfg')
    #logon = CloudbitLogon(None, connection, showSlide1, webSyncUrl)
    #about = CloudbitAbout(None, webSyncUrl+"/terms/")
    about = CloudbitMacAbout(None, webSyncUrl+"/terms/")
    #webView = MegafonWebView(None, connection, launchSync, startOffer)
    
    NSUpdateDynamicServices()
    
    app = NSApplication.sharedApplication()
    delegate = Cloudbit.alloc().init()
    app.setDelegate_(delegate)

    if connection.getAuthStage() < 2:
        if not connection.userName and not syncFolderIsEmpty:
            # We have non-empty folder and no known owner. Abort.
            logger.warn("Sync folder is not usable!")
            showAlert(_("Sync folder is not usable!"),_("Can't use folder (%s) because it's not empty. Please rename or erase current folder and try again.") % localSyncRoot)
            sys.exit(1)

        startLogon()
        '''    
        if connection.getAuthStage() == 0: 
            logger.info("Showing logon dialog.")
            startLogon()
        else: 
            logger.info("Showing offer dialog.")
            print connection.offer_url
            startOffer()
       '''
    else:
        #connection.rejectOffer()
        launchSync()
        
    if not BIZ_VERSION:
        updater = CloudikeUpdater(quitFromUpdater, updateUrl, connection)
        updater.start()

    settings = CloudbitSettings(None, connection, configFile, getSync)
    problems = CloudbitProblems(None, configFile)
    problemReport = CloudbitProblemReport(None, connection, os.path.join(localAppData,commonName))

    try:
        #thread.start_new_thread(runFinderExtension, ())
        #runFinderExtension()
        pass
    except: 
        logger.warn("Preparing finder extension error  : %s\n%s\n%s", sys.exc_info()[0], sys.exc_info()[1], traceback.format_exc() )
        
    if connection.isOutdated():
        upd_note = CloudbitUpdateNotifier(None, webSyncUrl, 'Cloudike Website')
        upd_note.Show()
        upd_note.Raise()        

    logger.info("Entering main message loop.")
    wxapp.MainLoop()
    logger.info("Exitting main message loop.")
    
    #logon.closeLogon()
       
    #AppHelper.runEventLoop()
    
    logger.info('Closing badges server')
    lt.close()

    del si

    if rerunOnExit:
        regPath = NSBundle.mainBundle().bundlePath()
        logger.info("Host app path: %s" % regPath)
        cmd = 'open -a "%s" -n' % regPath
        logger.info("Open app cmd: %s" % cmd)
        res = os.system(cmd)
        logger.info("Open cmd result: %s" % res)
    
    '''
    if rerunOnExit:
        logger.info('Must rerun on exit')
        appexe = os.path.dirname(esky.util.appexe_from_executable(sys.executable))+'/'+commonName
        os.execv(appexe,[appexe] + sys.argv[1:])
    logger.info('Exitting normally')
    '''
    os._exit(0)