diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index a640a6fcd1..72779d096f 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -381,7 +381,6 @@ class Win32Freeze(Command, WixMixIn): sys.exit(1) def build_portable_installer(self): - base = self.portable_base zf = self.a(self.j('dist', 'calibre-portable-%s.zip.lz'%VERSION)) usz = os.path.getsize(zf) def cc(src, obj): @@ -442,7 +441,7 @@ class Win32Freeze(Command, WixMixIn): '/RELEASE', '/ENTRY:wWinMainCRTStartup', '/OUT:'+exe, self.embed_resources(exe), - obj, 'User32.lib', 'Shlwapi.lib'] + obj, 'User32.lib'] self.run_builder(cmd) self.info('Creating portable installer') diff --git a/setup/installer/windows/portable.c b/setup/installer/windows/portable.c index 606057432f..874838c9a2 100644 --- a/setup/installer/windows/portable.c +++ b/setup/installer/windows/portable.c @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -90,7 +89,7 @@ LPTSTR get_app_dir() { return buf3; } -void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { +void launch_calibre(LPCTSTR exe, LPCTSTR config_dir) { DWORD dwFlags=0; STARTUPINFO si; PROCESS_INFORMATION pi; @@ -108,13 +107,12 @@ void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { } dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP; - _sntprintf_s(cmdline, BUFSIZE, _TRUNCATE, _T(" \"--with-library=%s\""), library_dir); ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); - fSuccess = CreateProcess(exe, cmdline, + fSuccess = CreateProcess(exe, NULL, NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE @@ -135,45 +133,6 @@ void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { } -static BOOL is_dots(LPCTSTR name) { - return _tcscmp(name, _T(".")) == 0 || _tcscmp(name, _T("..")) == 0; -} - -static void find_calibre_library(LPTSTR library_dir) { - TCHAR base[BUFSIZE] = {0}, buf[BUFSIZE] = {0}; - WIN32_FIND_DATA fdFile; - HANDLE hFind = NULL; - - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\metadata.db"), base); - - if (PathFileExists(buf)) return; // Calibre Library/metadata.db exists, we use it - - _tcscpy(base, library_dir); - PathRemoveFileSpec(base); - - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\*"), base); - - // Look for some other folder that contains a metadata.db file inside the Calibre Portable folder - if((hFind = FindFirstFileEx(buf, FindExInfoStandard, &fdFile, FindExSearchLimitToDirectories, NULL, 0)) - != INVALID_HANDLE_VALUE) { - do { - if(is_dots(fdFile.cFileName)) continue; - - if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\%s\\metadata.db"), base, fdFile.cFileName); - if (PathFileExists(buf)) { - // some dir/metadata.db exists, we use it as the library - PathRemoveFileSpec(buf); - _tcscpy(library_dir, buf); - FindClose(hFind); - return; - } - } - } while(FindNextFile(hFind, &fdFile)); - FindClose(hFind); - } - -} int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { @@ -181,26 +140,14 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine app_dir = get_app_dir(); config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - library_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); _sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir); _sntprintf_s(exe, BUFSIZE, _TRUNCATE, _T("%sCalibre\\calibre.exe"), app_dir); - _sntprintf_s(library_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Library"), app_dir); - find_calibre_library(library_dir); + launch_calibre(exe, config_dir); - if ( _tcscnlen(library_dir, BUFSIZE) <= 74 ) { - launch_calibre(exe, config_dir, library_dir); - } else { - too_long = (LPTSTR)calloc(BUFSIZE+300, sizeof(TCHAR)); - _sntprintf_s(too_long, BUFSIZE+300, _TRUNCATE, - _T("Path to Calibre Portable (%s) too long. Must be less than 59 characters."), app_dir); - - show_error(too_long); - } - - free(app_dir); free(config_dir); free(exe); free(library_dir); + free(app_dir); free(config_dir); free(exe); return 0; } diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 391a64027f..aafec33c3b 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -177,6 +177,11 @@ def get_version(): v += '*' return v +def get_portable_base(): + 'Return path to the directory that contains calibre-portable.exe or None' + if isportable: + return os.path.dirname(os.path.dirname(os.environ['CALIBRE_PORTABLE_BUILD'])) + def get_unicode_windows_env_var(name): import ctypes name = unicode(name) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 7a5e71e17b..771234691a 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -567,7 +567,8 @@ class FileDialog(QObject): modal = True, name = '', mode = QFileDialog.ExistingFiles, - default_dir='~' + default_dir='~', + no_save_dir=False ): QObject.__init__(self) ftext = '' @@ -586,8 +587,11 @@ class FileDialog(QObject): self.selected_files = None self.fd = None - initial_dir = dynamic.get(self.dialog_name, - os.path.expanduser(default_dir)) + if no_save_dir: + initial_dir = os.path.expanduser(default_dir) + else: + initial_dir = dynamic.get(self.dialog_name, + os.path.expanduser(default_dir)) if not isinstance(initial_dir, basestring): initial_dir = os.path.expanduser(default_dir) self.selected_files = [] @@ -629,7 +633,8 @@ class FileDialog(QObject): saved_loc = self.selected_files[0] if os.path.isfile(saved_loc): saved_loc = os.path.dirname(saved_loc) - dynamic[self.dialog_name] = saved_loc + if not no_save_dir: + dynamic[self.dialog_name] = saved_loc self.accepted = bool(self.selected_files) def get_files(self): @@ -638,10 +643,10 @@ class FileDialog(QObject): return tuple(self.selected_files) -def choose_dir(window, name, title, default_dir='~'): +def choose_dir(window, name, title, default_dir='~', no_save_dir=False): fd = FileDialog(title=title, filters=[], add_all_files_filter=False, parent=window, name=name, mode=QFileDialog.Directory, - default_dir=default_dir) + default_dir=default_dir, no_save_dir=no_save_dir) dir = fd.get_files() fd.setParent(None) if dir: diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 0a361209e6..829268adee 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, posixpath from functools import partial from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, @@ -13,7 +13,8 @@ from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, QCoreApplication, pyqtSignal) from calibre import isbytestring, sanitize_file_name_unicode -from calibre.constants import filesystem_encoding, iswindows +from calibre.constants import (filesystem_encoding, iswindows, + get_portable_base) from calibre.utils.config import prefs from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog, question_dialog, info_dialog, open_local_file, choose_dir) @@ -25,6 +26,17 @@ class LibraryUsageStats(object): # {{{ def __init__(self): self.stats = {} self.read_stats() + base = get_portable_base() + if base is not None: + lp = prefs['library_path'] + if lp: + # Rename the current library. Renaming of other libraries is + # handled by the switch function + q = os.path.basename(lp) + for loc in list(self.stats.iterkeys()): + bn = posixpath.basename(loc) + if bn.lower() == q.lower(): + self.rename(loc, lp) def read_stats(self): stats = gprefs.get('library_usage_stats', {}) @@ -417,6 +429,18 @@ class ChooseLibraryAction(InterfaceAction): finally: self.gui.status_bar.clear_message() + def look_for_portable_lib(self, db, location): + base = get_portable_base() + if base is None: + return False, None + loc = location.replace('/', os.sep) + candidate = os.path.join(base, os.path.basename(loc)) + if db.exists_at(candidate): + newloc = candidate.replace(os.sep, '/') + self.stats.rename(location, newloc) + return True, newloc + return False, None + def switch_requested(self, location): if not self.change_library_allowed(): return @@ -425,6 +449,12 @@ class ChooseLibraryAction(InterfaceAction): self.view_state_map[current_lib] = self.preserve_state_on_switch.state loc = location.replace('/', os.sep) exists = db.exists_at(loc) + if not exists: + exists, new_location = self.look_for_portable_lib(db, location) + if exists: + location = new_location + loc = location.replace('/', os.sep) + if not exists: d = MovedDialog(self.stats, location, self.gui) ret = d.exec_() diff --git a/src/calibre/gui2/dialogs/choose_library.py b/src/calibre/gui2/dialogs/choose_library.py index 62d6c4c437..91048e8ff1 100644 --- a/src/calibre/gui2/dialogs/choose_library.py +++ b/src/calibre/gui2/dialogs/choose_library.py @@ -11,8 +11,9 @@ from PyQt4.Qt import QDialog from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog from calibre.gui2 import error_dialog, choose_dir -from calibre.constants import filesystem_encoding, iswindows -from calibre import isbytestring, patheq +from calibre.constants import (filesystem_encoding, iswindows, + get_portable_base) +from calibre import isbytestring, patheq, force_unicode from calibre.gui2.wizard import move_library from calibre.library.database2 import LibraryDatabase2 @@ -39,18 +40,45 @@ class ChooseLibrary(QDialog, Ui_Dialog): self.copy_structure.setEnabled(to_what) def choose_loc(self, *args): - loc = choose_dir(self, 'choose library location', - _('Choose location for calibre library')) + 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'), diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index b52bd2cadb..0b4a755679 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -9,7 +9,7 @@ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer, from calibre import prints, plugins, force_unicode from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux, - filesystem_encoding) + filesystem_encoding, get_portable_base) from calibre.utils.ipc import gui_socket_address, RC from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider, Application, choose_dir, error_dialog, question_dialog, gprefs) @@ -21,6 +21,9 @@ from calibre.library.sqlite import sqlite, DatabaseException if iswindows: winutil = plugins['winutil'][0] +class AbortInit(Exception): + pass + def option_parser(): parser = _option_parser('''\ %prog [opts] [path_to_ebook] @@ -46,10 +49,43 @@ path_to_ebook to the database. 'will be silently aborted, so use with care.')) return parser +def find_portable_library(): + base = get_portable_base() + if base is None: return + import glob + candidates = [os.path.basename(os.path.dirname(x)) for x in glob.glob( + os.path.join(base, u'*%smetadata.db'%os.sep))] + if not candidates: + candidates = [u'Calibre Library'] + lp = prefs['library_path'] + if not lp: + lib = os.path.join(base, candidates[0]) + else: + lib = None + q = os.path.basename(lp) + for c in candidates: + c = c + if c.lower() == q.lower(): + lib = os.path.join(base, c) + break + if lib is None: + lib = os.path.join(base, candidates[0]) + + if len(lib) > 74: + error_dialog(None, _('Path too long'), + _("Path to Calibre Portable (%s) " + 'too long. Must be less than 59 characters.')%base, show=True) + raise AbortInit() + + prefs.set('library_path', lib) + if not os.path.exists(lib): + os.mkdir(lib) + def init_qt(args): from calibre.gui2.ui import Main parser = option_parser() opts, args = parser.parse_args(args) + find_portable_library() if opts.with_library is not None: if not os.path.exists(opts.with_library): os.makedirs(opts.with_library) @@ -360,7 +396,10 @@ def main(args=sys.argv): gui_debug = args[1] args = ['calibre'] - app, opts, args, actions = init_qt(args) + try: + app, opts, args, actions = init_qt(args) + except AbortInit: + return 1 from calibre.utils.lock import singleinstance from multiprocessing.connection import Listener si = singleinstance('calibre GUI')