#!/usr/local/bin/python3.13


__license__   = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import errno
import os
from threading import Event, Thread

from qt.core import QDialog, Qt, QTimer, pyqtSignal

from calibre import force_unicode, isbytestring, patheq
from calibre.constants import filesystem_encoding, get_portable_base, iswindows
from calibre.gui2 import choose_dir, error_dialog
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog
from calibre.gui2.dialogs.progress import ProgressDialog as PD
from calibre.utils.localization import localize_user_manual_link


class ProgressDialog(PD):

    on_progress_update = pyqtSignal(object, object, object)
    finished_moving = pyqtSignal()

    def __init__(self, *args, **kwargs):
        PD.__init__(self, *args, **kwargs)
        self.on_progress_update.connect(self.progressed, type=Qt.ConnectionType.QueuedConnection)
        self.finished_moving.connect(self.accept, type=Qt.ConnectionType.QueuedConnection)

    def reject(self):
        return

    def progressed(self, item_name, count, total):
        self.max = total
        self.value = count
        self.set_msg(item_name)

    def show_new_progress(self, *args):
        self.on_progress_update.emit(*args)


class ChooseLibrary(QDialog, Ui_Dialog):

    def __init__(self, db, callback, parent):
        super().__init__(parent)
        self.setupUi(self)
        self.nas_warning.setText(self.nas_warning.text().format(localize_user_manual_link(
            'https://manual.calibre-ebook.com/faq.html#i-am-getting-errors-with-my-calibre-library-on-a-networked-drive-nas')))
        self.nas_warning.setOpenExternalLinks(True)
        self.db = db
        self.new_db = None
        self.callback = callback
        self.location.initialize('choose_library_dialog')

        lp = db.library_path
        if isbytestring(lp):
            lp = lp.decode(filesystem_encoding)
        loc = str(self.old_location.text()).format(lp)
        self.old_location.setText(loc)
        self.browse_button.clicked.connect(self.choose_loc)
        self.empty_library.toggled.connect(self.empty_library_toggled)
        self.copy_structure.setEnabled(False)

    def empty_library_toggled(self, to_what):
        self.copy_structure.setEnabled(to_what)

    def choose_loc(self, *args):
        base = get_portable_base()
        if base is None:
            loc = choose_dir(self, 'choose library location',
                    _('Choose location for calibre library'))
        else:
            name = force_unicode('choose library loc at' + base,
                    filesystem_encoding)
            loc = choose_dir(self, name,
                    _('Choose location for calibre library'), default_dir=base,
                    no_save_dir=True)
        if loc is not None:
            self.location.setText(loc)

    def check_action(self, ac, loc):
        exists = self.db.exists_at(loc)
        base = get_portable_base()
        if patheq(loc, self.db.library_path):
            error_dialog(self, _('Same as current'),
                    _('The location %s contains the current calibre'
                        ' library')%loc, show=True)
            return False

        if base is not None and ac in ('new', 'move'):
            abase = os.path.normcase(os.path.abspath(base))
            cal = os.path.normcase(os.path.abspath(os.path.join(abase,
                'Calibre')))
            aloc = os.path.normcase(os.path.abspath(loc))
            if (aloc.startswith(cal+os.sep) or aloc == cal):
                error_dialog(self, _('Bad location'),
                    _('You should not create a library inside the calibre'
                        ' folder as this folder is automatically deleted during upgrades.'),
                    show=True)
                return False
            if aloc.startswith(abase) and os.path.dirname(aloc) != abase:
                error_dialog(self, _('Bad location'),
                    _('You can only create libraries inside %s at the top '
                        'level, not in sub-folders')%base, show=True)
                return False

        empty = not os.listdir(loc)
        if ac == 'existing' and not exists:
            error_dialog(self, _('No existing library found'),
                    _('There is no existing calibre library at %s')%loc,
                    show=True)
            return False
        if ac in ('new', 'move'):
            from calibre.db.legacy import LibraryDatabase
            if not empty:
                error_dialog(self, _('Not empty'),
                    _('The folder %s is not empty. Please choose an empty'
                       ' folder.')%loc,
                    show=True)
                return False
            if (iswindows and len(loc) >
                    LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT):
                error_dialog(self, _('Too long'),
                    _('Path to library too long. It must be less than'
                    ' %d characters.')%LibraryDatabase.WINDOWS_LIBRARY_PATH_LIMIT,
                    show=True)
                return False

        return True

    def perform_action(self, ac, loc):
        if ac in ('new', 'existing'):
            self.callback(loc, copy_structure=self.copy_structure.isChecked())
        else:
            # move library
            self.db.prefs.disable_setting = True
            abort_move = Event()
            pd = ProgressDialog(_('Moving library, please wait...'), _('Scanning...'), max=0, min=0, icon='lt.png', parent=self)
            pd.canceled_signal.connect(abort_move.set)
            self.parent().library_view.model().stop_metadata_backup()
            move_error = []

            def do_move():
                try:
                    self.db.new_api.move_library_to(loc, abort=abort_move, progress=pd.show_new_progress)
                except Exception:
                    import traceback
                    move_error.append(traceback.format_exc())
                finally:
                    pd.finished_moving.emit()

            t = Thread(name='MoveLibrary', target=do_move)
            QTimer.singleShot(0, t.start)
            pd.exec()
            if abort_move.is_set():
                self.callback(self.db.library_path)
                return
            if move_error:
                error_dialog(self.parent(), _('Failed to move library'), _(
                    'There was an error while moving the library. The operation has been aborted. Click'
                    ' "Show details" for details.'), det_msg=move_error[0], show=True)
                self.callback(self.db.library_path)
                return
            self.callback(loc, library_renamed=True)

    def accept(self):
        action = 'move'
        if self.existing_library.isChecked():
            action = 'existing'
        elif self.empty_library.isChecked():
            action = 'new'
        text = str(self.location.text()).strip()
        if not text:
            return error_dialog(self, _('No location'), _('No location selected'),
                    show=True)
        loc = os.path.abspath(text)
        if action == 'move':
            try:
                os.makedirs(loc)
            except OSError as e:
                if e.errno != errno.EEXIST:
                    raise
        if not loc or not os.path.exists(loc) or not os.path.isdir(loc):
            if action == 'new' and not os.path.exists(loc):
                os.makedirs(loc)
            else:
                return error_dialog(self, _('Bad location'),
                        _('%s is not an existing folder')%loc, show=True)
        if not self.check_action(action, loc):
            return
        self.location.save_history()
        self.perform_action(action, loc)
        QDialog.accept(self)  # Must be after perform action otherwise the progress dialog is not updated on windows
