doxieland 0.1.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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