rindle 0.1.0
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +19 -0
- data/Rakefile +2 -0
- data/lib/rindle/collection.rb +156 -0
- data/lib/rindle/collections.rb +43 -0
- data/lib/rindle/document.rb +131 -0
- data/lib/rindle/index.rb +43 -0
- data/lib/rindle/mixins/regexp.rb +16 -0
- data/lib/rindle/version.rb +3 -0
- data/lib/rindle.rb +58 -0
- data/rindle.gemspec +20 -0
- data/spec/data/README.md +1 -0
- data/spec/data/kindle/documents/A book in another collection.mobi +0 -0
- data/spec/data/kindle/documents/A test aswell.mobi +0 -0
- data/spec/data/kindle/documents/Definitely a Test.pdf +0 -0
- data/spec/data/kindle/documents/Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw +0 -0
- data/spec/data/kindle/documents/The Adventures of Sherlock Holme-asin_B000JQU1VS-type_EBOK-v_0.azw +0 -0
- data/spec/data/kindle/documents/This is a test document.rtf +0 -0
- data/spec/data/kindle/system/collections.json +14 -0
- data/spec/data/mount/.gitkeep +0 -0
- data/spec/rindle/collection_spec.rb +100 -0
- data/spec/rindle/collections_spec.rb +12 -0
- data/spec/rindle/document_spec.rb +59 -0
- data/spec/rindle/index_spec.rb +49 -0
- data/spec/rindle/mixins/regexp_spec.rb +11 -0
- data/spec/rindle_spec.rb +37 -0
- data/spec/spec_helper.rb +18 -0
- metadata +122 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create --install 1.9.3@rindle
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
# Capybara request specs
|
17
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
18
|
+
end
|
19
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Arthur Andersen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Rindle: A Gem packaging multiple kindle tools
|
2
|
+
|
3
|
+
Rindle is a package of useful classes to manage the kindles content.
|
4
|
+
In mature versions it should provide an object-oriented interface to
|
5
|
+
access the collections and documents on the kindle with all their
|
6
|
+
metadata. And it should provide a convenient way for converting
|
7
|
+
various eBook formats.
|
8
|
+
|
9
|
+
## Libraries for kindle access
|
10
|
+
|
11
|
+
First load the kindle:
|
12
|
+
|
13
|
+
Rindle::Kindle.load '/path/to/root'
|
14
|
+
|
15
|
+
Then just use `Kindle::Collection` or `Kindle::File` with an
|
16
|
+
ActiveRecord like interface:
|
17
|
+
|
18
|
+
Rindle::Collection.first named: 'Test Collection'
|
19
|
+
Rindle::Collection.all named: /(.*)[1|2]$/
|
data/Rakefile
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
module Rindle
|
2
|
+
class Collection
|
3
|
+
class << self
|
4
|
+
def all options = {}
|
5
|
+
filtered = []
|
6
|
+
Rindle.collections.each_pair do |name, collection|
|
7
|
+
match = true
|
8
|
+
options.each_pair do |key,value|
|
9
|
+
case key
|
10
|
+
when :named
|
11
|
+
match = match && name =~ /#{value}/
|
12
|
+
when :including
|
13
|
+
match = match && collection.include?(value)
|
14
|
+
when :accessed
|
15
|
+
time = value.is_a?(Integer) ? Time.at(value) : value
|
16
|
+
match = match && collection.last_access == time
|
17
|
+
end
|
18
|
+
end
|
19
|
+
filtered << collection if match
|
20
|
+
end
|
21
|
+
filtered
|
22
|
+
end
|
23
|
+
|
24
|
+
def first options = {}
|
25
|
+
Rindle.collections.each_pair do |name, collection|
|
26
|
+
match = true
|
27
|
+
options.each_pair do |key,value|
|
28
|
+
case key
|
29
|
+
when :named
|
30
|
+
match = match && collection.name =~ /#{value}/
|
31
|
+
when :including
|
32
|
+
match = match && collection.include?(value)
|
33
|
+
when :accessed
|
34
|
+
time = value.is_a?(Integer) ? Time.at(value) : value
|
35
|
+
match = match && collection.last_access == time
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return collection if match
|
39
|
+
end
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def find(method = :all, options={})
|
44
|
+
self.send method, options
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_by_name name
|
48
|
+
find(:first, :named => name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create name, options = {}
|
52
|
+
collection = Collection.new(name, options)
|
53
|
+
Rindle.collections[collection.name] = collection
|
54
|
+
end
|
55
|
+
|
56
|
+
def exists? options
|
57
|
+
!Collection.first(options).nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :name, :indices, :last_access
|
62
|
+
|
63
|
+
def == other
|
64
|
+
other.is_a?(Rindle::Collection) &&
|
65
|
+
name == other.name &&
|
66
|
+
indices == other.indices &&
|
67
|
+
last_access == other.last_access
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize name, options = {}
|
71
|
+
{ :indices => [], :last_access => nil }.merge!(options)
|
72
|
+
@name = name.gsub('@en-US', '')
|
73
|
+
@indices = options[:indices] || []
|
74
|
+
@last_access = Time.at(options[:last_access]) if options[:last_access]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a hash that may be saved to `collections.json` file.
|
78
|
+
def to_hash
|
79
|
+
{
|
80
|
+
"#{@name}@en-US" => {
|
81
|
+
"items" => @indices,
|
82
|
+
"lastAccess" => @last_access.to_i
|
83
|
+
}
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Renames the collection. This changes the collections name and
|
88
|
+
# updates the Collections hash.
|
89
|
+
def rename! new_name
|
90
|
+
Rindle.collections.delete @name
|
91
|
+
@name = new_name
|
92
|
+
Rindle.collections[@name] = self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Destroys the collection. This removes the collections key from
|
96
|
+
# the collections hash.
|
97
|
+
def destroy!
|
98
|
+
Rindle.collections.delete @name
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds an index or a document to the collection.
|
102
|
+
def add index
|
103
|
+
index = index.index if index.is_a?(Document)
|
104
|
+
unless indices.include?(index)
|
105
|
+
indices << obj.index
|
106
|
+
@documents = nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Removes an entry from this collection.
|
111
|
+
def remove index
|
112
|
+
index = index.index if index.is_a?(Document)
|
113
|
+
if indices.include?(index)
|
114
|
+
indices.delete index
|
115
|
+
@documents = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Sets the indices array and resets the documents memoized array
|
120
|
+
# of `Document` objects.
|
121
|
+
def indices= indices
|
122
|
+
@documents = nil
|
123
|
+
@indices = indices
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns an `Array` of `Document` objects.
|
127
|
+
def documents
|
128
|
+
@documents ||= @indices.map { |i| Rindle.index[i] }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sets the array of `Document` objects.
|
132
|
+
def documents= documents
|
133
|
+
indices = documents.map(&:index)
|
134
|
+
@documents = documents
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns true if the collection includes the given indec,
|
138
|
+
# `Document` or `Array`.
|
139
|
+
def include? obj
|
140
|
+
if obj.is_a?(Array)
|
141
|
+
obj.inject(true) { |acc, o| acc = acc and include?(o) }
|
142
|
+
elsif obj.is_a?(Document)
|
143
|
+
indices.include? obj.index
|
144
|
+
elsif obj.is_a?(String)
|
145
|
+
indices.include? obj
|
146
|
+
else
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Update the last access timestamp.
|
152
|
+
def touch
|
153
|
+
last_access = Time.now
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'rindle/collection'
|
5
|
+
|
6
|
+
module Rindle
|
7
|
+
class Collections < Hash
|
8
|
+
|
9
|
+
class NoSuchFile < Exception; end
|
10
|
+
|
11
|
+
def initialize(kindle_root)
|
12
|
+
@collections_file = File.join(kindle_root, 'system', 'collections.json')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load(kindle_root)
|
16
|
+
Collections.new(kindle_root).load
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
hash = {}
|
21
|
+
values.each do |coll|
|
22
|
+
hash.merge! col.to_hash
|
23
|
+
end
|
24
|
+
File.open(@collections_file, 'w+') do |f|
|
25
|
+
JSON.dump(hash, f)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def load
|
30
|
+
unless File.exists?(@collections_file)
|
31
|
+
raise NoSuchFile, "Not found: #{@collections_file}"
|
32
|
+
end
|
33
|
+
|
34
|
+
hash = File.open(@collections_file, 'r') { |file| JSON.load(file) }
|
35
|
+
hash.each_pair do |name, data|
|
36
|
+
col = Collection.new name, :indices => data['items'],
|
37
|
+
:last_access => data['lastAccess']
|
38
|
+
self[col.name] = col
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
|
3
|
+
module Rindle
|
4
|
+
class Document
|
5
|
+
class NotFound < Exception; end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def all options = {}
|
9
|
+
filtered = []
|
10
|
+
Rindle.index.each_pair do |index, doc|
|
11
|
+
match = true
|
12
|
+
options.each_pair do |key, value|
|
13
|
+
case key
|
14
|
+
when :named
|
15
|
+
match = match && doc.filename =~ /#{value.is_a?(String) ? Regexp.escape(value) : value}/
|
16
|
+
when :indexed
|
17
|
+
match = match && index =~ /#{value.is_a?(String) ? Regexp.escape(value) : value}/
|
18
|
+
end
|
19
|
+
end
|
20
|
+
filtered << doc if match
|
21
|
+
end
|
22
|
+
filtered
|
23
|
+
end
|
24
|
+
|
25
|
+
def first options = {}
|
26
|
+
Rindle.index.each_pair do |index, doc|
|
27
|
+
match = true
|
28
|
+
options.each_pair do |key, value|
|
29
|
+
case key
|
30
|
+
when :named
|
31
|
+
match = match && doc.filename =~ /#{value.is_a?(String) ? Regexp.escape(value) : value}/
|
32
|
+
when :indexed
|
33
|
+
match = match && index =~ /#{value.is_a?(String) ? Regexp.escape(value) : value}/
|
34
|
+
end
|
35
|
+
end
|
36
|
+
return doc if match
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def find method = :all, options = {}
|
42
|
+
self.send method, options
|
43
|
+
end
|
44
|
+
|
45
|
+
def unassociated
|
46
|
+
unassociated = []
|
47
|
+
Rindle.index.each_pair do |index, doc|
|
48
|
+
unless Rindle.collections.values.inject(false) { |acc, col| acc = acc or col.include?(index) }
|
49
|
+
unassociated << doc
|
50
|
+
end
|
51
|
+
end
|
52
|
+
unassociated
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_by_name name
|
56
|
+
Rindle.index.values.each do |doc|
|
57
|
+
return doc if doc.filename =~ /#{name.is_a?(String) ? Regexp.escape(name) : name}/
|
58
|
+
end
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_by_path path
|
63
|
+
Rindle.index.values.each do |doc|
|
64
|
+
return doc if doc.path =~ /#{path.is_a?(String) ? Regexp.escape(path) : path}/
|
65
|
+
end
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def find_by_index index
|
70
|
+
Rindle.index[index]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_reader :index, :path
|
75
|
+
|
76
|
+
def initialize path
|
77
|
+
self.path = path
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sets the path variable and updates the index
|
81
|
+
def path= path
|
82
|
+
@path = path
|
83
|
+
@index = generate_index
|
84
|
+
end
|
85
|
+
|
86
|
+
# Generates the index for the current path
|
87
|
+
def generate_index
|
88
|
+
if path =~ /([\w\s]+)-asin_([A-Z0-9]+)-type_([A-Z]+)-v_[0-9]+.azw/
|
89
|
+
"##{$2}^#{$3}"
|
90
|
+
else
|
91
|
+
"*#{Digest::SHA1.hexdigest(File.join('/mnt/us', path))}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Two documents are the same if the indices are equal.
|
96
|
+
def == other
|
97
|
+
@index == other.index
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the filesize of this document.
|
101
|
+
def filesize
|
102
|
+
@filesize ||= File.size(File.join(Rindle.root_path, @path))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the filename of this document.
|
106
|
+
def filename
|
107
|
+
File.basename(@path)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns true if the `path` looks like an amazon kindle store file.
|
111
|
+
def amazon?
|
112
|
+
!(path !~ /([\w\s]+)-asin_([A-Z0-9]+)-type_([A-Z]+)-v_[0-9]+.azw/)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Renames the document. This also means that the index is changed
|
116
|
+
# and the Index-hash is updated.
|
117
|
+
def rename! new_name
|
118
|
+
Rindle.index.delete(@index)
|
119
|
+
self.path = @path.gsub filename, new_name
|
120
|
+
# TODO: update references in Rindle.collections
|
121
|
+
Rindle.index[@index] = self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns an array of all the collections, this document is in.
|
125
|
+
def collections
|
126
|
+
Rindle.collections.select do |col|
|
127
|
+
col.include? self.index
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/rindle/index.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "rubygems"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Rindle
|
6
|
+
class Index < Hash
|
7
|
+
def initialize
|
8
|
+
@index = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.load(root_path)
|
12
|
+
Index.new.load(root_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(root_path)
|
16
|
+
@root_path = root_path
|
17
|
+
documents = Dir[File.join(@root_path, '{documents,pictures}', '*.{mobi,azw,azw1,pdf,rtf}')]
|
18
|
+
documents.each do |element|
|
19
|
+
add(element.gsub(@root_path, ''))
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a path to the index. This means that the correct sha1 sum
|
25
|
+
# is generated and used as index for the newly created document
|
26
|
+
# object.
|
27
|
+
def add path
|
28
|
+
doc = Document.new path
|
29
|
+
self[doc.index] = doc
|
30
|
+
doc.index
|
31
|
+
end
|
32
|
+
|
33
|
+
# Removes either an entry either by `Document` or index.
|
34
|
+
def remove obj
|
35
|
+
if obj.is_a?(Document)
|
36
|
+
delete(index(obj))
|
37
|
+
else
|
38
|
+
delete(obj)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rindle
|
2
|
+
module Mixins
|
3
|
+
module Regexp
|
4
|
+
|
5
|
+
# converts the expressin to a string and
|
6
|
+
# strips the special characters for marking
|
7
|
+
# the beginning and end of a string
|
8
|
+
def strip
|
9
|
+
self.source.gsub(/^\^/,'').gsub(/\$$/,'')
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Regexp.send :include, Rindle::Mixins::Regexp
|
data/lib/rindle.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rindle/version'
|
2
|
+
require 'rindle/index'
|
3
|
+
require 'rindle/collections'
|
4
|
+
require 'rindle/collection'
|
5
|
+
require 'rindle/document'
|
6
|
+
|
7
|
+
require 'rindle/mixins/regexp'
|
8
|
+
|
9
|
+
# This module is used to load the Kindles state into the
|
10
|
+
# data structures, which are used to let you access the Kindles
|
11
|
+
# content in a way, that reminds one of the ActiveRecord usage
|
12
|
+
#
|
13
|
+
# Load from a specific path
|
14
|
+
#
|
15
|
+
# Rindle.load(path)
|
16
|
+
#
|
17
|
+
# After that you may use the models Collection, Document, Album
|
18
|
+
module Rindle
|
19
|
+
@@root_path = nil
|
20
|
+
@@collections = nil
|
21
|
+
@@index = nil
|
22
|
+
|
23
|
+
class NotLoaded < Exception; end
|
24
|
+
|
25
|
+
def self.root_path
|
26
|
+
if @@root_path
|
27
|
+
@@root_path
|
28
|
+
else
|
29
|
+
raise NotLoaded
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.index
|
34
|
+
if @@index
|
35
|
+
@@index
|
36
|
+
else
|
37
|
+
raise NotLoaded
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.collections
|
42
|
+
if @@collections
|
43
|
+
@@collections
|
44
|
+
else
|
45
|
+
raise NotLoaded
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.load root_path
|
50
|
+
@@root_path = root_path
|
51
|
+
@@index = Index.load(root_path)
|
52
|
+
@@collections = Collections.load(root_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.save
|
56
|
+
@@collections.save
|
57
|
+
end
|
58
|
+
end
|
data/rindle.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/rindle/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Arthur Leonard Andersen"]
|
6
|
+
gem.email = ["leoc.git@gmail.com"]
|
7
|
+
gem.description = %q{The Rindle gem provides an object-oriented way to manage kindle collection data.}
|
8
|
+
gem.summary = %q{Access kindle collection data}
|
9
|
+
gem.homepage = "https://github.com/leoc/rindle"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "rindle"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Rindle::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
gem.add_development_dependency 'guard-rspec'
|
20
|
+
end
|
data/spec/data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This directory contains dummy data for the specs.
|
File without changes
|
File without changes
|
File without changes
|
data/spec/data/kindle/documents/Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw
ADDED
File without changes
|
data/spec/data/kindle/documents/The Adventures of Sherlock Holme-asin_B000JQU1VS-type_EBOK-v_0.azw
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"collection1@en-US":{
|
3
|
+
"items":["*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7", "*0849dd9b85fc341d10104f56985e423b3848e1f3"],
|
4
|
+
"lastAccess": 1298745909919
|
5
|
+
},
|
6
|
+
"collection2@en-US":{
|
7
|
+
"items":["*440f49b58ae78d34f4b8ad3233f04f6b8f5490c2"],
|
8
|
+
"lastAccess": 1298745909918
|
9
|
+
},
|
10
|
+
"amazon books@en-US":{
|
11
|
+
"items":["#B001UQ5HVA^EBSP","#B000JQU1VS^EBOK"],
|
12
|
+
"lastAccess": 1298745909917
|
13
|
+
}
|
14
|
+
}
|
File without changes
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rindle::Collection do
|
4
|
+
before(:all) do
|
5
|
+
Rindle.load(kindle_root)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
context '#exists?' do
|
10
|
+
it 'returns true if the collection exists' do
|
11
|
+
Rindle::Collection.exists?(:named => 'collection1').should == true
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns false if the collection does not exist' do
|
15
|
+
Rindle::Collection.exists?(:named => 'collection5').should == false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context '#all' do
|
20
|
+
it 'invokes Collection.find with parameter :all' do
|
21
|
+
Rindle::Collection.find(:all).map(&:name).should == Rindle::Collection.all.map(&:name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#first' do
|
26
|
+
it 'invokes Collection.find with parameter :first' do
|
27
|
+
Rindle::Collection.find(:first).should == Rindle::Collection.first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#find' do
|
32
|
+
it 'returns an array of Kindle::Collection objects' do
|
33
|
+
Rindle::Collection.find
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'finds all filtered' do
|
37
|
+
it 'by name with string' do
|
38
|
+
name = "collection"
|
39
|
+
collections = Rindle::Collection.find(:all, :named => name)
|
40
|
+
collections.map(&:name).should == ['collection1', 'collection2']
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'by name with regular expression' do
|
44
|
+
name = /collection[1|3]/
|
45
|
+
collections = Rindle::Collection.find(:all, :named => name)
|
46
|
+
collections.map(&:name).should == ['collection1']
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'by including index' do
|
50
|
+
collections = Rindle::Collection.find(:all, :including => '#B001UQ5HVA^EBSP')
|
51
|
+
collections.map(&:name).should == ['amazon books']
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'by including all of an array of indices' do
|
55
|
+
collections = Rindle::Collection.find(:all, :including => ["*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7", "*0849dd9b85fc341d10104f56985e423b3848e1f3"])
|
56
|
+
collections.map(&:name).should == ['collection1']
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'by last access time' do
|
60
|
+
collections = Rindle::Collection.find(:all, :accessed => 1298745909917)
|
61
|
+
collections.map(&:name).should == ['amazon books']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'finds first filtered' do
|
66
|
+
it 'by name' do
|
67
|
+
collection = Rindle::Collection.find(:first, :named => 'collection1')
|
68
|
+
collection.name.should == 'collection1'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'by included index' do
|
72
|
+
collection = Rindle::Collection.find(:first, :including => '*440f49b58ae78d34f4b8ad3233f04f6b8f5490c2')
|
73
|
+
collection.name.should == 'collection2'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'by last access time' do
|
77
|
+
collection = Rindle::Collection.find(:first, :accessed => 1298745909917)
|
78
|
+
collection.name.should == 'amazon books'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context '#documents' do
|
84
|
+
it 'returns an array of Rindle::Document objects' do
|
85
|
+
collection = Rindle::Collection.find(:first, :named => 'collection1')
|
86
|
+
collection.documents.each do |document|
|
87
|
+
document.should be_a(Rindle::Document)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context '#to_hash' do
|
93
|
+
it 'returns a hash that could be found in collections.json' do
|
94
|
+
now = Time.now.to_i
|
95
|
+
collection = Rindle::Collection.new("test", :indices => ['a','b','c'],
|
96
|
+
:last_access => now)
|
97
|
+
collection.to_hash.should == { "test@en-US" => { "items" => ['a', 'b', 'c'], "lastAccess" => now } }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rindle::Collections do
|
4
|
+
it 'loads the kindles collections.json file' do
|
5
|
+
collections = Rindle::Collections.load(kindle_root)
|
6
|
+
Hash[collections.map{|k,c| [k,c.indices] } ].should == {
|
7
|
+
"collection1" => ["*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7", "*0849dd9b85fc341d10104f56985e423b3848e1f3"],
|
8
|
+
"collection2" => ["*440f49b58ae78d34f4b8ad3233f04f6b8f5490c2"],
|
9
|
+
"amazon books" => ["#B001UQ5HVA^EBSP","#B000JQU1VS^EBOK"]
|
10
|
+
}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rindle::Document do
|
4
|
+
before(:all) do
|
5
|
+
Rindle.load(kindle_root)
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:all) do
|
9
|
+
Rindle.reset
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'equals another if the index is the same' do
|
13
|
+
doc1 = Rindle::Document.new('documents/ABC-asin_B001UQ5HVA-type_EBSP-v1.azw')
|
14
|
+
doc2 = Rindle::Document.new('documents/ABC-asin_B001UQ5HVA-type_EBSP-v1.azw')
|
15
|
+
doc1.should == doc2
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.find' do
|
19
|
+
context 'finds :all filtered' do
|
20
|
+
it 'by name with string' do
|
21
|
+
docs = Rindle::Document.find(:all, :named => 'A test aswell.mobi')
|
22
|
+
docs.map(&:filename).should == ['A test aswell.mobi']
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'by name with regular expression' do
|
26
|
+
docs = Rindle::Document.find(:all, :named => /t([es]+)t/)
|
27
|
+
docs.map(&:filename).should == ['A test aswell.mobi',
|
28
|
+
'This is a test document.rtf']
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'by index' do
|
32
|
+
docs = Rindle::Document.find(:all, :indexed => '*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7')
|
33
|
+
docs.map(&:filename).should == ['A test aswell.mobi']
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'finds :first filtered' do
|
38
|
+
it 'by name with string' do
|
39
|
+
doc = Rindle::Document.find(:first, :named => 'A test aswell.mobi')
|
40
|
+
doc.filename.should == 'A test aswell.mobi'
|
41
|
+
end
|
42
|
+
it 'by name with regular expression' do
|
43
|
+
doc = Rindle::Document.find(:first, :named => /t([es]+)t/)
|
44
|
+
doc.filename.should == 'A test aswell.mobi'
|
45
|
+
end
|
46
|
+
it 'by index' do
|
47
|
+
doc = Rindle::Document.find(:first, :indexed => '*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7')
|
48
|
+
doc.filename.should == 'A test aswell.mobi'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '.unassociated' do
|
54
|
+
it 'should return unassociated documents' do
|
55
|
+
docs = Rindle::Document.unassociated
|
56
|
+
docs.map(&:filename).should == [ 'This is a test document.rtf' ]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Rindle::Index do
|
4
|
+
before(:each) do
|
5
|
+
@index = Rindle::Index.new
|
6
|
+
end
|
7
|
+
|
8
|
+
context '#add' do
|
9
|
+
it 'generates and adds for sha1 sum for custom books' do
|
10
|
+
index = @index.add('documents/A test aswell.mobi')
|
11
|
+
index.should == "*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7"
|
12
|
+
@index.should have_key("*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7")
|
13
|
+
end
|
14
|
+
it 'generates and adds amazon indices for EBOK and EBSP files' do
|
15
|
+
index = @index.add('documents/Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw')
|
16
|
+
index.should == "#B001UQ5HVA^EBSP"
|
17
|
+
@index.should have_key("#B001UQ5HVA^EBSP")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context '#new' do
|
22
|
+
it 'creates an empty index' do
|
23
|
+
@index.should be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'derives from Hash' do
|
27
|
+
@index.should be_a(Hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'is not empty after #load if there are documents on the kindle' do
|
32
|
+
Dir[File.join(kindle_root, '{documents,pictures}', '*.{mobi,azw,azw1,pdf,rtf}')].should_not be_empty
|
33
|
+
Rindle::Index.load(kindle_root).should_not == {}
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
it 'indexes the dummy files from spec data' do
|
38
|
+
@index = Rindle::Index.load(kindle_root)
|
39
|
+
@index.values.map { |o| [ o.index, o.filename ] }.should =~
|
40
|
+
[
|
41
|
+
[ "*18be6fcd5d5df39c1a96cd22596bbe7fe01db9b7", "A test aswell.mobi" ],
|
42
|
+
[ "#B001UQ5HVA^EBSP", "Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw" ],
|
43
|
+
[ "*440f49b58ae78d34f4b8ad3233f04f6b8f5490c2", "A book in another collection.mobi" ],
|
44
|
+
[ "#B000JQU1VS^EBOK", "The Adventures of Sherlock Holme-asin_B000JQU1VS-type_EBOK-v_0.azw" ],
|
45
|
+
[ "*0849dd9b85fc341d10104f56985e423b3848e1f3", "Definitely a Test.pdf" ],
|
46
|
+
[ "*3a102b4032d485025650409b2f7753a1158b199d", "This is a test document.rtf" ]
|
47
|
+
]
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
describe 'Regexp Mixins' do
|
2
|
+
it 'should strip special char for beginning of string' do
|
3
|
+
expr = /^match/
|
4
|
+
expr.strip.should == 'match'
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'should strip special char for end of string' do
|
8
|
+
expr = /match$/
|
9
|
+
expr.strip.should == 'match'
|
10
|
+
end
|
11
|
+
end
|
data/spec/rindle_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rindle do
|
4
|
+
before(:each) do
|
5
|
+
Rindle.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
context '.collections' do
|
9
|
+
it 'raises a NotLoaded error if not loaded' do
|
10
|
+
lambda { Rindle::collections }.should raise_error(Rindle::NotLoaded)
|
11
|
+
end
|
12
|
+
it 'returns an instance of Kindle::Collections if module loaded' do
|
13
|
+
Rindle::load(kindle_root)
|
14
|
+
Rindle::collections.should be_a(Rindle::Collections)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '.index' do
|
19
|
+
it 'raises a NotLoaded error if not loaded' do
|
20
|
+
lambda { Rindle::index }.should raise_error(Rindle::NotLoaded)
|
21
|
+
end
|
22
|
+
it 'returns an instance of Kindle::Index if module loaded' do
|
23
|
+
Rindle::load(kindle_root)
|
24
|
+
Rindle::index.should be_a(Rindle::Index)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context '.root_path' do
|
29
|
+
it 'provieds a string if loaded' do
|
30
|
+
Rindle::load(kindle_root)
|
31
|
+
Rindle::root_path.should be_a(String)
|
32
|
+
end
|
33
|
+
it 'raises a NotLoaded error if not loaded' do
|
34
|
+
lambda { Rindle::root_path }.should raise_error(Rindle::NotLoaded)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rindle'
|
2
|
+
|
3
|
+
def kindle_root; File.join(File.dirname(__FILE__), 'data', 'kindle'); end
|
4
|
+
|
5
|
+
# this is to reset the Singleton'ish nature of the Kindle module
|
6
|
+
module Rindle
|
7
|
+
def self.reset
|
8
|
+
self.class_variables.each do |var|
|
9
|
+
eval "#{var} = nil"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
16
|
+
config.run_all_when_everything_filtered = true
|
17
|
+
config.filter_run :focus
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rindle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Arthur Leonard Andersen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &21636720 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *21636720
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: guard-rspec
|
27
|
+
requirement: &21636100 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *21636100
|
36
|
+
description: The Rindle gem provides an object-oriented way to manage kindle collection
|
37
|
+
data.
|
38
|
+
email:
|
39
|
+
- leoc.git@gmail.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- .rspec
|
46
|
+
- .rvmrc
|
47
|
+
- Gemfile
|
48
|
+
- Guardfile
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- lib/rindle.rb
|
53
|
+
- lib/rindle/collection.rb
|
54
|
+
- lib/rindle/collections.rb
|
55
|
+
- lib/rindle/document.rb
|
56
|
+
- lib/rindle/index.rb
|
57
|
+
- lib/rindle/mixins/regexp.rb
|
58
|
+
- lib/rindle/version.rb
|
59
|
+
- rindle.gemspec
|
60
|
+
- spec/data/README.md
|
61
|
+
- spec/data/kindle/documents/A book in another collection.mobi
|
62
|
+
- spec/data/kindle/documents/A test aswell.mobi
|
63
|
+
- spec/data/kindle/documents/Definitely a Test.pdf
|
64
|
+
- spec/data/kindle/documents/Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw
|
65
|
+
- spec/data/kindle/documents/The Adventures of Sherlock Holme-asin_B000JQU1VS-type_EBOK-v_0.azw
|
66
|
+
- spec/data/kindle/documents/This is a test document.rtf
|
67
|
+
- spec/data/kindle/system/collections.json
|
68
|
+
- spec/data/mount/.gitkeep
|
69
|
+
- spec/rindle/collection_spec.rb
|
70
|
+
- spec/rindle/collections_spec.rb
|
71
|
+
- spec/rindle/document_spec.rb
|
72
|
+
- spec/rindle/index_spec.rb
|
73
|
+
- spec/rindle/mixins/regexp_spec.rb
|
74
|
+
- spec/rindle_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
homepage: https://github.com/leoc/rindle
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: 391700311857255160
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
hash: 391700311857255160
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.8.17
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Access kindle collection data
|
106
|
+
test_files:
|
107
|
+
- spec/data/README.md
|
108
|
+
- spec/data/kindle/documents/A book in another collection.mobi
|
109
|
+
- spec/data/kindle/documents/A test aswell.mobi
|
110
|
+
- spec/data/kindle/documents/Definitely a Test.pdf
|
111
|
+
- spec/data/kindle/documents/Salvia Divinorum Shamanic Plant-asin_B001UQ5HVA-type_EBSP-v_0.azw
|
112
|
+
- spec/data/kindle/documents/The Adventures of Sherlock Holme-asin_B000JQU1VS-type_EBOK-v_0.azw
|
113
|
+
- spec/data/kindle/documents/This is a test document.rtf
|
114
|
+
- spec/data/kindle/system/collections.json
|
115
|
+
- spec/data/mount/.gitkeep
|
116
|
+
- spec/rindle/collection_spec.rb
|
117
|
+
- spec/rindle/collections_spec.rb
|
118
|
+
- spec/rindle/document_spec.rb
|
119
|
+
- spec/rindle/index_spec.rb
|
120
|
+
- spec/rindle/mixins/regexp_spec.rb
|
121
|
+
- spec/rindle_spec.rb
|
122
|
+
- spec/spec_helper.rb
|