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.
- 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
|