kamishibai 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/LICENSE +29 -0
  4. data/README.md +91 -0
  5. data/bin/kamishibai +59 -0
  6. data/changelogs.txt +6 -0
  7. data/images/reading.jpg +0 -0
  8. data/images/settings.jpg +0 -0
  9. data/images/view_browse.jpg +0 -0
  10. data/images/view_dir.jpg +0 -0
  11. data/kamishibai.gemspec +31 -0
  12. data/lib/kamishibai.rb +35 -0
  13. data/lib/kamishibai/book.rb +97 -0
  14. data/lib/kamishibai/config.rb +90 -0
  15. data/lib/kamishibai/database.rb +330 -0
  16. data/lib/kamishibai/functions.rb +332 -0
  17. data/lib/kamishibai/patches.rb +58 -0
  18. data/lib/kamishibai/version.rb +7 -0
  19. data/lib/kamishibai/webserver.rb +502 -0
  20. data/lib/kamishibai/webserver_cmds.rb +26 -0
  21. data/lib/kamishibai/webserver_config.rb +91 -0
  22. data/lib/kamishibai/webserver_filemanager.rb +29 -0
  23. data/lib/kamishibai/webserver_tablet.rb +259 -0
  24. data/lib/kamishibai/workers.rb +39 -0
  25. data/public/css/browse.css +390 -0
  26. data/public/css/config.css +3 -0
  27. data/public/css/reader.css +119 -0
  28. data/public/css/styles.css +30 -0
  29. data/public/css/tablet_browse.css +247 -0
  30. data/public/css/tablet_reader.css +323 -0
  31. data/public/css/tablet_reader_input.css +191 -0
  32. data/public/images/ajax-loader.gif +0 -0
  33. data/public/images/delete.png +0 -0
  34. data/public/images/folder-mini-up.png +0 -0
  35. data/public/images/folder-mini.png +0 -0
  36. data/public/images/spinner.gif +0 -0
  37. data/public/images/trash-empty-mini.png +0 -0
  38. data/public/images/trash-full-mini.png +0 -0
  39. data/public/js/browse.js +391 -0
  40. data/public/js/config.js +190 -0
  41. data/public/js/reader.js +497 -0
  42. data/public/js/swipeview-k.js +478 -0
  43. data/public/js/tablet3.js +241 -0
  44. data/public/js/tablet3_browse.js +173 -0
  45. data/public/js/tablet3_reader.js +534 -0
  46. data/public/js/utilities.js +170 -0
  47. data/public/lang/k-cn.json +82 -0
  48. data/public/lang/k-en.json +82 -0
  49. data/public/lang/k-ja.json +82 -0
  50. data/public/vendor/css/bootstrap-slider.css +252 -0
  51. data/public/vendor/css/bootstrap-theme.css +476 -0
  52. data/public/vendor/css/bootstrap-theme.css.map +1 -0
  53. data/public/vendor/css/bootstrap-theme.min.css +5 -0
  54. data/public/vendor/css/bootstrap.css +6584 -0
  55. data/public/vendor/css/bootstrap.css.map +1 -0
  56. data/public/vendor/css/bootstrap.min.css +5 -0
  57. data/public/vendor/css/jquery-ui-1.10.0.custom.css +1614 -0
  58. data/public/vendor/css/jquery.mobile-1.3.1.min.css +3 -0
  59. data/public/vendor/css/jqueryFileTree.css +91 -0
  60. data/public/vendor/fonts/glyphicons-halflings-regular.eot +0 -0
  61. data/public/vendor/fonts/glyphicons-halflings-regular.svg +288 -0
  62. data/public/vendor/fonts/glyphicons-halflings-regular.ttf +0 -0
  63. data/public/vendor/fonts/glyphicons-halflings-regular.woff +0 -0
  64. data/public/vendor/fonts/glyphicons-halflings-regular.woff2 +0 -0
  65. data/public/vendor/images/application.png +0 -0
  66. data/public/vendor/images/code.png +0 -0
  67. data/public/vendor/images/css.png +0 -0
  68. data/public/vendor/images/db.png +0 -0
  69. data/public/vendor/images/dim.png +0 -0
  70. data/public/vendor/images/directory.png +0 -0
  71. data/public/vendor/images/doc.png +0 -0
  72. data/public/vendor/images/file.png +0 -0
  73. data/public/vendor/images/film.png +0 -0
  74. data/public/vendor/images/flash.png +0 -0
  75. data/public/vendor/images/folder_open.png +0 -0
  76. data/public/vendor/images/grey.png +0 -0
  77. data/public/vendor/images/html.png +0 -0
  78. data/public/vendor/images/icons-18-black.png +0 -0
  79. data/public/vendor/images/icons-18-white.png +0 -0
  80. data/public/vendor/images/icons-36-black.png +0 -0
  81. data/public/vendor/images/icons-36-white.png +0 -0
  82. data/public/vendor/images/java.png +0 -0
  83. data/public/vendor/images/linux.png +0 -0
  84. data/public/vendor/images/music.png +0 -0
  85. data/public/vendor/images/pdf.png +0 -0
  86. data/public/vendor/images/php.png +0 -0
  87. data/public/vendor/images/picture.png +0 -0
  88. data/public/vendor/images/ppt.png +0 -0
  89. data/public/vendor/images/psd.png +0 -0
  90. data/public/vendor/images/ruby.png +0 -0
  91. data/public/vendor/images/script.png +0 -0
  92. data/public/vendor/images/spinner.gif +0 -0
  93. data/public/vendor/images/txt.png +0 -0
  94. data/public/vendor/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  95. data/public/vendor/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  96. data/public/vendor/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  97. data/public/vendor/images/ui-bg_flat_10_000000_40x100.png +0 -0
  98. data/public/vendor/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  99. data/public/vendor/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  100. data/public/vendor/images/ui-bg_flat_55_fbec88_40x100.png +0 -0
  101. data/public/vendor/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  102. data/public/vendor/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  103. data/public/vendor/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  104. data/public/vendor/images/ui-bg_glass_20_555555_1x400.png +0 -0
  105. data/public/vendor/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  106. data/public/vendor/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  107. data/public/vendor/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  108. data/public/vendor/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  109. data/public/vendor/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  110. data/public/vendor/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  111. data/public/vendor/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  112. data/public/vendor/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  113. data/public/vendor/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  114. data/public/vendor/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  115. data/public/vendor/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  116. data/public/vendor/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  117. data/public/vendor/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  118. data/public/vendor/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  119. data/public/vendor/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  120. data/public/vendor/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  121. data/public/vendor/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  122. data/public/vendor/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  123. data/public/vendor/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  124. data/public/vendor/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  125. data/public/vendor/images/ui-icons_217bc0_256x240.png +0 -0
  126. data/public/vendor/images/ui-icons_222222_256x240.png +0 -0
  127. data/public/vendor/images/ui-icons_228ef1_256x240.png +0 -0
  128. data/public/vendor/images/ui-icons_2e83ff_256x240.png +0 -0
  129. data/public/vendor/images/ui-icons_454545_256x240.png +0 -0
  130. data/public/vendor/images/ui-icons_469bdd_256x240.png +0 -0
  131. data/public/vendor/images/ui-icons_4b8e0b_256x240.png +0 -0
  132. data/public/vendor/images/ui-icons_6da8d5_256x240.png +0 -0
  133. data/public/vendor/images/ui-icons_888888_256x240.png +0 -0
  134. data/public/vendor/images/ui-icons_a83300_256x240.png +0 -0
  135. data/public/vendor/images/ui-icons_cccccc_256x240.png +0 -0
  136. data/public/vendor/images/ui-icons_cd0a0a_256x240.png +0 -0
  137. data/public/vendor/images/ui-icons_d8e7f3_256x240.png +0 -0
  138. data/public/vendor/images/ui-icons_ef8c08_256x240.png +0 -0
  139. data/public/vendor/images/ui-icons_f9bd01_256x240.png +0 -0
  140. data/public/vendor/images/ui-icons_ffd27a_256x240.png +0 -0
  141. data/public/vendor/images/ui-icons_ffffff_256x240.png +0 -0
  142. data/public/vendor/images/xls.png +0 -0
  143. data/public/vendor/images/zip.png +0 -0
  144. data/public/vendor/js/bootstrap-slider.js +1550 -0
  145. data/public/vendor/js/bootstrap.js +2317 -0
  146. data/public/vendor/js/bootstrap.min.js +7 -0
  147. data/public/vendor/js/jquery-2.1.4.js +9210 -0
  148. data/public/vendor/js/jquery-2.1.4.min.js +4 -0
  149. data/public/vendor/js/jquery-2.1.4.min.map +1 -0
  150. data/public/vendor/js/jquery-ui-1.10.2.custom.min.js +7 -0
  151. data/public/vendor/js/jquery.cookie.js +72 -0
  152. data/public/vendor/js/jquery.hammer.min.js +7 -0
  153. data/public/vendor/js/jquery.lazyload.min.js +15 -0
  154. data/public/vendor/js/jquery.localize.js +180 -0
  155. data/public/vendor/js/jquery.mobile-1.3.1.min.js +7 -0
  156. data/public/vendor/js/jquery.scrollTo-1.4.3.1.js +218 -0
  157. data/public/vendor/js/jqueryFileTree.js +95 -0
  158. data/public/vendor/js/swipeview.js +471 -0
  159. data/views/browse.haml +97 -0
  160. data/views/config.haml +115 -0
  161. data/views/layout.haml +12 -0
  162. data/views/login.haml +51 -0
  163. data/views/reader.haml +70 -0
  164. data/views/statistics.haml +108 -0
  165. data/views/tablet3.haml +80 -0
  166. metadata +334 -0
@@ -0,0 +1,330 @@
1
+ # encoding: utf-8
2
+
3
+ # License: refer to LICENSE file
4
+
5
+ #
6
+ # Functions for database and bookmarks
7
+ #
8
+
9
+ require 'json'
10
+ require 'fileutils'
11
+ require 'pathname'
12
+
13
+ module Kamishibai
14
+ class Database
15
+ attr_reader :files, :bookcodes
16
+
17
+ def initialize( db_filepath, bookmarks_filepath )
18
+ @db_savepath = db_filepath
19
+ @db_dirty = true
20
+
21
+ if File.exists?( @db_savepath )
22
+ load_database
23
+ else
24
+ # create fresh database
25
+ @db = {}
26
+ # indexes
27
+ @bookcodes = []
28
+ @files = {}
29
+ @inodes = {}
30
+ end
31
+
32
+
33
+ # bookmark section
34
+ @bookmarks_savepath = bookmarks_filepath
35
+ @bookmarks_dirty = false
36
+
37
+ if File.exists?( @bookmarks_savepath )
38
+ load_bookmarks
39
+ end
40
+ end
41
+
42
+ def add_book(filepath)
43
+ bookcode = gen_bookcode
44
+ o = Kamishibai::Book.new(bookcode, filepath)
45
+
46
+ if o.pages
47
+ @db[ bookcode ] = o
48
+ @bookcodes << bookcode
49
+ if File.exists?( filepath )
50
+ @files[ File.basename( filepath ).delete('ÿ') ] = bookcode # utf8-mac puts ÿ in filename, need to remove first for cross os support
51
+ @inodes[ o.inode ] = bookcode
52
+ end
53
+
54
+ @db_dirty = true
55
+
56
+ puts "added book. #{bookcode} #{filepath}" if $debug
57
+ bookcode
58
+ end
59
+ end
60
+
61
+ def has_bookcode?(bookcode)
62
+ @bookcodes.include?( bookcode )
63
+ end
64
+
65
+ def get_book(bookcode)
66
+ @db[ bookcode ]
67
+ end
68
+
69
+ def get_bookcode_byfilename(filename)
70
+ filename = File.basename( filename )
71
+ @files[ filename ]
72
+ end
73
+
74
+ def get_book_byfilename(filename)
75
+ filename = File.basename( filename )
76
+ bookcode = @files[ filename ]
77
+ get_book( bookcode )
78
+ end
79
+
80
+ def set_bookmark(bookcode, page)
81
+ puts "set_bookmark #{bookcode} #{page}" if $debug
82
+ @bookmarks_dirty = true
83
+ o = get_book(bookcode)
84
+ o.page = page
85
+ o.rtime = Time.now.to_i
86
+ end
87
+
88
+ def get_bookmark(bookcode)
89
+ get_book( bookcode ).page
90
+ end
91
+
92
+ def books
93
+ @db
94
+ end
95
+
96
+ # refresh @bookcodes, making sure all the books have valid path
97
+ def refresh_bookcodes
98
+ bookcodes = []
99
+ for bookcode, book in @db
100
+ if File.exists?( book.fullpath )
101
+ book.fullpath_valid = true
102
+ bookcodes << bookcode
103
+ else
104
+ book.fullpath_valid = false
105
+ end
106
+ end
107
+
108
+ @bookcodes = bookcodes
109
+ end
110
+
111
+
112
+ # big codes
113
+
114
+ # add book from directories, if existing booka are found, modify instead
115
+ def add_books(srcs, recursive = true)
116
+ srcs = cleanup_srcs( srcs )
117
+
118
+ for src in srcs
119
+ # stopped using find module, because it wrack encoding havoc in windows (gives ??? character in unicode filename)
120
+ # now using Dir.glob instead
121
+ if recursive
122
+ search = '/**/*.cbz'
123
+ else
124
+ search = '/*.cbz'
125
+ end
126
+
127
+ Dir.glob(File.expand_path(src).escape_glob + search).delete_if { |f|
128
+ restricted_dir?(f)
129
+ }.each { |f|
130
+ bc = get_bookcode_byfilename( f )
131
+ o = get_book( bc )
132
+ fs = File.stat(f)
133
+ o2 = get_book( @inodes[ fs.ino ] )
134
+
135
+ if o
136
+ # book exists in db
137
+ puts "book match(m1): #{File.basename(f)} == #{File.basename(o.fullpath)}" if $debug == 2
138
+
139
+ # update data with new path
140
+ o.fullpath = f
141
+
142
+ # update index, as bookcodes is the list that keeps actual books that exists
143
+ @bookcodes << o.bookcode
144
+ elsif o2 and fs.size == o2.size
145
+ # found existing book using inode and size, can detected changed filename in same filesystem
146
+ puts "book match(m2): #{File.basename(f)} == #{File.basename(o2.fullpath)}" if $debug == 2
147
+
148
+ o2.fullpath = f
149
+ o2.title = Kamishibai::CBZFilename.title( f )
150
+ o2.author = Kamishibai::CBZFilename.author( f )
151
+
152
+ # update indexes
153
+ @bookcodes << o2.bookcode
154
+ @files[ File.basename(f) ] = o2.bookcode
155
+ else
156
+ # book don't exist in db
157
+ self.add_book(f)
158
+ end
159
+ }
160
+ end
161
+
162
+ puts "Found #{@bookcodes.length} books" if $debug
163
+ end
164
+
165
+ # save database
166
+ def save
167
+ if @db_dirty
168
+ # create dir if dir doesn't exist
169
+ if ! FileTest.exists?( File.dirname( @db_savepath ) )
170
+ FileUtils.mkdir_p( File.dirname( @db_savepath ) )
171
+ end
172
+
173
+ db = {}
174
+ @db.each { |bookcode, book|
175
+ next unless book.pages # skip invalid book that contain no page/images
176
+
177
+ db[ bookcode ] = {
178
+ :title => book.title,
179
+ :author => book.author,
180
+ :fullpath => book.fullpath,
181
+ :size => book.size,
182
+ :inode => book.inode,
183
+ :mtime => book.mtime,
184
+ :itime => book.itime,
185
+ :rtime => book.rtime,
186
+ :page => book.page,
187
+ :pages => book.pages
188
+ }
189
+ }
190
+ File.binwrite( @db_savepath, JSON.pretty_generate( db ) )
191
+
192
+ @db_dirty = false
193
+
194
+ puts "db saved (#{db.length} books) #{Time.now}" if $debug
195
+ end
196
+ end
197
+
198
+ # save bookmarks
199
+ def save_bookmarks
200
+ if @bookmarks_dirty
201
+ # save bookmarks
202
+ if ! FileTest.exists?( File.dirname( @bookmarks_savepath ) )
203
+ FileUtils.mkdir_p( File.dirname( @bookmarks_savepath ) )
204
+ end
205
+
206
+ bookmarks = {}
207
+ @db.each { |bookcode, book|
208
+ page = book.page # last read page
209
+ time = book.rtime # last read time
210
+
211
+ if page and page > 1
212
+ dat = {
213
+ :p => page,
214
+ :r => time
215
+ }
216
+ bookmarks[bookcode] = dat
217
+ end
218
+ }
219
+
220
+ File.binwrite( @bookmarks_savepath, JSON.pretty_generate( bookmarks ) )
221
+
222
+ @bookmarks_dirty = false
223
+
224
+ puts "bookmarks saved (#{bookmarks.length} bookmarks) #{Time.now}" if $debug
225
+ end
226
+ end
227
+
228
+
229
+
230
+ private
231
+
232
+ # clean up and sanity check on directory lists
233
+ def cleanup_srcs( dir_lists )
234
+ srcs = dir_lists.collect { |src|
235
+ pn = Pathname.new( src )
236
+ fp = pn.expand_path.cleanpath.to_s
237
+
238
+ if FileTest.directory?( fp ) and not restricted_dir?( fp )
239
+ fp
240
+ end
241
+ }
242
+ srcs.compact!
243
+ p srcs if $debug
244
+
245
+ srcs
246
+ end
247
+
248
+ # generate a unique bookcode, lookup bookcode existance by using db
249
+ def gen_bookcode
250
+ while true
251
+ word = GenChar(3)
252
+ break unless @bookcodes.include?(word) # find next available word
253
+ end
254
+ return word
255
+ end
256
+
257
+ # load database
258
+ def load_database
259
+ @db = {}
260
+
261
+ # indexes for db, help to speed up database search
262
+ @files = {} # hash of files linked to bookcodes, format: files[basename] = bookcode
263
+ @bookcodes = [] # list of bookcodes, format: [ bookcode_a, bookcode_b, bookcode_c, ... ]
264
+ @inodes = {} # list of file inodes, format: inodes[inode] = bookcode
265
+
266
+ str = File.binread( @db_savepath )
267
+
268
+ JSON.parse( str ).each { |bookcode, h|
269
+ o = Kamishibai::Book.new
270
+ o.bookcode = bookcode
271
+ o.title = h['title']
272
+ o.author = h['author']
273
+ o.fullpath = h['fullpath']
274
+ o.size = h['size']
275
+ o.mtime = h['mtime']
276
+ o.inode = h['inode']
277
+ o.itime = h['itime'] # imported time
278
+ o.rtime = h['rtime'] # last read time
279
+ o.page = h['page'] # last read page
280
+ o.pages = h['pages']
281
+
282
+ unless o.pages
283
+ puts "Book contain no images!!! skipping... #{bookcode} #{o.fullpath}"
284
+ next
285
+ end
286
+
287
+ @db[ o.bookcode ] = o
288
+ # update indexes
289
+ @files[ File.basename( o.fullpath ).delete('ÿ') ] = o.bookcode # utf8-mac puts ÿ in filename, need to remove first for cross os support
290
+ @inodes[ o.inode ] = o.bookcode
291
+
292
+ # if book exists
293
+ if File.exists?( o.fullpath )
294
+ fs = File.stat( o.fullpath )
295
+ o.fullpath_valid = true
296
+
297
+ # repopulate data if doesn't exist
298
+ o.title = Kamishibai::CBZFilename.title( o.fullpath ) unless o.title
299
+ o.author = Kamishibai::CBZFilename.author( o.fullpath ) unless o.author
300
+ o.size = fs.size unless o.size
301
+ o.mtime = fs.mtime unless o.mtime
302
+ o.inode = fs.ino unless o.inode
303
+
304
+ # update indexes
305
+ @bookcodes << o.bookcode
306
+ @inodes[ o.inode ] = o.bookcode
307
+ end
308
+ }
309
+
310
+ # db format: @db[ bookcode ] = Book obj
311
+ # + indexes @files = { basename_a => bookcode_a, basename_b => bookcode_b, ... }
312
+ # @bookcodes = [ bookcode_a, bookcode_b, ... ]
313
+ # @inodes = { inode_a => bookcode_a, inode_b => bookcode_b, ... }
314
+ end
315
+
316
+ # load bookmarks
317
+ def load_bookmarks
318
+ bookmarks = {}
319
+
320
+ str = File.binread( @bookmarks_savepath )
321
+
322
+ JSON.parse( str ).each { |bookcode, h|
323
+ if get_book( bookcode )
324
+ get_book( bookcode ).page = h['p']
325
+ get_book( bookcode ).rtime = h['r']
326
+ end
327
+ }
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,332 @@
1
+ # encoding: utf-8
2
+
3
+ # License: refer to LICENSE file
4
+
5
+ # for available_drives
6
+ require 'win32ole' if RbConfig::CONFIG['host_os'] =~ /ming/
7
+
8
+
9
+ require 'zip/filesystem'
10
+ if RUBY_PLATFORM == 'java'
11
+ require 'image_voodoo'
12
+ else
13
+ require 'gd2-ffij'
14
+ end
15
+
16
+ # is dir restricted directory?
17
+ def restricted_dir?( dir, excludes=[] )
18
+ # restricted directory list
19
+ restricted_dirs = [
20
+ '.Trash', '.Trashes', '.fseventsd', '.Spotlight-V100', '.DocumentRevisions-V100',
21
+ '.$EXTEND',
22
+ '_SYNCAPP',
23
+ 'Corrupted',
24
+ 'System Volume Information', 'RECYCLER',
25
+ 'backup',
26
+ '.sparsebundle',
27
+ '.tmpdir', '.tmp7z',
28
+ '.AppleDouble'
29
+ ]
30
+
31
+ for word in restricted_dirs - excludes
32
+ return true if dir.include?( word )
33
+ end
34
+ return false
35
+ end
36
+
37
+ class String
38
+ # escape [] and {} character for Dir.glob to use
39
+ def escape_glob
40
+ s = self.dup
41
+ s.gsub(/([\[\]\{\}\*\?\\])/, '\\\\\1')
42
+ end
43
+
44
+ # escape glob and overwite it
45
+ def escape_glob!
46
+ self.replace( self.escape_glob )
47
+ end
48
+
49
+ # escape characters and make it html safe
50
+ def escape_html
51
+ s = self.dup
52
+
53
+ for i in 32..255
54
+ next if i == 35 # #
55
+ next if i == 38 # &
56
+ next if i == 59 # ;
57
+ next if i >= 48 && i <= 57 # 0-9
58
+ next if i >= 65 && i <= 90 # A-Z
59
+ next if i >= 97 && i <= 122 # a-z
60
+ next if i >= 127 && i <= 159 # not defined in html standards
61
+
62
+ c = [i].pack('U')
63
+ r = "\&\##{i.to_s}\;"
64
+ s.gsub!(c, r) # replace char to html number
65
+ end
66
+
67
+ s
68
+ end
69
+
70
+ # escape characters and make it html safe and overwrite
71
+ def escape_html!
72
+ self.replace( self.escape_html )
73
+ end
74
+ end
75
+
76
+ # generate random char
77
+ def GenChar(length)
78
+ length = length.to_i
79
+ return nil if length < 1
80
+
81
+ s = ''
82
+ w = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' # 62 uniq chars
83
+
84
+ while s.length < length
85
+ s = s + w[rand(62)]
86
+ end
87
+
88
+ return s
89
+ end
90
+
91
+ # return image type
92
+ def image_type(magic)
93
+ magic = magic[0..9]
94
+ case magic
95
+ when /^\xff\xd8/n
96
+ :jpeg
97
+ when /^\x89PNG/n
98
+ :png
99
+ when /^GIF8/n
100
+ :gif
101
+ when /^\x00/n
102
+ :wbmp
103
+ when /^gd2/n
104
+ :gd2
105
+ else
106
+ :unknown
107
+ end
108
+ end
109
+ # /class
110
+
111
+ # show list of drives in ms windows environment
112
+ def available_drives
113
+ if RbConfig::CONFIG['host_os'] =~ /ming/
114
+ drives = []
115
+
116
+ file_system = WIN32OLE.new("Scripting.FileSystemObject")
117
+ fs_drives = file_system.Drives
118
+ fs_drives.each do |drive|
119
+ next unless drive.IsReady
120
+ drives << drive.Path
121
+ end
122
+
123
+ drives
124
+ else
125
+ ['/']
126
+ end
127
+ end
128
+
129
+ # returns total number of pages for cbz file
130
+ def cbz_pages?( zfile )
131
+ i = 0
132
+ begin
133
+ Zip::File.open( zfile ) { |x|
134
+ x.each { |zobj|
135
+ if zobj.ftype == :file and File.basename(zobj.name)[0] != '.' and File.basename( zobj.name ) =~ /\.(jpg|jpeg|png|gif)$/i
136
+ i += 1
137
+ end
138
+ }
139
+ }
140
+ rescue
141
+ return nil
142
+ end
143
+ return i
144
+ end
145
+
146
+
147
+ # cbz file accessor, give file name and page and you shall receive
148
+ def open_cbz( zfile, page = 1, options = {} )
149
+ objs = []
150
+ # begin
151
+ Zip::File.open( zfile ) { |x|
152
+ x.each { |zobj|
153
+ if zobj.ftype == :file and File.basename(zobj.name)[0] != '.' and File.basename( zobj.name ) =~ /\.(jpg|jpeg|png|gif)$/i
154
+ objs << zobj
155
+ end
156
+ }
157
+ objs.sort!
158
+
159
+ if objs.length == 0
160
+ puts "error: no image detected. #{zfile}"
161
+ return nil
162
+ elsif page > objs.length or page < 1
163
+ puts "error: no such page #{page} : #{zfile}"
164
+ return nil
165
+ else
166
+ img = objs[page-1].name
167
+ uimg = img.clone.force_encoding('UTF-8') # unicode version of filename, or it won't print on puts
168
+ puts "reading image… #{page} : #{uimg} : #{zfile}" if $debug
169
+
170
+ simg = x.file.read(img)
171
+
172
+ begin
173
+ # load the image to check if the image is corrupted or not
174
+ GD2::Image.load( simg ) if defined?(GD2)
175
+ rescue => errmsg
176
+ puts "error: fail to load image #{page} : #{zfile}"
177
+ p errmsg
178
+ return nil
179
+ end
180
+
181
+ return simg
182
+ end
183
+ }
184
+ # rescue => e
185
+ # puts "Corrupted zip file."
186
+ # puts e.exception
187
+ # puts e.backtrace
188
+ # end
189
+ end
190
+
191
+ if defined?(ImageVoodoo)
192
+ # resize image using Java library
193
+ def img_resize( dat, w, h, options = {} )
194
+ puts "resizing jimage… #{w} : #{h} : #{options[:quality]} : #{options[:format]}" if $debug
195
+
196
+ quality = options[:quality]
197
+ format = options[:format]
198
+
199
+ ssimg = ''
200
+ ImageVoodoo.with_bytes(dat) { |img|
201
+ scale = 1280 / img.width
202
+
203
+ img.scale( scale ) do |simg|
204
+ ssimg = simg.bytes( image_type( dat ).to_s )
205
+ end
206
+ }
207
+
208
+ return ssimg
209
+ end
210
+ else
211
+ # resize image using GD library
212
+ # image will maintain aspect ratio and fit within width and height specified
213
+ # if width or height is 0, it will use the image original resolution
214
+ def img_resize( dat, w, h, options = {} )
215
+ quality = options[:quality]
216
+ format = options[:format]
217
+
218
+ begin
219
+ img = GD2::Image.load(dat)
220
+
221
+ # get image resolution
222
+ res = img.size
223
+ iw = res[0]
224
+ ih = res[1]
225
+
226
+ # calc new width and height
227
+ if w == 0
228
+ w = (h * img.aspect).to_i
229
+
230
+ elsif h == 0
231
+ h = (w / img.aspect).to_i
232
+
233
+ else
234
+ if iw >= ih
235
+ w = (h * img.aspect).to_i
236
+ else
237
+ h = (w / img.aspect).to_i
238
+ end
239
+ end
240
+
241
+ puts "resizing image… width: #{w}, height: #{h}, quality: #{quality}" if $debug
242
+
243
+ # make sure it doesn't upscale image
244
+ if iw > w and ih > h
245
+ img.resize!( w, h )
246
+ end
247
+
248
+ if format
249
+ case format
250
+ when :png
251
+ img.png
252
+ when :jpeg
253
+ if quality
254
+ img.jpeg( quality.to_i )
255
+ else
256
+ img.jpeg
257
+ end
258
+ when :gif
259
+ img.gif
260
+ else
261
+ raise 'img_resize(elsif format), unknown output format'
262
+ end
263
+ else
264
+ case image_type(dat)
265
+ when :png
266
+ img.png
267
+ when :jpeg
268
+ if quality
269
+ img.jpeg( quality.to_i )
270
+ else
271
+ img.jpeg
272
+ end
273
+ when :gif
274
+ img.gif
275
+ else
276
+ raise 'img_resize(else), unknown output format'
277
+ end
278
+ end
279
+
280
+ rescue => errmsg
281
+ puts "error: resize failed. #{w} #{h} #{quality}"
282
+ p errmsg
283
+ return nil
284
+ end
285
+ end
286
+ end
287
+
288
+ # create image thumbnail and save to cache
289
+ def mk_thumb(f_cbz, auto_gen = false)
290
+ unless auto_gen
291
+ # holds the last time the open_cbz is called
292
+ # to pause the Thumbnail Generator thread
293
+ $open_cbz_ltime = Time.now
294
+ end
295
+
296
+ quality = 60 #80
297
+ width = 220 #320
298
+ height = 0
299
+ page = 1
300
+
301
+ f = $settings.cache_path + '/' + File.basename( f_cbz.delete('ÿ') ).gsub('.cbz','.jpeg') # utf8-mac puts ÿ in filename, need to remove first for cross os support
302
+
303
+ if File.exists?( f )
304
+ if File.size( f ) == 0
305
+ File.delete( f )
306
+ else
307
+ if auto_gen
308
+ # no need to return data if this is called from auto thumbnail worker
309
+ return
310
+ else
311
+ # return file if it exists
312
+ puts "thumbnail found. #{f}" if $debug
313
+ return File.binread( f )
314
+ end
315
+ end
316
+ end
317
+
318
+ puts "thumbnail generated. #{f_cbz}" if $debug
319
+ image = open_cbz( f_cbz, page )
320
+ image = img_resize(image, width, height, { :quality => quality, :format => :jpeg })
321
+
322
+ begin
323
+ # store image to cache dir
324
+ FileUtils.mkdir_p( File.dirname( f ) ) if ! Dir.exists?( File.dirname( f ) )
325
+ File.binwrite( f, image )
326
+ rescue => errmsg
327
+ puts "\nthumbnail storage failed: #{f} >> #{errmsg} … retrying\n"
328
+ retry
329
+ end
330
+
331
+ return image
332
+ end