magicshelf 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ require 'magicshelf/exception'
2
+
3
+ module MagicShelf
4
+ class FileCleanerError < Error; end
5
+
6
+ class FileCleaner
7
+ attr_accessor :file
8
+
9
+ def enter()
10
+ #raise MagicShelf::FileCleanerError.new("workdir is not set") if @workdir == nil
11
+ yield
12
+ end
13
+
14
+ def process()
15
+ FileUtils.remove_file(@file)
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,48 @@
1
+ require 'filemagic'
2
+ require 'zip'
3
+ require 'open3'
4
+ require 'shellwords'
5
+ require 'magicshelf/exception'
6
+
7
+ module MagicShelf
8
+ class FileExtractorError < Error; end
9
+
10
+ class FileExtractor
11
+ attr_accessor :inputfile, :destdir
12
+
13
+ def enter()
14
+ raise MagicShelf::FileExtractorError.new("inputfile is not set") if @inputfile == nil
15
+ mimetype = FileMagic.new(FileMagic::MAGIC_MIME_TYPE).file(@inputfile)
16
+ raise MagicShelf::FileExtractorError.new("unsupported filetype: #{mimetype}") if not %w{application/x-rar application/zip}.include?(mimetype)
17
+ @mimetype = mimetype
18
+
19
+ yield
20
+ end
21
+
22
+ def process()
23
+ case @mimetype
24
+ when "application/zip"
25
+ Zip::File.open(@inputfile) do |zip_file|
26
+ zip_file.each do |entry|
27
+ MagicShelf.logger.info("Extracting #{entry.name} ...")
28
+ entry.extract(File.join(@destdir,entry.name))
29
+ end
30
+ end
31
+ when "application/x-rar"
32
+ out, err, status = Open3.capture3("which unrar")
33
+ if status.exitstatus != 0
34
+ raise MagicShelf::FileExtractorError.new("cannot execute unrar, is it on your PATH?")
35
+ end
36
+
37
+ out, err, status = Open3.capture3("unrar x #{Shellwords.escape(@inputfile)} #{Shellwords.escape(@destdir)}")
38
+ if status.exitstatus != 0
39
+ raise MagicShelf::FileExtractorError.new("unrar exits with status #{status.exitstatus}: \n #{out} \n #{err}")
40
+ end
41
+ else
42
+ raise MagicShelf::FileExtractorError.new("no way to extract file for the file with filetype: #{@mimetype}")
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ require 'magicshelf/exception'
2
+ require 'gepub'
3
+ require 'shellwords'
4
+
5
+ module MagicShelf
6
+ class FileMoverError < Error; end
7
+
8
+ class FileMover
9
+ attr_accessor :inputfile, :outputfile
10
+
11
+ def enter()
12
+ MagicShelf.logger.debug('enter FileMover')
13
+ raise MagicShelf::FileMoverError.new("inputfile is not set") if @inputfile == nil
14
+ raise MagicShelf::FileMoverError.new("outputfile is not set") if @outputfile == nil
15
+
16
+ yield
17
+ end
18
+
19
+ def process()
20
+ FileUtils.mv(@inputfile, @outputfile)
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,31 @@
1
+ require 'magicshelf/exception'
2
+
3
+ module MagicShelf
4
+ class FileNameValidatorError < Error; end
5
+
6
+ class FileNameValidator
7
+ attr_accessor :workdir
8
+ def initialize()
9
+ @erase_original = true
10
+ end
11
+
12
+ def enter()
13
+ yield
14
+ end
15
+
16
+ def process()
17
+ @workdir ||= Dir.pwd
18
+ Dir.glob(File.join(@workdir,'**/*')).select{|f|File.file?(f)}.each do |f|
19
+ dirname = File.dirname(f)
20
+ basename = File.basename(f,'.*')
21
+ extname = File.extname(f)
22
+ newbasename = basename.gsub(/#/, '_').gsub(/\+/, '_')
23
+ if not (basename == newbasename)
24
+ newfilename = File.join(dirname, newbasename + extname)
25
+ FileUtils.mv(f, newfilename)
26
+ MagicShelf.logger.info("move #{f} to #{newfilename}.")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,103 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/config_file'
3
+ require 'sinatra/reloader'
4
+ require 'tilt/erb'
5
+ require 'redis'
6
+ require 'magicshelf/mobitask'
7
+
8
+ module MagicShelf
9
+ class FileServer < Sinatra::Application
10
+ register Sinatra::ConfigFile
11
+ configure :development do
12
+ register Sinatra::Reloader
13
+ end
14
+
15
+ set :root, File.join(File.dirname(__FILE__), '../..') #set :views, File.join(File.dirname(__FILE__), '../..', 'views')
16
+ set :bind, '0.0.0.0'
17
+ config_file 'server_config.yml'
18
+
19
+ get '/' do
20
+ redirect "/files/", 303
21
+ end
22
+
23
+ get '/files/*' do |path|
24
+ sort_type = params['sort_by'] || "title"
25
+ files = []
26
+ Dir.chdir(settings.library_directory) {
27
+ files = Dir.glob(File.join('.',path, '*'))
28
+ case sort_type
29
+ when 'title'
30
+ files = files.sort
31
+ when 'title_reverse'
32
+ files = files.sort.reverse
33
+ when 'date'
34
+ files = files.sort_by{ |f| File.mtime(f) }
35
+ when 'date_reverse'
36
+ files = files.sort_by{ |f| File.mtime(f) }.reverse
37
+ else
38
+ end
39
+ files_withmtime = files.map do |f|
40
+ fname = (f.start_with?('./') ? f[2..-1] : f)
41
+ [fname, File.mtime(f).strftime("%Y/%m/%d %H:%M:%S")]
42
+ end
43
+ upperpath = nil
44
+ upperpath = path.split('/')[0...-1].join('/') if path != ""
45
+ erb :index, :locals => {:page_title => settings.page_title, :files_withmtime => files_withmtime, :path => path, :upperpath => upperpath, :sort_type => sort_type}
46
+ }
47
+ end
48
+
49
+ get '/get_file/*' do |path|
50
+ pass unless path # pass to a subsequent route
51
+ send_file(File.join(settings.library_directory, path))
52
+ end
53
+
54
+ get '/get_file*' do
55
+ 'you come to this page without specifying the path to file. go back to the previous page!'
56
+ end
57
+
58
+ get '/generate_mobi/*' do |path|
59
+ #path = params['splat'].first
60
+ pass unless path # pass to a subsequent route
61
+
62
+ erb :generate_mobi, :locals => {:page_title => settings.page_title, :library_directory => settings.library_directory, :path => path}
63
+ end
64
+
65
+ post '/generate_mobi/*' do |path|
66
+ title = params['title']
67
+ author = params['author']
68
+ booktype = params['booktype']
69
+ outputfile = params['outputfile']
70
+ outputfile = title + ".mobi" if outputfile.empty?
71
+
72
+ taskparams = {}
73
+ #taskparams.update(params)
74
+ taskparams['title'] = params['title']
75
+ taskparams['author'] = params['author']
76
+ taskparams['booktype'] = params['booktype']
77
+ taskparams['inputfile'] = File.join(settings.library_directory,path)
78
+ taskparams['outputfile'] = File.join(settings.library_directory,File.dirname(path),outputfile)
79
+
80
+ Resque.enqueue(MobiTask, taskparams)
81
+
82
+ <<-EOF
83
+ <!DOCTYPE html>
84
+ <html lang="en">
85
+ <head>
86
+ <meta charset="UTF-8">
87
+ <title></title>
88
+ </head>
89
+ <body>
90
+ Now generating mobi file. wait for a while. <br><a href="/">Back to Top</a>
91
+ </body>
92
+ </html>
93
+ EOF
94
+ end
95
+
96
+ get '/generate_mobi*' do |f|
97
+ 'you come to this page without specifying the path to file. go back to the previous page!'
98
+ end
99
+
100
+ run! if app_file == $0
101
+ end
102
+ end
103
+
@@ -0,0 +1,33 @@
1
+ require 'magicshelf/exception'
2
+ require 'open3'
3
+
4
+ module MagicShelf
5
+ class KindleGenWrapperError < Error; end
6
+
7
+ class KindleGenWrapper
8
+ attr_accessor :inputfile, :outputfile
9
+
10
+ def enter()
11
+ MagicShelf.logger.debug('enter KindleGenWrapper')
12
+
13
+ # check parameters
14
+ raise MagicShelf::KindleGenWrapperError.new("inputfile is not set") if @inputfile == nil
15
+ raise MagicShelf::KindleGenWrapperError.new("outputfile is not set") if @outputfile == nil
16
+ out, err, status = Open3.capture3("which kindlegen")
17
+ if status.exitstatus != 0
18
+ raise MagicShelf::KindleGenWrapperError.new("cannot execute kindlegen, is it on your PATH?")
19
+ end
20
+
21
+ yield
22
+ end
23
+
24
+ def process()
25
+ out, err, status = Open3.capture3("kindlegen #{@inputfile} -o #{@outputfile}")
26
+ if status.exitstatus != 0
27
+ raise MagicShelf::KindleGenWrapperError.new("kindlegen exited with #{status.exitstatus}: \n" + out + "\n" + err)
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,250 @@
1
+ #! ruby
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # It was translated into Ruby script by whiteleaf.
5
+ # ( https://github.com/whiteleaf7/narou/blob/master/lib/kindlestrip.rb )
6
+ #
7
+ # original source code:
8
+ # kindlestrip.py v.1.35 http://www.mobileread.com/forums/showthread.php?t=96903
9
+ #
10
+ # This script strips the penultimate record from a Mobipocket file.
11
+ # This is useful because the current KindleGen add a compressed copy
12
+ # of the source files used in this record, making the ebook produced
13
+ # about twice as big as it needs to be.
14
+ #
15
+ #
16
+ # This is free and unencumbered software released into the public domain.
17
+ #
18
+ # Anyone is free to copy, modify, publish, use, compile, sell, or
19
+ # distribute this software, either in source code form or as a compiled
20
+ # binary, for any purpose, commercial or non-commercial, and by any
21
+ # means.
22
+ #
23
+ # In jurisdictions that recognize copyright laws, the author or authors
24
+ # of this software dedicate any and all copyright interest in the
25
+ # software to the public domain. We make this dedication for the benefit
26
+ # of the public at large and to the detriment of our heirs and
27
+ # successors. We intend this dedication to be an overt act of
28
+ # relinquishment in perpetuity of all present and future rights to this
29
+ # software under copyright law.
30
+ #
31
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
34
+ # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
35
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
36
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
37
+ # OTHER DEALINGS IN THE SOFTWARE.
38
+ #
39
+ # For more information, please refer to <http://unlicense.org/>
40
+ #
41
+ # Written by Paul Durrant, 2010-2011, paul@durrant.co.uk, pdurrant on mobileread.com
42
+ # With enhancements by Kevin Hendricks, KevinH on mobileread.com
43
+ #
44
+ # Changelog
45
+ # 1.00 - Initial version
46
+ # 1.10 - Added an option to output the stripped data
47
+ # 1.20 - Added check for source files section (thanks Piquan)
48
+ # 1.30 - Added prelim Support for K8 style mobis
49
+ # 1.31 - removed the SRCS section but kept a 0 size entry for it
50
+ # 1.32 - removes the SRCS section and its entry, now updates metadata 121 if needed
51
+ # 1.33 - now uses and modifies mobiheader SRCS and CNT
52
+ # 1.34 - added credit for Kevin Hendricks
53
+ # 1.35 - fixed bug when more than one compilation (SRCS/CMET) records
54
+
55
+ KINDLESTRIP_VERSION = '1.35'
56
+
57
+ class StripException < StandardError; end
58
+
59
+ class SectionStripper
60
+ def load_section(section)
61
+ if section + 1 == @num_sections
62
+ endoff = @data_file.length
63
+ else
64
+ endoff = @sections[section + 1][0]
65
+ end
66
+ off = @sections[section][0]
67
+ @data_file[off...endoff]
68
+ end
69
+
70
+ def patch(off, _new)
71
+ @data_file = @data_file[0, off] + new + @data_file[off + _new.length .. -1]
72
+ end
73
+
74
+ def strip(off, len)
75
+ @data_file = @data_file[0, off] + @data_file[off + len .. -1]
76
+ end
77
+
78
+ def patch_section(section, _new, in_off = 0)
79
+ if section + 1 == @num_sections
80
+ endoff = @data_file.length
81
+ else
82
+ endoff = @sections[section + 1][0]
83
+ end
84
+ raise unless off + in_off + _new.length <= endoff
85
+ patch(off + in_off, _new)
86
+ end
87
+
88
+ def updateEXTH121(srcs_secnum, srcs_cnt, mobiheader)
89
+ mobi_length, = mobiheader[0x14...0x18].unpack("N")
90
+ exth_flag, = mobiheader[0x80...0x84].unpack("N")
91
+ exth = "NONE"
92
+ begin
93
+ if exth_flag & 0x40 != 0
94
+ exth = mobiheader[16 + mobi_length .. -1]
95
+ if exth.length >= 4 && exth[0, 4] == "EXTH"
96
+ nitems, = exth[8...12].unpack("N")
97
+ pos = 12
98
+ nitems.times do
99
+ type, size = exth[pos ... pos + 8].unpack("NN")
100
+ #puts "#{type}, #{size}"
101
+ if type == 121
102
+ boundaryptr, = exth[pos + 8 ... pos + size].unpack("N")
103
+ if srcs_secnum <= boundaryptr
104
+ boundaryptr -= srcs_cnt
105
+ prefix = mobiheader[0, 16 + mobi_length + pos + 8]
106
+ suffix = mobiheader[16 + mobi_length + pos + 8 + 4 .. -1]
107
+ nval = [boundaryptr].pack("N")
108
+ mobiheader = prefix + nval + suffix
109
+ end
110
+ end
111
+ pos += size
112
+ end
113
+ end
114
+ end
115
+ rescue
116
+ end
117
+ mobiheader
118
+ end
119
+
120
+ def initialize(datain, verbose = true)
121
+ @verbose = verbose
122
+ if datain[0x3C...0x3C+8] != "BOOKMOBI"
123
+ raise StripException, "invalid file format"
124
+ end
125
+ @num_sections, = datain[76...78].unpack("n")
126
+
127
+ # get mobiheader and check SRCS section number and count
128
+ offset0, = datain.unpack("@78N")
129
+ offset1, = datain.unpack("@86N")
130
+ mobiheader = datain[offset0 ... offset1]
131
+ srcs_secnum, srcs_cnt = mobiheader.unpack("@224NN")
132
+ if srcs_secnum == 0xffffffff || srcs_cnt == 0
133
+ raise StripException, "File doesn't contain the sources section."
134
+ end
135
+
136
+ puts "Found SRCS section number %d, and count %d" % [srcs_secnum, srcs_cnt] if @verbose
137
+ # find its offset and length
138
+ _next = srcs_secnum + srcs_cnt
139
+ srcs_offset, = datain.unpack("@#{78+srcs_secnum*8}NN")
140
+ next_offset, = datain.unpack("@#{78+_next*8}NN")
141
+ srcs_length = next_offset - srcs_offset
142
+ if datain[srcs_offset ... srcs_offset+4] != "SRCS"
143
+ raise StripException, "SRCS section num does not point to SRCS."
144
+ end
145
+ puts " beginning at offset %0x and ending at offset %0x" % [srcs_offset, srcs_length] if @verbose
146
+
147
+ # it appears bytes 68-71 always contain (2*num_sections) + 1
148
+ # this is not documented anyplace at all but it appears to be some sort of next
149
+ # available unique_id used to identify specific sections in the palm db
150
+ @data_file = datain[0, 68] + [(@num_sections - srcs_cnt) * 2 + 1].pack("N")
151
+ @data_file += datain[72...76]
152
+
153
+ # write out the number of sections reduced by srtcs_cnt
154
+ @data_file = @data_file + [@num_sections - srcs_cnt].pack("n")
155
+
156
+ # we are going to remove srcs_cnt SRCS sections so the offset of every entry in the table
157
+ # up to the srcs secnum must begin 8 bytes earlier per section removed (each table entry is 8 )
158
+ delta = -8 * srcs_cnt
159
+ srcs_secnum.times do |i|
160
+ offset, flgval = datain.unpack("@#{78+i*8}NN")
161
+ offset += delta
162
+ @data_file += [offset].pack("N") + [flgval].pack("N")
163
+ end
164
+
165
+ # for every record after the srcs_cnt SRCS records we must start it
166
+ # earlier by 8*srcs_cnt + the length of the srcs sections themselves)
167
+ delta = delta - srcs_length
168
+ (srcs_secnum + srcs_cnt ... @num_sections).each do |i|
169
+ offset, = datain.unpack("@#{78+i*8}NN")
170
+ offset += delta
171
+ flgval = 2 * (i - srcs_cnt)
172
+ @data_file += [offset].pack("N") + [flgval].pack("N")
173
+ end
174
+
175
+ # now pad it out to begin right at the first offset
176
+ # typically this is 2 bytes of nulls
177
+ first_offset, = @data_file.unpack("@78NN")
178
+ @data_file += "\0" * (first_offset - @data_file.length)
179
+
180
+ # now finally add on every thing up to the original src_offset
181
+ @data_file += datain[offset0...srcs_offset]
182
+
183
+ # and everything afterwards
184
+ @data_file += datain[srcs_offset + srcs_length .. -1]
185
+
186
+ #store away the SRCS section in case the user wants it output
187
+ @stripped_data_header = datain[srcs_offset ... srcs_offset + 16]
188
+ @stripped_data = datain[srcs_offset + 16 ... srcs_offset + srcs_length]
189
+
190
+ # update the number of sections count
191
+ @num_section = @num_sections - srcs_cnt
192
+
193
+ # update the srcs_secnum and srcs_cnt in the mobiheader
194
+ offset0, = @data_file.unpack("@78NN")
195
+ offset1, = @data_file.unpack("@86NN")
196
+ mobiheader = @data_file[offset0 ... offset1]
197
+ mobiheader = mobiheader[0, 0xe0] + [-1].pack("N") + [0].pack("N") + mobiheader[0xe8 .. -1]
198
+
199
+ # if K8 mobi, handle metadata 121 in old mobiheader
200
+ mobiheader = updateEXTH121(srcs_secnum, srcs_cnt, mobiheader)
201
+ @data_file = @data_file[0, offset0] + mobiheader + @data_file[offset1 .. -1]
202
+ puts "done" if @verbose
203
+ end
204
+
205
+ def get_result
206
+ @data_file
207
+ end
208
+
209
+ def get_stripped_data
210
+ @stripped_data
211
+ end
212
+
213
+ def get_header
214
+ @stripped_data_header
215
+ end
216
+
217
+ def self.strip(infile, outfile = nil, verbose = true)
218
+ outfile = infile unless outfile
219
+ data_file = File.binread(infile)
220
+ stripped_file = new(data_file, verbose)
221
+ File.binwrite(outfile, stripped_file.get_result)
222
+ stripped_file
223
+ end
224
+ end
225
+
226
+ if __FILE__ == $0
227
+ puts "KndleStrip v#{KINDLESTRIP_VERSION}. " +
228
+ "Written 2010-2012 by Paul Durrant and Kevin Hendricks."
229
+ if ARGV.length < 2 || ARGV.length > 3
230
+ puts "Strips the Sources record from Mobipocket ebooks"
231
+ puts "For ebooks generated using KindleGen 1.1 and later that add the source"
232
+ puts "Usage:"
233
+ puts " %s <infile> <outfile> <strippeddatafile>" % File.basename(__FILE__)
234
+ puts "<strippeddatafile> is optional."
235
+ exit Narou::EXIT_ERROR_CODE
236
+ else
237
+ infile = ARGV[0]
238
+ outfile = ARGV[1]
239
+ begin
240
+ stripped_file = SectionStripper.strip(infile, outfile)
241
+ #print "Header Bytes: " + binascii.b2a_hex(strippedFile.getHeader())
242
+ if ARGV.length == 3
243
+ File.binwrite(ARGV[2], stripped_file.get_stripped_data)
244
+ end
245
+ rescue StripException => e
246
+ warn "Error: #{e.message}"
247
+ exit Narou::EXIT_ERROR_CODE
248
+ end
249
+ end
250
+ end