diff --git a/installer/linux/freeze.py b/installer/linux/freeze.py index f32286092a..c063fb852a 100644 --- a/installer/linux/freeze.py +++ b/installer/linux/freeze.py @@ -22,6 +22,9 @@ LIBZ = '/lib/libz.so.1' LIBBZ2 = '/lib/libbz2.so.1' LIBUSB = '/usr/lib/libusb.so' LIBPOPPLER = '/usr/lib/libpoppler.so.3' +LIBXML2 = '/usr/lib/libxml2.so.2' +LIBXSLT = '/usr/lib/libxslt.so.1' +LIBEXSLT = '/usr/lib/libexslt.so.0' CALIBRESRC = os.path.join(CALIBREPREFIX, 'src') @@ -121,7 +124,7 @@ binaries += [('pdftohtml', PDFTOHTML, 'BINARY'), print 'Adding external libraries...' binaries += [ (os.path.basename(x), x, 'BINARY') for x in (SQLITE, DBUS, - LIBMNG, LIBZ, LIBBZ2, LIBUSB, LIBPOPPLER)] + LIBMNG, LIBZ, LIBBZ2, LIBUSB, LIBPOPPLER, LIBXML2, LIBXSLT, LIBEXSLT)] qt = [] diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 2703431835..f55c6a77a5 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -320,11 +320,10 @@ class LoggingInterface: def strftime(fmt, t=time.localtime()): - ''' - A version of strtime that returns unicode strings. - ''' - result = time.strftime(fmt, t) - return unicode(result, preferred_encoding, 'replace') + ''' A version of strtime that returns unicode strings. ''' + if iswindows: + return plugins['winutil'].strftime(unicode(fmt, preferred_encoding), t) + return time.strftime(fmt, t).decode(preferred_encoding, 'replace') def entity_to_unicode(match, exceptions=[], encoding='cp1252'): ''' diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 8d2951fb26..65f02b6f02 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = 'calibre' -__version__ = '0.4.84b1' +__version__ = '0.4.84b2' __author__ = "Kovid Goyal " ''' Various run time constants. @@ -27,4 +27,4 @@ except: win32event = __import__('win32event') if iswindows else None winerror = __import__('winerror') if iswindows else None win32api = __import__('win32api') if iswindows else None -fcntl = None if iswindows else __import__('fcntl') \ No newline at end of file +fcntl = None if iswindows else __import__('fcntl') diff --git a/src/calibre/ebooks/lrf/feeds/convert_from.py b/src/calibre/ebooks/lrf/feeds/convert_from.py index 4425877525..b401d6572d 100644 --- a/src/calibre/ebooks/lrf/feeds/convert_from.py +++ b/src/calibre/ebooks/lrf/feeds/convert_from.py @@ -9,11 +9,9 @@ from calibre.ebooks.lrf.html.convert_from import process_file from calibre.web.feeds.main import option_parser as feeds_option_parser from calibre.web.feeds.main import run_recipe from calibre.ptempfile import PersistentTemporaryDirectory -from calibre import sanitize_file_name +from calibre import sanitize_file_name, strftime -import sys, os, time - -import parser +import sys, os def option_parser(): parser = feeds_option_parser() @@ -51,7 +49,7 @@ def main(args=sys.argv, notification=None, handler=None): if not opts.output: ext = '.lrs' if opts.lrs else '.lrf' - fname = recipe.title + time.strftime(recipe.timefmt)+ext + fname = recipe.title + strftime(recipe.timefmt)+ext opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname)) print 'Generating LRF...' process_file(htmlfile, opts) diff --git a/src/calibre/ebooks/lrf/web/convert_from.py b/src/calibre/ebooks/lrf/web/convert_from.py index 6bd2eaf3e5..ca523e869b 100644 --- a/src/calibre/ebooks/lrf/web/convert_from.py +++ b/src/calibre/ebooks/lrf/web/convert_from.py @@ -2,10 +2,10 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' '''Convert websites into LRF files.''' -import sys, time, tempfile, shutil, os, logging, imp, inspect, re +import sys, tempfile, shutil, os, logging, imp, inspect, re from urlparse import urlsplit -from calibre import __appname__, setup_cli_handlers, CommandLineError +from calibre import __appname__, setup_cli_handlers, CommandLineError, strftime from calibre.ebooks.lrf import option_parser as lrf_option_parser from calibre.ebooks.lrf.html.convert_from import process_file @@ -128,7 +128,7 @@ def process_profile(args, options, logger=None): title = profile.title if not title: title = urlsplit(options.url).netloc - options.title = title + time.strftime(profile.timefmt, time.localtime()) + options.title = title + strftime(profile.timefmt) options.match_regexps += profile.match_regexps options.preprocess_regexps = profile.preprocess_regexps diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 83c9caefcc..cb699052da 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -306,7 +306,7 @@ class FileDialog(QObject): QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir) self.accepted = self.fd.exec_() == QFileDialog.Accepted else: - dir = dynamic.get(self.dialog_name, default=os.path.expanduser('~')) + dir = dynamic.get(self.dialog_name, os.path.expanduser('~')) self.selected_files = [] if mode == QFileDialog.AnyFile: f = qstring_to_unicode( diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index ba8101521c..cb296b637b 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -13,7 +13,7 @@ from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \ QCoreApplication, SIGNAL, QObject, QSize, QModelIndex -from calibre import preferred_encoding +from calibre import strftime from calibre.ptempfile import PersistentTemporaryFile from calibre.library.database import LibraryDatabase, text_to_tokens from calibre.gui2 import NONE, TableView, qstring_to_unicode, config @@ -370,7 +370,7 @@ class BooksModel(QAbstractTableModel): dt = self.db.timestamp(row) if dt: dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) - return QVariant(dt.strftime(BooksView.TIME_FMT).decode(preferred_encoding, 'replace')) + return QVariant(strftime(BooksView.TIME_FMT, dt.timetuple())) elif col == 4: r = self.db.rating(row) r = r/2 if r else 0 @@ -690,7 +690,7 @@ class DeviceBooksModel(BooksModel): dt = item.datetime dt = datetime(*dt[0:6]) dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) - data[_('Timestamp')] = dt.strftime('%a %b %d %H:%M:%S %Y') + data[_('Timestamp')] = strftime('%a %b %d %H:%M:%S %Y', dt.timetuple()) data[_('Tags')] = ', '.join(item.tags) self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data) @@ -731,7 +731,7 @@ class DeviceBooksModel(BooksModel): dt = self.db[self.map[row]].datetime dt = datetime(*dt[0:6]) dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) - return QVariant(dt.strftime(BooksView.TIME_FMT)) + return QVariant(strftime(BooksView.TIME_FMT, dt.timetuple())) elif col == 4: tags = self.db[self.map[row]].tags if tags: diff --git a/src/calibre/utils/windows/Makefile b/src/calibre/utils/windows/Makefile index 7507dee734..6e2dc51a7e 100644 --- a/src/calibre/utils/windows/Makefile +++ b/src/calibre/utils/windows/Makefile @@ -2,7 +2,8 @@ # Invoke with nmake /f Makefile.winutil test : winutil.pyd - python.exe -c "import winutil; winutil.set_debug(True); print winutil.get_usb_devices(); print winutil.get_mounted_volumes_for_usb_device(0x054c, 0x031e)" + python.exe -c "import winutil; winutil.set_debug(True); print repr(winutil.strftime(u'%b %a %A')); " +#python.exe -c "import winutil; winutil.set_debug(True); print winutil.get_usb_devices(); print winutil.get_mounted_volumes_for_usb_device(0x054c, 0x031e)" winutil.pyd : winutil.obj link.exe /DLL /nologo /INCREMENTAL:NO /LIBPATH:c:\Python25\libs \ diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 3be91cdb93..3419c8d2dc 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -57,12 +57,19 @@ wherever possible in this module. #define UNICODE #include #include +#include +#include #include #include #include #include #include #include +#include + +#define PyStructSequence_GET_ITEM(op, i) \ + (((PyStructSequence *)(op))->ob_item[i]) + #define BUFSIZE 512 #define MAX_DRIVES 26 @@ -515,6 +522,143 @@ winutil_is_usb_device_connected(PyObject *self, PyObject *args) { return ans; } +static int +gettmarg(PyObject *args, struct tm *p) +{ + int y; + memset((void *) p, '\0', sizeof(struct tm)); + + if (!PyArg_Parse(args, "(iiiiiiiii)", + &y, + &p->tm_mon, + &p->tm_mday, + &p->tm_hour, + &p->tm_min, + &p->tm_sec, + &p->tm_wday, + &p->tm_yday, + &p->tm_isdst)) + return 0; + if (y < 1900) { + if (69 <= y && y <= 99) + y += 1900; + else if (0 <= y && y <= 68) + y += 2000; + else { + PyErr_SetString(PyExc_ValueError, + "year out of range"); + return 0; + } + } + p->tm_year = y - 1900; + p->tm_mon--; + p->tm_wday = (p->tm_wday + 1) % 7; + p->tm_yday--; + return 1; +} + +static PyObject * +winutil_strftime(PyObject *self, PyObject *args) +{ + PyObject *tup = NULL; + struct tm buf; + PyObject *format; + const wchar_t *fmt; + size_t fmtlen, buflen; + wchar_t *outbuf = 0; + size_t i; + memset((void *) &buf, '\0', sizeof(buf)); + + if (!PyArg_ParseTuple(args, "U|O:strftime", &format, &tup)) + return NULL; + + if (tup == NULL) { + time_t tt = time(NULL); + buf = *localtime(&tt); + } else if (!gettmarg(tup, &buf)) + return NULL; + + if (buf.tm_mon == -1) + buf.tm_mon = 0; + else if (buf.tm_mon < 0 || buf.tm_mon > 11) { + PyErr_SetString(PyExc_ValueError, "month out of range"); + return NULL; + } + if (buf.tm_mday == 0) + buf.tm_mday = 1; + else if (buf.tm_mday < 0 || buf.tm_mday > 31) { + PyErr_SetString(PyExc_ValueError, "day of month out of range"); + return NULL; + } + if (buf.tm_hour < 0 || buf.tm_hour > 23) { + PyErr_SetString(PyExc_ValueError, "hour out of range"); + return NULL; + } + if (buf.tm_min < 0 || buf.tm_min > 59) { + PyErr_SetString(PyExc_ValueError, "minute out of range"); + return NULL; + } + if (buf.tm_sec < 0 || buf.tm_sec > 61) { + PyErr_SetString(PyExc_ValueError, "seconds out of range"); + return NULL; + } + /* tm_wday does not need checking of its upper-bound since taking + ``% 7`` in gettmarg() automatically restricts the range. */ + if (buf.tm_wday < 0) { + PyErr_SetString(PyExc_ValueError, "day of week out of range"); + return NULL; + } + if (buf.tm_yday == -1) + buf.tm_yday = 0; + else if (buf.tm_yday < 0 || buf.tm_yday > 365) { + PyErr_SetString(PyExc_ValueError, "day of year out of range"); + return NULL; + } + if (buf.tm_isdst < -1 || buf.tm_isdst > 1) { + PyErr_SetString(PyExc_ValueError, + "daylight savings flag out of range"); + return NULL; + } + + /* Convert the unicode string to a wchar one */ + fmtlen = PyUnicode_GET_SIZE(format); + fmt = (wchar_t *)PyMem_Malloc((fmtlen+1)*sizeof(wchar_t)); + if (fmt == NULL) return PyErr_NoMemory(); + i = PyUnicode_AsWideChar((PyUnicodeObject *)format, fmt, fmtlen); + if (i < fmtlen) { + PyErr_SetString(PyExc_RuntimeError, "Failed to convert format string"); + PyMem_Free(fmt); + return NULL; + } + + for (i = 1024; ; i += i) { + outbuf = (wchar_t *)PyMem_Malloc(i*sizeof(wchar_t)); + if (outbuf == NULL) { + return PyErr_NoMemory(); + } + buflen = wcsftime(outbuf, i, fmt, &buf); + if (buflen > 0 || i >= 256 * fmtlen) { + /* If the buffer is 256 times as long as the format, + it's probably not failing for lack of room! + More likely, the format yields an empty result, + e.g. an empty format, or %Z when the timezone + is unknown. */ + PyObject *ret; + ret = PyUnicode_FromWideChar(outbuf, buflen); + PyMem_Free(outbuf); PyMem_Free(fmt); + return ret; + } + PyMem_Free(outbuf); PyMem_Free(fmt); +#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) + /* VisualStudio .NET 2005 does this properly */ + if (buflen == 0 && errno == EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid format string"); + return NULL; + } +#endif + } +} + static PyMethodDef WinutilMethods[] = { {"special_folder_path", winutil_folder_path, METH_VARARGS, @@ -553,6 +697,15 @@ static PyMethodDef WinutilMethods[] = { "set_debug(bool)\n\nSet debugging mode." }, + {"strftime", winutil_strftime, METH_VARARGS, + "strftime(format[, tuple]) -> string\n\ +\n\ +Convert a time tuple to a string according to a format specification.\n\ +See the library reference manual for formatting codes. When the time tuple\n\ +is not present, current time as returned by localtime() is used. format must\n\ +be a unicode string. Returns unicode strings." + }, + {NULL, NULL, 0, NULL} }; diff --git a/src/calibre/web/feeds/recipes/new_yorker.py b/src/calibre/web/feeds/recipes/new_yorker.py index cdc6d8c0e3..3e8d324f6a 100644 --- a/src/calibre/web/feeds/recipes/new_yorker.py +++ b/src/calibre/web/feeds/recipes/new_yorker.py @@ -4,6 +4,7 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' import re, time +from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import NavigableString @@ -65,7 +66,7 @@ class NewYorker(BasicNewsRecipe): 'title': title, 'desc': desc, 'content':'', 'url': href, - 'date': time.strftime('%a, %d %b', time.localtime()), + 'date': strftime('%a, %d %b'), } articles.append(art) diff --git a/src/calibre/web/feeds/recipes/newsweek.py b/src/calibre/web/feeds/recipes/newsweek.py index cab07d974f..0da8b8965d 100644 --- a/src/calibre/web/feeds/recipes/newsweek.py +++ b/src/calibre/web/feeds/recipes/newsweek.py @@ -3,6 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import re, string, time +from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import BeautifulSoup @@ -68,7 +69,7 @@ class Newsweek(BasicNewsRecipe): small = img['src'] match = re.search(r'(\d+)_', small.rpartition('/')[-1]) if match is not None: - self.timefmt = time.strftime(' [%d %b, %Y]', time.strptime(match.group(1), '%y%m%d')) + self.timefmt = strftime(' [%d %b, %Y]', time.strptime(match.group(1), '%y%m%d')) self.cover_url = small.replace('coversmall', 'coverlarge') sections = self.get_sections(soup) @@ -84,7 +85,7 @@ class Newsweek(BasicNewsRecipe): 'title' : title, 'url' : a['href'], 'description':'', 'content':'', - 'date': time.strftime('%a, %d %b', time.localtime()) + 'date': strftime('%a, %d %b') } if art['title'] and art['url']: sections[0][1].append(art) diff --git a/src/calibre/web/feeds/recipes/nytimes.py b/src/calibre/web/feeds/recipes/nytimes.py index 3e1fca6279..17fe1b9b1b 100644 --- a/src/calibre/web/feeds/recipes/nytimes.py +++ b/src/calibre/web/feeds/recipes/nytimes.py @@ -5,7 +5,8 @@ __copyright__ = '2008, Kovid Goyal ' ''' nytimes.com ''' -import time, string +import string +from calibre import strftime from calibre.web.feeds.recipes import BasicNewsRecipe class NYTimes(BasicNewsRecipe): @@ -59,7 +60,7 @@ class NYTimes(BasicNewsRecipe): url = self.print_version(a['href']) title = self.tag_to_string(a, use_alt=True).strip() description = '' - pubdate = time.strftime('%a, %d %b', time.localtime()) + pubdate = strftime('%a, %d %b') summary = div.find(True, attrs={'class':'summary'}) if summary: description = self.tag_to_string(summary, use_alt=False) diff --git a/src/calibre/web/feeds/recipes/smh.py b/src/calibre/web/feeds/recipes/smh.py index 09c3347282..444b211084 100644 --- a/src/calibre/web/feeds/recipes/smh.py +++ b/src/calibre/web/feeds/recipes/smh.py @@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en' ''' smh.com.au ''' -import time +from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import BeautifulSoup @@ -44,7 +44,7 @@ class SMH(BasicNewsRecipe): articles.append({ 'title': title, 'url' : url, - 'date' : time.strftime('%a, %d %b'), + 'date' : strftime('%a, %d %b'), 'description' : '', 'content' : '', }) diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 37b36981ca..4438b18f0a 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -2,9 +2,8 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import datetime from calibre.utils.genshi.template import MarkupTemplate -from calibre import preferred_encoding +from calibre import preferred_encoding, strftime class Template(MarkupTemplate): @@ -126,8 +125,7 @@ class IndexTemplate(Template): def generate(self, title, datefmt, feeds): if isinstance(datefmt, unicode): datefmt = datefmt.encode(preferred_encoding) - date = datetime.datetime.now().strftime(datefmt) - date = date.decode(preferred_encoding, 'replace') + date = strftime(datefmt) return Template.generate(self, title=title, date=date, feeds=feeds)