magicshelf 0.2.1

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