doxieland 0.1.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 844c42e8cbc32d2537f7b4e0802f7504882f95a6
4
- data.tar.gz: 8ed0de9163005a259fa3f5c127f76e41f17a6402
3
+ metadata.gz: 3a8265c8aef3acb73110d4c084c45ee809391bb6
4
+ data.tar.gz: a6636e435c48b7a47fc0035a3d08635083b44c9c
5
5
  SHA512:
6
- metadata.gz: e2e25efc0b3df591063ef6b390b333cdc3530c5fa8d6388b37616223bfd0ea604b5f247a7aabf29fa7b8291a8e2e03c54583c05675f947f22f39ccaeec745f87
7
- data.tar.gz: c5b9a1490234bfa2ba1b6ad47e0bb27eeace7431ca159d550c40597bcf3c8aacbb7c9f3e5e4990ed08e9119ddf534727cb52bea8ec6771b880b1e36cb4709380
6
+ metadata.gz: bbc20ab3361ffadbc3d710b9a8c4e8d384470de16d3b86c6db1926d6542dc67aa2314e24f78de9c0bf51be927e0c81c332c8087c87089b86029d5f55e54276cf
7
+ data.tar.gz: f3128eb67754c15858636561f8b1588dd76271ca06414ab0a2a8ef39c03ee00a43a6c5293d4e34d343228666e26e92f813b9a757f2a1502899c6fdc06032f4d0
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ doxieland.sublime-*
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ doxieland
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.3.0
data/doxieland.gemspec CHANGED
@@ -20,8 +20,11 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ["lib"]
21
21
 
22
22
  spec.add_runtime_dependency 'activesupport', '>= 4.2'
23
- spec.add_runtime_dependency 'apidiesel', '>= 0.8'
24
- spec.add_runtime_dependency 'commander', '>= 4.3'
23
+ spec.add_runtime_dependency 'apidiesel', '>= 0.12'
24
+ spec.add_runtime_dependency 'thor', '>= 0.19'
25
+ spec.add_runtime_dependency 'ruby-progressbar', '>= 1.7'
26
+ spec.add_runtime_dependency 'hirb', '>= 0.7'
27
+ spec.add_runtime_dependency 'rainbow', '>= 2.1'
25
28
 
26
29
  spec.add_development_dependency "bundler", "~> 1.11"
27
30
  spec.add_development_dependency "rake", "~> 10.0"
data/exe/doxieland CHANGED
@@ -1,23 +1,206 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require "doxieland"
4
- require 'commander/import'
3
+ require "thor"
4
+ require "ruby-progressbar"
5
+ require 'hirb'
6
+
7
+ class CLI < Thor
8
+ include Thor::Actions
5
9
 
6
- program :name, 'doxieland'
7
- program :version, '0.0.1'
8
- program :description, 'A command line tool for downloading scans from the doxie go wi-fi'
10
+ class_option 'scanner-ip', desc:'your scanners network address', banner: 'xxx.xxx.xxx.xxx', type: :string, aliases: '-i'
11
+ class_option :password, desc:'your scanners password', type: :string, aliases: '-p'
9
12
 
10
- global_option('-i', '--scanner-ip=xxx.xxx.xxx.xxx', 'your scanners network address') { |ip| @ip = ip }
13
+ def initialize(*args, **kargs)
14
+ super
15
+
16
+ @config = (options || HashWithIndifferentAccess.new).reverse_merge(Doxieland.config)
17
+ @client = Doxieland::Client.new(@config)
18
+ end
19
+
20
+ desc "info", "show information about your scanner"
21
+ def info
22
+ response =
23
+ @client.api do |api|
24
+ api.get_info.result
25
+ end
26
+
27
+ puts Hirb::Helpers::AutoTable.render(
28
+ response,
29
+ description: false,
30
+ headers: false
31
+ )
32
+ end
11
33
 
12
- command :list do |c|
13
- c.syntax = 'doxieland list'
14
- c.description = 'lists all scans saved in your scanners memory'
15
- c.action do |args, options|
16
- Doxieland::Api.url "http://#{@ip}:8080"
17
- api = Doxieland::Api.new
34
+ desc "list", "list all scans saved in your scanners memory"
35
+ def list
36
+ scans = @client.api { |api| api.list_scans.result }
18
37
 
19
- api.list_scans.result.each do |scan|
20
- puts scan[:name]
38
+ if scans.none?
39
+ log.info "no scans found"
40
+ exit
21
41
  end
42
+
43
+ puts Hirb::Helpers::AutoTable.render(
44
+ scans,
45
+ fields: [:name, :path],
46
+ description: false
47
+ )
48
+
49
+ log.info "#{scans.length} scans available"
22
50
  end
23
- end
51
+
52
+ desc "download", <<-EOT.strip_heredoc
53
+ download all scans from your scanner to your computer
54
+
55
+ Available placeholders for filename format strings:
56
+ %{number} - the image number
57
+ %{date} - the current date as DD.MM.YYYY
58
+ %{time} - the current time as HH:MM:SS
59
+
60
+ You can further format date and time by passing format options inside the placeholders, separated by a colon:
61
+
62
+ %{date:%Y-%m-%d}
63
+
64
+ See http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime for all available date and time formatting options.
65
+
66
+ EOT
67
+ method_option :to, desc: "the directory to save the scans to. Defaults to .", type: :string, aliases: '-t'
68
+ method_option :filenames,
69
+ type: :string,
70
+ aliases: '-f',
71
+ banner: '"FORMAT_STRING"',
72
+ desc: 'filename format string. Default: "doxie_scan_%{date}-%{number}"'
73
+ method_option :pdf, desc: "convert scans to PDF. Requires ImageMagick to be installed", type: :boolean
74
+ method_option :delete, desc: "delete scans from scanner after download", type: :boolean, aliases: '-d'
75
+ def download
76
+ if @config[:pdf]
77
+ unless command_available?('convert')
78
+ log.fatal "could not find the 'convert' command. PDF conversion requires ImageMagick to be installed"
79
+ exit(false)
80
+ end
81
+ end
82
+
83
+ @client.create_save_path
84
+
85
+ Doxieland::Scan.save_path = @client.save_path
86
+ Doxieland::Scan.name_format = @config[:filenames] if @config[:filenames]
87
+
88
+ scans = @client.api do |api|
89
+ api.list_scans.result
90
+ end
91
+
92
+ if scans.none?
93
+ log.info "0 scans available for download on scanner"
94
+ exit
95
+ end
96
+
97
+ log.info "downloading #{scans.length} scans to " + Rainbow(@client.save_path).cyan.bright
98
+
99
+ progressbar = create_progressbar(starting_at: 0, total: scans.length)
100
+
101
+ deletable_paths = []
102
+ save_count = 0
103
+
104
+ scans.each do |remote_scan|
105
+ progressbar.title = "downloading #{remote_scan[:name]}".truncate(22).ljust(25)
106
+
107
+ scan = @client.api { |api| api.get_scan(path: remote_scan[:path]).result }
108
+
109
+ if @config[:pdf]
110
+ scan.file_type = 'pdf'
111
+ progressbar.title = "saving #{remote_scan[:name]} as PDF".truncate(22).ljust(25)
112
+ else
113
+ progressbar.title = "saving #{remote_scan[:name]}".truncate(22).ljust(25)
114
+ end
115
+
116
+ if scan.save
117
+ save_count += 1
118
+ deletable_paths << remote_scan[:delete_path]
119
+ else
120
+ log.progress_warn "skipped existing file #{scan.path}", progressbar
121
+ end
122
+
123
+ progressbar.increment
124
+ end
125
+
126
+ if options[:delete] && deletable_paths.any?
127
+ progressbar = create_progressbar(title: "deleting scans".ljust(20))
128
+
129
+ @client.api { |api| api.delete_scans(paths: deletable_paths).result }
130
+
131
+ progressbar.progress = 1
132
+ progressbar.total = 1
133
+ progressbar.finish
134
+ end
135
+
136
+ log.success "#{save_count} scans downloaded"
137
+ end
138
+
139
+ desc "defaults", "show or set default options for doxieland"
140
+ method_option :to, desc: "the directory to save the scans to. Defaults to .", type: :string, aliases: '-t'
141
+ method_option :filenames,
142
+ type: :string,
143
+ aliases: '-f',
144
+ banner: '"FORMAT_STRING"'
145
+ method_option :pdf, desc: "convert scans to PDF. Requires ImageMagick to be installed", type: :boolean
146
+ method_option :delete, desc: "delete scans from scanner after download", type: :boolean, aliases: '-d'
147
+ def defaults
148
+ if options.none?
149
+ config = Doxieland.config
150
+
151
+ if config.any?
152
+ puts Hirb::Helpers::AutoTable.render(
153
+ config,
154
+ description: false,
155
+ headers: { 0 => 'option', 1 => 'value' }
156
+ )
157
+ else
158
+ log.info "no defaults set"
159
+ end
160
+
161
+ else
162
+ File.open(Doxieland.config_path, 'w') { |file| file << YAML.dump(options) }
163
+
164
+ puts Hirb::Helpers::AutoTable.render(
165
+ config,
166
+ description: false,
167
+ headers: { 0 => 'option', 1 => 'value' }
168
+ )
169
+
170
+ log.success "new default settings saved"
171
+ end
172
+ end
173
+
174
+ desc "console", "starts a Pry REPL session", hide: true
175
+ def console
176
+ begin
177
+ require 'pry'
178
+ rescue LoadError
179
+ log.fatal "doxieland console requires Pry to be installed: gem install pry"
180
+ exit
181
+ end
182
+ binding.pry
183
+ end
184
+
185
+ no_commands do
186
+ def log
187
+ @client.log
188
+ end
189
+
190
+ def create_progressbar(**kargs)
191
+ kargs.reverse_merge!({
192
+ total: nil,
193
+ format: "%t (%c of %C) |%b\u{1F431}%i| %E",
194
+ progress_mark: "\u{2728}",
195
+ remainder_mark: " ", title: ' ' * 25
196
+ })
197
+ ProgressBar.create(**kargs)
198
+ end
199
+
200
+ def command_available?(command)
201
+ `which #{command}`.present? && $?.exitstatus == 0
202
+ end
203
+ end
204
+ end
205
+
206
+ CLI.start( ARGV.any? ? ARGV : ['help'] )
data/lib/doxieland.rb CHANGED
@@ -2,11 +2,35 @@ require "doxieland/version"
2
2
 
3
3
  require 'active_support/all'
4
4
  require 'apidiesel'
5
+ require 'rainbow'
6
+ require 'open-uri'
7
+ require 'cgi'
8
+ require 'pathname'
9
+ require 'fileutils'
10
+ require 'open3'
5
11
 
6
- require 'doxieland/handlers/json'
12
+ require 'doxieland/handlers/file_request'
7
13
  require 'doxieland/actions/list_scans'
14
+ require 'doxieland/actions/get_info'
15
+ require 'doxieland/actions/get_scan'
16
+ require 'doxieland/actions/delete_scans'
8
17
  require 'doxieland/api'
18
+ require 'doxieland/scan'
19
+ require 'doxieland/logger'
20
+ require 'doxieland/client'
9
21
 
10
22
  module Doxieland
11
- # Your code goes here...
23
+ class AuthenticationError < StandardError; end
24
+
25
+ def self.config_path
26
+ Pathname.new('~/.doxieland').expand_path
27
+ end
28
+
29
+ def self.config
30
+ if File.file?(config_path)
31
+ YAML.load( File.read(config_path) ).with_indifferent_access
32
+ else
33
+ HashWithIndifferentAccess.new
34
+ end
35
+ end
12
36
  end
@@ -0,0 +1,17 @@
1
+ module Doxieland
2
+ module Actions
3
+ class DeleteScans < ::Apidiesel::Action
4
+ url path: '/scans/delete.json'
5
+
6
+ http_method :post
7
+
8
+ expects do
9
+ object :paths, klass: Array
10
+ end
11
+
12
+ format_parameters do |params|
13
+ params[:paths]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Doxieland
2
+ module Actions
3
+ class GetInfo < ::Apidiesel::Action
4
+ http_method :get
5
+ url path: '/hello.json'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Doxieland
2
+ module Actions
3
+ class GetScan < ::Apidiesel::Action
4
+ use Apidiesel::Handlers::ActionResponseProcessor
5
+ use Handlers::FileRequest
6
+
7
+ url ->(base_url, request) {
8
+ base_url.path = request.parameters.delete(:path)
9
+
10
+ base_url
11
+ }
12
+
13
+ http_method :get
14
+
15
+ expects do
16
+ string :path
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,6 +3,22 @@ module Doxieland
3
3
  class ListScans < ::Apidiesel::Action
4
4
  http_method :get
5
5
  url path: '/scans.json'
6
+
7
+ responds_with do
8
+ array do
9
+ string :path,
10
+ at: :name,
11
+ filter: ->(s) { "/scans#{s}" }
12
+ string :delete_path,
13
+ at: :name
14
+ string :thumbnail_path,
15
+ at: :name,
16
+ filter: ->(s) { "/thumbnails#{s}" }
17
+ string :name,
18
+ filter: ->(s) { s.match(/[0-9]{4,}/)[0] }
19
+ integer :size
20
+ end
21
+ end
6
22
  end
7
23
  end
8
24
  end
data/lib/doxieland/api.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  module Doxieland
2
2
  class Api < Apidiesel::Api
3
- use Handlers::JSON
3
+ use Apidiesel::Handlers::JSON
4
4
 
5
5
  config :timeout, 4000
6
+ config :username, 'doxie'
7
+ config :password, nil
6
8
 
7
9
  register_actions
8
10
  end
@@ -0,0 +1,94 @@
1
+ module Doxieland
2
+ class Client
3
+ attr_reader :save_path
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @save_path = Pathname.new(options[:to] || '.').expand_path
8
+ end
9
+
10
+ def api(&block)
11
+ Doxieland::Api.url "http://#{scanner_ip}:8080"
12
+
13
+ if @options[:password]
14
+ Doxieland::Api.http_basic_auth 'doxie', @options[:password]
15
+ end
16
+
17
+ api = Doxieland::Api.new
18
+
19
+ begin
20
+ yield api
21
+ rescue AuthenticationError => e
22
+ log.fatal e.message
23
+ exit(false)
24
+ end
25
+ end
26
+
27
+ def create_save_path
28
+ raise ArgumentError, "#{@save_path} is a file" if @save_path.file?
29
+
30
+ unless @save_path.directory? || @save_path == Pathname.new('.')
31
+ FileUtils.mkdir_p(@save_path)
32
+ end
33
+ end
34
+
35
+ def loglevel
36
+ @options[:verbose] ? :debug : :info
37
+ end
38
+
39
+ def log
40
+ @logger ||= Logger.new(loglevel)
41
+ end
42
+
43
+ def scanner_ip
44
+ @scanner_ip ||= begin
45
+ if @options['scanner-ip']
46
+ @options['scanner-ip']
47
+ elsif @options[:ap]
48
+ '192.168.1.100'
49
+ else
50
+ discovered_ip = ssdp_discover
51
+
52
+ unless discovered_ip
53
+ log.fatal "… sorry, your scanner could not be found. Is WiFi turned on and the status light blue?"
54
+ log.info "If you know it, you can also provide the IP address manually via the --scanner-ip flag."
55
+ exit
56
+ end
57
+
58
+ discovered_ip
59
+ end
60
+ end
61
+ end
62
+
63
+ def ssdp_discover
64
+ socket = UDPSocket.new
65
+ socket.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
66
+ socket.setsockopt :IPPROTO_IP, :IP_MULTICAST_TTL, 1
67
+
68
+ query = [
69
+ 'M-SEARCH * HTTP/1.1',
70
+ 'HOST: 239.255.255.250:1900',
71
+ 'MAN: "ssdp:discover"',
72
+ 'ST: urn:schemas-getdoxie-com:device:Scanner:1',
73
+ # 'ST: ssdp:all',
74
+ 'MX: 3',
75
+ '',
76
+ ''
77
+ ].join("\r\n")
78
+
79
+ log.info "trying to find your scanner on the network. Here, Doxie Doxie…"
80
+
81
+ socket.send(query, 0, '239.255.255.250', 1900)
82
+
83
+ ready = IO.select([socket], nil, nil, 10)
84
+
85
+ if ready
86
+ _, message_sender = socket.recvfrom(65507)
87
+
88
+ log.success "found the little rascal hiding at #{message_sender.last}"
89
+
90
+ message_sender.last
91
+ end
92
+ end
93
+ end
94
+ end