kamishibai 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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