#-----------------------------------------------------------------------------
# Name:        ZopeExplorer.py
# Purpose:
#
# Author:      Riaan Booysen & Robert Boulanger
#
# Created:     2001/02/04
# RCS-ID:      $Id: ZopeExplorer.py,v 1.13.2.1 2005/03/07 14:24:17 riaan Exp $
# Copyright:   (c) 2001 - 2005
# Licence:     GPL
#-----------------------------------------------------------------------------
print 'importing ZopeLib.ZopeExplorer'

import os, urllib, urlparse, time, socket
from thread import start_new_thread

from wxPython.wx import *

from Explorers import ExplorerNodes
from Models import EditorHelper, Controllers
from ExternalLib import xmlrpclib, BasicAuthTransport
from Preferences import IS
import Utils, Preferences
import Views, Views.SourceViews, Views.PySourceView
import PaletteStore

import ZopeEditorModels, ZopeViews, Client, ExtMethDlg
from ZopeCompanions import ZopeConnection, ZopeCompanion, FolderZC

# XXX Add owner property

# XXX root attribute is no longer really necessary
# XXX Improve '/' management ;)

class ZopeEClip(ExplorerNodes.ExplorerClipboard):
    def __init__(self, globClip, props):
        ExplorerNodes.ExplorerClipboard.__init__(self, globClip)
        self.clipRef = ''

        self.props = props
        self.zc = ZopeConnection()
        self.zc.connect(props['host'], props['httpport'],
                        props['username'], props['passwd'])
    def callAndSetRef(self, objpath, method, nodes):
        names = map(lambda n :n.name, nodes)
        mime, res = self.zc.call(objpath, method, ids = names)
        self.clipRef = mime.get('Set-Cookie').split('"')[1]
    def clipCut(self, node, nodes):
        ExplorerNodes.ExplorerClipboard.clipCut(self, node, nodes)
        self.callAndSetRef(node.resourcepath, 'manage_cutObjects', nodes)
    def clipCopy(self, node, nodes):
        ExplorerNodes.ExplorerClipboard.clipCopy(self, node, nodes)
        self.callAndSetRef(node.resourcepath, 'manage_copyObjects', nodes)
#    def clipPaste(self, node):
#        ExplorerNodes.ExplorerClipboard.clipPaste(self, node)

    def clipPaste_ZopeEClip(self, node, nodes, mode):
        mime, res = self.zc.call(node.resourcepath,
              'manage_pasteObjects', cb_copy_data = self.clipRef)

    def clipPaste_FileSysExpClipboard(self, node, nodes, mode):
        for file in nodes:
            if file.isDir():
                node.newFolder(file.name)
                folderNode = node.createChildNode('Folder', file.name)
                self.clipPaste_FileSysExpClipboard(folderNode,
                                                   file.openList(), mode)
            else:
                node.uploadFromFS(file)

class ZopeCatNode(ExplorerNodes.CategoryNode):
    protocol = 'config.zope'
    itemProtocol = 'zope'

    defName = 'Zope'
    defaultStruct = {'host': 'localhost',
                     'httpport': 8080,
                     'localpath': '',
                     'passwd': '',
                     'path': '/',
                     'username': '',
                     'servicename': '',
                     'startuptimeout': 30}
    def __init__(self, globClip, config, parent, bookmarks):
        ExplorerNodes.CategoryNode.__init__(self, 'Zope', ('explorer', 'zope'),
              None, config, None)
        self.globClip = globClip
        self.bookmarks = bookmarks

    def createChildNode(self, name, props):
        # Zope clipboards should be global but unique on site / user
        clipboard = ZopeEClip(self.globClip, props)
        zin = ZopeItemNode('', props['path'], clipboard,
            EditorHelper.imgZopeConnection, self, None, None, props, 'Folder')
        zin.category = name
        zin.treename = name
        zin.bookmarks = self.bookmarks
        return zin

    def createCatCompanion(self, catNode):
        comp = ExplorerNodes.CategoryDictCompanion(catNode.treename, self)
        return comp

class ZopeItemNode(ExplorerNodes.ExplorerNode):
    protocol = 'zope'
    Model = ZopeEditorModels.ZopeBlankEditorModel
    defaultViews = ()
    additionalViews = (ZopeViews.ZopeSecurityView,
                       ZopeViews.ZopeUndoView)
    itemsSubPath = ''
    connection = false
    def __init__(self, name, resourcepath, clipboard, imgIdx, parent, xmlrpcsvr,
          root, properties, metatype):
        ExplorerNodes.ExplorerNode.__init__(self, name, resourcepath, clipboard,
              imgIdx, None, properties)
        self.metatype = metatype
        self.image = imgIdx
        self.root = None
        self.cache = {}
        self.server = xmlrpcsvr
        self.entries = None
        self.entryIds = None
        self.typ = None

    def getURI(self):
        return '%s://%s/<%s>%s'%(self.protocol, self.category, self.metatype, self.getTitle())

    def buildUrl(self):
        path = urllib.quote(self.resourcepath)
        if path:
            if path == '/':
                path = '//'
            if path[0] != '/':
                path = '/'+path
        else:
            path = '//'
        return '%(host)s:%(httpport)d' % self.properties + path

    def destroy(self):
        self.cache = {}
        self.root = None
        self.parent = None

    def canAdd(self, paletteName):
        return paletteName == 'Zope'

    def createChildNode(self, metatype, id, respath=None):
        if respath is None:
            respath = self.resourcepath

        if respath == '/':
            tmppath = respath + self.itemsSubPath + id
        else:
            tmppath = respath + self.itemsSubPath + '/' + id

        itm = self.checkentry(id, metatype, tmppath)
        if itm.imgIdx == -1:
            itm.imgIdx = ZopeEditorModels.ZOAIcons.get(metatype,
                         ZopeEditorModels.ZOAIcons['unknown'])
        itm.category = self.category
        itm.bookmarks = self.bookmarks
        return itm

    def checkentry(self, name, metatype, path):
        ZopeNodeClass = zopeClassMap.get(metatype, ZopeItemNode)
        return apply(ZopeNodeClass, (name, path, self.clipboard, -1, self,
            self.server, self.root, self.properties, metatype))

    def whole_name(self):
        return self.resourcepath

    def getResource(self, url=''):
        if not url:
            url = self.buildUrl()
        return getServer(url, self.properties['username'],
               self.properties['passwd'])

    def getParentResource(self):
        path, name = os.path.split(self.name)
        return self.getResource(os.path.dirname(self.buildUrl())), name

    def openList(self, root = None):
        url = self.buildUrl()+self.itemsSubPath
        if url[-1] == '/':
            url = url[:-1]
        self.server = self.getResource(url)
        try:
            self.entries, self.entryIds = self.server.zoa.items()
        except xmlrpclib.Fault, error:
            #print str(error)
            # see if zoa object is installed
            try:
                Client.call('http://%s/zoa'%self.buildUrl(),
                      self.properties['username'], self.properties['passwd'],
                      function='version')
            except Client.NotFound:
                if wxMessageBox(
                  'The zoa object not found in the root of your Zope tree.\n\n'
                  'Do you want to install it?', 'Install zoa',
                  wxYES_NO | wxICON_QUESTION) == wxYES:

                    import ZoaClient
                    conninfo = ('http://%s'%self.buildUrl(),
                         self.properties['username'], self.properties['passwd'])
                    ZoaClient.installFromFS(conninfo,
                          os.path.join(Preferences.pyPath, 'ZopeLib', 'zoa', ))

                    # try again, if this fails the real error should break thru
                    self.entries, self.entryIds = self.server.zoa.items()

            else:
                err = error.faultString
                raise zopeHtmlErr2Strs(err)

        self.cache = {}
        result = []
        if self.entryIds:
            for i in range(len(self.entries)):
                z = self.createChildNode(self.entries[i], self.entryIds[i] )
                if z:
                    result.append(z)
                    self.cache[self.entryIds[i]] = z
        return result

    def isFolderish(self):
        return true

    def getTitle(self):
        return self.resourcepath

    def open(self, editor):
        return editor.openOrGotoZopeDocument(self)

    def deleteItems(self, names):
        mime, res = self.clipboard.zc.call(self.resourcepath,
              'manage_delObjects', ids = names)

    def renameItem(self, name, newName):
        mime, res = self.clipboard.zc.call(self.resourcepath,
              'manage_renameObject', id = name, new_id = newName)

    def exportObj(self):
        mime, res = self.clipboard.zc.call(os.path.dirname(self.resourcepath),
              'manage_exportObject', download = 1, id = self.name)
        return res

    def uploadObj(self, content):
        mime, res = self.clipboard.zc.call(self.resourcepath,
              'manage_upload', file = content)
        return res

    def listImportFiles(self):
        if self.properties.has_key('localpath') and self.properties['localpath']:
            from Explorers import Explorer
            return Explorer.listdirEx(self.properties['localpath']+'/import', '.zexp')
        else:
            return []

    def importObj(self, name):
        try:
            mime, res = self.clipboard.zc.call(self.resourcepath, 'manage_importObject', file = name)
        except Exception, message:
            wxMessageBox(`message.args`, 'Error on import')
            #raise

    def newItem(self, name, Compn, getNewValidName = true):
        props = self.properties
        if getNewValidName:
            name = Utils.getValidName(self.cache.keys(), name)
        cmp = Compn(name, self.resourcepath, props.get('localpath', ''))
        cmp.connect(props['host'], props['httpport'],
                    props['username'], props['passwd'])
        cmp.create()

        return cmp.name

    def getUndoableTransactions(self):
        from ZopeLib.DateTime import DateTime
        svr, name = self.getParentResource()
        return eval(svr.zoa.undo(name), {})

    def undoTransaction(self, transactionIds):
        self.getResource().manage_undo_transactions(transactionIds)

    def getPermissions(self):
        return self.getResource().permission_settings()#ZOA('permissions')

    def getRoles(self):
        return self.getResource().valid_roles()

    def load(self, mode='rb'):
        return ''#return self.getResource().document_src()


    def save(self, filename, data, mode='wb'):
        """ Saves contents of data to Zope """
        pass#self.getResource().manage_upload(data)

    def newFolder(self, name):
        self.getResource().manage_addFolder(name)

    def newBlankDocument(self, name=''):
        try:
            self.getResource().manage_addDTMLDocument(name)
        except xmlrpclib.ProtocolError, error:
            if error.errcode != 302:
                raise

    def uploadFromFS(self, filenode):
        props = self.properties
        from ExternalLib.WebDAV.client import Resource
        r = Resource(('http://%(host)s:%(httpport)s/'+self.resourcepath+'/'+\
            filenode.name) % props, props['username'], props['passwd'])
        r.put(filenode.load())

    def downloadToFS(self, filename):
        open(filename, 'wb').write(self.getResource().manage_FTPget())

    def getNodeFromPath(self, respath, metatype):
        return self.createChildNode(metatype, os.path.basename(respath),
              os.path.dirname(respath))

    def findItems(self, obj_ids=(), obj_metatypes=None, obj_searchterm=None,
          search_sub=1):
        # silly xml-rpc restrictions
        if obj_metatypes is None: obj_metatypes=0
        if obj_searchterm is None: obj_searchterm=0
        if not search_sub: search_sub=''

        return self.getResource().zoa.find(obj_ids, obj_metatypes, obj_searchterm)


(wxID_ZOPEEXPORT, wxID_ZOPEIMPORT, wxID_ZOPEINSPECT, wxID_ZOPEOPENINEDITOR,
 wxID_ZOPEUPLOAD, wxID_ZOPESECURITY, wxID_ZOPEUNDO, wxID_ZOPEVIEWBROWSER,
 wxID_ZCCSTART, wxID_ZCCRESTART, wxID_ZCCSHUTDOWN, wxID_ZCCTEST, wxID_ZCCOPENLOG,
 wxID_ZACONFZ2, wxID_ZOPEMANAGEBROWSER, wxID_ZOPEFIND,
 wxID_ZCOPENZ2, wxID_ZCBREAKINTO,
) = Utils.wxNewIds(18)

class ZopeCatController(ExplorerNodes.CategoryController):
    protocol = 'config.zope'
    zopeStatupTimeout = 60 # default when not read from property
    zopeRunning = 'The server is available'
    err_zopeNotRunning = 'The server is not running'
    err_localpathBlank = 'The "localpath" property must be defined'
    def __init__(self, editor, list, inspector, controllers, menuDefs = []):
        zccMenuDef = [ (-1, '-', None, '-'),
                       (wxID_ZCCSTART, 'Start', self.OnStart, '-'),
                       (wxID_ZCCRESTART, 'Restart', self.OnRestart, '-'),
                       (wxID_ZCCSHUTDOWN, 'Shutdown', self.OnShutdown, '-'),
                       (wxID_ZCCTEST, 'Test', self.OnTest, '-'),
                       (-1, '-', None, '-'),
                       (wxID_ZCCOPENLOG, 'Open Zope log', self.OnOpenZopeLog, '-'),
#                       (wxID_ZACONFZ2, 'Configure z2.py', self.OnConfigureZ2py, '-'),
                       (-1, '-', None, '-'),
                       (wxID_ZCOPENZ2, 'Open z2.py', self.OnOpenZ2, '-'),
                       (wxID_ZCBREAKINTO, 'Break into', self.OnBreakInto, '-'),
                     ]
        ExplorerNodes.CategoryController.__init__(self, editor, list, inspector,
              controllers, menuDefs = menuDefs + zccMenuDef)

    def checkAvailability(self, props, timeout=10, showDlg=true):
        retry = true
        dlg = None
        while retry:
            retry = false
            if showDlg and not dlg:
                dlg = wxProgressDialog('Testing %s'% props['host'],
                               'Checking availability...', 100, self.editor,
                               wxPD_CAN_ABORT | wxPD_APP_MODAL | wxPD_AUTO_HIDE)
            try:
                now = time.time()
                res = ''
                while not timeout or time.time() < now + timeout:
                    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    try:
                        s.connect( (props['host'], props['httpport']) )
                    except socket.error, err:
                        if err[0] == 10061:
                            # not running
                            res = self.err_zopeNotRunning
                            if time.time() > now + timeout and \
                                  wxMessageBox('Keep checking for Zope to become available',
                                  'Retry?', style=wxYES_NO | wxICON_QUESTION) == wxYES:
                                retry = true
                        else:
                            res = 'Socket error: '+err[1]
                    else:
                        res = self.zopeRunning

                    if not timeout:
                        break
            finally:
                if showDlg:
                    dlg.Destroy()

        return res

    def getControlPanel(self, props):
        return getServer('%(host)s:%(httpport)d/Control_Panel' % props,
            props['username'], props['passwd'])

    def callControlPanelMethod(self, props, meth):
        zc = ZopeConnection()
        zc.connect(props['host'], props['httpport'],
                   props['username'], props['passwd'])
        zc.call('/Control_Panel', meth)

    def OnStart(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            props = node.properties
            try: zopeStatupTimeout = props['startuptimeout']
            except KeyError: zopeStatupTimeout = self.zopeStatupTimeout

            if props['servicename']:
                os.system('net start "%s"'%props['servicename'])
                self.checkAvailability(node.properties, zopeStatupTimeout)
            elif props['localpath']:
                if props['localpath'].find(' ') != -1:
                    wxLogError('Localpath property may not contain spaces (use SHORT~1 version if necessary)')
                else:
                    os.system('start %s\\start.bat'%props['localpath'])
                    self.checkAvailability(node.properties, zopeStatupTimeout)
            else:
                wxLogError('Unable to start '+node.treename)

    def OnRestart(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            props = node.properties
            try: zopeStatupTimeout = props['startuptimeout']
            except KeyError: zopeStatupTimeout = self.zopeStatupTimeout
            try:
                self.callControlPanelMethod(node.properties, 'manage_restart')
                resp = self.checkAvailability(node.properties, zopeStatupTimeout)
                if resp == 'The server is available':
                    self.editor.setStatus(resp)
                else:
                    self.editor.setStatus(resp, 'Warning')
            except Exception, error:
                wxLogError('Restart not supported for '+node.treename+'\n'+str(error))

    def OnShutdown(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            self.callControlPanelMethod(node.properties, 'manage_shutdown')

    def OnTest(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            wxLogMessage( '%s : %s' % (node.treename,
                self.checkAvailability(node.properties, 0)))

    def OnOpenZopeLog(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            props = node.properties
            if props['localpath']:
                self.editor.openOrGotoModule(os.path.join(props['localpath'],
                      'var', 'Z2.log'))
            else:
                wxLogError(self.err_localpathBlank)

    def OnConfigureZ2py(self, event):
        # XXX Disabled for now until only useful values are displayed
        # XXX Or until someone complains :)
        node = self.getNodesForSelection(self.list.getMultiSelection())
        if len(node):
            node = node[0]
        else:
            print 'Nothing selected'

        props = node.properties
        if props['localpath']:
            cfgZ2SrcNode = ZopeZ2pySourceBasedPrefColNode('Z2.py',
                  ('*',), props['localpath']+'/z2.py', -1, node)
            cfgZ2SrcNode.open(self.editor)
        else:
            wxLogError(self.err_localpathBlank)

    def OnOpenZ2(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            localpath = node.properties['localpath']
            if localpath:
                self.editor.openOrGotoModule(localpath+'/z2.py')
            else:
                wxLogError(self.err_localpathBlank)

    def breakpointInBackground(self, zc):
        zc.call('zoa', 'breakpoint')

    def OnBreakInto(self, event):
        for node in self.getNodesForSelection(self.list.getMultiSelection()):
            props = node.properties
            zc = ZopeConnection()
            zc.connect(props['host'], props['httpport'],
                       props['username'], props['passwd'])
            start_new_thread(self.breakpointInBackground, (zc,))


# XXX Better field validation and hints as to when path props shound end in / or not !!

class ZopeController(ExplorerNodes.Controller, ExplorerNodes.ClipboardControllerMix):
    inspectBmp = 'Images/Shared/Inspector.png'
    importBmp = 'Images/Shared/ZopeImport.png'
    exportBmp = 'Images/Shared/ZopeExport.png'
    uploadBmp = 'Images/ZOA/upload_doc.png'
    viewInBrowserBmp = 'Images/ZOA/ViewInBrowser.png'
    findBmp = 'Images/Shared/Find.png'
    def __init__(self, editor, list, inspector, controllers):
        ExplorerNodes.ClipboardControllerMix.__init__(self)
        ExplorerNodes.Controller.__init__(self, editor)

        self.list = list
        self.menu = wxMenu()
        self.inspector = inspector

        self.zopeMenuDef = [
            (wxID_ZOPEINSPECT, 'Inspect', self.OnInspectItem, self.inspectBmp),
            (-1, '-', None, '') ] +\
            self.clipMenuDef +\
          [ (-1, '-', None, ''),
            (wxID_ZOPEFIND, 'Find', self.OnFindZopeItems, self.findBmp),
            (-1, '-', None, ''),
            (wxID_ZOPEUPLOAD, 'Upload', self.OnUploadZopeItem, self.uploadBmp),
            (wxID_ZOPEEXPORT, 'Export', self.OnExportZopeItem, self.exportBmp),
            (wxID_ZOPEIMPORT, 'Import', self.OnImportZopeItem, self.importBmp),
            (-1, '-', None, '-'),
            (wxID_ZOPEOPENINEDITOR, 'Open in Editor', self.OnOpenInEditorZopeItem, '-'),
            (wxID_ZOPESECURITY, 'Security', self.OnSecurityZopeItem, '-'),
            (wxID_ZOPEUNDO,     'Undo',     self.OnUndoZopeItem, '-'),
            (-1, '-', None, ''),
            (wxID_ZOPEVIEWBROWSER,'View in browser', self.OnViewInBrowser, self.viewInBrowserBmp),
            (wxID_ZOPEMANAGEBROWSER,'Manage in browser', self.OnManageInBrowser, '-'),
        ]

        self.setupMenu(self.menu, self.list, self.zopeMenuDef)
        self.toolbarMenus = [self.zopeMenuDef]

    def destroy(self):
        ExplorerNodes.ClipboardControllerMix.destroy(self)
        self.zopeMenuDef = ()
        self.toolbarMenus = ()
        self.menu.Destroy()

    def OnExportZopeItem(self, event):
        if self.list.node:
            idxs = self.list.getMultiSelection()
            currPath = '.'
            for idx in idxs:
                item = self.list.items[idx]
                if item:
                    zexp = item.exportObj()

                    from FileDlg import wxFileDialog
                    dlg = wxFileDialog(self.list, 'Save as...', currPath,
                          item.name+'.zexp', '', wxSAVE | wxOVERWRITE_PROMPT)
                    try:
                        if dlg.ShowModal() == wxID_OK:
                            zexpFile = dlg.GetFilePath()
                            open(zexpFile, 'wb').write(zexp)
                            currPath = os.path.dirname(zexpFile)
                    finally:
                        dlg.Destroy()

    def OnImportZopeItem(self, event):
        fls = self.list.node.listImportFiles()

        if fls:
            dlg = wxSingleChoiceDialog(self.list, 'Choose the file to import', 'Import object', fls)
            try:
                if dlg.ShowModal() == wxID_OK:
                    zexp = dlg.GetStringSelection()
                else:
                    return
            finally:
                dlg.Destroy()
        else:
            dlg = wxTextEntryDialog(self.list, 'Enter file to import', 'Import object', '.zexp')
            try:
                if dlg.ShowModal() == wxID_OK:
                    zexp = dlg.GetValue()
                else:
                    return
            finally:
                dlg.Destroy()

        self.list.node.importObj(zexp)
        self.list.refreshCurrent()

    def doInspectZopeItem(self, zopeItem):
        props = zopeItem.properties
        try:
            ZComp = PaletteStore.compInfo[zopeItem.metatype][1]
        except KeyError:
            ZComp = ZopeCompanion

        zc = ZComp(zopeItem.name, zopeItem.resourcepath)
        zc.connect(props['host'], props['httpport'],
                   props['username'], props['passwd'])
        zc.updateZopeProps()

        self.inspector.selectObject(zc, false, focusPage=1)

    def OnInspectItem(self, event):
        if self.list.node:
            # Create new companion for selection
            zopeItem = self.list.getSelection()
            if not zopeItem: zopeItem = self.list.node
            self.doInspectZopeItem(zopeItem)

    def OnUploadZopeItem(self, event):
        if self.list.node:
            idxs = self.list.getMultiSelection()
            currPath = '.'
            for idx in idxs:
                item = self.list.items[idx]
                if item:
                    from FileDlg import wxFileDialog
                    dlg = wxFileDialog(self.list, 'Upload '+item.name, currPath,
                          item.name, '', wxOPEN)
                    try:
                        if dlg.ShowModal() == wxID_OK:
                            try:
                                # XXX Update to handle all transports
                                item.uploadObj(open(dlg.GetFilePath(), 'rb'))#.read())
                            except Client.NotFound:
                                wxMessageBox('Object does not support uploading', 'Error on upload')
                            currPath = dlg.GetDirectory()
                    finally:
                        dlg.Destroy()

    def OnSecurityZopeItem(self, event):
        if self.list.node:
            zopeItem = self.list.getSelection()
            if zopeItem:
                model, cntrlr = self.editor.openOrGotoZopeDocument(zopeItem)
                viewName = ZopeViews.ZopeSecurityView.viewName
                if not model.views.has_key(viewName):
                    resultView = self.editor.addNewView(viewName,
                          ZopeViews.ZopeSecurityView)
                else:
                    resultView = model.views[viewName]
                resultView.refresh()
                resultView.focus()

    def OnUndoZopeItem(self, event):
        if self.list.node:
            zopeItem = self.list.getSelection()
            if zopeItem and ZopeViews.ZopeUndoView in zopeItem.additionalViews:
                model, cntrlr = self.editor.openOrGotoZopeDocument(zopeItem)
                viewName = ZopeViews.ZopeUndoView.viewName
                if not model.views.has_key(viewName):
                    resultView = self.editor.addNewView(viewName,
                          ZopeViews.ZopeUndoView)
                else:
                    resultView = model.views[viewName]
                resultView.refresh()
                resultView.focus()

    def openSelItemInBrowser(self, addToUrl='', zopeItem=None):
        if self.list.node:
            if not zopeItem:
                zopeItem = self.list.getSelection()

            if not zopeItem:
                raise 'No item selected'
            try:
                import webbrowser
                webbrowser.open('http://%s%s'%(zopeItem.buildUrl(), addToUrl))
            except ImportError:
                raise 'Python 2.0 or higher required'

    def OnViewInBrowser(self, event):
        self.openSelItemInBrowser()

    def OnManageInBrowser(self, event):
        self.openSelItemInBrowser('/manage')

    def OnOpenInEditorZopeItem(self, event):
        if self.list.node:
            zopeItem = self.list.getSelection()
            if zopeItem:
                model, cntrlr = self.editor.openOrGotoZopeDocument(zopeItem)

    def OnFindZopeItems(self, event):
        node = self.list.node
        if node:
            from ZopeFindDlg import ZopeFindDlg
            dlg = ZopeFindDlg(self.editor)
            try:
                if dlg.ShowModal() == wxID_OK:
                    res = dlg.objIds.GetValue().split(',') or ()
                    obj_ids = []
                    for zid in res:
                        zid = zid.strip()
                        if zid:
                            obj_ids.append(zid)
                    meta_type = 0
                    search_text = dlg.searchText.GetValue() or 0
                    search_sub = dlg.recurse.GetValue()

                    wxBeginBusyCursor()
                    try:
                        results = node.findItems(obj_ids, meta_type,
                                                 search_text, search_sub)
                    finally:
                        wxEndBusyCursor()

                    bookmarks, category = node.bookmarks, node.category
                    self.list.node = node = ZopeResultsFolderNode(
                          'Zope Find Results', node.resourcepath,
                          node.clipboard, -1, node, node.server, node.root,
                          node.properties, 'Zope Find Results')
                    node.bookmarks = bookmarks
                    node.category = category

                    node.results = results
                    #node.lastSearch = dlg.GetValue()

                    # Collapse possible current contents in tree
                    tree = self.editor.explorer.tree
                    item = tree.GetSelection()
                    tree.CollapseAndReset(item)

                    self.list.refreshCurrent()

            finally:
                dlg.Destroy()


def getServer(url, user, password):
    return xmlrpclib.Server('http://' + url,
              BasicAuthTransport.BasicAuthTransport(user, password) )

class ZopeNode(ZopeItemNode):
    def load(self, mode='rb'):
        return self.getResource().document_src()

    def save(self, filename, data, mode='wb'):
        """ Saves contents of data to Zope """
        self.getResource().manage_upload(data)

    def isFolderish(self):
        return false

class ZopeImageNode(ZopeNode):
    pass

class DirNode(ZopeItemNode): pass

class UserFolderNode(ZopeItemNode):
    def deleteItems(self, names):
        print 'User Folder delete: %s'%names
#        mime, res = self.clipboard.zc.call(self.resourcepath,
#              'manage_delObjects', ids = names)

class ZopeUserNode(ZopeNode):
    def isFolderish(self):
        return false
    def open(self, editor):
        print 'Should inspect'
        #editor.openOrGotoZopeDocument(self)

class ControlNode(DirNode):
    def checkentry(self, id, entry, path):
        if entry == 'Product Management':
            childnode = PMNode(id, path, self.clipboard, -1, self,
                self.server, self.root, self.properties, entry)
        else:
            childnode = DirNode.checkentry(self, id, entry, path)
        return childnode

class PMNode(ControlNode):
    def checkentry(self,id,entry,path):
        if entry == 'Product' :
            childnode = ProductNode(id, path, self.clipboard, -1, self,
            self.server, self.root, self.properties, entry)
        else:
            childnode = ControlNode.checkentry(self, id, entry, path)
        return childnode

class ProductNode(DirNode):
    def checkentry(self, id, entry, path):
        if entry == 'Z Class' :
            childnode = ZClassNode(id, path, self.clipboard, -1, self,
                self.server, self.root, self.properties, entry)
        else:
            childnode = DirNode.checkentry(self, id, entry, path)
        return childnode

class ZClassNode(DirNode):
    itemsSubPath = '/propertysheets/methods'
##    def __init__(self, name, resourcepath, clipboard, imgIdx, parent, xmlrpcsvr, root, properties, metatype):
##        resourcepath = resourcepath + '/propertysheets/methods'
##        DirNode.__init__(self, name, resourcepath, clipboard, imgIdx, parent,
##              xmlrpcsvr, root, properties, metatype)

# Thanks to M. Adam Kendall (akendall) for fixing save
class ZSQLNode(ZopeNode):
    Model = ZopeEditorModels.ZopeSQLMethodModel
    defaultViews = (ZopeViews.ZopeHTMLSourceView,)
    additionalViews = (ZopeViews.ZopeSecurityView, ZopeViews.ZopeUndoView)
    def getParams(self, data):
        return data.split('<params>')[1].split('</params>')[0]
    def getBody(self, data):
        return data.split('</params>')[1].lstrip()
    def save(self, filename, data, mode='wb'):
        """ Saves contents of data to Zope """
        props = self.getResource().zoa.props.SQLMethod()
        try:
            title = props['title']
            connection_id = props['connection_id']
            arguments = self.getParams(data)
            template = self.getBody(data)
            self.getResource().manage_edit(title, connection_id, arguments, template)
        except xmlrpclib.ProtocolError, error:
            # Getting a zero content warning is not an error
            if error.errcode != 204:
                raise ExplorerNodes.TransportSaveError(error, filename)
        except Exception, error:
            raise ExplorerNodes.TransportSaveError(error, filename)

class PythonNode(ZopeNode):
    Model = ZopeEditorModels.ZopePythonScriptModel
    defaultViews = (Views.PySourceView.PythonSourceView,)
    additionalViews = (ZopeViews.ZopeSecurityView, Views.EditorViews.ToDoView,
                       ZopeViews.ZopeUndoView)

    def getParams(self, data):
        return data[data.find('(')+1 : data.find('):')]

    def getBody(self, data):
        tmp = data[data.find(':')+2:].split('\n')
        tmp2 = []
        for l in tmp:
            # Handle comments which may be indented less
            if l[:4].strip():
                l = l.lstrip()
            else:
                l = l[4:]
            tmp2.append(l)
        return '\n'.join(tmp2)

    def save(self, filename, data, mode='wb'):
        self.getResource().manage_edit(self.name, self.getParams(data),
              self.getBody(data))

class PythonScriptNode(PythonNode):
    additionalViews = (ZopeViews.ZopeSecurityView,
          ZopeViews.ZopeUndoView, Views.EditorViews.ToDoView)

    def preparedata(self, data):
        tmp=data.split('\n')
        tmp2=[]
        h=1
        for l in tmp:
            if l[:2] <> '##' :
                h=0
            if l[:12] == '##parameters':
                params = l[l.find('=')+1:]
            if h:
                pass # perhaps we need this anytime
            else:
                tmp2.append('    ' + l)
        return 'def %s(%s):\n%s' % (self.name, params, '\n'.join(tmp2))

    def load(self, mode='rb'):
        data = PythonNode.load(self, mode)
        return self.preparedata(data)

    def save(self, filename, data, mode='wb'):
        self.getResource().ZPythonScriptHTML_editAction('fake',
              self.name, self.getParams(data), self.getBody(data))

class ExtPythonNode(PythonNode):
    Model = ZopeEditorModels.ZopeExternalMethodModel
    defaultViews = (Views.PySourceView.PythonSourceView,
                    Views.EditorViews.ExploreView)
    additionalViews = (Views.EditorViews.HierarchyView,
                       Views.EditorViews.ModuleDocView,
                       ZopeViews.ZopeSecurityView, Views.EditorViews.ToDoView,
                       ZopeViews.ZopeUndoView)


    def openTransportFromProperties(self):
        zopePath = self.properties['localpath']

        svr, name = self.getParentResource()
        res = svr.zoa.props.ExternalMethod(name)

        module = res['module']

        emf = ExtMethDlg.ExternalMethodFinder(zopePath)
        extPath = emf.getExtPath(module)

        from Explorers import Explorer
        return Explorer.openEx(extPath)

    def load(self, mode='rb'):
        return self.openTransportFromProperties().load(mode=mode)

    def save(self, filename, data, mode='wb'):
        transp = self.openTransportFromProperties()
        transp.save(transp.currentFilename(), data, mode)

class DTMLDocNode(ZopeNode):
    Model = ZopeEditorModels.ZopeDTMLDocumentModel
    defaultViews = (ZopeViews.ZopeHTMLSourceView,)
    additionalViews = (ZopeViews.ZopeUndoView,
          ZopeViews.ZopeSecurityView, ZopeViews.ZopeHTMLView,)

class DTMLMethodNode(ZopeNode):
    Model = ZopeEditorModels.ZopeDTMLMethodModel
    defaultViews = (ZopeViews.ZopeHTMLSourceView,)
    additionalViews = (ZopeViews.ZopeUndoView,
          ZopeViews.ZopeSecurityView, ZopeViews.ZopeHTMLView)

class SiteErrorLogNode(ZopeItemNode):
    Model = ZopeEditorModels.ZopeSiteErrorLogModel
    defaultViews = (ZopeViews.ZopeSiteErrorLogView,)
    additionalViews = (ZopeViews.ZopeUndoView,
                       ZopeViews.ZopeSecurityView)
    def isFolderish(self):
        return false

class HelpTopicNode(ZopeItemNode):
    Model = ZopeEditorModels.ZopeHelpTopicModel
    defaultViews = (ZopeViews.ZopeHTMLView,)
    additionalViews = ()
    def isFolderish(self):
        return false

from Explorers.PrefsExplorer import SourceBasedPrefColNode
class ZopeZ2pySourceBasedPrefColNode(SourceBasedPrefColNode):
    pass

class ZopeResultsFolderNode(ZopeItemNode):
    results = []

    def createChildNode(self, metatype, id, respath, label):
        item = ZopeItemNode.createChildNode(self, metatype, id, respath)
        item.name = item.treename = label
        return item

    def openList(self):
        self.parentOpensChildren = true
        entries = []

        for zmeta, zid in self.results:
            zpath = os.path.dirname(self.resourcepath+'/'+zid)
            name = os.path.basename(zid)
            node = self.createChildNode(zmeta, name, zpath, '%s (%s)'%(name, zpath))
            if node:# and not node.isFolderish():
                entries.append(node)

        self.entries = entries
        return self.entries

    def openParent(self, editor):
        editor.explorer.tree.SelectItem(editor.explorer.tree.GetSelection())
        return true

    def open(self, node, editor):
        # recreate with proper name
        node = node.getNodeFromPath(node.resourcepath, node.metatype)
        return node.open(editor)

    def getTitle(self):
        return 'Zope Find Results'

def findBetween(strg, startMarker, endMarker):
    strL = strg.lower()
    found = ''
    idx = strL.find(startMarker)
    idx2 = -1
    if idx != -1:
        idx2 = strL.find(endMarker, idx)
        if idx2 != -1:
            found = strg[idx + len(startMarker)+1: idx2]
    return idx, idx2, found

class ZopeError(Exception):
    def __init__(self, htmlFaultStr):
        self.htmlFault = htmlFaultStr

        # find possible traceback
        idx, idx2, tracebk = findBetween(htmlFaultStr, '<pre>', '</pre>')
        if tracebk:
            tracebk = 'Traceback:\n'+tracebk
        self.traceback = tracebk

        txt = Utils.html2txt(htmlFaultStr)
        self.textFault = '%s\n%s\n' % (txt, tracebk)

        idx, idx1, self.ErrorType = findBetween(txt, 'Error Type:', '\n')
        idx, idx1, self.ErrorValue = findBetween(txt, 'Error Value:', '\n')

    def __str__(self):
        return self.ErrorType and ('%s:%s' % (self.ErrorType, self.ErrorValue)) or self.textFault


def zopeHtmlErr2Strs(faultStr):
    sFaultStr = str(faultStr)
    fs = sFaultStr.lower()
    idx = fs.find('<pre>')
    traceBk = ''
    if idx != -1:
        idx2 = fs.find('</pre>', idx)
        if idx2 != -1:
            traceBk = '\nTraceback:\n'+sFaultStr[idx + 5: idx2]
    txt = Utils.html2txt(sFaultStr)
    return txt+traceBk

def uriSplitZope(filename, filepath):
    # zope://[category]/<[meta type]>/[path] format
    segs = filepath.split('/')
    if len(segs) < 2:
        raise ExplorerNodes.TransportCategoryError(
              'Category not found', filepath)
    category = segs[0]+'|'+segs[1][1:-1]
    return 'zope', category, '/'.join(segs[2:]), filename

def uriSplitZopeDebug(filename, filepath):
    # zopedebug://[host[:port]]/[path]/[meta type]
    # magically maps zopedebug urls to Boa zope uris
    segs = filepath.split('/')
    if len(segs) < 3:
        raise ExplorerNodes.TransportCategoryError(
              'Zope debug path invalid', filepath)
    host, filepaths, meta = segs[0], segs[1:-1], segs[-1]
    try:               host, port = host.split(':')
    except ValueError: port = 80
    else:              port = int(port)
    # try to find category that can open this url
    for cat in ExplorerNodes.all_transports.entries:
        if cat.itemProtocol == 'zope':
            itms = cat.openList()
            for itm in itms:
                props = itm.properties
                # @@@ Gogo.
                if socket.gethostbyname(props['host']) == socket.gethostbyname(host) and \
                       props['httpport'] == port:
                # @@@ original code follows:
                # if props['host'].lower() == host.lower() and \
                #       props['httpport'] == port:
                    path = '/'.join(filepaths)
                    name = itm.name or itm.treename
                    return 'zope', '%s|%s' %(name, meta), path, \
                           'zope://%s/<%s>/%s'%(name, meta, path)

    raise ExplorerNodes.TransportCategoryError(\
          'Could not map Zope debug path to defined Zope Category item',
          filepath)

def findZopeExplorerNode(catandmeta, respath, transports):
    category, metatype = catandmeta.split('|')
    for cat in transports.entries:
        if hasattr(cat, 'itemProtocol') and cat.itemProtocol == 'zope':
            itms = cat.openList()
            for itm in itms:
                if itm.name == category or itm.treename == category:
                    return itm.getNodeFromPath('/'+respath, metatype)
    raise ExplorerNodes.TransportError(
          'Zope transport could not be found: %s || %s'%(category, respath))

#-------------------------------------------------------------------------------
# maps meta types to ExplorerNodes
zopeClassMap = { 'Folder': DirNode,
        'Product Help': DirNode,
        'Z Class': ZClassNode,
        'User Folder': UserFolderNode,
        'Control Panel': ControlNode,
        'Z SQL Method': ZSQLNode,
        'DTML Document': DTMLDocNode,
        'DTML Method': DTMLMethodNode,
        'Python Method': PythonNode,
        'External Method': ExtPythonNode,
        'Script (Python)': PythonScriptNode,
        'Image': ZopeImageNode,
        'File': ZopeNode,
        'User': ZopeUserNode,
        'Site Error Log': SiteErrorLogNode,
        'Help Topic': HelpTopicNode,
       }

ExplorerNodes.register(ZopeCatNode, controller=ZopeCatController)
ExplorerNodes.register(ZopeItemNode, clipboard='global',
  confdef=('explorer', 'zope'), controller=ZopeController, category=ZopeCatNode)
ExplorerNodes.uriSplitReg[('zope', 2)] = uriSplitZope
ExplorerNodes.uriSplitReg[('zopedebug', 2)] = uriSplitZopeDebug
ExplorerNodes.transportFindReg['zope'] = findZopeExplorerNode