Пример #1
0
    def on_enter(self):
        """Called when the screen is entered.  Sets up variables, and scans the import folders."""

        app = App.get_running_app()
        self.ids['leftpanel'].width = app.left_panel_width()
        self.duplicates = []
        self.import_photos = []
        self.folders = {}
        self.unsorted = []
        self.removed = []
        self.total_size = 0
        self.import_scanning = False

        # Display message that folder scanning is in progress
        self.cancel_scanning = False
        self.scanningpopup = ScanningPopup(title='Scanning Import Folders...',
                                           auto_dismiss=False,
                                           size_hint=(None, None),
                                           size=(app.popup_x,
                                                 app.button_scale * 4))
        self.scanningpopup.open()
        scanning_button = self.scanningpopup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_import)

        self.percent_completed = 0
        self.scanningthread = threading.Thread(target=self.scan_folders)
        self.import_scanning = True
        self.scanningthread.start()
        self.start_time = time.time()
Пример #2
0
    def export(self):
        """Begins the export process.  Opens a progress dialog, and starts the export thread."""

        self.ftp = False
        app = App.get_running_app()
        preset = app.exports[self.selected_preset]
        if preset['export'] == 'ftp':
            if not preset['ftp_address']:
                app.message(text="Please Set Export Location")
                return
        else:
            if not preset['export_folder']:
                app.message(text="Please Set Export Location")
                return
        if not self.photos_selected:
            app.message(text="Please Select Photos To Export")
            return
        self.cancel_exporting = False
        self.popup = ScanningPopup(title='Exporting Files',
                                   auto_dismiss=False,
                                   size_hint=(None, None),
                                   size=(app.popup_x, app.button_scale * 4))
        self.popup.open()
        scanning_button = self.popup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_export)
        self.scanningthread = threading.Thread(target=self.exporting_process)
        self.scanningthread.start()
Пример #3
0
    def move_folders(self, instance, answer):
        del instance
        app = App.get_running_app()
        self.dismiss_popup()
        if answer == 'yes':
            self.cancel_copying = False
            self.copyingpopup = ScanningPopup(title='Moving Files',
                                              auto_dismiss=False,
                                              size_hint=(None, None),
                                              size=(app.popup_x,
                                                    app.button_scale * 4))
            self.copyingpopup.open()
            scanning_button = self.copyingpopup.ids['scanningButton']
            scanning_button.bind(on_release=self.cancel_copy)

            # Start importing thread
            self.percent_completed = 0
            self.copyingthread = threading.Thread(target=self.move_process)
            self.copyingthread.start()
Пример #4
0
    def finalize_import(self):
        raise ValueError(
            "should not call this function anymore because the load is done on preset selection"
        )
        """Begin the final stage of the import - copying files."""
        app = App.get_running_app()

        # Create popup to show importing progress
        self.cancel_scanning = False
        self.scanningpopup = ScanningPopup(title='Importing Files',
                                           auto_dismiss=False,
                                           size_hint=(None, None),
                                           size=(app.popup_x,
                                                 app.button_scale * 4))
        self.scanningpopup.open()
        scanning_button = self.scanningpopup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_import)

        # Start importing thread
        self.percent_completed = 0
        self.scanningthread = threading.Thread(target=self.importing_process)
        self.import_scanning = True
        self.scanningthread.start()
        self.start_time = time.time()
Пример #5
0
class ScreenImporting(Screen):
    """Screen layout for photo importing process.
    Displays photos from directories and lets you select which ones to import.
    """

    type = StringProperty('')
    selected = StringProperty('')
    import_to = StringProperty('')
    naming_method = StringProperty('')
    delete_originals = BooleanProperty(False)
    single_folder = BooleanProperty(False)
    import_from = ListProperty()
    popup = None
    import_photos = []
    duplicates = []
    photos = []
    folders = {}
    unsorted = []
    removed = []
    total_size = 0
    cancel_scanning = BooleanProperty(
        False
    )  # The importing process thread checks this and will stop if set to True.
    scanningpopup = None  # Popup dialog showing the importing process progress.
    scanningthread = None  # Importing files thread.
    popup_update_thread = None  # Updates the percentage and time index on scanning popup
    percent_completed = NumericProperty()
    start_time = NumericProperty()
    import_scanning = BooleanProperty(False)

    def get_selected_photos(self, fullpath=False):
        photos = self.ids['photos']
        selected_indexes = photos.selected_nodes
        photos_container = self.ids['photosContainer']
        selected_photos = []
        for selected in selected_indexes:
            if fullpath:
                selected_photos.append(
                    photos_container.data[selected]['fullpath'])
            else:
                selected_photos.append(
                    photos_container.data[selected]['photoinfo'])
        return selected_photos

    def dismiss_extra(self):
        """Cancels the import process if it is running"""

        if self.import_scanning:
            self.cancel_import()
            return True
        else:
            return False

    def date_to_folder(self, date):
        """Generates a string from a date in the format YYYYMMDD."""

        date_info = datetime.datetime.fromtimestamp(date)
        return str(date_info.year) + str(date_info.month).zfill(2) + str(
            date_info.day).zfill(2)

    def on_enter(self):
        """Called when the screen is entered.  Sets up variables, and scans the import folders."""

        app = App.get_running_app()
        self.ids['leftpanel'].width = app.left_panel_width()
        self.duplicates = []
        self.import_photos = []
        self.folders = {}
        self.unsorted = []
        self.removed = []
        self.total_size = 0
        self.import_scanning = False

        # Display message that folder scanning is in progress
        self.cancel_scanning = False
        self.scanningpopup = ScanningPopup(title='Scanning Import Folders...',
                                           auto_dismiss=False,
                                           size_hint=(None, None),
                                           size=(app.popup_x,
                                                 app.button_scale * 4))
        self.scanningpopup.open()
        scanning_button = self.scanningpopup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_import)

        self.percent_completed = 0
        self.scanningthread = threading.Thread(target=self.scan_folders)
        self.import_scanning = True
        self.scanningthread.start()
        self.start_time = time.time()

    def scan_folders(self, *_):
        """Function that scans the import folders for valid files to import
            and now really import files without using the interface to select file.
            Will import all files in directories.
        """
        app = App.get_running_app()

        engine = create_engine(app.database_path,
                               echo=True,
                               connect_args={'check_same_thread': False})
        Session = sessionmaker(bind=engine)
        session = Session()

        # TODO: put this code in a secret file
        #gmaps = googlemaps.Client(key='xxx-LLtQzk0U')

        current_timestamp = time.time()

        # Scan the folders
        for folder in self.import_from:
            if os.path.isdir(folder):
                files = list_files(folder)
                print(folder)
                size = len(files)
                for file_info in files:
                    # update popup
                    if self.cancel_scanning:
                        self.scanning_canceled()
                        return
                    self.percent_completed = self.percent_completed + 1
                    if self.percent_completed > 100:
                        self.percent_completed = 0
                    self.scanningpopup.scanning_percentage = self.percent_completed

                    extension = os.path.splitext(file_info[0])[1].lower()
                    if extension in imagetypes or extension in movietypes:
                        photo = Photo()
                        photo.from_file_info(self.import_to, file_info)
                        self.import_photos.append(photo)

        nb_photo = len(self.import_photos)
        current_photo_index = 0
        for photo in self.import_photos:
            session.commit()
            self.scanningpopup.scanning_percentage = current_photo_index / nb_photo * 100
            current_photo_index += 1
            photo_in_db = session.query(Photo).filter_by(
                full_path=photo.full_path).first()
            if photo_in_db is None:
                folder = session.query(Folder).filter_by(
                    name=photo.folder_name()).first()
                if folder is None:
                    local_folder_path = os.path.join(self.import_to,
                                                     photo.folder_name())
                    if not os.path.isdir(local_folder_path):
                        os.makedirs(local_folder_path)

                    folder = Folder(name=photo.folder_name())
                    session.add(folder)
                    session.commit()

                photo.update_thumbnail()
                session.add(photo)
                session.commit()

                folder.photos.append(photo)
                session.commit()

                if self.delete_originals:
                    move(photo.old_full_filename(), photo.new_full_filename())
                else:
                    copy2(photo.old_full_filename(), photo.new_full_filename())

            # localize_photo(gmaps, session, photo)

            else:  # photo is already in database
                if self.delete_originals:
                    try:
                        os.remove(photo.old_full_filename())
                    except Exception as e:
                        print(e)

        for folder in self.import_from:
            while (removeEmptyFolders(folder, True)):
                pass

        self.scanningpopup.dismiss()
        self.scanningpopup = None
        self.scanningpopup = None
        self.import_scanning = False
        self.update_treeview()
        self.update_photolist()

    def scanning_canceled(self):
        app = App.get_running_app()
        app.message("Canceled import scanning.")
        self.scanningpopup.dismiss()
        self.scanningpopup = None
        self.import_scanning = False
        Clock.schedule_once(lambda *dt: app.show_database())

    def cancel_import(self, unknown=False):
        """Cancel the import process."""
        self.cancel_scanning = True

    def finalize_import(self):
        raise ValueError(
            "should not call this function anymore because the load is done on preset selection"
        )
        """Begin the final stage of the import - copying files."""
        app = App.get_running_app()

        # Create popup to show importing progress
        self.cancel_scanning = False
        self.scanningpopup = ScanningPopup(title='Importing Files',
                                           auto_dismiss=False,
                                           size_hint=(None, None),
                                           size=(app.popup_x,
                                                 app.button_scale * 4))
        self.scanningpopup.open()
        scanning_button = self.scanningpopup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_import)

        # Start importing thread
        self.percent_completed = 0
        self.scanningthread = threading.Thread(target=self.importing_process)
        self.import_scanning = True
        self.scanningthread.start()
        self.start_time = time.time()

    def importing_process(self):
        """Function that actually imports the files."""

        raise ValueError(
            "should not call this function anymore because the load is done on preset selection"
        )

        app = App.get_running_app()
        folders = self.folders
        import_to = self.import_to
        total_size = self.total_size
        imported_size = 0
        self.scanningpopup.scanning_text = "Importing " + format_size(
            total_size) + '  0%'
        imported_folders = []
        imported_files = 0
        failed_files = 0

        if disk_usage:
            free_space = disk_usage(import_to)[2]
            if total_size > free_space:
                self.scanningpopup.dismiss()
                self.scanningpopup = None
                app.message("Not enough free drive space! Cancelled import.")
                Clock.schedule_once(lambda *dt: app.show_import())

        # Scan folders
        for folder_path in folders:
            if self.cancel_scanning:
                break
            folder = folders[folder_path]
            folder_name = folder['name']
            if folder['photos']:
                if folder['naming']:
                    folder_name = naming(self.naming_method,
                                         title=folder['title'],
                                         year=folder['year'],
                                         month=folder['month'],
                                         day=folder['day'])
                photos = folder['photos']
                parent = folder['parent']
                if parent:
                    path_string = []
                    while parent:
                        newfolder = folders[parent]
                        newfolder_name = newfolder['name']
                        if newfolder['naming']:
                            newfolder_name = naming(self.naming_method,
                                                    title=newfolder['title'],
                                                    year=newfolder['year'],
                                                    month=newfolder['month'],
                                                    day=newfolder['day'])
                        path_string.append(newfolder_name)
                        parent = newfolder['parent']
                    for path in path_string:
                        folder_name = os.path.join(path, folder_name)
                folderinfo = [
                    folder_name, folder['title'], folder['description']
                ]
                path = os.path.join(import_to, folder_name)
                if not os.path.isdir(path):
                    os.makedirs(path)
                if not app.Folder.exist(folderinfo[0]):
                    app.Folder.insert(folderinfo)
                else:
                    if folderinfo[1]:
                        app.Folder.update_title(folderinfo[0],
                                                folderinfo[1]).commit()
                    if folderinfo[2]:
                        app.Folder.update_description(path, description)(
                            folderinfo[0], folderinfo[2])

                # Scan and import photos in folder
                for photo in photos:
                    if self.cancel_scanning:
                        break
                    completed = (imported_size / total_size)
                    remaining = 1 - completed
                    self.percent_completed = 100 * completed
                    self.scanningpopup.scanning_percentage = self.percent_completed

                    seconds_elapsed = time.time() - self.start_time
                    time_elapsed = '  Time: ' + str(
                        datetime.timedelta(seconds=int(seconds_elapsed)))
                    if self.percent_completed > 0:
                        seconds_remain = (seconds_elapsed *
                                          remaining) / completed
                        time_remain = '  Remaining: ' + str(
                            datetime.timedelta(seconds=int(seconds_remain)))
                    else:
                        time_remain = ''
                    self.scanningpopup.scanning_text = "Importing " + format_size(
                        total_size) + '  ' + str(
                            int(self.percent_completed
                                )) + '%  ' + time_elapsed + time_remain
                    old_full_filename = os.path.join(photo[2], photo[0])
                    new_photo_fullpath = os.path.join(folder_name, photo[10])
                    new_full_filename = os.path.join(import_to,
                                                     new_photo_fullpath)
                    thumbnail_data = app.Photo.thumbnail(photo[2],
                                                         temporary=True)
                    if not app.Photo.exist(new_photo_fullpath):
                        photo[0] = new_photo_fullpath
                        photo[1] = folder_name
                        photo[2] = import_to
                        photo[6] = int(time.time())

                        try:
                            copy2(old_full_filename, new_full_filename)
                        except:
                            failed_files = failed_files + 1
                            imported_size = imported_size + photo[4]
                        else:
                            if self.delete_originals:
                                if os.path.isfile(new_full_filename):
                                    if os.path.getsize(new_full_filename
                                                       ) == os.path.getsize(
                                                           old_full_filename):
                                        os.remove(old_full_filename)
                            app.Photo.add(photo)
                            # app.database_imported_add(photo[0], photo[10], photo[3])
                            if thumbnail_data:
                                thumbnail = thumbnail_data[2]
                                app.Photo.thumbnail_write(
                                    photo[0], int(time.time()), thumbnail,
                                    photo[13])
                            imported_size = imported_size + photo[4]
                            imported_files = imported_files + 1
                    else:
                        failed_files = failed_files + 1
                        imported_size = imported_size + photo[4]
                """
                imported_folders.append(folder_name)
        """

        raise ValueError('muste complete import')

        app.update_photoinfo(folders=imported_folders)
        self.scanningpopup.dismiss()
        if failed_files:
            failed = ' Could not import ' + str(failed_files) + ' files.'
        else:
            failed = ''
        if not self.cancel_scanning:
            if imported_files:
                app.message("Finished importing " + str(imported_files) +
                            " files." + failed)
        else:
            if imported_files:
                app.message("Canceled importing, " + str(imported_files) +
                            " files were imported." + failed)
            else:
                app.message("Canceled importing, no files were imported.")
        self.scanningpopup = None
        self.import_scanning = False
        Clock.schedule_once(lambda *dt: app.show_database())

    def set_delete_originals(self, state):
        """Enable the 'Delete Originals' option."""

        if state == 'down':
            self.delete_originals = True
        else:
            self.delete_originals = False

    def previous_album(self):
        """Switch to the previous album in the list."""

        database = self.ids['folders']
        selected_album = database.selected_node
        if selected_album:
            nodes = list(database.iterate_all_nodes())
            index = nodes.index(selected_album)
            if index <= 1:
                index = len(nodes)
            new_selected_album = nodes[index - 1]
            database.select_node(new_selected_album)
            new_selected_album.on_press()
            database_container = self.ids['foldersContainer']
            database_container.scroll_to(new_selected_album)

    def next_album(self):
        """Switch to the next item on the list."""

        database = self.ids['folders']
        selected_album = database.selected_node
        if selected_album:
            nodes = list(database.iterate_all_nodes())
            index = nodes.index(selected_album)
            if index >= len(nodes) - 1:
                index = 0
            new_selected_album = nodes[index + 1]
            database.select_node(new_selected_album)
            new_selected_album.on_press()
            database_container = self.ids['foldersContainer']
            database_container.scroll_to(new_selected_album)

    def delete(self):
        """Remove selected files and place them in the unsorted folder."""

        if self.type == 'folder' or (self.type == 'extra'
                                     and self.selected == 'unsorted'):
            selected_files = self.get_selected_photos()
            for photo in selected_files:
                if self.selected != 'unsorted':
                    self.folders[self.selected]['photos'].remove(photo)
                    self.unsorted.append(photo)
            self.update_treeview()
            self.update_photolist()

    def add_folder(self):
        """Begin the add folder process, create an input popup."""

        content = InputPopup(hint='Folder Name', text='Enter A Folder Name:')
        app = App.get_running_app()
        content.bind(on_answer=self.add_folder_answer)
        self.popup = NormalPopup(title='Create Folder',
                                 content=content,
                                 size_hint=(None, None),
                                 size=(app.popup_x, app.button_scale * 4),
                                 auto_dismiss=False)
        self.popup.open()

    def add_folder_answer(self, instance, answer):
        """Confirm adding the folder.
        Arguments:
            instance: Dialog that called this function.
            answer: String, if set to 'yes', folder is created.
        """

        app = App.get_running_app()
        if answer == 'yes':
            text = instance.ids['input'].text.strip(' ')
            if text:
                if self.type == 'extra':
                    root = ''
                else:
                    root = self.selected
                path = os.path.join(root, text)
                if path not in self.folders:
                    self.folders[path] = {
                        'name': text,
                        'parent': root,
                        'naming': False,
                        'title': '',
                        'description': '',
                        'year': 0,
                        'month': 0,
                        'day': 0,
                        'photos': []
                    }
                else:
                    app.message("Folder already exists.")

        self.dismiss_popup()
        self.update_treeview()

    def delete_folder(self):
        """Delete the selected import folder and move photos to the unsorted folder."""

        if self.type == 'folder' and self.selected:
            folder_info = self.folders[self.selected]
            photos = folder_info['photos']
            for photo in photos:
                self.unsorted.append(photo)
            del self.folders[self.selected]
            self.selected = ''
            self.type = 'None'
            self.update_treeview()
            self.update_photolist()

    def toggle_select(self):
        """Toggles the selection of photos in the current album."""

        photos = self.ids['photos']
        photos.toggle_select()
        self.update_selected()

    def select_none(self):
        """Deselects all photos."""

        photos = self.ids['photos']
        photos.clear_selection()
        self.update_selected()

    def update_treeview(self):
        """Clears and repopulates the left-side folder list."""

        folder_list = self.ids['folders']

        # Clear the treeview list
        nodes = list(folder_list.iterate_all_nodes())
        for node in nodes:
            folder_list.remove_node(node)
        selected_node = None

        # folder_item = TreeViewButton(target='removed', type='extra', owner=self, view_album=False)
        # folder_item.folder_name = 'Removed (Never Scan Again)'
        # total_photos = len(self.removed)
        # folder_item.total_photos_numeric = total_photos
        # if total_photos > 0:
        #    folder_item.total_photos = '('+str(total_photos)+')'
        # folder_list.add_node(folder_item)
        # if self.selected == 'removed' and self.type == 'extra':
        #    selected_node = folder_item

        # Populate the 'Already Imported' folder
        folder_item = TreeViewButton(target='duplicates',
                                     type='extra',
                                     owner=self,
                                     view_album=False)
        folder_item.folder_name = 'Already Imported (Never Import Again)'
        total_photos = len(self.duplicates)
        folder_item.total_photos_numeric = total_photos
        if total_photos > 0:
            folder_item.total_photos = '(' + str(total_photos) + ')'
        folder_list.add_node(folder_item)
        if self.selected == 'duplicates' and self.type == 'extra':
            selected_node = folder_item

        # Populate the 'Unsorted' folder
        folder_item = TreeViewButton(target='unsorted',
                                     type='extra',
                                     owner=self,
                                     view_album=False)
        folder_item.folder_name = 'Unsorted (Not Imported This Time)'
        total_photos = len(self.unsorted)
        folder_item.total_photos_numeric = total_photos
        if total_photos > 0:
            folder_item.total_photos = '(' + str(total_photos) + ')'
        folder_list.add_node(folder_item)
        if self.selected == 'unsorted' and self.type == 'extra':
            selected_node = folder_item

        # Populate the importing folders
        sorted_folders = sorted(self.folders)
        self.total_size = 0
        to_parent = []
        added_nodes = {}
        for folder_date in sorted_folders:
            folder_info = self.folders[folder_date]
            target = folder_date
            folder_item = TreeViewButton(is_open=True,
                                         fullpath=target,
                                         dragable=True,
                                         target=target,
                                         type='folder',
                                         owner=self,
                                         view_album=False)
            if folder_info['naming']:
                folder_item.folder_name = naming(self.naming_method,
                                                 title=folder_info['title'],
                                                 year=folder_info['year'],
                                                 month=folder_info['month'],
                                                 day=folder_info['day'])
            else:
                folder_item.folder_name = folder_info['name']
            added_nodes[folder_date] = folder_item
            photos = folder_info['photos']
            for photo in photos:
                self.total_size = self.total_size + photo[4]
            total_photos = len(photos)
            folder_item.total_photos_numeric = total_photos
            if total_photos > 0:
                folder_item.total_photos = '(' + str(total_photos) + ')'
            if folder_info['parent']:
                to_parent.append([folder_item, folder_info['parent']])
            else:
                folder_list.add_node(folder_item)
            if self.selected == target and self.type == 'folder':
                selected_node = folder_item
        for item in to_parent:
            node = item[0]
            parent_name = item[1]
            if parent_name in added_nodes.keys():
                folder_list.add_node(node, parent=added_nodes[parent_name])
            else:
                folder_list.add_node(node)
        if selected_node:
            folder_list.select_node(selected_node)
        size_display = self.ids['totalSize']
        size_display.text = 'Total Size: ' + format_size(self.total_size)

    def on_selected(self, instance, value):
        """Called when a photo is selected.  Activate the delete button, and update photo view."""

        delete_button = self.ids['deleteButton']
        delete_button.disabled = True
        self.update_photolist()

    def new_description(self, description_editor):
        """Called when the description field of the currently selected folder is edited.
        Update internal variables to match.
        Argument:
            description_editor: The input box that has been edited.
        """

        description = description_editor.text
        if self.type == 'folder':
            self.folders[self.selected]['description'] = description

    def new_title(self, title_editor):
        """Called when the title field of the currently selected folder is edited.
        Update internal variables to match.
        Argument:
            title_editor: The input box that has been edited.
        """

        title = title_editor.text
        if self.type == 'folder':
            self.folders[self.selected]['title'] = title
            folder_info = self.folders[self.selected]
            self.update_treeview()
            folder_name = self.ids['folderName']
            folder_name.text = naming(self.naming_method,
                                      title=folder_info['title'],
                                      year=folder_info['year'],
                                      month=folder_info['month'],
                                      day=folder_info['day'])

    def update_photolist(self):
        """Redraw the photo list view for the currently selected folder."""

        folder_name = self.ids['folderName']
        photos = []
        name = ''
        title_editor = self.ids['folderTitle']
        description_editor = self.ids['folderDescription']
        dragable = True

        # Viewing an input folder.
        if self.type == 'folder':
            if self.selected in self.folders:
                folder_info = self.folders[self.selected]
                title_editor.text = folder_info['title']
                title_editor.disabled = False
                description_editor.text = folder_info['description']
                description_editor.disabled = False
                photos = folder_info['photos']
                if folder_info['naming']:
                    name = naming(self.naming_method,
                                  title=folder_info['title'],
                                  year=folder_info['year'],
                                  month=folder_info['month'],
                                  day=folder_info['day'])
                else:
                    name = self.selected

        # Viewing a special sorting folder.
        else:
            title_editor.text = ''
            title_editor.disabled = True
            description_editor.text = ''
            description_editor.disabled = True
            if self.selected == 'unsorted':
                photos = self.unsorted
                name = 'Unsorted (Not Imported This Time)'
            elif self.selected == 'removed':
                photos = self.removed
                name = 'Removed (Never Scanned Again)'
            elif self.selected == 'duplicates':
                dragable = False
                photos = self.duplicates
                name = 'Already Imported (Never Import Again)'

        folder_name.text = name

        # Populate photo view
        photos_container = self.ids['photosContainer']
        datas = []
        for photo in photos:
            full_filename = os.path.join(photo.database_folder, photo.fullpath)
            fullpath = photo.fullpath
            database_folder = photo.database_folder
            video = os.path.splitext(full_filename)[1].lower() in movietypes
            data = {
                'fullpath': fullpath,
                'temporary': True,
                'photo': photo,
                'folder': self.selected,
                'database_folder': database_folder,
                'filename': full_filename,
                'target': self.selected,
                'type': self.type,
                'owner': self,
                'video': video,
                'photo_orientation': photo.orientation,
                'source': full_filename,
                'title': photo.owner,
                'selected': False,
                'selectable': True,
                'dragable': dragable
            }
            datas.append(data)
        photos_container.data = datas
        self.select_none()

    def find_photo(self, photo_path, photo_list):
        """Searches through a list of photoinfo objects to find the specified photo.
        Arguments:
            photo_path: The screenDatabase-relative path to the photo to search for.
            photo_list: The list of photo info objects to look through.
        Returns:
            False if nothing found
            Photo info list if match found.
        """

        for photo in photo_list:
            if photo[0] == photo_path:
                return photo
        return False

    def drop_widget(self, fullpath, position, dropped_type='file', aspect=1):
        """Called when a widget is dropped after being dragged.
        Determines what to do with the widget based on where it is dropped.
        Arguments:
            fullpath: String, file location of the object being dragged.
            position: List of X,Y window coordinates that the widget is dropped on.
            dropped_type: String, describes the object being dropped.  May be: 'folder' or 'file'
        """

        app = App.get_running_app()
        folder_list = self.ids['folders']
        folder_container = self.ids['foldersContainer']
        if folder_container.collide_point(position[0], position[1]):
            offset_x, offset_y = folder_list.to_widget(position[0],
                                                       position[1])
            for widget in folder_list.children:
                if widget.collide_point(
                        position[0], offset_y
                ) and widget.type != 'None' and self.type != 'None' and not (
                        widget.target == 'duplicates'
                        and widget.type == 'extra'):

                    if dropped_type == 'folder':
                        # Dropped a folder
                        dropped_data = self.folders[fullpath]
                        new_path = os.path.join(widget.fullpath,
                                                dropped_data['name'])
                        if widget.fullpath != fullpath:
                            # this was actually a drag and not a long click
                            if new_path not in self.folders:
                                # this folder can be dropped here
                                old_parent = fullpath
                                dropped_data['parent'] = widget.fullpath
                                self.folders[new_path] = dropped_data
                                del self.folders[fullpath]

                                new_folders = {}
                                # rename child folders
                                for folder in self.folders:
                                    folder_info = self.folders[folder]
                                    parent = folder_info['parent']
                                    if old_parent and folder.startswith(
                                            old_parent):
                                        new_folder_path = new_path + folder[
                                            len(old_parent):]
                                        new_parent = new_path + parent[
                                            len(old_parent):]
                                        folder_info['parent'] = new_parent
                                        new_folders[
                                            new_folder_path] = folder_info
                                    else:
                                        new_folders[folder] = folder_info

                                self.folders = new_folders
                                self.update_treeview()
                                self.update_photolist()
                            else:
                                app.message("Invalid folder location.")
                    elif dropped_type == 'file':
                        # Dropped a file
                        photo_list = self.get_selected_photos(fullpath=True)
                        if fullpath not in photo_list:
                            photo_list.append(fullpath)
                        for photo_path in photo_list:
                            photo_info = False
                            if self.type == 'folder':
                                photo_info = self.find_photo(
                                    photo_path,
                                    self.folders[self.selected]['photos'])
                                if photo_info:
                                    self.folders[self.selected][
                                        'photos'].remove(photo_info)
                            else:
                                if self.selected == 'unsorted':
                                    photo_info = self.find_photo(
                                        photo_path, self.unsorted)
                                    if photo_info:
                                        self.unsorted.remove(photo_info)
                                elif self.selected == 'removed':
                                    photo_info = self.find_photo(
                                        photo_path, self.removed)
                                    if photo_info:
                                        self.removed.remove(photo_info)
                            if photo_info:
                                if widget.type == 'folder':
                                    self.folders[widget.target][
                                        'photos'].append(photo_info)
                                else:
                                    if widget.target == 'unsorted':
                                        self.unsorted.append(photo_info)
                                    elif widget.target == 'removed':
                                        self.removed.append(photo_info)

                        self.type = widget.type
                        self.selected = widget.target
                        self.update_treeview()
                        self.select_none()
                    break

    def update_selected(self):
        """Updates the delete button when files are selected or unselected.  Disables button if nothing is selected."""

        if self.type == 'folder' or (self.type == 'extra'
                                     and self.selected == 'unsorted'):
            photos = self.ids['photos']
            if photos.selected_nodes:
                selected = True
            else:
                selected = False
            delete_button = self.ids['deleteButton']
            if self.type != 'extra' and self.selected != 'unsorted':
                delete_button.disabled = not selected

    def has_popup(self):
        """Detects if the current screen has a popup active.
        Returns: True or False
        """

        if self.popup:
            if self.popup.open:
                return True
        return False

    def dismiss_popup(self, *_):
        """Close a currently open popup for this screen."""

        if self.popup:
            self.popup.dismiss()
            self.popup = None

    def text_input_active(self):
        """Detects if any text input fields are currently active (being typed in).
        Returns: True or False
        """

        input_active = False
        for widget in self.walk(restrict=True):
            if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput':
                if widget.focus:
                    input_active = True
                    break
        return input_active

    def key(self, key):
        """Handles keyboard shortcuts, performs the actions needed.
        Argument:
            key: The name of the key command to perform.
        """

        if self.text_input_active():
            pass
        else:
            if not self.popup or (not self.popup.open):
                if key == 'left' or key == 'up':
                    self.previous_album()
                if key == 'right' or key == 'down':
                    self.next_album()
                if key == 'delete':
                    self.delete()
                if key == 'a':
                    self.toggle_select()
            elif self.popup and self.popup.open:
                if key == 'enter':
                    self.popup.content.dispatch('on_answer', 'yes')
Пример #6
0
class ScreenDatabaseTransfer(Screen):
    """Database folder transfer screen layout."""

    popup = None
    database_dropdown_left = ObjectProperty()
    database_dropdown_right = ObjectProperty()
    left_database = StringProperty()
    right_database = StringProperty()
    left_sort_method = StringProperty()
    right_sort_method = StringProperty()
    left_sort_reverse = BooleanProperty()
    right_sort_reverse = BooleanProperty()
    left_sort_dropdown = ObjectProperty()
    right_sort_dropdown = ObjectProperty()
    quick = BooleanProperty(False)

    transfer_from = StringProperty()
    transfer_to = StringProperty()
    folders = ListProperty()

    cancel_copying = BooleanProperty(False)
    copying = BooleanProperty(False)
    copyingpopup = ObjectProperty()
    percent_completed = NumericProperty(0)
    copyingthread = ObjectProperty()

    selected = ''
    expanded_folders = []

    def has_popup(self):
        """Detects if the current screen has a popup active.
        Returns: True or False
        """

        if self.popup:
            if self.popup.open:
                return True
        return False

    def dismiss_extra(self):
        """Cancels the copy process if it is running"""

        if self.copying:
            self.cancel_copy()
            return True
        else:
            return False

    def dismiss_popup(self, *_):
        """Close a currently open popup for this screen."""

        if self.popup:
            self.popup.dismiss()
            self.popup = None

    def key(self, key):
        """Dummy function, not valid for this screen but the app calls it."""

        if not self.popup or (not self.popup.open):
            del key

    def resort_method_left(self, method):
        self.left_sort_method = method
        self.refresh_left_database()

    def resort_method_right(self, method):
        self.right_sort_method = method
        self.refresh_right_database()

    def left_resort_reverse(self, reverse):
        sort_reverse = True if reverse == 'down' else False
        self.left_sort_reverse = sort_reverse
        self.refresh_left_database()

    def right_resort_reverse(self, reverse):
        sort_reverse = True if reverse == 'down' else False
        self.right_sort_reverse = sort_reverse
        self.refresh_right_database()

    def on_enter(self):
        """Called when screen is entered, set up the needed variables and image viewer."""

        app = App.get_running_app()

        #set up sort buttons
        self.left_sort_dropdown = DatabaseSortDropDown()
        self.left_sort_dropdown.bind(
            on_select=lambda instance, x: self.resort_method_left(x))
        self.left_sort_method = app.config.get('Sorting', 'database_sort')
        self.left_sort_reverse = to_bool(
            app.config.get('Sorting', 'database_sort_reverse'))
        self.right_sort_dropdown = DatabaseSortDropDown()
        self.right_sort_dropdown.bind(
            on_select=lambda instance, x: self.resort_method_right(x))
        self.right_sort_method = app.config.get('Sorting', 'database_sort')
        self.right_sort_reverse = to_bool(
            app.config.get('Sorting', 'database_sort_reverse'))

        databases = app.get_database_directories()
        self.database_dropdown_left = NormalDropDown()
        self.database_dropdown_right = NormalDropDown()
        for database in databases:
            database_button_left = MenuButton(text=database)
            database_button_left.bind(on_release=self.set_database_left)
            self.database_dropdown_left.add_widget(database_button_left)
            database_button_right = MenuButton(text=database)
            database_button_right.bind(on_release=self.set_database_right)
            self.database_dropdown_right.add_widget(database_button_right)
        self.left_database = databases[0]
        self.right_database = databases[1]
        self.update_treeview()

    def set_database_left(self, button):
        self.database_dropdown_left.dismiss()
        if self.right_database == button.text:
            self.right_database = self.left_database
            self.refresh_right_database()
        self.left_database = button.text
        self.refresh_left_database()

    def set_database_right(self, button):
        self.database_dropdown_right.dismiss()
        if self.left_database == button.text:
            self.left_database = self.right_database
            self.refresh_left_database()
        self.right_database = button.text
        self.refresh_right_database()

    def refresh_left_database(self):
        database_area = self.ids['leftDatabaseHolder']
        self.refresh_database_area(database_area, self.left_database,
                                   self.left_sort_method,
                                   self.left_sort_reverse)

    def refresh_right_database(self):
        database_area = self.ids['rightDatabaseHolder']
        self.refresh_database_area(database_area, self.right_database,
                                   self.right_sort_method,
                                   self.right_sort_reverse)

    def drop_widget(self, fullpath, position, dropped_type, aspect=1):
        """Called when a widget is dropped after being dragged.
        Determines what to do with the widget based on where it is dropped.
        Arguments:
            fullpath: String, file location of the object being dragged.
            position: List of X,Y window coordinates that the widget is dropped on.
            dropped_type: String, describes the object's screenDatabase origin directory
        """

        app = App.get_running_app()
        transfer_from = dropped_type
        left_database_holder = self.ids['leftDatabaseHolder']
        left_database_area = self.ids['leftDatabaseArea']
        right_database_holder = self.ids['rightDatabaseHolder']
        right_database_area = self.ids['rightDatabaseArea']
        transfer_to = None
        folders = []
        if left_database_holder.collide_point(position[0], position[1]):
            if transfer_from != self.left_database:
                selects = right_database_area.selects
                for select in selects:
                    folders.append(local_path(select['fullpath']))
                transfer_to = self.left_database
        elif right_database_holder.collide_point(position[0], position[1]):
            if transfer_from != self.right_database:
                selects = left_database_area.selects
                for select in selects:
                    folders.append(local_path(select['fullpath']))
                transfer_to = self.right_database
        if transfer_to:
            if fullpath not in folders:
                folders.append(fullpath)
            #remove subfolders
            removes = []
            for folder in folders:
                for fold in folders:
                    if folder.startswith(fold + os.path.sep):
                        removes.append(folder)
                        break
            reduced_folders = []
            for folder in folders:
                if folder not in removes:
                    reduced_folders.append(folder)

            content = ConfirmPopup(text='Move These Folders From "' +
                                   transfer_from + '" to "' + transfer_to +
                                   '"?',
                                   yes_text='Move',
                                   no_text="Don't Move",
                                   warn_yes=True)
            content.bind(on_answer=self.move_folders)
            self.transfer_to = transfer_to
            self.transfer_from = transfer_from
            self.folders = reduced_folders
            self.popup = MoveConfirmPopup(title='Confirm Move',
                                          content=content,
                                          size_hint=(None, None),
                                          size=(app.popup_x,
                                                app.button_scale * 4),
                                          auto_dismiss=False)
            self.popup.open()

    def cancel_copy(self, *_):
        self.cancel_copying = True

    def move_folders(self, instance, answer):
        del instance
        app = App.get_running_app()
        self.dismiss_popup()
        if answer == 'yes':
            self.cancel_copying = False
            self.copyingpopup = ScanningPopup(title='Moving Files',
                                              auto_dismiss=False,
                                              size_hint=(None, None),
                                              size=(app.popup_x,
                                                    app.button_scale * 4))
            self.copyingpopup.open()
            scanning_button = self.copyingpopup.ids['scanningButton']
            scanning_button.bind(on_release=self.cancel_copy)

            # Start importing thread
            self.percent_completed = 0
            self.copyingthread = threading.Thread(target=self.move_process)
            self.copyingthread.start()

    def move_process(self):
        app = App.get_running_app()
        self.quick = app.config.get("Settings", "quicktransfer")
        transfer_from = self.transfer_from
        transfer_to = self.transfer_to
        folders = self.folders

        total_files = 0
        total_size = 0
        for folder in folders:
            origin = os.path.join(transfer_from, folder)
            for root, dirs, files in os.walk(origin):
                for file in files:
                    total_files = total_files + 1
                    total_size = total_size + os.path.getsize(
                        os.path.join(root, file))

        current_files = 0
        current_size = 0
        for folder in folders:
            origin = os.path.join(transfer_from, folder)
            #target = os.path.join(transfer_to, folder)
            for root, dirs, files in os.walk(origin, topdown=False):
                for file in files:
                    copy_from = os.path.join(root, file)
                    fullpath = os.path.relpath(copy_from, transfer_from)
                    copy_to = os.path.join(transfer_to, fullpath)
                    directory = os.path.split(copy_to)[0]
                    if not os.path.isdir(directory):
                        os.makedirs(directory)
                    self.copyingpopup.scanning_text = "Moving " + str(
                        current_files) + " of " + str(total_files) + "."
                    self.copyingpopup.scanning_percentage = (current_size /
                                                             total_size) * 100

                    if self.cancel_copying:
                        app.message("Canceled Moving Files, " +
                                    str(current_files) + " Files Moved.")
                        app.photos.commit()
                        self.copyingpopup.dismiss()
                        return
                    fileinfo = app.Photo.exist(fullpath)
                    copied = False
                    if self.quick == '1':
                        try:
                            move(copy_from, copy_to)
                            copied = True
                        except:
                            pass
                    else:
                        result = verify_copy(copy_from, copy_to)
                        if result is True:
                            os.remove(copy_from)
                            copied = True
                    if copied:
                        if fileinfo:
                            fileinfo[2] = transfer_to
                            app.Photo.moved(fileinfo)
                        current_files = current_files + 1
                        current_size = current_size + os.path.getsize(copy_to)
                    if os.path.isfile(copy_from):
                        if os.path.split(copy_from)[1] == '.photoinfo.ini':
                            os.remove(copy_from)
                try:
                    os.rmdir(root)
                except:
                    pass
        self.copyingpopup.dismiss()
        app.photos.commit()
        app.message("Finished Moving " + str(current_files) + " Files.")
        Clock.schedule_once(self.update_treeview)

    def toggle_expanded_folder(self, folder):
        if folder in self.expanded_folders:
            self.expanded_folders.remove(folder)
        else:
            self.expanded_folders.append(folder)
        self.update_treeview()

    def refresh_database_area(self, database, database_folder, sort_method,
                              sort_reverse):
        app = App.get_running_app()

        database.data = []
        data = []

        #Get and sort folder list
        unsorted_folders = app.Photo.by_folder(database_folder=database_folder)
        if sort_method in ['Amount', 'Title', 'Imported', 'Modified']:
            folders = []
            for folder in unsorted_folders:
                sortby = 0
                folderpath = folder
                if sort_method == 'Amount':
                    sortby = len(
                        app.Photo.by_folder(folderpath,
                                            database=database_folder))
                elif sort_method == 'Title':
                    folderinfo = app.Folder.exist(folderpath)
                    if folderinfo:
                        sortby = folderinfo[1]
                    else:
                        sortby = folderpath
                elif sort_method == 'Imported':
                    folder_photos = app.Photo.by_folder(
                        folderpath, database=database_folder)
                    for folder_photo in folder_photos:
                        if folder_photo[6] > sortby:
                            sortby = folder_photo[6]
                elif sort_method == 'Modified':
                    folder_photos = app.Photo.by_folder(
                        folderpath, database=database_folder)
                    for folder_photo in folder_photos:
                        if folder_photo[7] > sortby:
                            sortby = folder_photo[7]

                folders.append([sortby, folderpath])
            sorted_folders = sorted(folders,
                                    key=lambda x: x[0],
                                    reverse=sort_reverse)
            sorts, all_folders = zip(*sorted_folders)
        else:
            all_folders = sorted(unsorted_folders, reverse=sort_reverse)

        #Parse and sort folders and subfolders
        root_folders = []
        for full_folder in all_folders:
            if full_folder and not any(avoidfolder in full_folder
                                       for avoidfolder in avoidfolders):
                newname = full_folder
                children = root_folders
                parent_folder = ''
                while os.path.sep in newname:
                    #split the base path and the leaf paths
                    root, leaf = newname.split(os.path.sep, 1)
                    parent_folder = os.path.join(parent_folder, root)

                    #check if the root path is already in the tree
                    root_element = False
                    for child in children:
                        if child['folder'] == root:
                            root_element = child
                    if not root_element:
                        children.append({
                            'folder': root,
                            'full_folder': parent_folder,
                            'children': []
                        })
                        root_element = children[-1]
                    children = root_element['children']
                    newname = leaf
                root_element = False
                for child in children:
                    if child['folder'] == newname:
                        root_element = child
                if not root_element:
                    children.append({
                        'folder': newname,
                        'full_folder': full_folder,
                        'children': []
                    })

        folder_data = self.populate_folders(root_folders,
                                            self.expanded_folders, sort_method,
                                            sort_reverse, database_folder)
        data = data + folder_data

        database.data = data

    def populate_folders(self, folder_root, expanded, sort_method,
                         sort_reverse, database_folder):
        app = App.get_running_app()
        folders = []
        folder_root = self.sort_folders(folder_root, sort_method, sort_reverse)
        for folder in folder_root:
            full_folder = folder['full_folder']
            expandable = True if len(folder['children']) > 0 else False
            is_expanded = True if full_folder in expanded else False
            folder_info = app.Folder.exist(full_folder)
            if folder_info:
                subtext = folder_info[1]
            else:
                subtext = ''
            folder_element = {
                'fullpath': full_folder,
                'folder_name': folder['folder'],
                'target': full_folder,
                'type': 'Folder',
                'total_photos': '',
                'total_photos_numeric': 0,
                'displayable': True,
                'expandable': expandable,
                'expanded': is_expanded,
                'owner': self,
                'indent': 1 + full_folder.count(os.path.sep),
                'subtext': subtext,
                'height': app.button_scale * (1.5 if subtext else 1),
                'end': False,
                'droptype': database_folder,
                'dragable': True
            }
            folders.append(folder_element)
            if is_expanded:
                if len(folder['children']) > 0:
                    more_folders = self.populate_folders(
                        folder['children'], expanded, sort_method,
                        sort_reverse, database_folder)
                    folders = folders + more_folders
                    folders[-1]['end'] = True
                    folders[-1]['height'] = folders[-1]['height'] + int(
                        app.button_scale * 0.1)
        return folders

    def sort_folders(self, sort_folders, sort_method, sort_reverse):
        if sort_method in ['Amount', 'Title', 'Imported', 'Modified']:
            app = App.get_running_app()
            folders = []
            for folder in sort_folders:
                sortby = 0
                folderpath = folder['full_folder']
                if sort_method == 'Amount':
                    sortby = len(app.Photo.by_folder(folderpath))
                elif sort_method == 'Title':
                    folderinfo = app.Folder.exist(folderpath)
                    if folderinfo:
                        sortby = folderinfo[1]
                    else:
                        sortby = folderpath
                elif sort_method == 'Imported':
                    folder_photos = app.Photo.by_folder(folderpath)
                    for folder_photo in folder_photos:
                        if folder_photo[6] > sortby:
                            sortby = folder_photo[6]
                elif sort_method == 'Modified':
                    folder_photos = app.Photo.by_folder(folderpath)
                    for folder_photo in folder_photos:
                        if folder_photo[7] > sortby:
                            sortby = folder_photo[7]

                folders.append([sortby, folder])
            sorted_folders = sorted(folders,
                                    key=lambda x: x[0],
                                    reverse=sort_reverse)
            sorts, all_folders = zip(*sorted_folders)
        else:
            all_folders = sorted(sort_folders,
                                 key=lambda x: x['folder'],
                                 reverse=sort_reverse)

        return all_folders

    def update_treeview(self, *_):
        self.refresh_left_database()
        self.refresh_right_database()
Пример #7
0
class ScreenExporting(Screen):
    popup = None
    sort_dropdown = ObjectProperty()
    sort_method = StringProperty()
    sort_reverse = BooleanProperty(False)
    target = StringProperty()
    type = StringProperty()
    photos_selected = BooleanProperty(False)
    photos = []
    cancel_exporting = BooleanProperty(False)
    total_export_files = NumericProperty(0)
    exported_files = NumericProperty(0)
    total_export = NumericProperty(0)
    exported_size = NumericProperty(0)
    current_upload_blocks = NumericProperty(0)
    exporting = BooleanProperty(False)
    export_start_time = NumericProperty(0)
    scanningthread = None  #Holder for the exporting process thread.
    ftp = None
    sort_reverse_button = StringProperty('normal')
    selected_preset = NumericProperty(-1)

    def get_selected_photos(self, fullpath=False):
        photos = self.ids['photos']
        selected_indexes = photos.selected_nodes
        photos_container = self.ids['photosContainer']
        selected_photos = []
        for selected in selected_indexes:
            if fullpath:
                selected_photos.append(
                    photos_container.data[selected]['fullpath'])
            else:
                selected_photos.append(
                    photos_container.data[selected]['photoinfo'])
        return selected_photos

    def on_sort_reverse(self, *_):
        """Updates the sort reverse button's state variable, since kivy doesnt just use True/False for button states."""

        app = App.get_running_app()
        self.sort_reverse_button = 'down' if to_bool(
            app.config.get('Sorting', 'album_sort_reverse')) else 'normal'

    def can_export(self):
        return self.photos_selected

    def dismiss_extra(self):
        """Dummy function, not valid for this screen, but the app calls it when escape is pressed."""
        return False

    def resort_method(self, method):
        """Sets the album sort method.
        Argument:
            method: String, the sort method to use
        """

        self.sort_method = method
        app = App.get_running_app()
        app.config.set('Sorting', 'album_sort', method)
        self.update_photolist()

    def resort_reverse(self, reverse):
        """Sets the album sort reverse.
        Argument:
            reverse: String, if 'down', reverse will be enabled, disabled on any other string.
        """

        app = App.get_running_app()
        sort_reverse = True if reverse == 'down' else False
        app.config.set('Sorting', 'album_sort_reverse', sort_reverse)
        self.sort_reverse = sort_reverse
        self.update_photolist()

    def toggle_select(self):
        """Select all files, or unselect all selected files."""

        photos = self.ids['photos']
        photos.toggle_select()
        self.update_selected()

    def select_all(self):
        photos = self.ids['photos']
        photos.select_all()
        self.update_selected()

    def update_selected(self):
        """Checks if any viewed photos are selected."""

        photos = self.ids['photos']
        if photos.selected_nodes:
            selected = True
        else:
            selected = False
        self.photos_selected = selected

    def on_enter(self):
        """Called when this screen is entered.  Sets up widgets and gets the photo list."""

        self.selected_preset = -1
        app = App.get_running_app()
        self.exporting = False
        self.sort_dropdown = AlbumSortDropDown()
        self.sort_dropdown.bind(
            on_select=lambda instance, x: self.resort_method(x))
        self.sort_method = app.config.get('Sorting', 'album_sort')
        self.sort_reverse = to_bool(
            app.config.get('Sorting', 'album_sort_reverse'))
        self.target = app.export_target
        self.type = app.export_type

        #Get photos
        self.photos = []
        if self.type == 'Album':
            for albuminfo in app.albums:
                if albuminfo['name'] == self.target:
                    photo_paths = albuminfo['photos']
                    for fullpath in photo_paths:
                        photoinfo = app.Photo.exist(fullpath)
                        if photoinfo:
                            self.photos.append(photoinfo)
        elif self.type == 'Tag':
            self.photos = app.Tag.photos(self.target)
        else:
            self.photos = app.Photo.by_folder(self.target)

        self.update_treeview()
        self.update_photolist()
        photos = self.ids['photos']
        photos.select_all()
        self.update_selected()

    def update_photolist(self, select=True):
        """Clears and refreshes the grid view of photos."""

        #sort photo list
        if self.sort_method == 'Imported':
            sorted_photos = sorted(self.photos,
                                   key=lambda x: x[6],
                                   reverse=self.sort_reverse)
        elif self.sort_method == 'Modified':
            sorted_photos = sorted(self.photos,
                                   key=lambda x: x[7],
                                   reverse=self.sort_reverse)
        elif self.sort_method == 'Owner':
            sorted_photos = sorted(self.photos,
                                   key=lambda x: x[11],
                                   reverse=self.sort_reverse)
        elif self.sort_method == 'Name':
            sorted_photos = sorted(self.photos,
                                   key=lambda x: os.path.basename(x[0]),
                                   reverse=self.sort_reverse)
        else:
            sorted_photos = sorted(self.photos,
                                   key=lambda x: x[0],
                                   reverse=self.sort_reverse)

        #Create photo widgets
        photos_container = self.ids['photosContainer']
        datas = []
        for photo in sorted_photos:
            full_filename = os.path.join(photo[2], photo[0])
            tags = photo[8].split(',')
            favorite = True if 'favorite' in tags else False
            fullpath = photo[0]
            database_folder = photo[2]
            video = os.path.splitext(full_filename)[1].lower() in movietypes
            data = {
                'fullpath': fullpath,
                'photoinfo': photo,
                'folder': self.target,
                'database_folder': database_folder,
                'filename': full_filename,
                'target': self.target,
                'type': self.type,
                'owner': self,
                'favorite': favorite,
                'video': video,
                'photo_orientation': photo[13],
                'source': full_filename,
                'temporary': False,
                'selected': False,
                'selectable': True,
                'dragable': False,
                'view_album': False
            }
            datas.append(data)
        photos_container.data = datas
        if select:
            self.select_all()

    def on_leave(self):
        """Called when the screen is left, clean up data."""

        presets = self.ids['presets']
        presets.clear_widgets()
        photo_container = self.ids['photosContainer']
        photo_container.data = []

    def update_treeview(self):
        """Clears and populates the export presets list on the left side."""

        app = App.get_running_app()
        presets = self.ids['presets']

        #Clear old presets
        presets.clear_widgets()

        #Populate export presets nodes
        for index, export_preset in enumerate(app.exports):
            preset = ExportPreset(index=index,
                                  text=export_preset['name'],
                                  data=export_preset,
                                  owner=self)
            if index == self.selected_preset:
                preset.expanded = True
            presets.add_widget(preset)

    def cancel_export(self, *_):
        """Signal to stop the exporting process.  Will also try to close the ftp connection if it exists."""

        self.cancel_exporting = True
        try:
            self.ftp.close()
        except:
            pass

    def export(self):
        """Begins the export process.  Opens a progress dialog, and starts the export thread."""

        self.ftp = False
        app = App.get_running_app()
        preset = app.exports[self.selected_preset]
        if preset['export'] == 'ftp':
            if not preset['ftp_address']:
                app.message(text="Please Set Export Location")
                return
        else:
            if not preset['export_folder']:
                app.message(text="Please Set Export Location")
                return
        if not self.photos_selected:
            app.message(text="Please Select Photos To Export")
            return
        self.cancel_exporting = False
        self.popup = ScanningPopup(title='Exporting Files',
                                   auto_dismiss=False,
                                   size_hint=(None, None),
                                   size=(app.popup_x, app.button_scale * 4))
        self.popup.open()
        scanning_button = self.popup.ids['scanningButton']
        scanning_button.bind(on_release=self.cancel_export)
        self.scanningthread = threading.Thread(target=self.exporting_process)
        self.scanningthread.start()

    def update_percentage(self, *_):
        """Updates the exporting process percentage value in the exporting dialog."""

        self.current_upload_blocks = self.current_upload_blocks + 1
        file_completed = (8192 * self.current_upload_blocks)
        percent_completed = int(
            100 * ((self.exported_size + file_completed) / self.total_export))
        self.popup.scanning_percentage = percent_completed
        time_taken = time.time() - self.export_start_time
        if percent_completed > 0:
            total_time = (100 / percent_completed) * time_taken
            time_remaining = total_time - time_taken
            str(datetime.timedelta(seconds=time_remaining))
            remaining = ', ' + str(
                datetime.timedelta(seconds=int(time_remaining))) + ' Remaining'
        else:
            remaining = ''
        self.popup.scanning_text = 'Uploading: ' + str(
            self.exported_files) + ' out of ' + str(
                self.total_export_files) + ' files' + remaining

    def exporting_process(self):
        """Handles exporting the files.  This should be in a different thread so the interface can still respond."""

        self.exporting = True
        app = App.get_running_app()
        preset = app.exports[self.selected_preset]

        #Get photo list
        ignore_tags = preset['ignore_tags']
        exported_photos = 0
        selected_photos = self.get_selected_photos()
        photos = []
        for photo in selected_photos:
            if photo[12] != 0:
                ignore_file = False
                if ignore_tags:
                    for tag in ignore_tags:
                        photo_tags = photo[8].split(',')
                        if tag in photo_tags:
                            ignore_file = True
                if not preset['export_videos']:
                    path, extension = os.path.splitext(photo[0])
                    if extension.lower() in movietypes:
                        ignore_file = True
                if not ignore_file:
                    photos.append(photo)

        if not photos:
            return

        #determine export filenames (prevent any duplicate filenames)
        export_photos = []
        for photo in photos:
            photo_filename = os.path.basename(photo[0])
            basename, extension = os.path.splitext(photo_filename)
            test_name = photo_filename
            add_number = 0
            while test_name in export_photos:
                add_number = add_number + 1
                test_name = basename + "(" + str(add_number) + ")" + extension
            export_photos.append(test_name)

        if self.type == 'tag':
            subfolder = 'Photos Tagged As ' + self.target.title()
        else:
            subfolder = os.path.split(self.target)[1]

        #ftp export mode
        if preset['export'] == 'ftp':
            subfolder = subfolder.replace("'", "").replace("/", " - ").replace(
                "\\", " - ")
            if '/' in preset['ftp_address']:
                ftp_host, ftp_folder = preset['ftp_address'].split('/', 1)
                ftp_folder = ftp_folder.strip('/')
            else:
                ftp_host = preset['ftp_address']
                ftp_folder = ''
            from ftplib import FTP
            try:
                self.ftp = ftp = FTP()
                self.popup.scanning_text = 'Connecting To FTP...'
                ftp.connect(ftp_host, preset['ftp_port'])
                self.popup.scanning_text = 'Logging In To FTP...'
                ftp.login(preset['ftp_user'], preset['ftp_password'])
                ftp.set_pasv(preset['ftp_passive'])
                self.popup.scanning_text = 'Creating Folders...'
                ftp_filelist = ftp.nlst()

                #set the ftp folder and create if needed
                if ftp_folder:
                    subfolders = ftp_folder.split('/')
                    for folder in subfolders:
                        if folder not in ftp_filelist:
                            ftp.mkd(folder)
                        ftp.cwd(folder)
                        ftp_filelist = ftp.nlst()
                if preset['create_subfolder']:
                    file_list = ftp.nlst()
                    if subfolder not in file_list:
                        ftp.mkd(subfolder)
                    ftp.cwd(subfolder)
                    ftp_filelist = ftp.nlst()

                if preset['export_info']:
                    self.popup.scanning_text = 'Uploading Photo Info...'
                    infofile = os.path.join(".photoinfo.ini")
                    if os.path.exists(infofile):
                        os.remove(infofile)
                    app.save_photoinfo(self.target,
                                       '.',
                                       '',
                                       photos=photos,
                                       newnames=export_photos)
                    if '.photoinfo.ini' in ftp_filelist:
                        ftp.delete('.photoinfo.ini')
                    if os.path.exists(infofile):
                        ftp.storbinary("STOR .photoinfo.ini",
                                       open(infofile, 'rb'))
                        os.remove(infofile)
                self.total_export = 0
                for photo in photos:
                    photofile = os.path.join(photo[2], photo[0])
                    if os.path.exists(photofile):
                        self.total_export = self.total_export + os.path.getsize(
                            photofile)
                self.popup.scanning_text = 'Uploading ' + str(
                    len(photos)) + ' Files'
                self.exported_size = 0
                self.total_export_files = len(photos)
                self.export_start_time = time.time()
                for index, photo in enumerate(photos):
                    self.exported_files = index + 1
                    percent_completed = 100 * (self.exported_size /
                                               self.total_export)
                    self.popup.scanning_percentage = percent_completed
                    if self.cancel_exporting:
                        self.popup.scanning_text = 'Upload Canceled, ' + str(
                            index) + ' Files Uploaded'
                        break
                    photofile = os.path.join(photo[2], photo[0])
                    if os.path.exists(photofile):
                        photo_size = os.path.getsize(photofile)
                        extension = os.path.splitext(photofile)[1]
                        photofilename = export_photos[index]
                        #photofilename = os.path.basename(photofile)
                        if photofilename in ftp_filelist:
                            ftp.delete(photofilename)

                        if extension.lower() in imagetypes and (
                                preset['scale_image'] or preset['watermark']):
                            #image needs to be edited in some way
                            imagedata = Image.open(photofile)
                            if imagedata.mode != 'RGB':
                                imagedata = imagedata.convert('RGB')

                            orientation = photo[13]
                            imagedata = app.edit_fix_orientation(
                                imagedata, orientation)

                            if preset['scale_image']:
                                imagedata = app.edit_scale_image(
                                    imagedata, preset['scale_size'],
                                    preset['scale_size_to'])
                            if preset['watermark']:
                                imagedata = app.edit_add_watermark(
                                    imagedata, preset['watermark_image'],
                                    preset['watermark_opacity'],
                                    preset['watermark_horizontal'],
                                    preset['watermark_vertical'],
                                    preset['watermark_size'])
                            output = BytesIO()
                            imagedata.save(output,
                                           'JPEG',
                                           quality=preset['jpeg_quality'])
                            output.seek(0)
                            self.current_upload_blocks = 0
                            ftp.storbinary("STOR " + photofilename,
                                           output,
                                           callback=self.update_percentage)
                        else:
                            #image or video should just be uploaded
                            self.current_upload_blocks = 0
                            ftp.storbinary("STOR " + photofilename,
                                           open(photofile, 'rb'),
                                           callback=self.update_percentage)
                        exported_photos = exported_photos + 1
                        self.exported_size = self.exported_size + photo_size

                        #check that the file was uploaded
                        ftp_filelist = ftp.nlst()
                        if photofilename not in ftp_filelist:
                            self.cancel_exporting = True
                            self.popup.scanning_text = 'Unable To Upload "' + photo[
                                0] + '".'
                ftp.quit()
                ftp.close()
                self.ftp = False
            except Exception as e:
                if self.cancel_exporting:
                    self.popup.scanning_text = 'Canceled Upload. Partial Files May Be Left On The Server.'
                else:
                    self.cancel_exporting = True
                    self.popup.scanning_text = 'Unable To Upload: ' + str(e)

        #local directory export mode
        else:
            if preset['create_subfolder']:
                save_location = os.path.join(preset['export_folder'],
                                             subfolder)
            else:
                save_location = preset['export_folder']
            if not os.path.exists(save_location):
                os.makedirs(save_location)
            if preset['export_info']:
                app.save_photoinfo(self.target,
                                   save_location,
                                   self.type.lower(),
                                   photos=photos,
                                   newnames=export_photos)
            self.total_export = 0
            for photo in photos:
                photofile = os.path.join(photo[2], photo[0])
                if os.path.exists(photofile):
                    self.total_export = self.total_export + os.path.getsize(
                        photofile)
            self.popup.scanning_text = 'Exporting ' + str(
                len(photos)) + ' Files'
            self.exported_size = 0
            self.total_export_files = len(photos)
            self.export_start_time = time.time()
            for index, photo in enumerate(photos):
                self.exported_files = index + 1
                percent_completed = 100 * (self.exported_size /
                                           self.total_export)
                self.popup.scanning_percentage = percent_completed
                if self.cancel_exporting:
                    self.popup.scanning_text = 'Export Canceled, ' + str(
                        index) + ' Files Exported'
                    break
                photofile = os.path.join(photo[2], photo[0])
                if os.path.exists(photofile):
                    photo_size = os.path.getsize(photofile)
                    extension = os.path.splitext(photofile)[1]
                    #photofilename = os.path.basename(photofile)
                    photofilename = export_photos[index]
                    savefile = os.path.join(save_location, photofilename)
                    if os.path.exists(savefile):
                        os.remove(savefile)
                    if extension.lower() in imagetypes and (
                            preset['scale_image'] or preset['watermark']):
                        #image needs to be edited in some way
                        imagedata = Image.open(photofile)
                        if imagedata.mode != 'RGB':
                            imagedata = imagedata.convert('RGB')
                        orientation = photo[13]
                        imagedata = app.edit_fix_orientation(
                            imagedata, orientation)

                        if preset['scale_image']:
                            imagedata = app.edit_scale_image(
                                imagedata, preset['scale_size'],
                                preset['scale_size_to'])
                        if preset['watermark']:
                            imagedata = app.edit_add_watermark(
                                imagedata, preset['watermark_image'],
                                preset['watermark_opacity'],
                                preset['watermark_horizontal'],
                                preset['watermark_vertical'],
                                preset['watermark_size'])
                        imagedata.save(savefile,
                                       'JPEG',
                                       quality=preset['jpeg_quality'])
                    else:
                        #image or video should just be copied
                        copy2(photofile, savefile)
                    exported_photos = exported_photos + 1
                    self.exported_size = self.exported_size + photo_size
            self.exporting = False
        if not self.cancel_exporting:
            app.message('Completed Exporting ' + str(len(photos)) + ' files.')
            Clock.schedule_once(self.finish_export)
        else:
            scanning_button = self.popup.ids['scanningButton']
            scanning_button.text = 'OK'
            scanning_button.bind(on_release=self.finish_export)

    def finish_export(self, *_):
        """Closes the export popup and leaves this screen."""

        self.popup.dismiss()
        app = App.get_running_app()
        app.show_database()

    def add_preset(self):
        """Create a new export preset and refresh the preset list."""

        app = App.get_running_app()
        app.export_preset_new()
        self.selected_preset = len(app.exports) - 1
        self.update_treeview()

    def has_popup(self):
        """Detects if the current screen has a popup active.
        Returns: True or False
        """

        if self.popup:
            if self.popup.open:
                return True
        return False

    def dismiss_popup(self, *_):
        """Close a currently open popup for this screen."""

        if self.exporting:
            self.cancel_export()
        else:
            if self.popup:
                self.popup.dismiss()
                self.popup = None

    def text_input_active(self):
        """Detects if any text input fields are currently active (being typed in).
        Returns: True or False
        """

        input_active = False
        for widget in self.walk(restrict=True):
            if widget.__class__.__name__ == 'NormalInput' or widget.__class__.__name__ == 'FloatInput' or widget.__class__.__name__ == 'IntegerInput':
                if widget.focus:
                    input_active = True
                    break
        return input_active

    def key(self, key):
        """Handles keyboard shortcuts, performs the actions needed.
        Argument:
            key: The name of the key command to perform.
        """

        if self.text_input_active():
            pass
        else:
            if not self.popup or (not self.popup.open):
                if key == 'a':
                    self.toggle_select()