From 03fd7f164e391693838f7d53f802b935542a10c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 16:30:59 -0700 Subject: [PATCH 1/5] Support for ejecting devices on windows --- installer/osx/freeze.py | 2 +- src/calibre/devices/usbms/device.py | 21 ++- src/calibre/utils/windows/winutil.c | 213 +++++++++++++++++++++++++++- 3 files changed, 233 insertions(+), 3 deletions(-) diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 28d2bd9aa2..b380d69d92 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -230,7 +230,7 @@ _check_symlinks_prescript() all_functions = main_functions['console'] + main_functions['gui'] print print 'Adding PoDoFo' - pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/*.dylib'))[0] + pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo.0.7.0.dylib'))[0] shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf))) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index f0c54d7051..d5dcf3e440 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -492,7 +492,26 @@ class Device(DeviceConfig, DevicePlugin): self.open_osx() def eject_windows(self): - pass + from calibre.constants import plugins + from threading import Thread + winutil, winutil_err = plugins['winutil'] + drives = [] + for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'): + x = getattr(self, x, None) + if x is not None: + drives.append(x[0].upper()) + + def do_it(drives): + for d in drives: + try: + winutil.eject_drive(d) + except: + pass + + t = Thread(target=do_it, args=[drives]) + t.daemon = True + t.start() + self.__save_win_eject_thread = t def eject_osx(self): for x in ('_main_prefix', '_card_a_prefix', '_card_b_prefix'): diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 3388ba557b..5666c5bc0f 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -271,7 +271,7 @@ get_all_removable_disks(struct tagDrives *g_drives) // Loop for all drives (MAX_DRIVES = 26) - for(nLoopIndex = 0; nLoopIndex< MAX_DRIVES; nLoopIndex++) + for(nLoopIndex = 0; nLoopIndex < MAX_DRIVES; nLoopIndex++) { // if a drive is present, if(dwDriveMask & 1) @@ -306,6 +306,213 @@ get_all_removable_disks(struct tagDrives *g_drives) } +static DEVINST +GetDrivesDevInstByDeviceNumber(long DeviceNumber, + UINT DriveType, char* szDosDeviceName) +{ + GUID *guid; + HDEVINFO hDevInfo; + DWORD dwIndex, dwBytesReturned; + BOOL bRet, IsFloppy; + BYTE Buf[1024]; + PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd; + long res; + HANDLE hDrive; + STORAGE_DEVICE_NUMBER sdn; + + IsFloppy = (strstr(szDosDeviceName, "\\Floppy") != NULL); // is there a better way? + + switch (DriveType) { + case DRIVE_REMOVABLE: + if ( IsFloppy ) { + guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY; + } else { + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + } + break; + case DRIVE_FIXED: + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + break; + case DRIVE_CDROM: + guid = (GUID*)&GUID_DEVINTERFACE_CDROM; + break; + default: + PyErr_SetString(PyExc_ValueError, "Invalid drive type"); + return 0; + } + + // Get device interface info set handle + // for all devices attached to system + hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (hDevInfo == INVALID_HANDLE_VALUE) { + PyErr_SetString(PyExc_ValueError, "Invalid handle value"); + return 0; + } + + // Retrieve a context structure for a device interface + // of a device information set. + dwIndex = 0; + bRet = FALSE; + + + pspdidd = + (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; + SP_DEVICE_INTERFACE_DATA spdid; + SP_DEVINFO_DATA spdd; + DWORD dwSize; + + spdid.cbSize = sizeof(spdid); + + while ( TRUE ) { + bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, + guid, dwIndex, &spdid); + if ( !bRet ) { + break; + } + + dwSize = 0; + SetupDiGetDeviceInterfaceDetail(hDevInfo, + &spdid, NULL, 0, &dwSize, NULL); + + if ( dwSize!=0 && dwSize<=sizeof(Buf) ) { + pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes! + + ZeroMemory((PVOID)&spdd, sizeof(spdd)); + spdd.cbSize = sizeof(spdd); + + res = + SetupDiGetDeviceInterfaceDetail(hDevInfo, & + spdid, pspdidd, + dwSize, &dwSize, + &spdd); + if ( res ) { + hDrive = CreateFile(pspdidd->DevicePath,0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if ( hDrive != INVALID_HANDLE_VALUE ) { + dwBytesReturned = 0; + res = DeviceIoControl(hDrive, + IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, &sdn, sizeof(sdn), + &dwBytesReturned, NULL); + if ( res ) { + if ( DeviceNumber == (long)sdn.DeviceNumber ) { + CloseHandle(hDrive); + SetupDiDestroyDeviceInfoList(hDevInfo); + return spdd.DevInst; + } + } + CloseHandle(hDrive); + } + } + } + dwIndex++; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + PyErr_SetString(PyExc_ValueError, "Invalid device number"); + + return 0; +} + + + +static BOOL +eject_drive_letter(char DriveLetter) { + char szRootPath[4], szDevicePath[3], szVolumeAccessPath[7], szDosDeviceName[MAX_PATH]; + long DeviceNumber, res, tries; + HANDLE hVolume; + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned; + DEVINST DevInst; + ULONG Status; + ULONG ProblemNumber; + PNP_VETO_TYPE VetoType; + WCHAR VetoNameW[MAX_PATH]; + BOOL bSuccess; + DEVINST DevInstParent; + + szRootPath[0] = DriveLetter; szRootPath[1] = ':'; szRootPath[2] = '\\'; szRootPath[3] = (char)0; + szDevicePath[0] = DriveLetter; szDevicePath[1] = ':'; szDevicePath[2] = (char)0; + szVolumeAccessPath[0] = '\\'; szVolumeAccessPath[1] = '\\'; szVolumeAccessPath[2] = '.'; + szVolumeAccessPath[3] = '\\'; szVolumeAccessPath[4] = DriveLetter; szVolumeAccessPath[5] = ':'; + szVolumeAccessPath[6] = (char)0; + + + DeviceNumber = -1; + + hVolume = CreateFile(szVolumeAccessPath, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (hVolume == INVALID_HANDLE_VALUE) { + PyErr_SetString(PyExc_ValueError, "Invalid handle value for drive letter"); + return FALSE; + } + + dwBytesReturned = 0; + res = DeviceIoControl(hVolume, + IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, &sdn, sizeof(sdn), + &dwBytesReturned, NULL); + if ( res ) { + DeviceNumber = sdn.DeviceNumber; + } + CloseHandle(hVolume); + + if ( DeviceNumber == -1 ) { + PyErr_SetString(PyExc_ValueError, "Can't find drive number"); + return FALSE; + } + + res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH); + if ( !res ) { + PyErr_SetString(PyExc_ValueError, "Can't find dos device"); + return FALSE; + } + + DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, + DriveType, szDosDeviceName); + if (DevInst == 0) return FALSE; + + DevInstParent = 0; + Status = 0; + ProblemNumber = 0; + PNP_VETO_TYPE VetoType; + bSuccess = FALSE; + + res = CM_Get_Parent(&DevInstParent, DevInst, 0); + + for ( tries = 0; tries < 3; tries++ ) { + VetoNameW[0] = 0; + + res = CM_Request_Device_EjectW(DevInstParent, + &VetoType, VetoNameW, MAX_PATH, 0); + + bSuccess = (res==CR_SUCCESS && + VetoType==PNP_VetoTypeUnknown); + if ( bSuccess ) { + break; + } + + Sleep(500); // required to give the next tries a chance! + } + if (!bSuccess) PyErr_SetString(PyExc_ValueError, "Failed to eject drive after three tries"); + return bSuccess; +} + +static PyObject * +winutil_eject_drive(PyObject *self, PyObject *args) { + char DriveLetter; + + if (!PyArg_ParseTuple(args, "c", &DriveLetter)) return NULL; + + if (!eject_drive_letter(DriveLetter)) return NULL; + Py_RETURN_NONE; +} + + PSP_DEVICE_INTERFACE_DETAIL_DATA get_device_grandparent(HDEVINFO hDevInfo, DWORD index, PWSTR buf, PWSTR volume_id, BOOL *iterate) { @@ -697,6 +904,10 @@ is not present, current time as returned by localtime() is used. format must\n\ be a unicode string. Returns unicode strings." }, + {"eject_drive", winutil_eject_drive, METH_VARARGS, + "eject_drive(drive_letter)\n\nEject a drive. Raises an exception on failure." + }, + {NULL, NULL, 0, NULL} }; From 8e64f31eed20c592c7176dc05778201f56a64f71 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 18:23:06 -0700 Subject: [PATCH 2/5] Fix #2540 (Internal collections showing as on memory stick (MS)) --- src/calibre/devices/prs505/books.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/books.py b/src/calibre/devices/prs505/books.py index 528770d3c5..6e268e734a 100644 --- a/src/calibre/devices/prs505/books.py +++ b/src/calibre/devices/prs505/books.py @@ -403,7 +403,8 @@ def fix_ids(main, carda, cardb): for child in db.root_element.childNodes: if child.nodeType == child.ELEMENT_NODE and child.hasAttribute('id'): id_map[child.getAttribute('id')] = str(cid) - child.setAttribute("sourceid", '1') + child.setAttribute("sourceid", + '0' if getattr(child, 'tagName', '').endswith('playlist') else '1') child.setAttribute('id', str(cid)) cid += 1 From 209980eb0442a0c254100c4415b63a9b46c01e83 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 18:59:27 -0700 Subject: [PATCH 3/5] Fix #2542 (Error in parsing the data from google books) --- src/calibre/ebooks/metadata/google_books.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/google_books.py b/src/calibre/ebooks/metadata/google_books.py index a2eaf6cae4..82f11c2010 100644 --- a/src/calibre/ebooks/metadata/google_books.py +++ b/src/calibre/ebooks/metadata/google_books.py @@ -77,6 +77,7 @@ class Query(object): if verbose: print 'Query:', self.url feed = etree.fromstring(browser.open(self.url).read()) + #print etree.tostring(feed, pretty_print=True) total = int(total_results(feed)[0].text) start = int(start_index(feed)[0].text) entries = entry(feed) @@ -104,12 +105,9 @@ class ResultList(list): except: report(verbose) - - def get_title(self, entry): candidates = [x.text for x in title(entry)] - candidates.sort(cmp=lambda x,y: cmp(len(x), len(y)), reverse=True) - return candidates[0] + return ': '.join(candidates) def get_authors(self, entry): m = creator(entry) @@ -182,7 +180,7 @@ class ResultList(list): self.get_identifiers(x, mi) mi.tags = self.get_tags(x, verbose) mi.publisher = self.get_publisher(x, verbose) - mi.timestamp = self.get_date(x, verbose) + mi.pubdate = self.get_date(x, verbose) mi.language = self.get_language(x, verbose) self.append(mi) From fab6127966c0b29ae1b8717a02a6cbf616ed3376 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 19:26:59 -0700 Subject: [PATCH 4/5] OS X builds again --- installer/osx/freeze.py | 2 +- setup.py | 4 ++-- upload.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index b380d69d92..6c0abb6ebe 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -230,7 +230,7 @@ _check_symlinks_prescript() all_functions = main_functions['console'] + main_functions['gui'] print print 'Adding PoDoFo' - pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo.0.7.0.dylib'))[0] + pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo.0.7.00.dylib'))[0] shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf))) diff --git a/setup.py b/setup.py index c83a93fa51..89c4f5c925 100644 --- a/setup.py +++ b/setup.py @@ -62,9 +62,9 @@ if __name__ == '__main__': podofo_inc = '/usr/include/podofo' if islinux else \ 'C:\\podofo\\include\\podofo' if iswindows else \ - '/Volumes/sw/podofo/include/podofo' + '/usr/local/include/podofo' podofo_lib = '/usr/lib' if islinux else r'C:\podofo' if iswindows else \ - '/Volumes/sw/podofo/lib' + '/usr/local/lib' podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc) if os.path.exists(os.path.join(podofo_inc, 'podofo.h')): optional.append(Extension('calibre.plugins.podofo', diff --git a/upload.py b/upload.py index de68082db4..bc0df0dcc8 100644 --- a/upload.py +++ b/upload.py @@ -548,7 +548,7 @@ class build_windows(VMInstaller): class build_osx(VMInstaller): description = 'Build OS X app bundle' - VM = '/vmware/Mac OSX/Mac OSX.vmx' + VM = '/mnt/backup/calibre_os_x/Mac OSX.vmx' if not os.path.exists(VM): VM = '/home/kovid/calibre_os_x/Mac OSX.vmx' From 2c39eda812eb92bac55671716d95c3d15ef3d3ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 5 Jun 2009 20:46:45 -0700 Subject: [PATCH 5/5] calibre-server nows runs on linux without requiring a X server --- installer/osx/freeze.py | 8 ++++++ src/calibre/ebooks/metadata/__init__.py | 9 ++++--- src/calibre/library/server.py | 34 ++++++++++++------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 6c0abb6ebe..f5e62c4649 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -264,6 +264,14 @@ _check_symlinks_prescript() if os.path.exists(dst): shutil.rmtree(dst) shutil.copytree('/usr/local/etc/fonts', dst, symlinks=False) + for x in os.listdir('/usr/local/etc/fonts'): + dst = os.path.join(frameworks_dir, x) + y = os.path.join('/usr/local/etc/fonts', x) + if os.path.isdir(dst): + if os.path.exists(dst): shutil.rmtree(dst) + shutil.copytree(y, dst) + else: + os.link(y, dst) print print 'Adding IPython' diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 6d0d14f6c2..1a5af8966c 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -63,9 +63,12 @@ def roman(num): def fmt_sidx(i, fmt='%.2f', use_roman=False): if i is None: i = 1 - if int(i) == i: - return roman(i) if use_roman else '%d'%i - return fmt%i + if int(i) == float(i): + return roman(int(i)) if use_roman else '%d'%int(i) + try: + return fmt%i + except TypeError: + return fmt%float(i) class Resource(object): diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index 5e18c16281..071d4c285a 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -7,14 +7,18 @@ __docformat__ = 'restructuredtext en' HTTP server for remote access to the calibre database. ''' -import sys, textwrap, operator, os, re, logging +import sys, textwrap, operator, os, re, logging, cStringIO from itertools import repeat from logging.handlers import RotatingFileHandler from datetime import datetime from threading import Thread import cherrypy -from PyQt4.Qt import QImage, QApplication, QByteArray, Qt, QBuffer +try: + from PIL import Image as PILImage + PILImage +except ImportError: + import Image as PILImage from calibre.constants import __version__, __appname__ from calibre.utils.genshi.template import MarkupTemplate @@ -195,25 +199,21 @@ class LibraryServer(object): updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and os.access(path, os.R_OK) else build_time cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) try: - if QApplication.instance() is None: - QApplication([]) - - im = QImage() - im.loadFromData(cover) - if im.isNull(): + f = cStringIO.StringIO(cover) + try: + im = PILImage.open(f) + except IOError: raise cherrypy.HTTPError(404, 'No valid cover found') - width, height = im.width(), im.height() + width, height = im.size scaled, width, height = fit_image(width, height, 60 if thumbnail else self.max_cover_width, 80 if thumbnail else self.max_cover_height) if not scaled: return cover - im = im.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation) - ba = QByteArray() - buf = QBuffer(ba) - buf.open(QBuffer.WriteOnly) - im.save(buf, 'PNG') - return str(ba.data()) + im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) + of = cStringIO.StringIO() + im.convert('RGB').save(of, 'JPEG') + return of.getvalue() except Exception, err: import traceback traceback.print_exc() @@ -294,7 +294,7 @@ class LibraryServer(object): series = record[FIELD_MAP['series']] if series: extra.append('SERIES: %s [%s]
'%(series, - fmt_sidx(record[FIELD_MAP['series_index']]))) + fmt_sidx(float(record[FIELD_MAP['series_index']])))) fmt = 'epub' if 'EPUB' in r else 'pdb' mimetype = guess_type('dummy.'+fmt)[0] books.append(self.STANZA_ENTRY.generate( @@ -343,7 +343,7 @@ class LibraryServer(object): for record in items[start:start+num]: aus = record[2] if record[2] else __builtins__._('Unknown') authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) - r[10] = fmt_sidx(r[10]) + record[10] = fmt_sidx(float(record[10])) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) updated = self.db.last_modified()