ruby-dovado 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 493334755f4182fb4c6ffbf010747a6212f48352
4
+ data.tar.gz: 67798f1509b7ad86e95dd321a92267a0fd8a80f4
5
+ SHA512:
6
+ metadata.gz: 9ffe73cdf1b1a4b89f85c006d7597ad0fb0410614fc542c3bce3dfb62a917b031194a955be6aa26f2c06fb63bbc78762732f3c6e160f9aa072b0bd0f71b80dfd
7
+ data.tar.gz: bbf0eed5cd3ee58b89cc52642eb1b7d6a7a35eaf9158d1587cf09f1e0b9bcfcd0f1239505963cd830e9c971b3de48fa2548c95ec559cfffbe0a3025be4c31d9b
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Jan Lindblom
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Ruby-Dovado
2
+
3
+ A Dovado Router API for Ruby.
4
+
5
+ [![Build Status](https://drone.io/bitbucket.org/janlindblom/ruby-dovado/status.png)](https://drone.io/bitbucket.org/janlindblom/ruby-dovado/latest)
6
+
7
+ This library serves to enable easy access to the built in, Telnet-based, rudimentary API of the [routers from Dovado](http://www.dovado.com/en/products) running software version 6 and 7 (applies to the original Tiny and Go routers, among others). It might work with software version 8 routers (the Tiny AC) too but I have no means to test against that since I don't have one of the later routers.
8
+
9
+ ## Purpose
10
+
11
+ The original purpose of this library was to enable addition of router information about connection state, mobile data connection quality and data quota usage on a wall-mounted TV or a small touch screen connected to a Raspberry Pi, accessing a dashboard implemented using [Dashing](https://shopify.github.io/dashing/).
12
+
13
+ ## Usage
14
+
15
+ Add it to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "ruby-dovado"
19
+ ```
20
+
21
+ They load it in your code:
22
+
23
+ ```ruby
24
+ require "dovado"
25
+
26
+ router = Dovado::Router.new(address: "192.168.0.1", user: "admin", password: "password")
27
+ router.info
28
+ router.sms.load_messages
29
+ message = router.sms.get_message 12
30
+ ```
31
+
32
+ ## Design Considerations
33
+
34
+ Since the API published by these routers is Telnet-based, it stands to reason to limit simultaneous connections. This is achieved by a single client object implemented as a Celluloid Actor. The reason for this is because Celluloid Actor objects can be supervised, block threads and be accessed from multiple threads simultaneously without the need to implement any special locking or waiting mechanisms.
35
+
36
+ Additionally, all replies are cached internally to limit the number of calls to the router API. This is done because the API is rather slow which would make any calls using this library wait for several seconds for a reply. These replies are cached inside Celluloid actors so that multiple, seemingly parallel requests will get the same response object. Caching can be overridden by forcing the call through to the router. Otherwise, the reply becomes invalid within a couple of seconds so that the next call will go through to the router and fetch any updates. Think of it as a cheap rate-limiter.
37
+
38
+ ## Copyright
39
+
40
+ Ruby-Dovado © 2015 by [Jan Lindblom](mailto:janlindblom@fastmail.fm).
41
+ Ruby-Dovado is licensed under the MIT license. Please see the
42
+ {file:LICENSE.txt} file for more information.
43
+
44
+ The Dovado, Dovado Tiny, Dovado Go, Dovado Tiny AC et.al brands are © 2004 - 2015 Dovado FZ-LLC.
45
+
46
+ This library is neither endorsed nor supported by Dovado.
47
+
48
+ ## Development
49
+
50
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
51
+
52
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( https://bitbucket.org/janlindblom/ruby-dovado/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+ require "yard"
4
+ require "yard/rake/yardoc_task"
5
+ require "rspec/core/rake_task"
6
+
7
+ desc "Run RSpec code examples"
8
+ namespace :spec do
9
+ desc "Run offline RSpec code examples"
10
+ RSpec::Core::RakeTask.new(:offline) do |t|
11
+ t.rspec_opts = "--tag offline"
12
+ end
13
+
14
+ desc "Run online RSpec code examples"
15
+ RSpec::Core::RakeTask.new(:online) do |t|
16
+ t.rspec_opts = "--tag online"
17
+ end
18
+
19
+ desc "Run all RSpec code examples"
20
+ RSpec::Core::RakeTask.new(:all) do |t|
21
+ t.rspec_opts = "--tag offline --tag online"
22
+ end
23
+ end
24
+
25
+ YARD::Rake::YardocTask.new do |t|
26
+ t.files = ['lib/**/*.rb']
27
+ t.stats_options = ['--list-undoc']
28
+ end
29
+
30
+ task :default => "spec:offline"
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/dovado.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'celluloid/current'
2
+
3
+ require 'dovado/connection_error'
4
+ require 'dovado/utilities'
5
+ require 'dovado/client'
6
+
7
+ require 'dovado/router'
8
+ require 'dovado/router/services'
9
+ require 'dovado/router/info'
10
+ require 'dovado/router/info/operator'
11
+ require 'dovado/router/info/operator/telia'
12
+
13
+ require 'dovado/router/sms'
14
+ require 'dovado/router/sms/messages'
15
+ require 'dovado/router/sms/message'
16
+
17
+ # The Ruby-Dovado library.
18
+ #
19
+ # @author Jan Lindblom <janlindblom@fastmail.fm>
20
+ # @version 1.0.0
21
+ module Dovado
22
+ end
@@ -0,0 +1,154 @@
1
+ require 'net/telnet'
2
+
3
+ module Dovado
4
+ # Internal API client.
5
+ #
6
+ # @api private
7
+ # @since 1.0.0
8
+ class Client
9
+ include Celluloid
10
+
11
+ # Create a new {Client} object.
12
+ #
13
+ # The default options are:
14
+ # - Address: 192.168.0.1
15
+ # - Port: 6435
16
+ # - User: admin
17
+ # - Password: password
18
+ #
19
+ # @param [Hash] args option arguments.
20
+ # @option args [String] :server The server (router) address.
21
+ # @option args [Integer] :port The server (router) port.
22
+ # @option args [String] :user The user name.
23
+ # @option args [String] :password The user password.
24
+ def initialize(args=nil)
25
+ # Defaults
26
+ @address = '192.168.0.1'
27
+ @user = 'admin'
28
+ @password = 'password'
29
+ @port = 6435
30
+ unless args.nil?
31
+ @address = args[:server] if args.has_key? :server
32
+ @port = args[:port] if args.has_key? :port
33
+ @user = args[:user] if args.has_key? :user
34
+ @password = args[:password] if args.has_key? :password
35
+ end
36
+ end
37
+
38
+ # Run a command on the router.
39
+ #
40
+ # @param [String] text the command to run.
41
+ # @raise [ConnectionError] if there is an error in the communication with
42
+ # the router.
43
+ def command(text=nil)
44
+ perform_command text
45
+ rescue IOError
46
+ disconnect
47
+ connect unless connected?
48
+ authenticate unless authenticated?
49
+ perform_command text
50
+ rescue Net::ReadTimeout => ex
51
+ disconnect
52
+ connect unless connected?
53
+ authenticate unless authenticated?
54
+ perform_command text
55
+ #raise ConnectionError.new "Error connecting to router: #{ex.message}"
56
+ end
57
+
58
+ # Connect to the router.
59
+ # @raise [ConnectionError] if there is an error in the communication with
60
+ # the router.
61
+ def connect
62
+ if @server.nil?
63
+ @server = Net::Telnet.new(
64
+ 'Host' => @address,
65
+ 'Port' => @port,
66
+ 'Telnetmode' => false,
67
+ 'Prompt' => />>\s/)
68
+ end
69
+ rescue IOError
70
+ disconnect
71
+ raise ConnectionError.new "Error connecting to router: #{ex.message}"
72
+ rescue Net::ReadTimeout => ex
73
+ disconnect
74
+ raise ConnectionError.new "Error connecting to router: #{ex.message}"
75
+ end
76
+
77
+ # Disconnect from the router.
78
+ def disconnect
79
+ unless @server.nil?
80
+ @server.cmd "quit"
81
+ @server.close
82
+ end
83
+ @authenticated = false
84
+ @server = nil
85
+ end
86
+
87
+ # Check if we are connected to the router.
88
+ #
89
+ # @return [Boolean] +true+ or +false+.
90
+ def connected?
91
+ unless @server.nil?
92
+ true
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ # Authenticate user.
99
+ #
100
+ # @todo Verify authentication properly.
101
+ # @raise [ConnectionError] if there is an error in the communication with
102
+ # the router.
103
+ def authenticate
104
+ perform_authentication
105
+ rescue IOError
106
+ disconnect
107
+ connect unless connected?
108
+ perform_authentication
109
+ rescue Net::ReadTimeout => ex
110
+ disconnect
111
+ connect unless connected?
112
+ perform_authentication
113
+ #raise ConnectionError.new "Error connecting to router: #{ex.message}"
114
+ end
115
+
116
+ # Check if we're authenticated.
117
+ #
118
+ # @return [Boolean] +true+ or +false+.
119
+ def authenticated?
120
+ @authenticated
121
+ end
122
+
123
+ private
124
+
125
+ def perform_command(text)
126
+ unless text.nil?
127
+ res = @server.puts(text)
128
+ res = @server.waitfor(/>>\s/)
129
+ res
130
+ end
131
+ end
132
+
133
+ def perform_authentication
134
+ if connected?
135
+ unless authenticated?
136
+ raise ArgumentError.new "Username cannot be nil" if @user.nil?
137
+ raise ArgumentError.new "Password cannot be nil" if @password.nil?
138
+
139
+ @server.cmd "user #{@user}"
140
+ @server.waitfor />>\s/
141
+ @server.cmd "pass #{@password}"
142
+
143
+ # TODO: verify authentication for real.
144
+ @authenticated = true
145
+ else
146
+ @authenticated = false
147
+ end
148
+ else
149
+ @authenticated = false
150
+ end
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,5 @@
1
+ # Custom error for failed connections.
2
+ #
3
+ # @since 1.0.0
4
+ class Dovado::ConnectionError < StandardError
5
+ end
@@ -0,0 +1,96 @@
1
+ module Dovado
2
+ # A Dovado Router.
3
+ #
4
+ # @since 1.0.0
5
+ class Router
6
+ include Celluloid
7
+
8
+ # Create a new {Router} object representing an actual Dovado router on the local
9
+ # network.
10
+ #
11
+ # The default router options are:
12
+ # - Address: 192.168.0.1
13
+ # - Port: 6435
14
+ # - User: admin
15
+ # - Password: password
16
+ #
17
+ # @param [Hash] args optional arguments.
18
+ # @option args [String] :address IP address or DNS name
19
+ # @option args [Integer] :port Port which the router is listening on
20
+ # @option args [String] :user User name
21
+ # @option args [String] :password Password
22
+ def initialize(args=nil)
23
+ @address = '192.168.0.1' # Default address
24
+ @port = 6435
25
+ user = "admin" # Default username
26
+ password = "password" # Default password
27
+ @connected = false
28
+ unless args.nil?
29
+ @address = args[:address] if args.has_key? :address and !args[:address].nil?
30
+ @port = args[:port] if args.has_key? :port and !args[:port].nil?
31
+ user = args[:user] if args.has_key? :user and !args[:user].nil?
32
+ password = args[:password] if args.has_key? :password and !args[:password].nil?
33
+ end
34
+
35
+ Client.supervise as: :client, size: 1, args: [{
36
+ server: @address,
37
+ port: @port,
38
+ user: user,
39
+ password: password
40
+ }]
41
+ end
42
+
43
+ # Fetch services information from the router.
44
+ #
45
+ # @return [Services] The {Services} object
46
+ # @see {Services}
47
+ def services
48
+ Services.supervise as: :router_services, size: 1 unless Actor[:router_services]
49
+ client = Actor[:client]
50
+ router_services = Actor[:router_services]
51
+
52
+ unless router_services.valid?
53
+ client.connect unless client.connected?
54
+ client.authenticate unless client.authenticated?
55
+ string = client.command('services')
56
+ router_services.create_from_string string
57
+ end
58
+
59
+ if router_services[:sms] == 'enabled'
60
+
61
+ Sms.supervise as: :sms, size: 1 unless Actor[:sms]
62
+ sms.enabled = true
63
+ end
64
+ router_services
65
+ end
66
+
67
+ # Fetch information from the router.
68
+ #
69
+ # @return [Info] The {Info} object.
70
+ # @see {Info}
71
+ def info
72
+ Info.supervise as: :router_info, size: 1 unless Actor[:router_info]
73
+ router_info = Actor[:router_info]
74
+ client = Actor[:client]
75
+ router_info = Actor[:router_info]
76
+ unless router_info.valid?
77
+ client.connect unless client.connected?
78
+ client.authenticate unless client.authenticated?
79
+ info = client.command('info')
80
+ router_info.create_from_string info
81
+ end
82
+ services
83
+ router_info
84
+ end
85
+
86
+ # Fetch text messages from the router.
87
+ #
88
+ # @return [Sms] The {Sms} object.
89
+ # @see {Sms}
90
+ def sms
91
+ Sms.supervise as: :sms, size: 1 unless Actor[:sms]
92
+ Actor[:sms]
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,98 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'socket'
4
+ require 'thread_safe'
5
+
6
+ module Dovado
7
+ class Router
8
+ # Router information.
9
+ #
10
+ # @since 1.0.0
11
+ class Info
12
+ include Celluloid
13
+
14
+ # Create a new {Info} object.
15
+ #
16
+ # @param [Hash] args optional hash to initialize with.
17
+ def initialize(args=nil)
18
+ # Defaults
19
+ @data = ThreadSafe::Cache.new
20
+
21
+ @last_update = nil
22
+ unless args.nil?
23
+ @data = args
24
+ end
25
+ end
26
+
27
+ # Create a new {Info} object from a +String+.
28
+ #
29
+ # @param [String] data_string router information string from the router.
30
+ def create_from_string(data_string=nil)
31
+ unless Actor[:sms]
32
+ Sms.supervise as: :sms, size: 1
33
+ end
34
+ sms = Actor[:sms]
35
+ data_array = data_string.split("\n")
36
+ data_array.each do |data_entry|
37
+ entry_array = data_entry.split('=')
38
+ if entry_array.length == 2
39
+ key = entry_array[0].downcase
40
+ val = entry_array[1]
41
+ keysym = Utilities.name_to_sym(key)
42
+ case key
43
+ when 'traffic_modem_tx'
44
+ @data[:traffic_modem_tx] = val.strip.to_i
45
+ when 'traffic_modem_rx'
46
+ @data[:traffic_modem_rx] = val.strip.to_i
47
+ when 'time'
48
+ @data[:time] = Time.parse(val)
49
+ when 'date'
50
+ @data[:date] = Date.parse(val)
51
+ when 'sms_unread'
52
+ @data[:sms] = sms if @data[:sms].nil?
53
+ @data[:sms].unread = val.to_i
54
+ when 'sms_total'
55
+ @data[:sms] = sms if @data[:sms].nil?
56
+ @data[:sms].total = val.to_i
57
+ when 'connected_devices'
58
+ val = val.split(',')
59
+ @data[keysym] = val
60
+ else
61
+ @data[keysym] = val
62
+ end
63
+ end
64
+ end
65
+ touch!
66
+ end
67
+
68
+ # Determine if this info object is valid.
69
+ #
70
+ # @return [Boolean] true or false.
71
+ def valid?
72
+ return false if @last_update.nil?
73
+ (@last_update + SecureRandom.random_number(9) + 1 <= Time.now.to_i)
74
+ end
75
+
76
+ # Fetch an entry from the {Info} object.
77
+ #
78
+ # @param [Symbol] key The key to fetch.
79
+ def [](key)
80
+ @data[key]
81
+ end
82
+
83
+ # Fetch the list of entries in the {Info} object.
84
+ #
85
+ # @return [Array<Symbol>]
86
+ def keys
87
+ @data.keys
88
+ end
89
+
90
+ private
91
+
92
+ def touch!
93
+ @last_update = Time.now.to_i
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,82 @@
1
+ module Dovado
2
+ class Router
3
+ class Info
4
+ # An ISP/operator.
5
+ #
6
+ # Extend this class to create a new operator or use it as it is.
7
+ #
8
+ # @example Extending the Operator class
9
+ # class MyOperator < Dovado::Router::Info::Operator
10
+ # def initialize
11
+ # super(name: "MyOperator", number: "s1234", commands: {data_remaining: "quota"})
12
+ # end
13
+ # end
14
+ #
15
+ # @example Using the Operator class as it is
16
+ # my_operator = Dovado::Router::Info::Operator.new(name: "MyOperator", number: "s1234", commands: {data_remaining: "quota"})
17
+ #
18
+ # @since 1.0.0
19
+ class Operator
20
+ include Celluloid
21
+
22
+ # Number to send messages to for the operator.
23
+ # @return [String]
24
+ attr_accessor :number
25
+ # Name of the operator.
26
+ # @return [String]
27
+ attr_accessor :name
28
+ # List of commands supported by the operator.
29
+ # @return [Hash]
30
+ attr_accessor :commands
31
+
32
+ # Create a new Operator object.
33
+ #
34
+ # @example Initializing with custom commands
35
+ # my_commands = { data_remaining: "datamängd".encode("UTF-8") }
36
+ # my_operator = Operator.new(name: "MyOperator", number: "s1234", commands: my_commands)
37
+ #
38
+ # @param [Hash] args optional arguments
39
+ # @option args [String] :number The recipient number for this operator.
40
+ # Use the prefix +s+ to indicate a "short" number, e.g "s4466".
41
+ # @option args [String] :name Name of the operator.
42
+ # @option args [Hash] :commands Supported commands.
43
+ def initialize(args=nil)
44
+ self.name = "Unknown"
45
+ unless args.nil?
46
+ @number = ""
47
+ @name = "NoOperator"
48
+ @commands = Operator.default_commands
49
+ @number = args[:number] unless args[:number].nil?
50
+ @name = args[:name] unless args[:name].nil?
51
+ unless args[:commands].nil?
52
+ missing_keys = []
53
+ Operator.required_commands.each do |req|
54
+ missing_keys << req unless args[:commands].has_key?(req)
55
+ end
56
+ raise ArgumentError.new "Missing required keys in hash: #{Utilities.array_to_sentence(missing_keys)}" unless missing_keys.empty?
57
+ @commands = args[:commands]
58
+ end
59
+ end
60
+ end
61
+
62
+ # Default commands for an operator.
63
+ def self.default_commands
64
+ commands = {}
65
+ required_commands.each do |command|
66
+ commands[command] = ""
67
+ end
68
+ commands
69
+ end
70
+
71
+ private
72
+
73
+ def self.required_commands
74
+ [
75
+ :data_remaining
76
+ ]
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ require "thread_safe"
2
+
3
+ module Dovado
4
+ class Router
5
+ class Info
6
+ class Operator
7
+ # The Swedish operator Telia.
8
+ #
9
+ # @since 1.0.0
10
+ class Telia < Operator
11
+
12
+ # Create a new Telia Operator object.
13
+ def initialize
14
+ super(name: "Telia", number: "s4466", commands: {
15
+ data_remaining: 'datamängd'.encode('UTF-8')
16
+ })
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ require "time"
2
+ require "thread_safe"
3
+
4
+ module Dovado
5
+ class Router
6
+ # Router Services.
7
+ #
8
+ # @since 1.0.0
9
+ class Services
10
+ include Celluloid
11
+
12
+ # Create a new {Services} object.
13
+ #
14
+ # @param [Hash] args optional argiments
15
+ def initialize(args=nil)
16
+ @list = ThreadSafe::Cache.new
17
+ @last_update = nil
18
+ unless args.nil?
19
+ args.each do |k,v|
20
+ @list[Utilities.name_to_sym(k)] = v
21
+ end
22
+ touch!
23
+ end
24
+ end
25
+
26
+ # Create a new {Services} object from a string with values from the router
27
+ # API.
28
+ #
29
+ # @param [String] data_string +String+ with data from fetched from the
30
+ # router.
31
+ # @return [Services] a new {Services} object.
32
+ def create_from_string(data_string=nil)
33
+ data_array = data_string.split("\n")
34
+ data_array.each do |data_entry|
35
+ entry_array = data_entry.split('=')
36
+ if entry_array.length == 2
37
+ key = entry_array[0].downcase
38
+ val = entry_array[1]
39
+ keysym = Utilities.name_to_sym(key)
40
+ @list[keysym] = val
41
+ end
42
+ end
43
+ touch!
44
+ end
45
+
46
+ # Fetch an entry from the {Services} object.
47
+ #
48
+ # @param [Symbol] key The key to fetch.
49
+ def [](key)
50
+ @list[key]
51
+ end
52
+
53
+ # Fetch the list of entries in the {Services} object.
54
+ #
55
+ # @return [Array<Symbol>]
56
+ def keys
57
+ @list.keys
58
+ end
59
+
60
+ # Checks if this {Services} object is still valid.
61
+ #
62
+ # @return [Boolean] +true+ or +false+.
63
+ def valid?
64
+ return false if @last_update.nil?
65
+ (@last_update + SecureRandom.random_number(9) + 1 <= Time.now.to_i)
66
+ end
67
+
68
+ private
69
+
70
+ def touch!
71
+ @last_update = Time.now.to_i
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,97 @@
1
+ module Dovado
2
+ class Router
3
+ # Text messages.
4
+ #
5
+ # @since 1.0.0
6
+ class Sms
7
+ include Celluloid
8
+
9
+ # Unread messages.
10
+ # @return [Integer] number of unread
11
+ attr_accessor :unread
12
+ # Total number of messages.
13
+ # @return [Integer] total number of messages
14
+ attr_accessor :total
15
+ # Is the SMS handler enabled?
16
+ # @return [Boolean] +true+ or +false+
17
+ attr_accessor :enabled
18
+ # Message Id's.
19
+ # @return [Array] list of message Id's
20
+ attr_accessor :ids
21
+
22
+ # Create a new {Sms} object.
23
+ #
24
+ # @param [Hash] args optional arguments.
25
+ # @option args [Integer] :unread number of unread messages
26
+ # @option args [Integer] :total total number of messages
27
+ def initialize(args=nil)
28
+ Messages.supervise as: :messages, size: 1
29
+ messages = Actor[:messages]
30
+ @enabled = false
31
+ @ids = ThreadSafe::Array.new
32
+ unless args.nil?
33
+ @unread = args[:unread] unless args[:unread].nil?
34
+ @total = args[:total] unless args[:total].nil?
35
+ end
36
+ client = Actor[:client]
37
+ create_from_string(client.command('sms list'))
38
+ end
39
+
40
+ # Text messages.
41
+ #
42
+ # @return [Sms::Messages]
43
+ def messages
44
+ Actor[:messages]
45
+ end
46
+
47
+ # Number of read messages.
48
+ #
49
+ # @return [Integer] the number of read messages.
50
+ def read
51
+ (@total - @unread)
52
+ end
53
+
54
+ # Assign number of read messages.
55
+ #
56
+ # @param [Integer] read Number of read messages.
57
+ def read=(read=nil)
58
+ @unread = (@total - read) unless read.nil?
59
+ end
60
+
61
+ # Create a new {Sms} object from a +String+ with data fetched from the
62
+ # router.
63
+ #
64
+ # @param [String] data_string String with text message data from the
65
+ # router.
66
+ def create_from_string(data_string=nil)
67
+ data_array = data_string.split("\n")
68
+ data_array.each do |data_entry|
69
+ entry_array = data_entry.split(':')
70
+ if entry_array.length == 2
71
+ key = entry_array[0].downcase
72
+ val = entry_array[1]
73
+ if key.downcase.tr(' ', '_') == 'stored_ids'
74
+ idlist = val.split(' ')
75
+ idlist.each do |id|
76
+ @ids << id
77
+ end
78
+ end
79
+ end
80
+ end
81
+ @ids.map! { |id| id.to_i }.sort!
82
+ end
83
+
84
+ # Load text messages.
85
+ def load_messages
86
+ client = Actor[:client]
87
+ messages = Actor[:messages]
88
+ client.connect unless client.connected?
89
+ client.authenticate unless client.authenticated?
90
+ @ids.each do |id|
91
+ messages.add_message Message.from_string(client.command("sms recvtxt #{id}"))
92
+ end
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,92 @@
1
+ require 'date'
2
+
3
+ module Dovado
4
+ class Router
5
+ class Sms
6
+ # A text message (SMS).
7
+ #
8
+ # @since 1.0.0
9
+ class Message
10
+ include Celluloid
11
+
12
+ # Message Id.
13
+ # @return [String,Integer,Symbol]
14
+ attr_reader :id
15
+ # Message body.
16
+ # @return [String]
17
+ attr_reader :body
18
+ # Message PDU's.
19
+ # @return [Array]
20
+ attr_reader :pdus
21
+ # Message sender.
22
+ # @return [String]
23
+ attr_reader :from
24
+ # Message send timestamp.
25
+ # @return [DateTime]
26
+ attr_reader :sent
27
+ # Message text encoding.
28
+ # @return [Encoding]
29
+ attr_reader :encoding
30
+
31
+ # Create a new {Message} object.
32
+ #
33
+ # @param [Hash] args arguments.
34
+ # @option args [Integer,String,Symbol] :id Message Id.
35
+ # @option args [String] :body Message body.
36
+ # @option args [Array] :pdus Message PDU's.
37
+ # @option args [String] :from Message sender.
38
+ # @option args [DateTime] :sent Message send timestamp.
39
+ # @option args [Encoding] :encoding Message text encoding.
40
+ def initialize(args=nil)
41
+ unless args.nil?
42
+ @id = args[:id] unless args[:id].nil?
43
+ @body = args[:body] unless args[:body].nil?
44
+ @pdus = args[:pdus] unless args[:pdus].nil?
45
+ @from = args[:from] unless args[:from].nil?
46
+ @sent = args[:sent] unless args[:sent].nil?
47
+ @encoding = args[:encoding] unless args[:encoding].nil?
48
+ end
49
+ end
50
+
51
+ # Create a new {Message} object from a +String+.
52
+ #
53
+ # @param [String] string message data to create object from.
54
+ # @return [Message] a new {Message} object.
55
+ def self.from_string(string=nil)
56
+ hash = ThreadSafe::Cache.new
57
+ message_body = ""
58
+ array = string.split("\n")
59
+ array.each do |row|
60
+ row_array = row.split(':')
61
+ if row_array.length == 2
62
+ key = row_array[0].downcase
63
+ val = row_array[1]
64
+ case key.strip
65
+ when 'from'
66
+ hash[:from] = val.strip
67
+ when 'alphabet'
68
+ begin
69
+ hash[:encoding] = Encoding.find(val.strip)
70
+ rescue ArgumentError
71
+ hash[:encoding] = Encoding::UTF_8
72
+ end
73
+ when 'id'
74
+ hash[:id] = val.strip.to_i
75
+ end
76
+ elsif row_array.length > 2
77
+ sent = row.match(/[Ss]ent\:\W(.*)/)
78
+ hash[:sent] = DateTime.parse(sent[1].strip)
79
+ else
80
+ unless row.downcase =~ /end of sms/
81
+ message_body += "#{row}\n"
82
+ end
83
+ end
84
+ end
85
+ hash[:body] = message_body.tr(">>", "").tr("\x17", "").strip.force_encoding(hash[:encoding])
86
+
87
+ Message.new(hash)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,39 @@
1
+ require 'thread_safe'
2
+
3
+ module Dovado
4
+ class Router
5
+ class Sms
6
+ # Text messages.
7
+ #
8
+ # @since 1.0.0
9
+ class Messages
10
+ include Celluloid
11
+
12
+ # Create a new {Messages} object.
13
+ def initialize
14
+ @messages = ThreadSafe::Cache.new
15
+ end
16
+
17
+ # Add a message to the local cache.
18
+ # @param [Message] message
19
+ def add_message(message)
20
+ @messages[message.id] = message unless message.nil?
21
+ end
22
+
23
+ # Fetch a {Message} from the cache.
24
+ #
25
+ # @param [String,Integer,Symbol] id Id of the message.
26
+ # @return [Message] message object.
27
+ # @see {Message}
28
+ def get_message(id)
29
+ @messages[id] unless id.nil?
30
+ end
31
+
32
+ def ids
33
+ @messages.keys
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Dovado
2
+ # Library utilities.
3
+ #
4
+ # @api private
5
+ # @since 1.0.0
6
+ class Utilities
7
+ include Celluloid
8
+
9
+ # Convert a key name to symbol.
10
+ #
11
+ # @param name [String] the key name to convert.
12
+ # @return [Symbol] the key name converted to a symbol.
13
+ def self.name_to_sym(name=nil)
14
+ name.downcase.tr(' ', '_').to_sym
15
+ end
16
+
17
+ # Build a sentence from an array.
18
+ #
19
+ # Ported from ActiveSupport:
20
+ # - File activesupport/lib/active_support/core_ext/array/conversions.rb, line 59
21
+ #
22
+ # @param [Array] ary the +Array+ to make a sentence of.
23
+ # @param [Hash] options optional settings.
24
+ # @option options [String] :words_connector
25
+ # @option options [String] :two_words_connector
26
+ # @option options [String] :last_word_connector
27
+ def self.array_to_sentence(ary, options = {:words_connector => ', ', :two_words_connector => ' and ', :last_word_connector => ' and '})
28
+ case ary.length
29
+ when 0
30
+ ''
31
+ when 1
32
+ ary[0].to_s.dup
33
+ when 2
34
+ "#{ary[0]}#{options[:two_words_connector]}#{ary[1]}"
35
+ else
36
+ "#{ary[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{ary[-1]}"
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,4 @@
1
+ module Dovado
2
+ # Current version of the library.
3
+ VERSION = '1.0.0'
4
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dovado/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby-dovado"
8
+ spec.version = Dovado::VERSION
9
+ spec.authors = ["Jan Lindblom"]
10
+ spec.email = ["janlindblom@fastmail.fm"]
11
+
12
+ spec.summary = %q{Dovado Router API for Ruby.}
13
+ spec.homepage = "https://bitbucket.org/janlindblom/ruby-dovado"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "celluloid", "~> 0.17"
22
+ spec.add_runtime_dependency "thread_safe", "~> 0.3"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "pry", "~> 0.10"
26
+ spec.add_development_dependency "yard", "~> 0.8"
27
+ spec.add_development_dependency "rspec", "~> 3.3"
28
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-dovado
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Lindblom
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: celluloid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thread_safe
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.3'
111
+ description:
112
+ email:
113
+ - janlindblom@fastmail.fm
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".document"
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".ruby-version"
122
+ - ".travis.yml"
123
+ - CODE_OF_CONDUCT.md
124
+ - Gemfile
125
+ - LICENSE.txt
126
+ - README.md
127
+ - Rakefile
128
+ - VERSION
129
+ - lib/dovado.rb
130
+ - lib/dovado/client.rb
131
+ - lib/dovado/connection_error.rb
132
+ - lib/dovado/router.rb
133
+ - lib/dovado/router/info.rb
134
+ - lib/dovado/router/info/operator.rb
135
+ - lib/dovado/router/info/operator/telia.rb
136
+ - lib/dovado/router/services.rb
137
+ - lib/dovado/router/sms.rb
138
+ - lib/dovado/router/sms/message.rb
139
+ - lib/dovado/router/sms/messages.rb
140
+ - lib/dovado/utilities.rb
141
+ - lib/dovado/version.rb
142
+ - ruby-dovado.gemspec
143
+ homepage: https://bitbucket.org/janlindblom/ruby-dovado
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.4.5
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Dovado Router API for Ruby.
167
+ test_files: []
168
+ has_rdoc: