Merge lp:~realender/calibre/calibre into lp:~user-none/calibre/store

Proposed by Alex Stanev
Status: Merged
Merged at revision: 8446
Proposed branch: lp:~realender/calibre/calibre
Merge into: lp:~user-none/calibre/store
Diff against target: 1189 lines (+803/-91)
14 files modified
recipes/idg_se.recipe (+33/-0)
recipes/united_daily.recipe (+1/-1)
recipes/utrinski.recipe (+71/-0)
src/calibre/customize/builtins.py (+11/-0)
src/calibre/devices/android/driver.py (+5/-3)
src/calibre/devices/eb600/driver.py (+3/-3)
src/calibre/ebooks/mobi/debug.py (+293/-8)
src/calibre/ebooks/mobi/tbs_periodicals.rst (+189/-0)
src/calibre/ebooks/mobi/utils.py (+5/-3)
src/calibre/gui2/store/stores/chitanka_plugin.py (+16/-46)
src/calibre/gui2/store/stores/eknigi_plugin.py (+88/-0)
src/calibre/translations/msgfmt.py (+83/-24)
src/calibre/utils/localization.py (+3/-3)
src/calibre/web/feeds/recipes/model.py (+2/-0)
To merge this branch: bzr merge lp:~realender/calibre/calibre
Reviewer Review Type Date Requested Status
John Schember Pending
Review via email: mp+68588@code.launchpad.net
To post a comment you must log in.
lp:~realender/calibre/calibre updated
9904. By Alex Stanev

Remove affiliate id for now

9905. By Alex Stanev

Use Kovid's affiliate id 30% of the time

9906. By Alex Stanev

sync to trunc

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'recipes/idg_se.recipe'
--- recipes/idg_se.recipe 1970-01-01 00:00:00 +0000
+++ recipes/idg_se.recipe 2011-07-22 18:03:36 +0000
@@ -0,0 +1,33 @@
1__license__ = 'GPLv3'
2
3from calibre.web.feeds.news import BasicNewsRecipe
4
5class IDGse(BasicNewsRecipe):
6 title = 'IDG'
7 description = 'IDG.se'
8 language = 'se'
9 __author__ = 'zapt0'
10 oldest_article = 1
11 max_articles_per_feed = 40
12 no_stylesheets = True
13 encoding = 'ISO-8859-1'
14 remove_javascript = True
15
16 feeds = [(u'Senaste nytt',u'http://feeds.idg.se/idg/vzzs')]
17
18 def print_version(self,url):
19 return url + '?articleRenderMode=print&m=print'
20
21 def get_cover_url(this):
22 return 'http://idgmedia.idg.se/polopoly_fs/2.3275!images/idgmedia_logo_75.jpg'
23
24 keep_only_tags = [
25 dict(name='h1'),
26 dict(name='div', attrs={'class':['divColumn1Article']}),
27 ]
28 #remove ads
29 remove_tags = [
30 dict(name='div', attrs={'id':['preamble_ad']}),
31 dict(name='ul', attrs={'class':['share']})
32 ]
33
034
=== modified file 'recipes/united_daily.recipe'
--- recipes/united_daily.recipe 2011-05-17 15:30:51 +0000
+++ recipes/united_daily.recipe 2011-07-22 18:03:36 +0000
@@ -64,7 +64,7 @@
6464
65 __author__ = 'Eddie Lau'65 __author__ = 'Eddie Lau'
66 __version__ = '1.1'66 __version__ = '1.1'
67 language = 'zh-TW'67 language = 'zh_TW'
68 publisher = 'United Daily News Group'68 publisher = 'United Daily News Group'
69 description = 'United Daily (Taiwan)'69 description = 'United Daily (Taiwan)'
70 category = 'News, Chinese, Taiwan'70 category = 'News, Chinese, Taiwan'
7171
=== added file 'recipes/utrinski.recipe'
--- recipes/utrinski.recipe 1970-01-01 00:00:00 +0000
+++ recipes/utrinski.recipe 2011-07-22 18:03:36 +0000
@@ -0,0 +1,71 @@
1#!/usr/bin/env python
2
3__license__ = 'GPL v3'
4__copyright__ = '2011, Darko Spasovski <darko.spasovski at gmail.com>'
5'''
6utrinski.com.mk
7'''
8
9import re
10import datetime
11from calibre.web.feeds.news import BasicNewsRecipe
12
13class UtrinskiVesnik(BasicNewsRecipe):
14
15 __author__ = 'Darko Spasovski'
16 INDEX = 'http://www.utrinski.com.mk/'
17 title = 'Utrinski Vesnik'
18 description = 'Daily Macedonian newspaper'
19 masthead_url = 'http://www.utrinski.com.mk/images/LogoTop.jpg'
20 language = 'mk'
21 remove_javascript = True
22 publication_type = 'newspaper'
23 category = 'news, Macedonia'
24 oldest_article = 2
25 max_articles_per_feed = 100
26 no_stylesheets = True
27 use_embedded_content = False
28 preprocess_regexps = [(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
29 [
30 ## Remove anything before the start of the article.
31 (r'<body.*?Article start-->', lambda match: '<body>'),
32
33 ## Remove anything after the end of the article.
34 (r'<!--Article end.*?</body>', lambda match : '</body>'),
35 ]
36 ]
37 extra_css = """
38 body{font-family: Arial,Helvetica,sans-serif}
39 .WB_UTRINSKIVESNIK_Naslov{FONT-WEIGHT: bold; FONT-SIZE: 18px; FONT-FAMILY: Arial, Verdana, Tahoma; TEXT-DECORATION: none}
40 """
41
42 conversion_options = {
43 'comment' : description,
44 'tags' : category,
45 'language' : language,
46 'linearize_tables' : True
47 }
48
49 def parse_index(self):
50 soup = self.index_to_soup(self.INDEX)
51 feeds = []
52 for section in soup.findAll('a', attrs={'class':'WB_UTRINSKIVESNIK_TOCTitleBig'}):
53 sectionTitle = section.contents[0].string
54 tocItemTable = section.findAllPrevious('table')[1]
55 if tocItemTable is None: continue
56 articles = []
57 while True:
58 tocItemTable = tocItemTable.nextSibling
59 if tocItemTable is None: break
60 article = tocItemTable.findAll('a', attrs={'class': 'WB_UTRINSKIVESNIK_TocItem'})
61 if len(article)==0: break
62 title = self.tag_to_string(article[0], use_alt=True).strip()
63 articles.append({'title': title, 'url':'http://www.utrinski.com.mk/' + article[0]['href'], 'description':'', 'date':''})
64 if articles:
65 feeds.append((sectionTitle, articles))
66 return feeds
67
68
69 def get_cover_url(self):
70 datum = datetime.datetime.today().strftime('%d_%m_%Y')
71 return 'http://www.utrinski.com.mk/WBStorage/Files/' + datum + '.jpg'
072
=== modified file 'src/calibre/customize/builtins.py'
--- src/calibre/customize/builtins.py 2011-07-19 22:48:25 +0000
+++ src/calibre/customize/builtins.py 2011-07-22 18:03:36 +0000
@@ -1258,6 +1258,16 @@
1258 formats = ['EPUB', 'PDF']1258 formats = ['EPUB', 'PDF']
1259 affiliate = True1259 affiliate = True
12601260
1261class StoreEKnigiStore(StoreBase):
1262 name = u'еКниги'
1263 author = 'Alex Stanev'
1264 description = u'Онлайн книжарница за електронни книги и аудио риалити романи'
1265 actual_plugin = 'calibre.gui2.store.stores.eknigi_plugin:eKnigiStore'
1266
1267 headquarters = 'BG'
1268 formats = ['EPUB', 'PDF', 'HTML']
1269 #affiliate = True
1270
1261class StoreEpubBudStore(StoreBase):1271class StoreEpubBudStore(StoreBase):
1262 name = 'ePub Bud'1272 name = 'ePub Bud'
1263 description = 'Well, it\'s pretty much just "YouTube for Children\'s eBooks. A not-for-profit organization devoted to brining self published childrens books to the world.'1273 description = 'Well, it\'s pretty much just "YouTube for Children\'s eBooks. A not-for-profit organization devoted to brining self published childrens books to the world.'
@@ -1483,6 +1493,7 @@
1483 StoreEBookShoppeUKStore,1493 StoreEBookShoppeUKStore,
1484# StoreEPubBuyDEStore,1494# StoreEPubBuyDEStore,
1485 StoreEHarlequinStore,1495 StoreEHarlequinStore,
1496 StoreEKnigiStore,
1486 StoreEpubBudStore,1497 StoreEpubBudStore,
1487 StoreFeedbooksStore,1498 StoreFeedbooksStore,
1488 StoreFoylesUKStore,1499 StoreFoylesUKStore,
14891500
=== modified file 'src/calibre/devices/android/driver.py'
--- src/calibre/devices/android/driver.py 2011-07-16 16:01:32 +0000
+++ src/calibre/devices/android/driver.py 2011-07-22 18:03:36 +0000
@@ -47,10 +47,12 @@
4747
48 # Google48 # Google
49 0x18d1 : {49 0x18d1 : {
50 0x0001 : [0x0223],
50 0x4e11 : [0x0100, 0x226, 0x227],51 0x4e11 : [0x0100, 0x226, 0x227],
51 0x4e12: [0x0100, 0x226, 0x227],52 0x4e12 : [0x0100, 0x226, 0x227],
52 0x4e21: [0x0100, 0x226, 0x227],53 0x4e21 : [0x0100, 0x226, 0x227],
53 0xb058: [0x0222, 0x226, 0x227]},54 0xb058 : [0x0222, 0x226, 0x227]
55 },
5456
55 # Samsung57 # Samsung
56 0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400],58 0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400],
5759
=== modified file 'src/calibre/devices/eb600/driver.py'
--- src/calibre/devices/eb600/driver.py 2011-06-07 00:26:05 +0000
+++ src/calibre/devices/eb600/driver.py 2011-07-22 18:03:36 +0000
@@ -35,9 +35,9 @@
35 PRODUCT_ID = [0x1688]35 PRODUCT_ID = [0x1688]
36 BCD = [0x110]36 BCD = [0x110]
3737
38 VENDOR_NAME = ['NETRONIX', 'WOLDER']38 VENDOR_NAME = ['NETRONIX', 'WOLDER', 'MD86371']
39 WINDOWS_MAIN_MEM = ['EBOOK', 'MIBUK_GAMMA_6.2']39 WINDOWS_MAIN_MEM = ['EBOOK', 'MIBUK_GAMMA_6.2', 'MD86371']
40 WINDOWS_CARD_A_MEM = 'EBOOK'40 WINDOWS_CARD_A_MEM = ['EBOOK', 'MD86371']
4141
42 OSX_MAIN_MEM = 'EB600 Internal Storage Media'42 OSX_MAIN_MEM = 'EB600 Internal Storage Media'
43 OSX_CARD_A_MEM = 'EB600 Card Storage Media'43 OSX_CARD_A_MEM = 'EB600 Card Storage Media'
4444
=== modified file 'src/calibre/ebooks/mobi/debug.py'
--- src/calibre/ebooks/mobi/debug.py 2011-07-20 20:01:41 +0000
+++ src/calibre/ebooks/mobi/debug.py 2011-07-22 18:03:36 +0000
@@ -8,7 +8,7 @@
8__docformat__ = 'restructuredtext en'8__docformat__ = 'restructuredtext en'
99
10import struct, datetime, sys, os, shutil10import struct, datetime, sys, os, shutil
11from collections import OrderedDict11from collections import OrderedDict, defaultdict
12from calibre.utils.date import utc_tz12from calibre.utils.date import utc_tz
13from calibre.ebooks.mobi.langcodes import main_language, sub_language13from calibre.ebooks.mobi.langcodes import main_language, sub_language
14from calibre.ebooks.mobi.utils import (decode_hex_number, decint,14from calibre.ebooks.mobi.utils import (decode_hex_number, decint,
@@ -530,21 +530,21 @@
530 },530 },
531531
532 'chapter_with_subchapters' : {532 'chapter_with_subchapters' : {
533 22 : ('First subchapter index', 'first_subchapter_index'),533 22 : ('First subchapter index', 'first_child_index'),
534 23 : ('Last subchapter index', 'last_subchapter_index'),534 23 : ('Last subchapter index', 'last_child_index'),
535 },535 },
536536
537 'periodical' : {537 'periodical' : {
538 5 : ('Class offset in cncx', 'class_offset'),538 5 : ('Class offset in cncx', 'class_offset'),
539 22 : ('First section index', 'first_section_index'),539 22 : ('First section index', 'first_child_index'),
540 23 : ('Last section index', 'last_section_index'),540 23 : ('Last section index', 'last_child_index'),
541 },541 },
542542
543 'section' : {543 'section' : {
544 5 : ('Class offset in cncx', 'class_offset'),544 5 : ('Class offset in cncx', 'class_offset'),
545 21 : ('Periodical index', 'periodical_index'),545 21 : ('Periodical index', 'parent_index'),
546 22 : ('First article index', 'first_article_index'),546 22 : ('First article index', 'first_child_index'),
547 23 : ('Last article index', 'last_article_index'),547 23 : ('Last article index', 'last_child_index'),
548 },548 },
549 }549 }
550550
@@ -625,11 +625,56 @@
625 return tag.cncx_value625 return tag.cncx_value
626 return ''626 return ''
627627
628 @property
629 def offset(self):
630 for tag in self.tags:
631 if tag.attr == 'offset':
632 return tag.value
633 return 0
634
635 @property
636 def size(self):
637 for tag in self.tags:
638 if tag.attr == 'size':
639 return tag.value
640 return 0
641
642 @property
643 def depth(self):
644 for tag in self.tags:
645 if tag.attr == 'depth':
646 return tag.value
647 return 0
648
649 @property
650 def parent_index(self):
651 for tag in self.tags:
652 if tag.attr == 'parent_index':
653 return tag.value
654 return -1
655
656 @property
657 def first_child_index(self):
658 for tag in self.tags:
659 if tag.attr == 'first_child_index':
660 return tag.value
661 return -1
662
663 @property
664 def last_child_index(self):
665 for tag in self.tags:
666 if tag.attr == 'last_child_index':
667 return tag.value
668 return -1
669
628 def __str__(self):670 def __str__(self):
629 ans = ['Index Entry(index=%s, entry_type=%s, length=%d)'%(671 ans = ['Index Entry(index=%s, entry_type=%s, length=%d)'%(
630 self.index, self.entry_type, len(self.tags))]672 self.index, self.entry_type, len(self.tags))]
631 for tag in self.tags:673 for tag in self.tags:
632 ans.append('\t'+str(tag))674 ans.append('\t'+str(tag))
675 if self.first_child_index != -1:
676 ans.append('\tNumber of children: %d'%(self.last_child_index -
677 self.first_child_index + 1))
633 return '\n'.join(ans)678 return '\n'.join(ans)
634679
635# }}}680# }}}
@@ -679,6 +724,15 @@
679 entry_type = ord(indxt[off+consumed])724 entry_type = ord(indxt[off+consumed])
680 self.indices.append(IndexEntry(index, entry_type,725 self.indices.append(IndexEntry(index, entry_type,
681 indxt[off+consumed+1:next_off], cncx, index_header.tagx_entries))726 indxt[off+consumed+1:next_off], cncx, index_header.tagx_entries))
727 index = self.indices[-1]
728
729 def get_parent(self, index):
730 if index.depth < 1:
731 return None
732 parent_depth = index.depth - 1
733 for p in self.indices:
734 if p.depth != parent_depth:
735 continue
682736
683737
684 def __str__(self):738 def __str__(self):
@@ -793,6 +847,231 @@
793847
794# }}}848# }}}
795849
850class TBSIndexing(object): # {{{
851
852 def __init__(self, text_records, indices, doc_type):
853 self.record_indices = OrderedDict()
854 self.doc_type = doc_type
855 self.indices = indices
856 pos = 0
857 for r in text_records:
858 start = pos
859 pos += len(r.raw)
860 end = pos - 1
861 self.record_indices[r] = x = {'starts':[], 'ends':[],
862 'complete':[], 'geom': (start, end)}
863 for entry in indices:
864 istart, sz = entry.offset, entry.size
865 iend = istart + sz - 1
866 has_start = istart >= start and istart <= end
867 has_end = iend >= start and iend <= end
868 rec = None
869 if has_start and has_end:
870 rec = 'complete'
871 elif has_start and not has_end:
872 rec = 'starts'
873 elif not has_start and has_end:
874 rec = 'ends'
875 if rec:
876 x[rec].append(entry)
877
878 def get_index(self, idx):
879 for i in self.indices:
880 if i.index == idx: return i
881 raise IndexError('Index %d not found'%idx)
882
883 def __str__(self):
884 ans = ['*'*20 + ' TBS Indexing (%d records) '%len(self.record_indices)+ '*'*20]
885 for r, dat in self.record_indices.iteritems():
886 ans += self.dump_record(r, dat)[-1]
887 return '\n'.join(ans)
888
889 def dump(self, bdir):
890 types = defaultdict(list)
891 for r, dat in self.record_indices.iteritems():
892 tbs_type, strings = self.dump_record(r, dat)
893 if tbs_type == 0: continue
894 types[tbs_type] += strings
895 for typ, strings in types.iteritems():
896 with open(os.path.join(bdir, 'tbs_type_%d.txt'%typ), 'wb') as f:
897 f.write('\n'.join(strings))
898
899 def dump_record(self, r, dat):
900 ans = []
901 ans.append('\nRecord #%d: Starts at: %d Ends at: %d'%(r.idx,
902 dat['geom'][0], dat['geom'][1]))
903 s, e, c = dat['starts'], dat['ends'], dat['complete']
904 ans.append(('\tContains: %d index entries '
905 '(%d ends, %d complete, %d starts)')%tuple(map(len, (s+e+c, e,
906 c, s))))
907 byts = bytearray(r.trailing_data.get('indexing', b''))
908 sbyts = tuple(hex(b)[2:] for b in byts)
909 ans.append('TBS bytes: %s'%(' '.join(sbyts)))
910 for typ, entries in (('Ends', e), ('Complete', c), ('Starts', s)):
911 if entries:
912 ans.append('\t%s:'%typ)
913 for x in entries:
914 ans.append(('\t\tIndex Entry: %d (Parent index: %d, '
915 'Depth: %d, Offset: %d, Size: %d) [%s]')%(
916 x.index, x.parent_index, x.depth, x.offset, x.size, x.label))
917 def bin3(num):
918 ans = bin(num)[2:]
919 return '0'*(3-len(ans)) + ans
920
921 tbs_type = 0
922 if len(byts):
923 outer, consumed = decint(byts)
924 byts = byts[consumed:]
925 tbs_type = outer & 0b111
926 ans.append('TBS Type: %s (%d)'%(bin3(tbs_type), tbs_type))
927 ans.append('Outer Index entry: %d'%(outer >> 3))
928 arg1, consumed = decint(byts)
929 byts = byts[consumed:]
930 ans.append('Unknown (vwi: always 0?): %d'%arg1)
931 if self.doc_type in (257, 259): # Hierarchical periodical
932 byts, a = self.interpret_periodical(tbs_type, byts)
933 ans += a
934 if byts:
935 sbyts = tuple(hex(b)[2:] for b in byts)
936 ans.append('Remaining bytes: %s'%' '.join(sbyts))
937
938 ans.append('')
939 return tbs_type, ans
940
941 def interpret_periodical(self, tbs_type, byts):
942 ans = []
943
944 def tbs_type_6(byts, psi=None, msg=None): # {{{
945 if psi is None:
946 # Assume parent section is 1
947 psi = self.get_index(1)
948 if msg is None:
949 msg = ('Article index at start of record or first article'
950 ' index, relative to parent section')
951 if byts:
952 # byts could be empty
953 arg, consumed = decint(byts)
954 byts = byts[consumed:]
955 flags = (arg & 0b1111)
956 ai = (arg >> 4)
957 ans.append('%s (fvwi): %d [%d absolute]'%(msg, ai,
958 ai+psi.index))
959 if flags == 1:
960 arg, consumed = decint(byts)
961 byts = byts[consumed:]
962 ans.append('EOF (vwi: should be 0): %d'%arg)
963 elif flags in (4, 5):
964 num = byts[0]
965 byts = byts[1:]
966 ans.append('Number of article nodes in the record (byte): %d'%num)
967 if flags == 5:
968 arg, consumed = decint(byts)
969 byts = byts[consumed:]
970 ans.append('Unknown ??? (vwi)): %d'%(arg))
971 elif flags == 0:
972 pass
973 else:
974 raise ValueError('Unknown flags: %d'%flags)
975 return byts
976
977 # }}}
978
979 if tbs_type == 3: # {{{
980 arg2, consumed = decint(byts)
981 byts = byts[consumed:]
982 ans.append('Unknown (vwi: always 0?): %d'%arg2)
983
984 arg3, consumed = decint(byts)
985 byts = byts[consumed:]
986 fsi = arg3 >> 4
987 extra = arg3 & 0b1111
988 ans.append('First section index (fvwi): %d'%fsi)
989 psi = self.get_index(fsi)
990 ans.append('Extra bits (flag: always 0?): %d'%extra)
991
992 byts = tbs_type_6(byts, psi=psi,
993 msg=('First article of ending section, relative to its'
994 ' parent\'s index'))
995 if byts:
996 # We have a transition not just an opening first section
997 psi = self.get_index(psi.index+1)
998 arg, consumed = decint(byts)
999 off = arg >> 4
1000 byts = byts[consumed:]
1001 flags = arg & 0b1111
1002 ans.append('Last article of ending section w.r.t. starting'
1003 ' section offset (fvwi): %d [%d absolute]'%(off,
1004 psi.index+off))
1005 ans.append('Flags (always 8?): %d'%flags)
1006 byts = tbs_type_6(byts, psi=psi)
1007 # }}}
1008
1009 elif tbs_type == 7: # {{{
1010 # This occurs for records that have no section nodes and
1011 # whose parent section's index == 1
1012 ans.append('Unknown (maybe vwi?): %r'%bytes(byts[:2]))
1013 byts = byts[2:]
1014 arg, consumed = decint(byts)
1015 byts = byts[consumed:]
1016 ai = arg >> 4
1017 flags = arg & 0b1111
1018 ans.append('Article at start of record (fvwi): %d'%ai)
1019 if flags == 4:
1020 num = byts[0]
1021 byts = byts[1:]
1022 ans.append('Number of articles in record (byte): %d'%num)
1023 elif flags == 0:
1024 pass
1025 elif flags == 1:
1026 arg, consumed = decint(byts)
1027 byts = byts[consumed:]
1028 ans.append('EOF (vwi: should be 0): %d'%arg)
1029 else:
1030 raise ValueError('Unknown flags value: %d'%flags)
1031 # }}}
1032
1033 elif tbs_type == 6: # {{{
1034 # This is used for records spanned by an article whose parent
1035 # section's index == 1 or for the opening record if it contains the
1036 # periodical start, section 1 start and at least one article. The
1037 # two cases are distinguished by the flags on the article index
1038 # vwi.
1039 unk = byts[0]
1040 byts = byts[1:]
1041 ans.append('Unknown (byte: always 2?): %d'%unk)
1042 byts = tbs_type_6(byts)
1043 # }}}
1044
1045 elif tbs_type == 2: # {{{
1046 # This occurs for records with no section nodes and whose parent
1047 # section's index != 1 (undefined (records before the first
1048 # section) or > 1)
1049 # This is also used for records that are spanned by an article
1050 # whose parent section index > 1. In this case the flags of the
1051 # vwi referring to the article at the start
1052 # of the record are set to 1 instead of 4.
1053 arg, consumed = decint(byts)
1054 byts = byts[consumed:]
1055 flags = (arg & 0b1111)
1056 psi = (arg >> 4)
1057 ans.append('Parent section index (fvwi): %d'%psi)
1058 psi = self.get_index(psi)
1059 ans.append('Flags: %d'%flags)
1060 if flags == 1:
1061 arg, consumed = decint(byts)
1062 byts = byts[consumed:]
1063 ans.append('Unknown (vwi?: always 0?): %d'%arg)
1064 byts = tbs_type_6(byts, psi=psi)
1065 elif flags == 0:
1066 byts = tbs_type_6(byts, psi=psi)
1067 else:
1068 raise ValueError('Unkown flags: %d'%flags)
1069 # }}}
1070
1071 return byts, ans
1072
1073# }}}
1074
796class MOBIFile(object): # {{{1075class MOBIFile(object): # {{{
7971076
798 def __init__(self, stream):1077 def __init__(self, stream):
@@ -874,6 +1153,9 @@
874 else:1153 else:
875 self.binary_records.append(BinaryRecord(i, r))1154 self.binary_records.append(BinaryRecord(i, r))
8761155
1156 if self.index_record is not None:
1157 self.tbs_indexing = TBSIndexing(self.text_records,
1158 self.index_record.indices, self.mobi_header.type_raw)
8771159
878 def print_header(self, f=sys.stdout):1160 def print_header(self, f=sys.stdout):
879 print (str(self.palmdb).encode('utf-8'), file=f)1161 print (str(self.palmdb).encode('utf-8'), file=f)
@@ -905,6 +1187,9 @@
905 print(str(f.cncx).encode('utf-8'), file=out)1187 print(str(f.cncx).encode('utf-8'), file=out)
906 print('\n\n', file=out)1188 print('\n\n', file=out)
907 print(str(f.index_record), file=out)1189 print(str(f.index_record), file=out)
1190 with open(os.path.join(ddir, 'tbs_indexing.txt'), 'wb') as out:
1191 print(str(f.tbs_indexing), file=out)
1192 f.tbs_indexing.dump(ddir)
9081193
909 for tdir, attr in [('text', 'text_records'), ('images', 'image_records'),1194 for tdir, attr in [('text', 'text_records'), ('images', 'image_records'),
910 ('binary', 'binary_records')]:1195 ('binary', 'binary_records')]:
9111196
=== added file 'src/calibre/ebooks/mobi/tbs_periodicals.rst'
--- src/calibre/ebooks/mobi/tbs_periodicals.rst 1970-01-01 00:00:00 +0000
+++ src/calibre/ebooks/mobi/tbs_periodicals.rst 2011-07-22 18:03:36 +0000
@@ -0,0 +1,189 @@
1Reverse engineering the trailing byte sequences for hierarchical periodicals
2===============================================================================
3
4In the following, *vwi* means variable width integer and *fvwi* means a vwi whose lowest four bits are used as a flag.
5
6Opening record
7----------------
8
9The text record that contains the opening node for the periodical (depth=0 node in the NCX) can have TBS of 3 different forms:
10
11 1. If it has only the periodical node and no section/article nodes, TBS of type 2, like this::
12
13 Record #1: Starts at: 0 Ends at: 4095
14 Contains: 1 index entries (0 ends, 0 complete, 1 starts)
15 TBS bytes: 82 80
16 Starts:
17 Index Entry: 0 (Parent index: -1, Depth: 0, Offset: 215, Size: 68470) [j_x's Google reader]
18 TBS Type: 010 (2)
19 Outer Index entry: 0
20 Unknown (vwi: always 0?): 0
21
22 2. A periodical and a section node, but no article nodes, TBS type of 6, like this::
23
24 Record #1: Starts at: 0 Ends at: 4095
25 Contains: 2 index entries (0 ends, 0 complete, 2 starts)
26 TBS bytes: 86 80 2
27 Starts:
28 Index Entry: 0 (Parent index: -1, Depth: 0, Offset: 215, Size: 93254) [j_x's Google reader]
29 Index Entry: 1 (Parent index: 0, Depth: 1, Offset: 541, Size: 49280) [Ars Technica]
30 TBS Type: 110 (6)
31 Outer Index entry: 0
32 Unknown (vwi: always 0?): 0
33 Unknown (byte: always 2?): 2
34
35 3. If it has both the section 1 node and at least one article node, TBS of type 6, like this::
36
37 Record #1: Starts at: 0 Ends at: 4095
38 Contains: 4 index entries (0 ends, 1 complete, 3 starts)
39 TBS bytes: 86 80 2 c4 2
40 Complete:
41 Index Entry: 5 (Parent index: 1, Depth: 2, Offset: 549, Size: 1866) [Week in gaming: 3DS review, Crysis 2, George Hotz]
42 Starts:
43 Index Entry: 0 (Parent index: -1, Depth: 0, Offset: 215, Size: 79253) [j_x's Google reader]
44 Index Entry: 1 (Parent index: 0, Depth: 1, Offset: 541, Size: 35279) [Ars Technica]
45 Index Entry: 6 (Parent index: 1, Depth: 2, Offset: 2415, Size: 2764) [Week in Apple: ZFS on Mac OS X, rogue tethering, DUI apps, and more]
46 TBS Type: 110 (6)
47 Outer Index entry: 0
48 Unknown (vwi: always 0?): 0
49 Unknown (byte: always 2?): 2
50 Article index at start of record or first article index, relative to parent section (fvwi): 4 [5 absolute]
51 Number of article nodes in the record (byte): 2
52
53 If there was only a single article, instead of 2, then the last two bytes would be: c0, i.e. there would be no byte giving the number of articles in the record.
54
55
56Records with no nodes
57------------------------
58
59These records are spanned by a single article. They are of two types:
60
61 1. If the parent section index is 1, TBS type of 6, like this::
62
63 Record #4: Starts at: 12288 Ends at: 16383
64 Contains: 0 index entries (0 ends, 0 complete, 0 starts)
65 TBS bytes: 86 80 2 c1 80
66 TBS Type: 110 (6)
67 Outer Index entry: 0
68 Unknown (vwi: always 0?): 0
69 Unknown (byte: always 2?): 2
70 Article index at start of record or first article index, relative to parent section (fvwi): 4 [5 absolute]
71 EOF (vwi: should be 0): 0
72
73 If the record is before the first article, the TBS bytes would be: 86 80 2
74
75 2. If the parent section index is > 1, TBS type of 2, like this::
76
77 Record #14: Starts at: 53248 Ends at: 57343
78 Contains: 0 index entries (0 ends, 0 complete, 0 starts)
79 TBS bytes: 82 80 a0 1 e1 80
80 TBS Type: 010 (2)
81 Outer Index entry: 0
82 Unknown (vwi: always 0?): 0
83 Parent section index (fvwi): 2
84 Flags: 0
85 Article index at start of record or first article index, relative to parent section (fvwi): 14 [16 absolute]
86 EOF (vwi: should be 0): 0
87
88Records with only article nodes
89-----------------------------------
90
91Such records have no section transitions (i.e. a section end/section start pair). They have only one or more article nodes. They are of two types:
92
93 1. If the parent section index is 1, TBS type of 7, like this::
94
95 Record #6: Starts at: 20480 Ends at: 24575
96 Contains: 2 index entries (1 ends, 0 complete, 1 starts)
97 TBS bytes: 87 80 2 80 1 84 2
98 Ends:
99 Index Entry: 9 (Parent index: 1, Depth: 2, Offset: 16453, Size: 4199) [Vaccine's success spurs whooping cough comeback]
100 Starts:
101 Index Entry: 10 (Parent index: 1, Depth: 2, Offset: 20652, Size: 4246) [Apple's mobile products do not violate Nokia patents, says ITC]
102 TBS Type: 111 (7)
103 Outer Index entry: 0
104 Unknown (vwi: always 0?): 0
105 Unknown: '\x02\x80' (vwi?: Always 256)
106 Article at start of record (fvwi): 8
107 Number of articles in record (byte): 2
108
109 If there was only one article in the record, the last two bytes would be replaced by a single byte: 80
110
111 If this record is the first record with an article, then the article at the start of the record should be the last section index. At least, that's what kindlegen does, though if you ask me, it should be the first section index.
112
113
114 2. If the parent section index is > 1, TBS type of 2, like this::
115
116 Record #16: Starts at: 61440 Ends at: 65535
117 Contains: 5 index entries (1 ends, 3 complete, 1 starts)
118 TBS bytes: 82 80 a1 80 1 f4 5
119 Ends:
120 Index Entry: 17 (Parent index: 2, Depth: 2, Offset: 60920, Size: 1082) [Microsoft's Joe Belfiore still working on upcoming Zune hardware]
121 Complete:
122 Index Entry: 18 (Parent index: 2, Depth: 2, Offset: 62002, Size: 1016) [Rumour: OS X Lion nearing Golden Master stage]
123 Index Entry: 19 (Parent index: 2, Depth: 2, Offset: 63018, Size: 1045) [iOS 4.3.1 released]
124 Index Entry: 20 (Parent index: 2, Depth: 2, Offset: 64063, Size: 972) [Windows 8 'system reset' image leaks]
125 Starts:
126 Index Entry: 21 (Parent index: 2, Depth: 2, Offset: 65035, Size: 1057) [Windows Phone 7: Why it's failing]
127 TBS Type: 010 (2)
128 Outer Index entry: 0
129 Unknown (vwi: always 0?): 0
130 Parent section index (fvwi) : 2
131 Flags: 1
132 Unknown (vwi: always 0?): 0
133 Article index at start of record or first article index, relative to parent section (fvwi): 15 [17 absolute]
134 Number of article nodes in the record (byte): 5
135
136 If there was only one article in the record, the last two bytes would be replaced by a single byte: f0
137
138Records with a section transition
139-----------------------------------
140
141In such a record there is a transition from one section to the next. As such the record must have at least one article ending and one article starting, except in the case of the first section.
142
143TODO: Note you have to test the cases of first section, a single transition and multiple transitions.
144
145 1. The first section::
146
147 Record #2: Starts at: 4096 Ends at: 8191
148 Contains: 2 index entries (0 ends, 0 complete, 2 starts)
149 TBS bytes: 83 80 80 90 c0
150 Starts:
151 Index Entry: 1 (Parent index: 0, Depth: 1, Offset: 7758, Size: 26279) [Ars Technica]
152 Index Entry: 5 (Parent index: 1, Depth: 2, Offset: 7766, Size: 1866) [Week in gaming: 3DS review, Crysis 2, George Hotz]
153 TBS Type: 011 (3)
154 Outer Index entry: 0
155 Unknown (vwi: always 0?): 0
156 Unknown (vwi: always 0?): 0
157 First section index (fvwi) : 1
158 Extra bits: 0
159 First section starts
160 Article at start of block as offset from parent index (fvwi): 4 [5 absolute]
161 Flags: 0
162
163 If there was more than one article at the start then the last byte would be replaced by: c4 n where n is the number of articles
164
165
166Ending record
167----------------
168
169Logically, ending records must have at least one article ending, one section ending and the periodical ending. They are of TBS type 2, like this::
170
171 Record #17: Starts at: 65536 Ends at: 68684
172 Contains: 4 index entries (3 ends, 1 complete, 0 starts)
173 TBS bytes: 82 80 c0 4 f4 2
174 Ends:
175 Index Entry: 0 (Parent index: -1, Depth: 0, Offset: 215, Size: 68470) [j_x's Google reader]
176 Index Entry: 4 (Parent index: 0, Depth: 1, Offset: 51234, Size: 17451) [Slashdot]
177 Index Entry: 43 (Parent index: 4, Depth: 2, Offset: 65422, Size: 1717) [US ITC May Reverse Judge&#39;s Ruling In Kodak vs. Apple]
178 Complete:
179 Index Entry: 44 (Parent index: 4, Depth: 2, Offset: 67139, Size: 1546) [Google Starts Testing Google Music Internally]
180 TBS Type: 010 (2)
181 Outer Index entry: 0
182 Unknown (vwi: always 0?): 0
183 Parent section index (fvwi): 4
184 Flags: 0
185 Article at start of block as offset from parent index (fvwi): 39 [43 absolute]
186 Number of nodes (byte): 2
187
188If the record had only a single article end, the last two bytes would be replaced with: f0
189
0190
=== modified file 'src/calibre/ebooks/mobi/utils.py'
--- src/calibre/ebooks/mobi/utils.py 2011-07-20 20:01:41 +0000
+++ src/calibre/ebooks/mobi/utils.py 2011-07-22 18:03:36 +0000
@@ -79,7 +79,7 @@
7979
80def decint(raw, forward=True):80def decint(raw, forward=True):
81 '''81 '''
82 Read a variable width integer from the bytestring raw and return the82 Read a variable width integer from the bytestring or bytearray raw and return the
83 integer and the number of bytes read. If forward is True bytes are read83 integer and the number of bytes read. If forward is True bytes are read
84 from the start of raw, otherwise from the end of raw.84 from the start of raw, otherwise from the end of raw.
8585
@@ -88,8 +88,10 @@
88 '''88 '''
89 val = 089 val = 0
90 byts = bytearray()90 byts = bytearray()
91 for byte in raw if forward else reversed(raw):91 src = bytearray(raw)
92 bnum = ord(byte)92 if not forward:
93 src.reverse()
94 for bnum in src:
93 byts.append(bnum & 0b01111111)95 byts.append(bnum & 0b01111111)
94 if bnum & 0b10000000:96 if bnum & 0b10000000:
95 break97 break
9698
=== modified file 'src/calibre/gui2/store/stores/chitanka_plugin.py'
--- src/calibre/gui2/store/stores/chitanka_plugin.py 2011-07-19 06:16:55 +0000
+++ src/calibre/gui2/store/stores/chitanka_plugin.py 2011-07-22 18:03:36 +0000
@@ -55,36 +55,21 @@
55 if counter <= 0:55 if counter <= 0:
56 break56 break
5757
58 id = ''.join(data.xpath('.//a[@class="booklink"]/@href'))58 id = ''.join(data.xpath('.//a[@class="booklink"]/@href')).strip()
59 if not id:59 if not id:
60 continue60 continue
6161
62 cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src'))
63 title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()'))
64 author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()'))
65 fb2 = ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href'))
66 epub = ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href'))
67 txt = ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href'))
68
69 # remove .zip extensions
70 if fb2.find('.zip') != -1:
71 fb2 = fb2[:fb2.find('.zip')]
72 if epub.find('.zip') != -1:
73 epub = epub[:epub.find('.zip')]
74 if txt.find('.zip') != -1:
75 txt = txt[:txt.find('.zip')]
76
77 counter -= 162 counter -= 1
7863
79 s = SearchResult()64 s = SearchResult()
80 s.cover_url = cover_url65 s.cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src')).strip()
81 s.title = title.strip()66 s.title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()')).strip()
82 s.author = author.strip()67 s.author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()')).strip()
83 s.detail_item = id.strip()68 s.detail_item = id
84 s.drm = SearchResult.DRM_UNLOCKED69 s.drm = SearchResult.DRM_UNLOCKED
85 s.downloads['FB2'] = base_url + fb2.strip()70 s.downloads['FB2'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href')).strip().replace('.zip', '')
86 s.downloads['EPUB'] = base_url + epub.strip()71 s.downloads['EPUB'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href')).strip().replace('.zip', '')
87 s.downloads['TXT'] = base_url + txt.strip()72 s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
88 s.formats = 'FB2, EPUB, TXT, SFB'73 s.formats = 'FB2, EPUB, TXT, SFB'
89 yield s74 yield s
9075
@@ -106,35 +91,20 @@
106 if counter <= 0:91 if counter <= 0:
107 break92 break
10893
109 id = ''.join(data.xpath('.//a[@class="booklink"]/@href'))94 id = ''.join(data.xpath('.//a[@class="booklink"]/@href')).strip()
110 if not id:95 if not id:
111 continue96 continue
11297
113 cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src'))
114 title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()'))
115 author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()'))
116 fb2 = ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href'))
117 epub = ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href'))
118 txt = ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href'))
119
120 # remove .zip extensions
121 if fb2.find('.zip') != -1:
122 fb2 = fb2[:fb2.find('.zip')]
123 if epub.find('.zip') != -1:
124 epub = epub[:epub.find('.zip')]
125 if txt.find('.zip') != -1:
126 txt = txt[:txt.find('.zip')]
127
128 counter -= 198 counter -= 1
12999
130 s = SearchResult()100 s = SearchResult()
131 s.cover_url = cover_url101 s.cover_url = ''.join(data.xpath('.//a[@class="booklink"]/img/@src')).strip()
132 s.title = title.strip()102 s.title = ''.join(data.xpath('.//a[@class="booklink"]/i/text()')).strip()
133 s.author = author.strip()103 s.author = ''.join(data.xpath('.//span[@class="bookauthor"]/a/text()')).strip()
134 s.detail_item = id.strip()104 s.detail_item = id
135 s.drm = SearchResult.DRM_UNLOCKED105 s.drm = SearchResult.DRM_UNLOCKED
136 s.downloads['FB2'] = base_url + fb2.strip()106 s.downloads['FB2'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-fb2"]/@href')).strip().replace('.zip', '')
137 s.downloads['EPUB'] = base_url + epub.strip()107 s.downloads['EPUB'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-epub"]/@href')).strip().replace('.zip', '')
138 s.downloads['TXT'] = base_url + txt.strip()108 s.downloads['TXT'] = base_url + ''.join(data.xpath('.//a[@class="dl dl-txt"]/@href')).strip().replace('.zip', '')
139 s.formats = 'FB2, EPUB, TXT, SFB'109 s.formats = 'FB2, EPUB, TXT, SFB'
140 yield s110 yield s
141111
=== added file 'src/calibre/gui2/store/stores/eknigi_plugin.py'
--- src/calibre/gui2/store/stores/eknigi_plugin.py 1970-01-01 00:00:00 +0000
+++ src/calibre/gui2/store/stores/eknigi_plugin.py 2011-07-22 18:03:36 +0000
@@ -0,0 +1,88 @@
1# -*- coding: utf-8 -*-
2
3from __future__ import (unicode_literals, division, absolute_import, print_function)
4
5__license__ = 'GPL 3'
6__copyright__ = '2011, Alex Stanev <alex@stanev.org>'
7__docformat__ = 'restructuredtext en'
8
9import random
10import urllib2
11from contextlib import closing
12
13from lxml import html
14
15from PyQt4.Qt import QUrl
16
17from calibre import browser, url_slash_cleaner
18from calibre.gui2 import open_url
19from calibre.gui2.store import StorePlugin
20from calibre.gui2.store.basic_config import BasicStoreConfig
21from calibre.gui2.store.search_result import SearchResult
22from calibre.gui2.store.web_store_dialog import WebStoreDialog
23
24class eKnigiStore(BasicStoreConfig, StorePlugin):
25
26 def open(self, parent=None, detail_item=None, external=False):
27 # Use Kovid's affiliate id 30% of the time
28 if random.randint(1, 10) in (1, 2, 3):
29 aff_suffix = '&amigosid=23'
30 else:
31 aff_suffix = '&amigosid=22'
32 url = 'http://e-knigi.net/?' + aff_suffix[1:]
33
34 if external or self.config.get('open_external', False):
35 if detail_item:
36 url = detail_item + aff_suffix
37 open_url(QUrl(url_slash_cleaner(url)))
38 else:
39 detail_url = None
40 if detail_item:
41 url = detail_item + aff_suffix
42 d = WebStoreDialog(self.gui, url, parent, detail_url)
43 d.setWindowTitle(self.name)
44 d.set_tags(self.config.get('tags', ''))
45 d.exec_()
46
47 def search(self, query, max_results=10, timeout=60):
48 base_url = 'http://e-knigi.net'
49 url = base_url + '/virtuemart?page=shop.browse&search_category=0&search_limiter=anywhere&limitstart=0&limit=' + str(max_results) + '&keyword=' + urllib2.quote(query)
50
51 br = browser()
52
53 counter = max_results
54 with closing(br.open(url, timeout=timeout)) as f:
55 doc = html.fromstring(f.read())
56
57 # if the store finds only one product, it opens directly detail view
58 for data in doc.xpath('//div[@class="prod_details"]'):
59 s = SearchResult()
60 s.cover_url = ''.join(data.xpath('.//div[@class="vm_main_info clearfix"]/div[@class="lf"]/a/img/@src')).strip()
61 s.title = ''.join(data.xpath('.//div[@class="vm_main_info clearfix"]/div[@class="lf"]/a/img/@alt')).strip()
62 s.author = ''.join(data.xpath('.//div[@class="td_bg clearfix"]/div[@class="gk_product_tab"]/div/table/tr[3]/td[2]/text()')).strip()
63 s.price = ''.join(data.xpath('.//span[@class="productPrice"]/text()')).strip()
64 s.detail_item = url
65 s.drm = SearchResult.DRM_UNLOCKED
66
67 yield s
68 return
69
70 # search in store results
71 for data in doc.xpath('//div[@class="browseProductContainer"]'):
72 if counter <= 0:
73 break
74 id = ''.join(data.xpath('.//a[1]/@href')).strip()
75 if not id:
76 continue
77
78 counter -= 1
79
80 s = SearchResult()
81 s.cover_url = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@src')).strip()
82 s.title = ''.join(data.xpath('.//a[@class="gk_vm_product_image"]/img/@title')).strip()
83 s.author = ''.join(data.xpath('.//div[@style="float:left;width:90%"]/b/text()')).strip().replace('Автор: ', '')
84 s.price = ''.join(data.xpath('.//span[@class="productPrice"]/text()')).strip()
85 s.detail_item = base_url + id
86 s.drm = SearchResult.DRM_UNLOCKED
87
88 yield s
089
=== modified file 'src/calibre/translations/msgfmt.py'
--- src/calibre/translations/msgfmt.py 2008-05-02 16:41:12 +0000
+++ src/calibre/translations/msgfmt.py 2011-07-22 18:03:36 +0000
@@ -1,20 +1,39 @@
1#! /usr/bin/env python1#! /usr/bin/env python
2# Written by Martin v. Loewis <loewis@informatik.hu-berlin.de>2# Written by Martin v. Loewis <loewis@informatik.hu-berlin.de>
3# Modified by Kovid Goyal <kovid@kovidgoyal.net>
43
5"""Generate binary message catalog from textual translation description.4"""Generate binary message catalog from textual translation description.
65
7This program converts a textual Uniforum-style message catalog (.po file) into6This program converts a textual Uniforum-style message catalog (.po file) into
8a binary GNU catalog (.mo file). This is essentially the same function as the7a binary GNU catalog (.mo file). This is essentially the same function as the
9GNU msgfmt program, however, it is a simpler implementation.8GNU msgfmt program, however, it is a simpler implementation.
9
10Usage: msgfmt.py [OPTIONS] filename.po
11
12Options:
13 -o file
14 --output-file=file
15 Specify the output file to write to. If omitted, output will go to a
16 file named filename.mo (based off the input file name).
17
18 -h
19 --help
20 Print this message and exit.
21
22 -V
23 --version
24 Display version information and exit.
10"""25"""
1126
12import sys27import sys
13import os28import os
29import getopt
14import struct30import struct
15import array31import array
1632
17__version__ = "1.2"33__version__ = "1.1"
34
35MESSAGES = {}
36
1837
19def usage(code, msg=''):38def usage(code, msg=''):
20 print >> sys.stderr, __doc__39 print >> sys.stderr, __doc__
@@ -23,16 +42,16 @@
23 sys.exit(code)42 sys.exit(code)
2443
2544
2645def add(id, str, fuzzy):
27def add(id, str, fuzzy, MESSAGES):
28 "Add a non-fuzzy translation to the dictionary."46 "Add a non-fuzzy translation to the dictionary."
47 global MESSAGES
29 if not fuzzy and str:48 if not fuzzy and str:
30 MESSAGES[id] = str49 MESSAGES[id] = str
3150
3251
3352def generate():
34def generate(MESSAGES):
35 "Return the generated output."53 "Return the generated output."
54 global MESSAGES
36 keys = MESSAGES.keys()55 keys = MESSAGES.keys()
37 # the keys are sorted in the .mo file56 # the keys are sorted in the .mo file
38 keys.sort()57 keys.sort()
@@ -44,6 +63,7 @@
44 offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))63 offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
45 ids += id + '\0'64 ids += id + '\0'
46 strs += MESSAGES[id] + '\0'65 strs += MESSAGES[id] + '\0'
66 output = ''
47 # The header is 7 32-bit unsigned integers. We don't use hash tables, so67 # The header is 7 32-bit unsigned integers. We don't use hash tables, so
48 # the keys start right after the index tables.68 # the keys start right after the index tables.
49 # translated string.69 # translated string.
@@ -71,9 +91,7 @@
71 return output91 return output
7292
7393
74
75def make(filename, outfile):94def make(filename, outfile):
76 MESSAGES = {}
77 ID = 195 ID = 1
78 STR = 296 STR = 2
7997
@@ -101,7 +119,7 @@
101 lno += 1119 lno += 1
102 # If we get a comment line after a msgstr, this is a new entry120 # If we get a comment line after a msgstr, this is a new entry
103 if l[0] == '#' and section == STR:121 if l[0] == '#' and section == STR:
104 add(msgid, msgstr, fuzzy, MESSAGES)122 add(msgid, msgstr, fuzzy)
105 section = None123 section = None
106 fuzzy = 0124 fuzzy = 0
107 # Record a fuzzy mark125 # Record a fuzzy mark
@@ -111,16 +129,39 @@
111 if l[0] == '#':129 if l[0] == '#':
112 continue130 continue
113 # Now we are in a msgid section, output previous section131 # Now we are in a msgid section, output previous section
114 if l.startswith('msgid'):132 if l.startswith('msgid') and not l.startswith('msgid_plural'):
115 if section == STR:133 if section == STR:
116 add(msgid, msgstr, fuzzy, MESSAGES)134 add(msgid, msgstr, fuzzy)
117 section = ID135 section = ID
118 l = l[5:]136 l = l[5:]
119 msgid = msgstr = ''137 msgid = msgstr = ''
138 is_plural = False
139 # This is a message with plural forms
140 elif l.startswith('msgid_plural'):
141 if section != ID:
142 print >> sys.stderr, 'msgid_plural not preceeded by msgid on %s:%d' %\
143 (infile, lno)
144 sys.exit(1)
145 l = l[12:]
146 msgid += '\0' # separator of singular and plural
147 is_plural = True
120 # Now we are in a msgstr section148 # Now we are in a msgstr section
121 elif l.startswith('msgstr'):149 elif l.startswith('msgstr'):
122 section = STR150 section = STR
123 l = l[6:]151 if l.startswith('msgstr['):
152 if not is_plural:
153 print >> sys.stderr, 'plural without msgid_plural on %s:%d' %\
154 (infile, lno)
155 sys.exit(1)
156 l = l.split(']', 1)[1]
157 if msgstr:
158 msgstr += '\0' # Separator of the various plural forms
159 else:
160 if is_plural:
161 print >> sys.stderr, 'indexed msgstr required for plural on %s:%d' %\
162 (infile, lno)
163 sys.exit(1)
164 l = l[6:]
124 # Skip empty lines165 # Skip empty lines
125 l = l.strip()166 l = l.strip()
126 if not l:167 if not l:
@@ -138,22 +179,40 @@
138 sys.exit(1)179 sys.exit(1)
139 # Add last entry180 # Add last entry
140 if section == STR:181 if section == STR:
141 add(msgid, msgstr, fuzzy, MESSAGES)182 add(msgid, msgstr, fuzzy)
142183
143 # Compute output184 # Compute output
144 output = generate(MESSAGES)185 output = generate()
145186
187 outfile.write(output)
188
189
190def main():
146 try:191 try:
147 outfile.write(output)192 opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
148 except IOError,msg:193 ['help', 'version', 'output-file='])
149 print >> sys.stderr, msg194 except getopt.error, msg:
150195 usage(1, msg)
151196
152197 outfile = None
153def main(outfile, args=sys.argv[1:]):198 # parse options
199 for opt, arg in opts:
200 if opt in ('-h', '--help'):
201 usage(0)
202 elif opt in ('-V', '--version'):
203 print >> sys.stderr, "msgfmt.py", __version__
204 sys.exit(0)
205 elif opt in ('-o', '--output-file'):
206 outfile = arg
207 # do it
208 if not args:
209 print >> sys.stderr, 'No input file given'
210 print >> sys.stderr, "Try `msgfmt --help' for more information."
211 return
212
154 for filename in args:213 for filename in args:
155 make(filename, outfile)214 make(filename, outfile)
156 return 0215
157216
158if __name__ == '__main__':217if __name__ == '__main__':
159 sys.exit(main(sys.stdout))218 main()
160219
=== modified file 'src/calibre/utils/localization.py'
--- src/calibre/utils/localization.py 2011-07-14 22:55:58 +0000
+++ src/calibre/utils/localization.py 2011-07-22 18:03:36 +0000
@@ -71,13 +71,13 @@
71 lang = get_lang()71 lang = get_lang()
72 if lang:72 if lang:
73 buf = iso639 = None73 buf = iso639 = None
74 if os.access(lang+'.po', os.R_OK):74 mpath = get_lc_messages_path(lang)
75 if mpath and os.access(mpath+'.po', os.R_OK):
75 from calibre.translations.msgfmt import make76 from calibre.translations.msgfmt import make
76 buf = cStringIO.StringIO()77 buf = cStringIO.StringIO()
77 make(lang+'.po', buf)78 make(mpath+'.po', buf)
78 buf = cStringIO.StringIO(buf.getvalue())79 buf = cStringIO.StringIO(buf.getvalue())
7980
80 mpath = get_lc_messages_path(lang)
81 if mpath is not None:81 if mpath is not None:
82 with ZipFile(P('localization/locales.zip',82 with ZipFile(P('localization/locales.zip',
83 allow_user_override=False), 'r') as zf:83 allow_user_override=False), 'r') as zf:
8484
=== modified file 'src/calibre/web/feeds/recipes/model.py'
--- src/calibre/web/feeds/recipes/model.py 2011-06-25 04:47:59 +0000
+++ src/calibre/web/feeds/recipes/model.py 2011-07-22 18:03:36 +0000
@@ -217,6 +217,8 @@
217 self.all_urns.add(urn)217 self.all_urns.add(urn)
218 if ok(urn):218 if ok(urn):
219 lang = x.get('language', 'und')219 lang = x.get('language', 'und')
220 if lang:
221 lang = lang.replace('-', '_')
220 if lang not in lang_map:222 if lang not in lang_map:
221 lang_map[lang] = factory(NewsCategory, new_root, lang)223 lang_map[lang] = factory(NewsCategory, new_root, lang)
222 factory(NewsItem, lang_map[lang], urn, x.get('title'))224 factory(NewsItem, lang_map[lang], urn, x.get('title'))

Subscribers

People subscribed via source and target branches