mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Portable: Add support for multiple libraries
This commit is contained in:
parent
3c1053b765
commit
bd83b0532e
@ -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')
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
|
||||
#include <windows.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <tchar.h>
|
||||
#include <wchar.h>
|
||||
#include <stdio.h>
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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_()
|
||||
|
@ -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'),
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user