def to_db_format(path):
    '''
        Converts slashes of the given path from current OS format to UNIX format.
    Note: all paths in reggata database are stored in UNIX format.
    '''
    if platform.system() == "Windows":
        return path.replace(os.sep, "/")
    else:
        # Linux and MacOS use UNIX format
        return path class User(Base):
    __tablename__ = "users"

    USER = 'USER'
    ADMIN = 'ADMIN'

    login = sqa.Column(sqa.String, primary_key=True)
    password = sqa.Column(sqa.String)
    group = sqa.Column(sqa.Enum(USER, ADMIN), nullable=False, default=USER)
    date_created = sqa.Column(sqa.DateTime)

    def __init__(self, login=None, password=None, group=USER):
        self.login = login
        self.password = password
        self.group = group
        self.date_created = datetime.datetime.today()

    def checkValid(self):
        if helpers.is_none_or_empty(self.login):
            raise ValueError("Attribute User.login shouldn't be empty.")
        return True class GetExpungedItemCommand(AbstractCommand):
    '''
        Returns expunged (detached) object of Item class from database with given id.
    '''
    def __init__(self, itemId):
        self.__itemId = itemId

    def _execute(self, uow):
        self._session = uow.session
        item = self._session.query(db.Item)\
            .options(joinedload_all('data_ref'))\
            .options(joinedload_all('item_tags.tag'))\
            .options(joinedload_all('item_fields.field'))\
            .get(self.__itemId)

        if item is None:
            raise err.NotFoundError()

        self._session.expunge(item)
        return item 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 AbstractToolGui(object):

    def __init__(self):
        logger.debug("AbstractToolGui init")
        super(AbstractToolGui, self).__init__() class EditItemsActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(EditItemsActionHandler, self).__init__(tool)
        self._dialogs = dialogs

    def handle(self):
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

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

            if len(itemIds) > 1:
                updated, skipped = self.__editManyItems(itemIds)
            else:
                updated, skipped = self.__editSingleItem(itemIds.pop())

            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Editing done. Updated={}, skipped={} items.".format(updated, skipped)))
            self._emitHandlerSignal(HandlerSignals.ITEM_CHANGED)

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



    def __editSingleItem(self, itemId):
        updated, skipped = 0, 0
        uow = self._tool.repo.createUnitOfWork()
        try:
            item = uow.executeCommand(cmds.GetExpungedItemCommand(itemId))

            if not self._dialogs.execItemDialog(
                item=item, gui=self._tool.gui, repo=self._tool.repo, dialogMode=ItemDialog.EDIT_MODE):
                return updated, skipped

            srcAbsPath = item.data_ref.srcAbsPath if item.data_ref is not None else None
            dstRelPath = item.data_ref.dstRelPath if item.data_ref is not None else None
            cmd = cmds.UpdateExistingItemCommand(item, srcAbsPath, dstRelPath, self._tool.user.login)
            uow.executeCommand(cmd)
            updated += 1
        except:
            skipped += 1
        finally:
            uow.close()
        return updated, skipped

    def __editManyItems(self, itemIds):
        updated, skipped = 0, 0
        uow = self._tool.repo.createUnitOfWork()
        try:
            items = []
            for itemId in itemIds:
                items.append(uow.executeCommand(cmds.GetExpungedItemCommand(itemId)))

            if not self._dialogs.execItemsDialog(
                items, self._tool.gui, self._tool.repo, ItemsDialog.EDIT_MODE, sameDstPath=False):
                return updated, skipped

            thread = UpdateGroupOfItemsThread(self._tool.gui, self._tool.repo, items)
            self._dialogs.startThreadWithWaitDialog(thread, self._tool.gui, indeterminate=False)
            updated = thread.updatedCount
            skipped = thread.skippedCount
        finally:
            uow.close()
        return updated, skipped class AddItemsActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(AddItemsActionHandler, self).__init__(tool)
        self.__dialogs = dialogs
        self._itemsCreatedCount = 0
        self._filesSkippedCount = 0
        self.lastSavedItemIds = []

    def handle(self):
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            files = self.__dialogs.getOpenFilesAndDirs(
                self._tool.gui, self.tr("Select files you want to add to the repository"))

            # NOTE: if len(files) == 0 (user haven't selected any files) then it is supposed that
            # an Item without file should be created.

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

            self._emitHandlerSignal(HandlerSignals.ITEM_CREATED)

            stats.sendEvent("items_table.add_items")

        except errors.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 DeleteItemActionHandler(AbstractActionHandler):
    def __init__(self, tool, dialogs):
        super(DeleteItemActionHandler, self).__init__(tool)
        self._dialogs = dialogs

    def handle(self):
        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            itemIds = self._tool.gui.selectedItemIds()
            if len(itemIds) == 0:
                raise errors.MsgException(self.tr("There are no selected items."))

            mbResult = self._dialogs.execMessageBox(self._tool.gui,
                text=self.tr("Do you really want to delete {} selected file(s)?").format(len(itemIds)),
                buttons=[QtGui.QMessageBox.Yes, QtGui.QMessageBox.No])
            if mbResult != QtGui.QMessageBox.Yes:
                raise errors.CancelOperationError()

            thread = threads.DeleteGroupOfItemsThread(
                self._tool.gui, self._tool.repo, itemIds, self._tool.user.login)

            self._dialogs.startThreadWithWaitDialog(thread, self._tool.gui, indeterminate=False)

            if thread.errors > 0:
                self._dialogs.execMessageBox(self._tool.gui,
                    text=self.tr("There were {0} errors.").format(thread.errors),
                    detailedText=thread.detailed_message)

            stats.sendEvent("items_table.delete_item")

        except errors.CancelOperationError:
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation cancelled."), consts.STATUSBAR_TIMEOUT)

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

        else:
            #TODO: display information about how many items were deleted
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation completed."), consts.STATUSBAR_TIMEOUT)
            self._emitHandlerSignal(HandlerSignals.ITEM_DELETED)
 class RebuildItemThumbnailActionHandler(AbstractActionHandler):
    def __init__(self, tool):
        super(RebuildItemThumbnailActionHandler, self).__init__(tool)

    def handle(self):

        def refresh(percent, row):
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Rebuilding thumbnails ({0}%)").format(percent))
            self._emitHandlerSignal(HandlerSignals.RESET_SINGLE_ROW, row)
            QtCore.QCoreApplication.processEvents()

        try:
            self._tool.checkActiveRepoIsNotNone()
            self._tool.checkActiveUserIsNotNone()

            rows = self._tool.gui.selectedRows()
            if len(rows) == 0:
                raise errors.MsgException(self.tr("There are no selected items."))


            uow = self._tool.repo.createUnitOfWork()
            try:
                items = []
                for row in rows:
                    item = self._tool.gui.itemAtRow(row)
                    item.table_row = row
                    items.append(item)

                thread = threads.ThumbnailBuilderThread(
                    self._tool.gui, self._tool.repo, items, self._tool.itemsLock,
                    rebuild=True)

                self.connect(thread, QtCore.SIGNAL("exception"),
                    lambda exc_info: show_exc_info(
                        self._tool.gui, exc_info[1], details=format_exc_info(*exc_info)))
                self.connect(thread, QtCore.SIGNAL("progress"),
                    lambda percents, row: refresh(percents, row))
                self.connect(thread, QtCore.SIGNAL("finished"),
                    lambda: self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                        self.tr("Rebuild thumbnails is done.")))
                thread.start()

            finally:
                uow.close()

            stats.sendEvent("items_table.rebuild_item_thumbnail")

        except Exception as ex:
            show_exc_info(self._tool.gui, ex)
 class AbstractTestCaseWithRepo(unittest.TestCase):

    def setUp(self):
        self.repoBasePath = tests_context.TEST_REPO_BASE_PATH
        self.copyOfRepoBasePath = tests_context.COPY_OF_TEST_REPO_BASE_PATH

        if (os.path.exists(self.copyOfRepoBasePath)):
            shutil.rmtree(self.copyOfRepoBasePath)
        shutil.copytree(self.repoBasePath, self.copyOfRepoBasePath)

        self.repo = RepoMgr(self.copyOfRepoBasePath)

    def tearDown(self):
        self.repo = None
        shutil.rmtree(self.copyOfRepoBasePath)

    def getExistingItem(self, itemId):
        try:
            uow = self.repo.createUnitOfWork()
            item = uow.executeCommand(GetExpungedItemCommand(itemId))
            self.assertIsNotNone(item)
        finally:
            uow.close()
        return item

    def getDataRef(self, url):
        # In the case when this DataRef is a file, url should be a relative path
        try:
            uow = self.repo.createUnitOfWork()
            dataRef = uow._session.query(DataRef) \
                    .filter(DataRef.url_raw==helpers.to_db_format(url)).first()
            # NOTE: dataRef could be None
        finally:
            uow.close()
        return dataRef


    def getDataRefById(self, objId):
        '''
            Returns a DataRef object with given id or None, if DataRef object not found.
        '''
        try:
            uow = self.repo.createUnitOfWork()
            dataRef = uow._session.query(DataRef).filter(DataRef.id==objId).first()
        finally:
            uow.close()
        return dataRef

    
    def getDataRefsFromDir(self, dirRelPath):
        dataRefs = []
        try:
            uow = self.repo.createUnitOfWork()
            dataRefs = uow._session.query(DataRef).filter(
                DataRef.url_raw.like(helpers.to_db_format(dirRelPath) + "/%")).all()
        finally:
            uow.close()
        return dataRefs


    def updateExistingItem(self, detachedItem, newSrcAbsPath, newDstRelPath, user_login):
        item = None
        try:
            uow = self.repo.createUnitOfWork()
            cmd = UpdateExistingItemCommand(detachedItem, newSrcAbsPath, newDstRelPath, user_login)
            item = uow.executeCommand(cmd)
        finally:
            uow.close()
        return item TEST_REPO = "testrepo.rgt"
COPY_OF_TEST_REPO = "copy_of_testrepo.rgt"
TEST_REPO_BASE_PATH = os.path.join(__dirAbsPath, TEST_REPO)
COPY_OF_TEST_REPO_BASE_PATH = os.path.join(__dirAbsPath, COPY_OF_TEST_REPO)
class ItemReliableFacts(object): class TestsDialogsFacade(AbstractDialogsFacade):

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


    def execUserDialog(self, user, gui, dialogMode):
        return True

    def execChangeUserPasswordDialog(self, user, gui):
        return True

    def execItemDialog(self, item, gui, repo, dialogMode):
        if item.data_ref is not None:
            item.data_ref.srcAbsPath = os.path.join(repo.base_path, item.data_ref.url)
            item.data_ref.dstRelPath = os.path.basename(item.data_ref.url)
        return True

    def execItemsDialog(self, items, gui, repo, dialogMode, sameDstPath):
        for item in items:
            if item.data_ref is None:
                continue

            srcAbsPath = os.path.join(repo.base_path, item.data_ref.url)
            item.data_ref.srcAbsPath = srcAbsPath

            if item.data_ref.srcAbsPathToRoot is None:
                item.data_ref.dstRelPath = os.path.basename(srcAbsPath)
            else:
                item.data_ref.dstRelPath = os.path.relpath(srcAbsPath, item.data_ref.srcAbsPathToRoot)
        return True


    def startThreadWithWaitDialog(self, thread, gui, indeterminate):
        thread.run()



    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



    def execMessageBox(self, parent, text, title=None, buttons=None, detailedText=None):
        return buttons[0]
import os
import time
from PyQt4 import QtCore
from reggata.helpers import to_db_format
from reggata.data.db_schema import User
from reggata.data.commands import GetExpungedItemCommand
from reggata.logic.abstract_tool import AbstractTool
from reggata.logic.abstract_tool_gui import AbstractToolGui
from reggata.logic.common_action_handlers import EditItemsActionHandler
from reggata.logic.items_table_action_handlers import AddItemsActionHandler,\
    DeleteItemActionHandler, RebuildItemThumbnailActionHandler
from reggata.tests.abstract_test_cases import AbstractTestCaseWithRepo
from reggata.tests.tests_context import itemWithTagsAndFields, itemWithFile, itemWithoutFile
from reggata.tests.tests_dialogs_facade import TestsDialogsFacade
        return self._model
    model = property(fget=_get_model)

    def setItems(self, items):
        self._items = items

    def selectedItemIds(self):
        return self._selectedItemIds

    def setSelectedItemIds(self, itemIds):
        self._selectedItemIds = itemIds

    def selectedRows(self):
        return [x for x in range(len(self._selectedItemIds))]

    def itemAtRow(self, row):
        foundItems = [item for item in self._items if item.id == self._selectedItemIds[row]]
        assert len(foundItems) == 1
        return foundItems[0]



class AddItemsActionHandlerTest(AbstractTestCaseWithRepo):

    def test_addFileFromOutsideOfRepo(self):
        user = User(login="user", password="")
        srcAbsPath = os.path.abspath(os.path.join(self.repo.base_path, "..", "tmp", "file.txt"))
        dstRelPath = "file.txt"

        self.assertFalse(os.path.exists(os.path.join(self.repo.base_path, dstRelPath)),
            "Target file should not be already in the repo root")


        tool = TestsToolModel(self.repo, user)
        dialogs = TestsDialogsFacade(selectedFiles=[srcAbsPath])
        handler = AddItemsActionHandler(tool, dialogs)
        handler.handle()

        self.assertEqual(len(handler.lastSavedItemIds), 1)
        try:
            uow = self.repo.createUnitOfWork()
            savedItem = uow.executeCommand(GetExpungedItemCommand(handler.lastSavedItemIds[0]))

            self.assertIsNotNone(savedItem,
                "Item should exist")
            self.assertIsNotNone(savedItem.data_ref,
                "Item should have a DataRef object")
            self.assertEqual(savedItem.data_ref.url_raw, to_db_format(dstRelPath),
                "Item's file should be located in the root of repo")
            self.assertTrue(os.path.exists(os.path.join(self.repo.base_path, savedItem.data_ref.url)),
                "Item's file should exist")
        finally:
            uow.close()


    def test_addTwoFilesFromOutsideOfRepo(self):
        user = User(login="user", password="")
        srcAbsPath = []
        dstRelPath = []
        srcAbsPath.append(os.path.abspath(os.path.join(self.repo.base_path, "..", "tmp", "file.txt")))
        dstRelPath.append("file.txt")
        srcAbsPath.append(os.path.abspath(os.path.join(self.repo.base_path, "..", "tmp", "grub.conf")))
        dstRelPath.append("grub.conf")

        self.assertFalse(os.path.exists(os.path.join(self.repo.base_path, dstRelPath[0])),
            "Target file should not be already in the repo root")
        self.assertFalse(os.path.exists(os.path.join(self.repo.base_path, dstRelPath[1])),
            "Target file should not be already in the repo root")


        tool = TestsToolModel(self.repo, user)
        dialogs = TestsDialogsFacade(selectedFiles=[srcAbsPath[0], srcAbsPath[1]])
        handler = AddItemsActionHandler(tool, dialogs)
        handler.handle()


        self.assertEqual(len(dstRelPath), len(handler.lastSavedItemIds))
        for i, savedItemId in enumerate(handler.lastSavedItemIds):
            try:
                uow = self.repo.createUnitOfWork()
                savedItem = uow.executeCommand(GetExpungedItemCommand(savedItemId))

                self.assertIsNotNone(savedItem,
                    "Item should exist")
                self.assertIsNotNone(savedItem.data_ref,
                    "Item should have a DataRef object")
                self.assertEqual(savedItem.data_ref.url_raw, to_db_format(dstRelPath[i]),
                    "Item's file should be located in the root of repo")
                self.assertTrue(os.path.exists(os.path.join(self.repo.base_path, savedItem.data_ref.url)),
                    "Item's file should exist")
            finally:
                uow.close()


    def test_addRecursivelyDirFromOutsideOfRepo(self):
        user = User(login="user", password="")
        srcDirAbsPath = os.path.abspath(os.path.join(self.repo.base_path, "..", "tmp"))

        dstRelPaths = []
        for root, _dirs, files in os.walk(srcDirAbsPath):
            for file in files:
                dstRelPath = os.path.relpath(os.path.join(root, file), os.path.join(srcDirAbsPath, ".."))
                dstRelPaths.append(dstRelPath)

        tool = TestsToolModel(self.repo, user)
        dialogs = TestsDialogsFacade(selectedFiles=[srcDirAbsPath])
        handler = AddItemsActionHandler(tool, dialogs)
        handler.handle()


        self.assertEqual(len(handler.lastSavedItemIds), len(dstRelPaths))
        for i, savedItemId in enumerate(handler.lastSavedItemIds):
            try:
                uow = self.repo.createUnitOfWork()
                savedItem = uow.executeCommand(GetExpungedItemCommand(savedItemId))

                self.assertIsNotNone(savedItem,
                    "Item should exist")
                self.assertIsNotNone(savedItem.data_ref,
                    "Item should have a DataRef object")

                self.assertEqual(savedItem.data_ref.url_raw, to_db_format(dstRelPaths[i]),