kamishibai 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/LICENSE +29 -0
- data/README.md +91 -0
- data/bin/kamishibai +59 -0
- data/changelogs.txt +6 -0
- data/images/reading.jpg +0 -0
- data/images/settings.jpg +0 -0
- data/images/view_browse.jpg +0 -0
- data/images/view_dir.jpg +0 -0
- data/kamishibai.gemspec +31 -0
- data/lib/kamishibai.rb +35 -0
- data/lib/kamishibai/book.rb +97 -0
- data/lib/kamishibai/config.rb +90 -0
- data/lib/kamishibai/database.rb +330 -0
- data/lib/kamishibai/functions.rb +332 -0
- data/lib/kamishibai/patches.rb +58 -0
- data/lib/kamishibai/version.rb +7 -0
- data/lib/kamishibai/webserver.rb +502 -0
- data/lib/kamishibai/webserver_cmds.rb +26 -0
- data/lib/kamishibai/webserver_config.rb +91 -0
- data/lib/kamishibai/webserver_filemanager.rb +29 -0
- data/lib/kamishibai/webserver_tablet.rb +259 -0
- data/lib/kamishibai/workers.rb +39 -0
- data/public/css/browse.css +390 -0
- data/public/css/config.css +3 -0
- data/public/css/reader.css +119 -0
- data/public/css/styles.css +30 -0
- data/public/css/tablet_browse.css +247 -0
- data/public/css/tablet_reader.css +323 -0
- data/public/css/tablet_reader_input.css +191 -0
- data/public/images/ajax-loader.gif +0 -0
- data/public/images/delete.png +0 -0
- data/public/images/folder-mini-up.png +0 -0
- data/public/images/folder-mini.png +0 -0
- data/public/images/spinner.gif +0 -0
- data/public/images/trash-empty-mini.png +0 -0
- data/public/images/trash-full-mini.png +0 -0
- data/public/js/browse.js +391 -0
- data/public/js/config.js +190 -0
- data/public/js/reader.js +497 -0
- data/public/js/swipeview-k.js +478 -0
- data/public/js/tablet3.js +241 -0
- data/public/js/tablet3_browse.js +173 -0
- data/public/js/tablet3_reader.js +534 -0
- data/public/js/utilities.js +170 -0
- data/public/lang/k-cn.json +82 -0
- data/public/lang/k-en.json +82 -0
- data/public/lang/k-ja.json +82 -0
- data/public/vendor/css/bootstrap-slider.css +252 -0
- data/public/vendor/css/bootstrap-theme.css +476 -0
- data/public/vendor/css/bootstrap-theme.css.map +1 -0
- data/public/vendor/css/bootstrap-theme.min.css +5 -0
- data/public/vendor/css/bootstrap.css +6584 -0
- data/public/vendor/css/bootstrap.css.map +1 -0
- data/public/vendor/css/bootstrap.min.css +5 -0
- data/public/vendor/css/jquery-ui-1.10.0.custom.css +1614 -0
- data/public/vendor/css/jquery.mobile-1.3.1.min.css +3 -0
- data/public/vendor/css/jqueryFileTree.css +91 -0
- data/public/vendor/fonts/glyphicons-halflings-regular.eot +0 -0
- data/public/vendor/fonts/glyphicons-halflings-regular.svg +288 -0
- data/public/vendor/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/public/vendor/fonts/glyphicons-halflings-regular.woff +0 -0
- data/public/vendor/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/public/vendor/images/application.png +0 -0
- data/public/vendor/images/code.png +0 -0
- data/public/vendor/images/css.png +0 -0
- data/public/vendor/images/db.png +0 -0
- data/public/vendor/images/dim.png +0 -0
- data/public/vendor/images/directory.png +0 -0
- data/public/vendor/images/doc.png +0 -0
- data/public/vendor/images/file.png +0 -0
- data/public/vendor/images/film.png +0 -0
- data/public/vendor/images/flash.png +0 -0
- data/public/vendor/images/folder_open.png +0 -0
- data/public/vendor/images/grey.png +0 -0
- data/public/vendor/images/html.png +0 -0
- data/public/vendor/images/icons-18-black.png +0 -0
- data/public/vendor/images/icons-18-white.png +0 -0
- data/public/vendor/images/icons-36-black.png +0 -0
- data/public/vendor/images/icons-36-white.png +0 -0
- data/public/vendor/images/java.png +0 -0
- data/public/vendor/images/linux.png +0 -0
- data/public/vendor/images/music.png +0 -0
- data/public/vendor/images/pdf.png +0 -0
- data/public/vendor/images/php.png +0 -0
- data/public/vendor/images/picture.png +0 -0
- data/public/vendor/images/ppt.png +0 -0
- data/public/vendor/images/psd.png +0 -0
- data/public/vendor/images/ruby.png +0 -0
- data/public/vendor/images/script.png +0 -0
- data/public/vendor/images/spinner.gif +0 -0
- data/public/vendor/images/txt.png +0 -0
- data/public/vendor/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/public/vendor/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/public/vendor/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/public/vendor/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/public/vendor/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
- data/public/vendor/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
- data/public/vendor/images/ui-bg_flat_55_fbec88_40x100.png +0 -0
- data/public/vendor/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/public/vendor/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_20_555555_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
- data/public/vendor/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/public/vendor/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
- data/public/vendor/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/public/vendor/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
- data/public/vendor/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/public/vendor/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/public/vendor/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/public/vendor/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
- data/public/vendor/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
- data/public/vendor/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
- data/public/vendor/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
- data/public/vendor/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
- data/public/vendor/images/ui-icons_217bc0_256x240.png +0 -0
- data/public/vendor/images/ui-icons_222222_256x240.png +0 -0
- data/public/vendor/images/ui-icons_228ef1_256x240.png +0 -0
- data/public/vendor/images/ui-icons_2e83ff_256x240.png +0 -0
- data/public/vendor/images/ui-icons_454545_256x240.png +0 -0
- data/public/vendor/images/ui-icons_469bdd_256x240.png +0 -0
- data/public/vendor/images/ui-icons_4b8e0b_256x240.png +0 -0
- data/public/vendor/images/ui-icons_6da8d5_256x240.png +0 -0
- data/public/vendor/images/ui-icons_888888_256x240.png +0 -0
- data/public/vendor/images/ui-icons_a83300_256x240.png +0 -0
- data/public/vendor/images/ui-icons_cccccc_256x240.png +0 -0
- data/public/vendor/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/public/vendor/images/ui-icons_d8e7f3_256x240.png +0 -0
- data/public/vendor/images/ui-icons_ef8c08_256x240.png +0 -0
- data/public/vendor/images/ui-icons_f9bd01_256x240.png +0 -0
- data/public/vendor/images/ui-icons_ffd27a_256x240.png +0 -0
- data/public/vendor/images/ui-icons_ffffff_256x240.png +0 -0
- data/public/vendor/images/xls.png +0 -0
- data/public/vendor/images/zip.png +0 -0
- data/public/vendor/js/bootstrap-slider.js +1550 -0
- data/public/vendor/js/bootstrap.js +2317 -0
- data/public/vendor/js/bootstrap.min.js +7 -0
- data/public/vendor/js/jquery-2.1.4.js +9210 -0
- data/public/vendor/js/jquery-2.1.4.min.js +4 -0
- data/public/vendor/js/jquery-2.1.4.min.map +1 -0
- data/public/vendor/js/jquery-ui-1.10.2.custom.min.js +7 -0
- data/public/vendor/js/jquery.cookie.js +72 -0
- data/public/vendor/js/jquery.hammer.min.js +7 -0
- data/public/vendor/js/jquery.lazyload.min.js +15 -0
- data/public/vendor/js/jquery.localize.js +180 -0
- data/public/vendor/js/jquery.mobile-1.3.1.min.js +7 -0
- data/public/vendor/js/jquery.scrollTo-1.4.3.1.js +218 -0
- data/public/vendor/js/jqueryFileTree.js +95 -0
- data/public/vendor/js/swipeview.js +471 -0
- data/views/browse.haml +97 -0
- data/views/config.haml +115 -0
- data/views/layout.haml +12 -0
- data/views/login.haml +51 -0
- data/views/reader.haml +70 -0
- data/views/statistics.haml +108 -0
- data/views/tablet3.haml +80 -0
- 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
|