class AbstractTool(QtCore.QObject):

    def __init__(self):
        super(AbstractTool, self).__init__()

    def id(self):
        raise NotImplementedError()

    def title(self):
        return "Untitled Tool"

    def relatedToolIds(self):
        return []

    def connectRelatedTool(self, relatedTool):
        pass

    def setRepo(self, repo):
        pass

    def setUser(self, user):
        pass

    def storeCurrentState(self):
        pass

    def restoreRecentState(self):
        pass

    def connectActionsWithActionHandlers(self):
        pass

    def createGui(self, guiParent):
        pass

    gui = property(fget=None, fset=None)


    def buildGuiMainMenu(self):
        self.gui.buildActions()
        self.gui.buildMainMenu()
        self.connectActionsWithActionHandlers()
        return self.gui.mainMenu

    def enable(self):
        pass

    def disable(self):
        pass

    def toggleEnableDisable(self, enable):
        if enable:
            self.enable()
        else:
            self.disable()


    def handlerSignals(self):
        return [] class FileBrowserGui(ToolGui):

    def __init__(self, parent, fileBrowserTool):
        super(FileBrowserGui, self).__init__(parent)
        self.ui = Ui_FileBrowserGui()
        self.ui.setupUi(self)

        self.__fileBrowserTool = fileBrowserTool
        self.__tableModel = None
        self.__proxyModel = None

        self.ui.filesTableView = UnivTableView(self)
        self.ui.tableViewContainer.addWidget(self.ui.filesTableView)

        self.connect(self.ui.filesTableView,
                     QtCore.SIGNAL("activated(const QModelIndex&)"),
                     self.__onTableCellActivated)

        self.resetTableModel(mutex=None)

        self.__context_menu = None
        self.__initContextMenu()

        self.__prevSelRows = []  # This is a stack of row indices


    def resetTableModel(self, mutex):
        self.__tableModel = FileBrowserTableModel(self, self.__fileBrowserTool, mutex)
        self.__tableModel.setObjs(self.__fileBrowserTool.listDir())

        self.__proxyModel = FileBrowserSortProxyModel(self)
        self.__proxyModel.setSourceModel(self.__tableModel)
        self.__proxyModel.setDynamicSortFilter(True)

        self.ui.filesTableView.setModel(self.__proxyModel)
        self.ui.filesTableView.setSortingEnabled(True)
        self.ui.filesTableView.resizeRowsToContents()

        if self.__fileBrowserTool.repo is not None:
            relCurrDir = os.path.relpath(self.__fileBrowserTool.currDir, self.__fileBrowserTool.repo.base_path)
            self.ui.currDirLineEdit.setText(relCurrDir)
            self.ui.currDirLineEdit.setToolTip(self.__fileBrowserTool.currDir)
        else:
            self.ui.currDirLineEdit.setText("")
            self.ui.currDirLineEdit.setToolTip("")


    def __initContextMenu(self):
        self.buildActions()
        self.__buildContextMenu()
        self.__addContextMenu()


    def __addContextMenu(self):
        assert self.__context_menu is not None, "Context menu is not built"
        self.ui.filesTableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self.ui.filesTableView,
                     QtCore.SIGNAL("customContextMenuRequested(const QPoint &)"),
                     self.showContextMenu)


    def showContextMenu(self, pos):
        self.__context_menu.exec_(self.ui.filesTableView.mapToGlobal(pos))



    def __onTableCellActivated(self, index):
        try:
            sourceIndex = self.__proxyModel.mapToSource(index)
            filename = self.__tableModel.objAtRow(sourceIndex.row()).fileBaseName
            absFilename = os.path.join(self.__fileBrowserTool.currDir, filename)
            if os.path.isdir(absFilename):
                self.__fileBrowserTool.changeRelDir(filename)
                self.__handleCurrentlySelectedRow(isGoingUp=(filename == ".."),
                                                  newRow=index.row())
            else:
                self.actions['openFile'].trigger()
        except Exception as ex:
            logger.debug("Cannot change current directory: " + str(ex))


    def event(self, e):
        #print(e.__class__.__name__ + " " + str(e.type()))
        if isinstance(e, QtGui.QKeyEvent):
            if e.key() == Qt.Key_Home:
                self.ui.filesTableView.selectRow(0)
                e.accept()
                return True

            elif e.key() == Qt.Key_End:
                rowCount = self.ui.filesTableView.model().rowCount()
                self.ui.filesTableView.selectRow(rowCount - 1)
                e.accept()
                return True
        return super(FileBrowserGui, self).event(e)


    def __handleCurrentlySelectedRow(self, isGoingUp, newRow):
        if isGoingUp:
            assert len(self.__prevSelRows) > 0
            row = self.__prevSelRows.pop()
            self.ui.filesTableView.selectRow(row)
        else:
            self.ui.filesTableView.selectRow(0)
            self.__prevSelRows.append(newRow)

    # NOTE: topSourceModelRow, bottomSourceModelRow are NOT visible rows
    def resetTableRows(self, topSourceModelRow, bottomSourceModelRow):
        self.__tableModel.resetRowRange(topSourceModelRow, bottomSourceModelRow)

        rowsToResize = []
        for srcRow in range(topSourceModelRow, bottomSourceModelRow+1):
            proxyIndex = self.__proxyModel.mapFromSource(self.__tableModel.createIndex(srcRow, 0))
            rowsToResize.append(proxyIndex.row())
        for row in rowsToResize:
            self.ui.filesTableView.resizeRowToContents(row)

    def buildActions(self):
        if len(self.actions) > 0:
            logger.info("Actions already built")
            return

        self.actions['openFile'] = self._createAction(self.tr("Open"))
        self.actions['addFiles'] = self._createAction(self.tr("Add"))
        self.actions['editItems'] = self._createAction(self.tr("Edit"))
        self.actions['moveFiles'] = self._createAction(self.tr("Move"))
        self.actions['renameFile'] = self._createAction(self.tr("Rename"))
        self.actions['deleteFiles'] = self._createAction(self.tr("Delete"))


    def __buildContextMenu(self):
        if self.__context_menu is not None:
            logger.info("Context menu of this Tool already built")
            return

        self.__context_menu = self._createMenu(menuTitle=None, menuParent=self)
        menu = self.__context_menu

        menu.addAction(self.actions['openFile'])
        menu.addAction(self.actions['addFiles'])
        menu.addAction(self.actions['editItems'])
        menu.addAction(self.actions['moveFiles'])
        menu.addAction(self.actions['renameFile'])
        menu.addAction(self.actions['deleteFiles'])


    def selectedItemIds(self):
        #We use set, because selectedIndexes() may return duplicates
        fileTableRows = set()
        for index in self.ui.filesTableView.selectionModel().selectedIndexes():
            sourceIndex = self.__proxyModel.mapToSource(index)
            fileTableRows.add(sourceIndex.row())

        itemIds = []
        for row in fileTableRows:
            finfo = self.__fileBrowserTool.listDir()[row]
            if finfo.type == FileInfo.FILE:
                for itemId in finfo.itemIds:
                    itemIds.append(itemId)
            elif finfo.type == FileInfo.DIR:
                assert os.path.isabs(finfo.path)
                itemIds = itemIds + self.__fileBrowserTool.itemIdsForDir(finfo.path)
        return itemIds


    def selectedFiles(self):
        '''
            Returns a list of selected files (abs paths?..).
        '''
        #We use set, because selectedIndexes() may return duplicates
        result = set()
        for index in self.ui.filesTableView.selectionModel().selectedIndexes():
            sourceIndex = self.__proxyModel.mapToSource(index)
            row = sourceIndex.row()
            finfo = self.__fileBrowserTool.listDir()[row]
            result.add(finfo.path)
        return list(result)

    def restoreColumnsWidth(self):
        self.ui.filesTableView.restoreColumnsWidth("file_browser")

    def restoreColumnsVisibility(self):
        self.ui.filesTableView.restoreColumnsVisibility("file_browser")

    def saveColumnsWidth(self):
        self.ui.filesTableView.saveColumnsWidth("file_browser")

    def saveColumnsVisibility(self):
        self.ui.filesTableView.saveColumnsVisibility("file_browser")
 class NoneError(Exception):
    def __init__(self, msg=None):
        super(NoneError, self).__init__(msg) class NotExistError(Exception):
    def __init__(self, msg=None, cause=None):
        super(NotExistError, self).__init__(msg)
        self.cause = cause class CurrentRepoIsNoneError(Exception):
    def __init__(self, msg=None, cause=None):
        super(CurrentRepoIsNoneError, self).__init__(msg)
        self.cause = cause class CurrentUserIsNoneError(Exception):
    def __init__(self, msg=None, cause=None):
        super(CurrentUserIsNoneError, self).__init__(msg)
        self.cause = cause class FileInfo(object):

    UNTRACKED = "UNTRACKED"
    STORED = "STORED"

    FILE = "FILE"
    DIR = "DIRECTORY"
    OTHER = "OTHER"

    def __init__(self, path, objType=None, status=None):
        assert not hlp.is_none_or_empty(path)
        assert objType is not None or status is not None

        #remove trailing slashes in the path
        while path.endswith(os.sep):
            path = path[0:-1]

        self.path = path
        self.status = status
        self.type = objType
        self.tags = []
        self.fields = []
        self.itemIds = []

    @property
    def fileBaseName(self):
        return os.path.basename(self.path)

    def __str__(self):
        return self.fileBaseName()

    def isDir(self):
        return self.type == FileInfo.DIR class GetFileInfoCommand(AbstractCommand):
    def __init__(self, relPath):
        self.__relPath = relPath

    def _execute(self, uow):
        self._session = uow.session
        return self.__getFileInfo(self.__relPath)

    def __getFileInfo(self, path):
        try:
            data_ref = self._session.query(db.DataRef)\
                .filter(db.DataRef.url_raw==hlp.to_db_format(path))\
                .options(joinedload_all("items"))\
                .options(joinedload_all("items.item_tags.tag"))\
                .options(joinedload_all("items.item_fields.field"))\
                .one()
            self._session.expunge(data_ref)
            finfo = FileInfo(data_ref.url, objType=FileInfo.FILE, status=FileInfo.STORED)

            for item in data_ref.items:
                finfo.itemIds.append(item.id)
                for item_tag in item.item_tags:
                    finfo.tags.append(item_tag.tag.name)
                for item_field in item.item_fields:
                    finfo.fields.append((item_field.field.name, item_field.field_value))
            return finfo

        except NoResultFound:
            return FileInfo(self.__relPath, status=FileInfo.UNTRACKED) class GetItemIdsWithFilesFrom(AbstractCommand):
    def __init__(self, dirRelPath):
        self._dirRelPath = dirRelPath

    def _execute(self, uow):
        items = []
        try:
            items = uow._session.query(db.Item) \
                .join(db.Item.data_ref) \
                .filter(db.DataRef.url_raw.like(hlp.to_db_format(self._dirRelPath) + "/%")) \
                .all()
        finally:
            uow.close()
        return [item.id for item in items] class ActionHandlerStorage():
    def __init__(self, widgetsUpdateManager):
        self.__actions = dict()
        self.__widgetsUpdateManager = widgetsUpdateManager

    def register(self, qAction, actionHandler):
        assert not (qAction in self.__actions), "Given qAction already registered"

        QtCore.QObject.connect(qAction, QtCore.SIGNAL("triggered()"), actionHandler.handle)
        actionHandler.connectSignals(self.__widgetsUpdateManager)

        self.__actions[qAction] = actionHandler

    def unregister(self, qAction):
        assert qAction in self.__actions, "qAction must be registered before"
        actionHandler = self.__actions[qAction]

        actionHandler.disconnectSignals(self.__widgetsUpdateManager)
        QtCore.QObject.disconnect(qAction, QtCore.SIGNAL("triggered()"), actionHandler.handle)

        del self.__actions[qAction]

    def unregisterAll(self):
        for qAction, actionHandler in self.__actions.items():
            actionHandler.disconnectSignals(self.__widgetsUpdateManager)
            QtCore.QObject.disconnect(qAction, QtCore.SIGNAL("triggered()"), actionHandler.handle)

        self.__actions.clear() class HandlerSignals():
    '''Named constants in this class is not the signal names, but the signal types.
    They are arguments of the following signals: handlerSignal, handlerSignals.
    handlerSignal accepts single signal type. handlerSignals accepts a list of
    signal types. See also WidgetsUpdateManager, ActionHandlerStorage and
    AbstractActionHandler classes.
    '''

    ITEM_CREATED = "itemCreated"
    ITEM_CHANGED = "itemChanged"
    ITEM_DELETED = "itemDeleted"
    LIST_OF_FAVORITE_REPOS_CHANGED = "listOfFavoriteReposChanged"
    STATUS_BAR_MESSAGE = "statusBarMessage"
    RESET_SINGLE_ROW = "resetSingleRow"     # TODO: This signal type should be hidden in ItemsTableTool, because it's not global
    RESET_ROW_RANGE = "resetRowRange"       # TODO: This signal type should be hidden in ItemsTableTool too
    REGGATA_CONF_CHANGED = "reggata.conf file has changed"


    @staticmethod
    def allPossibleSignals():
        return [HandlerSignals.ITEM_CREATED,
                HandlerSignals.ITEM_CHANGED,
                HandlerSignals.ITEM_DELETED,
                HandlerSignals.LIST_OF_FAVORITE_REPOS_CHANGED,
                HandlerSignals.STATUS_BAR_MESSAGE,
                HandlerSignals.RESET_SINGLE_ROW,
                HandlerSignals.RESET_ROW_RANGE,
                HandlerSignals.REGGATA_CONF_CHANGED,
                ] class DropFilesDialogsFacade(AbstractDialogsFacade):

    def __init__(self, userDialogs):
        self.__selectedFiles = []
        self.__userDialogs = userDialogs


    def setSelectedFiles(self, selectedFiles):
        self.__selectedFiles = selectedFiles


    def execItemDialog(self, item, gui, repo, dialogMode):
        return self.__userDialogs.execItemDialog(item, gui, repo, dialogMode)


    def execItemsDialog(self, items, gui, repo, dialogMode, sameDstPath):
        return self.__userDialogs.execItemsDialog(items, gui, repo, dialogMode, sameDstPath)


    def startThreadWithWaitDialog(self, thread, gui, indeterminate):
        wd = WaitDialog(gui, indeterminate)
        wd.connect(thread, QtCore.SIGNAL("finished"), wd.reject)
        wd.connect(thread, QtCore.SIGNAL("exception"), wd.exception)
        wd.connect(thread, QtCore.SIGNAL("progress"), wd.set_progress)
        wd.startWithWorkerThread(thread)


    def getOpenFileName(self, gui, textMessageForUser):
        if (len(self.__selectedFiles) == 0):
            return None

        fileName = self.__selectedFiles[0]
        if os.path.exists(fileName) and os.path.isfile(fileName):
            return fileName

        return None


    def getOpenFileNames(self, gui, textMessageForUser):
        if (len(self.__selectedFiles) == 0):
            return []

        fileNames = []
        for path in self.__selectedFiles:
            if os.path.exists(path) and os.path.isfile(path):
                fileNames.append(path)

        return fileNames


    def getExistingDirectory(self, gui, textMessageForUser):
        if (len(self.__selectedFiles) == 0):
            return None

        for path in self.__selectedFiles:
            if os.path.exists(path) and os.path.isdir(path):
                return path

        return None


    def getOpenFilesAndDirs(self, gui, textMessageForUser):
        if (len(self.__selectedFiles) == 0):
            return []

        result = []
        for path in self.__selectedFiles:
            if os.path.exists(path) and (os.path.isfile(path) or os.path.isdir(path)):
                result.append(path)

        return result class AddFilesToRepoActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(AddFilesToRepoActionHandler, self).__init__(tool)
        self._dialogs = dialogs
        self._itemsCreatedCount = 0
        self._filesSkippedCount = 0
        self.lastSavedItemIds = []

    def handle(self):
        logger.info("AddFilesToRepoActionHandler.handle invoked")
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            files = self._tool.gui.selectedFiles()
            if len(files) == 0:
                raise err.MsgException(self.tr("There are no selected files."))

            (self._itemsCreatedCount, self._filesSkippedCount, self.lastSavedItemIds) = \
                AddItemAlgorithms.addItems(self._tool, self._dialogs, files)

            self._emitHandlerSignal(HandlerSignals.ITEM_CREATED)

            stats.sendEvent("file_browser.add_files")

        except err.CancelOperationError:
            return
        except Exception as ex:
            show_exc_info(self._tool.gui, ex)
        finally:
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation completed. Added {}, skipped {} files.")
                    .format(self._itemsCreatedCount, self._filesSkippedCount)) class OpenFileActionHandler(AbstractActionHandler):
    def __init__(self, tool, extAppMgr):
        super(OpenFileActionHandler, self).__init__(tool)
        self._extAppMgr = extAppMgr

    def handle(self):
        logger.info("OpenFileActionHandler.handle invoked")
        try:
            selFiles = self._tool.gui.selectedFiles()
            if len(selFiles) != 1:
                raise err.MsgException(self.tr("Select one file, please."))

            selFile = selFiles[0]
            assert os.path.isabs(selFiles[0]), "Path '{}' should be absolute".format(selFile)

            if os.path.isdir(selFile):
                self._extAppMgr.openContainingDirWithExtAppManager(selFile)
            elif os.path.isfile(selFile):
                self._extAppMgr.openFileWithExtApp(selFile)
            else:
                raise err.MsgException(self.tr("This is not a file and not a directory, cannot open it."))

            stats.sendEvent("file_browser.open_file")

        except Exception as ex:
            show_exc_info(self._tool.gui, ex)

        else:
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Done."), consts.STATUSBAR_TIMEOUT) class MoveFilesActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(MoveFilesActionHandler, self).__init__(tool)
        self._dialogs = dialogs

    def handle(self):
        logger.info("MoveFilesActionHandler.handle invoked")
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            selFilesAndDirs = self._tool.gui.selectedFiles()
            selFilesAndDirs = MoveFilesActionHandler.__filterSelectedFilesDirs(selFilesAndDirs)
            selFileAbsPaths = self.__getListOfAllAffectedFiles(selFilesAndDirs)

            if len(selFileAbsPaths) == 0:
                return

            dstDir = self._dialogs.getExistingDirectory(self._tool.gui, self.tr("Select destination directory"))
            if not dstDir:
                return

            if not hlp.is_internal(dstDir, self._tool.repo.base_path):
                raise err.WrongValueError(self.tr("Selected directory should be within the current repo"))

            dstFileAbsPaths = []
            currDir = self._tool.currDir
            for absFile in selFileAbsPaths:
                relFile = os.path.relpath(absFile, currDir)
                dstAbsFile = os.path.join(dstDir, relFile)
                dstFileAbsPaths.append((absFile, dstAbsFile))

            thread = MoveFilesThread(self._tool.gui, self._tool.repo, dstFileAbsPaths, selFilesAndDirs)
            self._dialogs.startThreadWithWaitDialog(thread, self._tool.gui, indeterminate=False)

            if thread.errors > 0:
                self._dialogs.execMessageBox(self._tool.gui,
                                             text="There were {} errors.".format(thread.errors),
                                             detailedText=thread.detailed_message)
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE, self.tr("Done."),
                                consts.STATUSBAR_TIMEOUT)
            self._emitHandlerSignal(HandlerSignals.ITEM_CHANGED)

            stats.sendEvent("file_browser.move_files")

        except Exception as ex:
            show_exc_info(self._tool.gui, ex)



    @staticmethod
    def __filterSelectedFilesDirs(selFiles):
        result = []
        for selFile in selFiles:
            if selFile == ".":
                continue
            elif selFile == "..":
                continue
            else:
                result.append(selFile)
        return result

    def __getListOfAllAffectedFiles(self, selFiles):
        paths = []
        for selFile in selFiles:
            if os.path.isdir(selFile):
                for root, _subdirs, files,  in os.walk(selFile):
                    for file in files:
                        fileAbsPath = os.path.join(root, file)
                        paths.append(fileAbsPath)
            elif os.path.isfile(selFile):
                paths.append(selFile)
            else:
                pass
        return paths class RenameFileActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(RenameFileActionHandler, self).__init__(tool)
        self._dialogs = dialogs

    def handle(self):
        logger.info("RenameFileActionHandler.handle invoked")
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            selFiles = self._tool.gui.selectedFiles()
            if len(selFiles) != 1:
                raise err.MsgException(self.tr("Please select one file or directory"))

            selFile = selFiles[0]

            newName, isOk = self._dialogs.execGetTextDialog(
                self._tool.gui, self.tr("Rename File"), self.tr("Enter new file name:"),
                os.path.basename(selFile))

            if not isOk or newName == os.path.basename(selFile):
                return

            if is_none_or_empty(newName.strip()):
                raise err.MsgException(self.tr("Wrong input, file name cannot be empty."))

            if os.path.isdir(selFile):
                self.__renameDir(selFile, newName)
            elif os.path.isfile(selFile):
                self.__renameFile(selFile, newName)
            else:
                raise err.MsgException(self.tr(
                    "Cannot rename '{}' because it is not a file or directory."
                    .format(selFile)))

            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE, self.tr("Done."),
                                    consts.STATUSBAR_TIMEOUT)
            self._emitHandlerSignal(HandlerSignals.ITEM_CHANGED)

            stats.sendEvent("file_browser.rename_file")

        except Exception as ex:
            show_exc_info(self._tool.gui, ex)

    def __renameDir(self, dirAbsPath, newDirName):
        uow = self._tool.repo.createUnitOfWork()
        try:
            uow.executeCommand(cmds.RenameDirectoryCommand(dirAbsPath, newDirName))
        finally:
            uow.close()

    def __renameFile(self, fileAbsPath, newFileName):
        uow = self._tool.repo.createUnitOfWork()
        try:
            uow.executeCommand(cmds.RenameFileCommand(fileAbsPath, newFileName))
        finally:
            uow.close() class DeleteFilesActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(DeleteFilesActionHandler, self).__init__(tool)
        self._dialogs = dialogs

    def handle(self):
        logger.info("DeleteFilesActionHandler.handle invoked")
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            selFilesAndDirs = self._tool.gui.selectedFiles()
            selFilesAndDirs = DeleteFilesActionHandler.__filterSelectedFilesDirs(selFilesAndDirs)
            selFileAbsPaths = self.__getListOfAllAffectedFiles(selFilesAndDirs)

            thread = DeleteFilesThread(self._tool.gui, self._tool.repo, selFileAbsPaths, selFilesAndDirs)
            self._dialogs.startThreadWithWaitDialog(thread, self._tool.gui, indeterminate=False)

            if thread.errors > 0:
                self._dialogs.execMessageBox(self._tool.gui,
                                             text="There were {} errors.".format(thread.errors),
                                             detailedText=thread.detailed_message)
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE, self.tr("Done."),
                                    consts.STATUSBAR_TIMEOUT)
            self._emitHandlerSignal(HandlerSignals.ITEM_CHANGED)

            stats.sendEvent("file_browser.delete_files")

        except Exception as ex:
            show_exc_info(self._tool.gui, ex)

    @staticmethod
    def __filterSelectedFilesDirs(selFiles):
        result = []
        for selFile in selFiles:
            if selFile == ".":
                continue
            elif selFile == "..":
                continue
            else:
                result.append(selFile)
        return result

    def __getListOfAllAffectedFiles(self, selFiles):
        paths = []
        for selFile in selFiles:
            if os.path.isdir(selFile):
                for root, _subdirs, files,  in os.walk(selFile):
                    for file in files:
                        fileAbsPath = os.path.join(root, file)
                        paths.append(fileAbsPath)
            elif os.path.isfile(selFile):
                paths.append(selFile)
            else:
                pass
        return paths class EditItemsActionHandlerFileBrowser(EditItemsActionHandler):
    def __init__(self, tool, dialogs):
        super(EditItemsActionHandlerFileBrowser, self).__init__(tool, dialogs)

    def handle(self):
        super(EditItemsActionHandlerFileBrowser, self).handle()
        stats.sendEvent("file_browser.edit_items") class ExtAppMgr(QtCore.QObject):
    '''
        This class invokes preferred external applications to open repository files.
    Preferred applications are configured in text file reggata.conf.
    '''

    def __init__(self, parent=None):
        super(ExtAppMgr, self).__init__(parent)

        self.updateState()


    def updateState(self):
        self.__state = ExtAppMgr.readCurrentState()

        # Key - file extension (in lowercase), Value - external app executable
        extentionsDict = dict()
        for appDescription in self.__state.appDescriptions:
            for ext in appDescription.fileExtentions:
                ext = ext.lower()

                if ext in extentionsDict.keys():
                    msg = QCoreApplication.translate("ext_app_mgr",
                        "File extension {} cannot be in more than one file_type group.",
                        None, QCoreApplication.UnicodeUTF8)
                    raise ValueError(msg.format(ext))

                extentionsDict[ext] = appDescription.appCommandPattern
        self.__extensions = extentionsDict


    @staticmethod
    def readCurrentState():
        filesCategories = eval(UserConfig().get('ext_app_mgr_file_types', "[]"))

        appDescriptions = []
        for filesCategory in filesCategories:
            extentions = eval(UserConfig().get('ext_app_mgr.{}.extensions'
                                                      .format(filesCategory)))
            appCmd = UserConfig().get("ext_app_mgr.{}.command"
                                       .format(filesCategory))
            appDescriptions.append(ExtAppDescription(filesCategory, appCmd, extentions))

        extFileManagerCommandPattern = UserConfig().get('ext_file_manager')

        state = ExtAppMgrState(appDescriptions,
                               extFileManagerCommandPattern)
        return state


    @staticmethod
    def setCurrentState(extAppMgrState):
        extAppMgrState.raiseErrorIfNotValid()

        stateDict = dict()
        categories = []
        for appDescription in extAppMgrState.appDescriptions:
            category = appDescription.filesCategory
            categories.append(category)
            stateDict["ext_app_mgr.{}.command".format(category)] = appDescription.appCommandPattern
            stateDict["ext_app_mgr.{}.extensions".format(category)] = appDescription.fileExtentions

        stateDict["ext_app_mgr_file_types"] = categories
        stateDict["ext_file_manager"] = extAppMgrState.extFileMgrCommandPattern

        UserConfig().storeAll(stateDict)



    def openFileWithExtApp(self, abs_path):

        _, ext = os.path.splitext(abs_path)
        appCommandPattern = self.__extensions.get(ext.lower(), None)

        if appCommandPattern is None:
            msg = QCoreApplication.translate("ext_app_mgr",
                        "External application for {0} file extension is not set. "
                        "Please, go to 'Main Menu -> Repository -> Manage External Applications' "
                        "and configure it",
                        None, QCoreApplication.UnicodeUTF8)
            raise Exception(msg.format(ext))

        appCommand = self.__replaceCommandPatternKeys(appCommandPattern,
                                                      filePath=abs_path,
                                                      dirPath=os.path.dirname(abs_path))
        self.__createSubprocess(appCommand)


    def openContainingDirWithExtAppManager(self, abs_path):
        if self.__state.extFileMgrCommandPattern is None:
            msg = QCoreApplication.translate("ext_app_mgr",
                        "No external file manager command is set. "
                        "Please, go to 'Main Menu -> Repository -> Manage External Applications' "
                        "and configure it",
                        None, QCoreApplication.UnicodeUTF8)
            raise MsgException(msg.format(consts.USER_CONFIG_FILE))

        appCommand = self.__replaceCommandPatternKeys(self.__state.extFileMgrCommandPattern,
                                                      filePath=abs_path,
                                                      dirPath=os.path.dirname(abs_path))
        self.__createSubprocess(appCommand)


    def __replaceCommandPatternKeys(self, commandPattern, filePath, dirPath):
        result = commandPattern.replace('%f', '"' + filePath + '"')
        result = result.replace('%d', '"' + dirPath + '"')
        return result


    def __createSubprocess(self, commandWithArgs):
        args = shlex.split(commandWithArgs)
        args = self.__modifyArgsIfOnWindowsAndPathIsNetwork(args)
        logger.debug("subprocess.Popen(args), args=%s", args)

        #subprocess.call(args) #This call would block the current thread
        pid = subprocess.Popen(args).pid #This call would not block the current thread
        logger.info("Created subprocess with PID = %d", pid)

        # TODO: raise an exception if application executable file not found


    def __modifyArgsIfOnWindowsAndPathIsNetwork(self, args):
        # This is a hack... On Windows shlex fails to handle correctly network paths such as
        # \\Tiger\SYSTEM (C)\home\testrepo.rgt\file.txt
        if not sys.platform.startswith("win"):
            return args
        if args[1].startswith("\\"):
            args[1] = "\\" + args[1]
        return args
import logging
import os
import traceback
import reggata.helpers as helpers
from PyQt4 import QtCore
from datetime import datetime
from reggata.logic.abstract_tool import AbstractTool
from reggata.gui.file_browser_gui import FileBrowserGui
from reggata.errors import NoneError
from reggata.errors import NotExistError
from reggata.errors import CurrentRepoIsNoneError
from reggata.errors import CurrentUserIsNoneError
from reggata.data.commands import FileInfo
from reggata.data.commands import GetFileInfoCommand
from reggata.data.commands import GetItemIdsWithFilesFrom
from reggata.logic.action_handlers import ActionHandlerStorage
from reggata.logic.handler_signals import HandlerSignals
from reggata.gui.drop_files_dialogs_facade import DropFilesDialogsFacade
from reggata.logic.file_browser_action_handlers import AddFilesToRepoActionHandler
from reggata.logic.file_browser_action_handlers import OpenFileActionHandler
from reggata.logic.file_browser_action_handlers import MoveFilesActionHandler
from reggata.logic.file_browser_action_handlers import RenameFileActionHandler
from reggata.logic.file_browser_action_handlers import DeleteFilesActionHandler
from reggata.logic.file_browser_action_handlers import EditItemsActionHandlerFileBrowser
from reggata.logic.ext_app_mgr import ExtAppMgr

        self._repo = None
        self._user = None
        self._currDir = None
        self._listCache = None
        self._mutex = None
        self._thread = None
        self._enabled = False
        self._extAppMgr = ExtAppMgr()

        self._guiUpdater.subscribe(
            self._extAppMgr, self._extAppMgr.updateState,
            [HandlerSignals.REGGATA_CONF_CHANGED])

        logger.debug("File Browser __init__ finished.")


    def id(self):
        return self.TOOL_ID

    def title(self):
        return self.tr("File Browser")

    def createGui(self, guiParent):
        self._gui = FileBrowserGui(guiParent, self)
        self._actionHandlers = ActionHandlerStorage(self._guiUpdater)
        logger.debug("File Browser GUI created.")
        return self._gui

    def __getGui(self):
        return self._gui
    gui = property(fget=__getGui)


    def connectActionsWithActionHandlers(self):
        assert len(self._gui.actions) > 0, "Actions should be already built in ToolGui"

        self._actionHandlers.register(
            self._gui.actions['editItems'],
            EditItemsActionHandlerFileBrowser(self, self._dialogsFacade))

        self._actionHandlers.register(
            self._gui.actions['addFiles'],
            AddFilesToRepoActionHandler(self, self._dialogsFacade))

        self._actionHandlers.register(
            self._gui.actions['openFile'],
            OpenFileActionHandler(self, self._extAppMgr))

        self._actionHandlers.register(
            self._gui.actions['moveFiles'],
            MoveFilesActionHandler(self, self._dialogsFacade))

        self._actionHandlers.register(
            self._gui.actions['renameFile'],
            RenameFileActionHandler(self, self._dialogsFacade))

        self._actionHandlers.register(
            self._gui.actions['deleteFiles'],
            DeleteFilesActionHandler(self, self._dialogsFacade))




    def handlerSignals(self):
        return [([HandlerSignals.ITEM_CHANGED,
                  HandlerSignals.ITEM_CREATED,
                  HandlerSignals.ITEM_DELETED], self.refreshDir)]


    @property
    def repo(self):
        return self._repo

    def setRepo(self, repo):
        self._repo = repo
        if repo is not None:
            self.changeDir(repo.base_path)
            logger.debug("File Browser curr dir has been SET.")
        else:
            self.unsetDir()
            logger.debug("File Browser curr dir has been UNSET.")

    def checkActiveRepoIsNotNone(self):
        if self._repo is None:
            raise CurrentRepoIsNoneError("Current repository is None")


    @property
    def user(self):
        return self._user

    def setUser(self, user):
        self._user = user
        # TODO: update Gui according to the user change

    def checkActiveUserIsNotNone(self):
        if self._user is None:
            raise CurrentUserIsNoneError("Current user is None")


    def enable(self):
        self._enabled = True
        self.refreshDir()
        logger.debug("File Browser enabled.")


    def disable(self):
        self._enabled = False
        logger.debug("File Browser disabled.")


    @property
    def currDir(self):
        if self._currDir is None:
            raise NoneError()
        return self._currDir


    def repoBasePath(self):
        if self._repo is None:

            raise NoneError()
