diff --git a/setup/installer/windows/__init__.py b/setup/installer/windows/__init__.py index b51eccc832..324dea6e0f 100644 --- a/setup/installer/windows/__init__.py +++ b/setup/installer/windows/__init__.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import os, shutil, subprocess -from setup import Command, __appname__ +from setup import Command, __appname__, __version__ from setup.installer import VMInstaller class Win(Command): @@ -43,4 +43,11 @@ class Win32(VMInstaller): self.warn('Failed to freeze') raise SystemExit(1) + installer = 'dist/%s-portable-%s.zip'%(__appname__, __version__) + subprocess.check_call(('scp', + 'xp_build:build/%s/%s'%(__appname__, installer), 'dist')) + if not os.path.exists(installer): + self.warn('Failed to get portable installer') + raise SystemExit(1) + diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index 63993c19f0..7d1928bd13 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -50,7 +50,7 @@ def walk(dir): class Win32Freeze(Command, WixMixIn): - description = 'Free windows calibre installation' + description = 'Freeze windows calibre installation' def add_options(self, parser): parser.add_option('--no-ice', default=False, action='store_true', @@ -74,6 +74,8 @@ class Win32Freeze(Command, WixMixIn): self.pylib = self.j(self.base, 'pylib.zip') self.dll_dir = self.j(self.base, 'DLLs') self.plugins_dir = os.path.join(self.base, 'plugins2') + self.portable_base = self.j(self.d(self.base), 'Calibre Portable') + self.obj_dir = self.j(self.src_root, 'build', 'launcher') self.initbase() self.build_launchers() @@ -84,6 +86,7 @@ class Win32Freeze(Command, WixMixIn): self.archive_lib_dir() self.remove_CRT_from_manifests() self.create_installer() + self.build_portable() def remove_CRT_from_manifests(self): ''' @@ -287,7 +290,7 @@ class Win32Freeze(Command, WixMixIn): def embed_resources(self, module, desc=None): icon_base = self.j(self.src_root, 'icons') icon_map = {'calibre':'library', 'ebook-viewer':'viewer', - 'lrfviewer':'viewer'} + 'lrfviewer':'viewer', 'calibre-portable':'library'} file_type = 'DLL' if module.endswith('.dll') else 'APP' template = open(self.rc_template, 'rb').read() bname = self.b(module) @@ -344,8 +347,62 @@ class Win32Freeze(Command, WixMixIn): self.info(p.stderr.read()) sys.exit(1) + def build_portable(self): + base = self.portable_base + if os.path.exists(base): + shutil.rmtree(base) + os.makedirs(base) + src = self.j(self.src_root, 'setup', 'installer', 'windows', + 'portable.c') + obj = self.j(self.obj_dir, self.b(src)+'.obj') + cflags = '/c /EHsc /MT /W3 /Ox /nologo /D_UNICODE'.split() + + if self.newer(obj, [src]): + self.info('Compiling', obj) + cmd = [msvc.cc] + cflags + ['/Fo'+obj, '/Tc'+src] + self.run_builder(cmd) + + exe = self.j(base, 'calibre-portable.exe') + if self.newer(exe, [obj]): + self.info('Linking', exe) + cmd = [msvc.linker] + ['/INCREMENTAL:NO', '/MACHINE:X86', + '/LIBPATH:'+self.obj_dir, '/SUBSYSTEM:WINDOWS', + '/RELEASE', + '/OUT:'+exe, self.embed_resources(exe), + obj, 'User32.lib'] + self.run_builder(cmd) + + self.info('Creating portable installer') + shutil.copytree(self.base, self.j(base, 'Calibre')) + os.mkdir(self.j(base, 'Calibre Library')) + os.mkdir(self.j(base, 'Calibre Settings')) + + name = '%s-portable-%s.zip'%(__appname__, __version__) + with zipfile.ZipFile(self.j('dist', name), 'w', zipfile.ZIP_DEFLATED) as zf: + self.add_dir_to_zip(zf, base) + + def add_dir_to_zip(self, zf, path, prefix=''): + ''' + Add a directory recursively to the zip file with an optional prefix. + ''' + if prefix: + zi = zipfile.ZipInfo(prefix+'/') + zi.external_attr = 0700 << 16 + zf.writestr(zi, '') + cwd = os.path.abspath(os.getcwd()) + try: + os.chdir(path) + fp = (prefix + ('/' if prefix else '')).replace('//', '/') + for f in os.listdir('.'): + arcname = fp + f + if os.path.isdir(f): + self.add_dir_to_zip(zf, f, prefix=arcname) + else: + zf.write(f, arcname) + finally: + os.chdir(cwd) + def build_launchers(self): - self.obj_dir = self.j(self.src_root, 'build', 'launcher') if not os.path.exists(self.obj_dir): os.makedirs(self.obj_dir) base = self.j(self.src_root, 'setup', 'installer', 'windows') diff --git a/setup/installer/windows/portable.c b/setup/installer/windows/portable.c new file mode 100644 index 0000000000..2a557e9174 --- /dev/null +++ b/setup/installer/windows/portable.c @@ -0,0 +1,146 @@ +#ifndef UNICODE +#define UNICODE +#endif + +#include +#include +#include + +#define BUFSIZE 4096 + +void show_error(LPCTSTR msg) { + MessageBeep(MB_ICONERROR); + MessageBox(NULL, msg, TEXT("Error"), MB_OK|MB_ICONERROR); +} + +void show_detailed_error(LPCTSTR preamble, LPCTSTR msg, int code) { + LPTSTR buf; + buf = (LPTSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(TCHAR)* + (_tcslen(msg) + _tcslen(preamble) + 80)); + + _sntprintf_s(buf, + LocalSize(buf) / sizeof(TCHAR), _TRUNCATE, + TEXT("%s\r\n %s (Error Code: %d)\r\n"), + preamble, msg, code); + + show_error(buf); + LocalFree(buf); +} + +void show_last_error_crt(LPCTSTR preamble) { + TCHAR buf[BUFSIZE]; + int err = 0; + + _get_errno(&err); + _wcserror_s(buf, BUFSIZE, err); + show_detailed_error(preamble, buf, err); +} + +void show_last_error(LPCTSTR preamble) { + TCHAR *msg = NULL; + DWORD dw = GetLastError(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + &msg, + 0, NULL ); + + show_detailed_error(preamble, msg, (int)dw); +} + + +LPTSTR get_app_dir() { + LPTSTR buf, buf2, buf3; + DWORD sz; + TCHAR drive[4] = TEXT("\0\0\0"); + errno_t err; + + buf = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); + buf2 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); + buf3 = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); + + sz = GetModuleFileName(NULL, buf, BUFSIZE); + + if (sz == 0 || sz > BUFSIZE-1) { + show_error(TEXT("Failed to get path to calibre-portable.exe")); + ExitProcess(1); + } + + err = _tsplitpath_s(buf, drive, 4, buf2, BUFSIZE, NULL, 0, NULL, 0); + + if (err != 0) { + show_last_error_crt(TEXT("Failed to split path to calibre-portable.exe")); + ExitProcess(1); + } + + _sntprintf_s(buf3, BUFSIZE-1, _TRUNCATE, TEXT("%s%s"), drive, buf2); + free(buf); free(buf2); + return buf3; +} + +void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { + DWORD dwFlags=0; + STARTUPINFO si; + PROCESS_INFORMATION pi; + BOOL fSuccess; + TCHAR cmdline[BUFSIZE]; + + if (! SetEnvironmentVariable(TEXT("CALIBRE_CONFIG_DIRECTORY"), config_dir)) { + show_last_error(TEXT("Failed to set environment variables")); + ExitProcess(1); + } + + dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP; + _sntprintf_s(cmdline, BUFSIZE, _TRUNCATE, TEXT(" \"--with-library=%s\""), library_dir); + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + fSuccess = CreateProcess(exe, cmdline, + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + dwFlags, // Creation flags http://msdn.microsoft.com/en-us/library/ms684863(v=vs.85).aspx + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + + if (fSuccess == 0) { + show_last_error(TEXT("Failed to launch the calibre program")); + } + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + +} + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) +{ + LPTSTR app_dir, config_dir, exe, library_dir; + + 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, TEXT("%sCalibre Settings"), app_dir); + _sntprintf_s(exe, BUFSIZE, _TRUNCATE, TEXT("%sCalibre\\calibre.exe"), app_dir); + _sntprintf_s(library_dir, BUFSIZE, _TRUNCATE, TEXT("%sCalibre Library"), app_dir); + + launch_calibre(exe, config_dir, library_dir); + + free(app_dir); free(config_dir); free(exe); free(library_dir); + + return 0; +} + + diff --git a/setup/upload.py b/setup/upload.py index a0138a42e4..ab0044eaa2 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -26,6 +26,7 @@ def installers(): installers = list(map(installer_name, ('dmg', 'msi', 'tar.bz2'))) installers.append(installer_name('tar.bz2', is64bit=True)) installers.insert(0, 'dist/%s-%s.tar.gz'%(__appname__, __version__)) + installers.append('dist/%s-portable-%s.zip'%(__appname__, __version__)) return installers def installer_description(fname): @@ -38,6 +39,8 @@ def installer_description(fname): return 'Windows installer' if fname.endswith('.dmg'): return 'OS X dmg' + if fname.endswith('.zip'): + return 'Calibre Portable' return 'Unknown file' class ReUpload(Command): # {{{