fcs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fcs.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mark Quezada
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.
@@ -0,0 +1,90 @@
1
+ # FCS: FreeSWITCH Command Socket
2
+
3
+ FCS is a lightweight api wrapper for [FreeSWITCH's event socket][event_socket].
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'fcs'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install fcs
18
+
19
+ ## Usage
20
+
21
+ Using FCS is easy and intuitive. After initializing, dispatch commands to
22
+ FreeSWITCH using the `.dispatch!` method. See the examples below.
23
+
24
+ ### Initializing
25
+
26
+ fs = FCS.new(
27
+ remote_host: 'localhost',
28
+ remote_port: '8021'
29
+ )
30
+
31
+ ### Authorizing
32
+
33
+ fs.auth('ClueCon').dispatch!
34
+
35
+ ### Examples
36
+
37
+ FCS is basically a naive command builder for freeSWITCH. You build commands by
38
+ chaining method calls that match FreeSWITCH's [mod_commands][mod_commands] like
39
+ so:
40
+
41
+ request = fs.api.originate 'sofia/mydomain.com 1000'
42
+ response = request.dispatch!
43
+
44
+ This is equivalent:
45
+
46
+ response = fs.api.originate('sofia/mydomain.com', '1000').dispatch!
47
+
48
+ Both these commands result in this api command being sent to FreeSWITCH:
49
+
50
+ api originate sofia/mydomain.com 1000
51
+
52
+ These two commands are also equivalent:
53
+
54
+ fs.api.uuid_getvar('fs_id', 'varname').dispatch!
55
+ fs.api.uuid_getvar('fs_id').varname.dispatch!
56
+
57
+ This flexible syntax allows you to build commands in a very natural way that
58
+ mimics FreeSWITCH's command syntax.
59
+
60
+ A more complex example:
61
+
62
+ fs.sendmsg(fs_id).call_command(:execute).execute_app_name(:playback).execute_app_arg('/tmp/sound.wav').loops(-1).event_lock(true)
63
+
64
+ The `sendmsg` command takes an optional uuid parameter. Since `execute` is so
65
+ common, there's a helper method that shortens the above to:
66
+
67
+ fs.sendmsg(fs_id).execute(:playback, '/tmp/sound.wav').loops(-1).event_lock(true)
68
+
69
+ Pretty much any api method should work out of the box. A few more examples:
70
+
71
+ fs.api.sched_api('+20').originate('sofia/external &echo()').dispatch!
72
+ fs.api.uuid_send_dtmf(fs_id, 'W0W011W@250')
73
+ fs.api.uuid_kill(fs_id, 'NORMAL_CLEARING')
74
+ fs.api.sched_broadcast('+20', fs_id, 'commercial.wav').aleg
75
+ fs.bgapi.originate 'sofia/mydomain.com'
76
+
77
+ Calling `dispatch!` at the end of the chain is necessary in order to dispatch
78
+ the command to FreeSWITCH. Otherwise you'll get back a `Request` object.
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
87
+
88
+
89
+ [event_socket]: http://wiki.freeswitch.org/wiki/Event_Socket
90
+ [mod_commands]: http://wiki.freeswitch.org/wiki/Mod_commands
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/fcs/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mark Quezada"]
6
+ gem.email = ["mark@mirthlab.com"]
7
+ gem.description = %q{Library for interacting with FreeSWITCH via event socket.}
8
+ gem.summary = %q{Library for interacting with FreeSWITCH via event socket.}
9
+ gem.homepage = "https://github.com/mirthlab/fcs"
10
+
11
+ gem.files = `git ls-files`.split($\)
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 = "fcs"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = FCS::VERSION
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'fcs/version'
2
+ require 'fcs/config'
3
+ require 'fcs/client'
4
+
5
+ module FCS
6
+ extend Config
7
+ class << self
8
+ # Alias for FCS::Client.new
9
+ #
10
+ # @return [FCS::Client]
11
+ def new(options={})
12
+ FCS::Client.new(options)
13
+ end
14
+
15
+ # Delegate to FCS::Client
16
+ def method_missing(method, *args, &block)
17
+ return super unless new.respond_to?(method)
18
+ new.send(method, *args, &block)
19
+ end
20
+
21
+ # Include FCS::Client methods in respond_to?
22
+ def respond_to?(method, include_private=false)
23
+ new.respond_to?(method, include_private) || super(method, include_private)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ require 'socket'
2
+ require 'fcs/config'
3
+ require 'fcs/response'
4
+ require 'fcs/request'
5
+ require 'fcs/string_request'
6
+ require 'fcs/hash_request'
7
+ require 'fcs/sendmsg_request'
8
+
9
+ module FCS
10
+ class Client
11
+ attr_accessor *Config::VALID_OPTIONS_KEYS
12
+
13
+ # Initialize a new Client object
14
+ def initialize(attrs={})
15
+ attrs = FCS.options.merge(attrs)
16
+ Config::VALID_OPTIONS_KEYS.each do |key|
17
+ instance_variable_set("@#{key}".to_sym, attrs[key])
18
+ end
19
+
20
+ connect if @auto_connect
21
+ end
22
+
23
+ def connect
24
+ unless @socket
25
+ @socket = TCPSocket.open(@remote_host, @remote_port, @local_host, @local_port)
26
+ Response.new(@socket).parse_response
27
+ end
28
+ @socket
29
+ end
30
+
31
+ # Delgate api call to Request
32
+ def method_missing(method, *args, &block)
33
+ klass = class_for_api_command(method)
34
+
35
+ return klass.new(@socket).send(method, *args, &block) if klass
36
+ super(method, *args, &block)
37
+ end
38
+
39
+ private
40
+
41
+ def class_for_api_command(method)
42
+ if method == :sendmsg
43
+ SendmsgRequest
44
+ elsif Request::VALID_HASH_COMMANDS.include?(method)
45
+ HashRequest
46
+ elsif Request::VALID_STRING_COMMANDS.include?(method)
47
+ StringRequest
48
+ else
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ module FCS
2
+ module Config
3
+ # Remote host used if none is set
4
+ DEFAULT_REMOTE_HOST = 'http://127.0.0.1'
5
+
6
+ # Remote port used if none is set
7
+ DEFAULT_REMOTE_PORT = '8021'
8
+
9
+ # Local host used if none is set
10
+ DEFAULT_LOCAL_HOST = nil
11
+
12
+ # Local port used if none is set
13
+ DEFAULT_LOCAL_PORT = nil
14
+
15
+ # Auto connect value used if none is set
16
+ DEFAULT_AUTO_CONNECT = true
17
+
18
+ # An array of valid keys in the options hash when configuring a {FCS::Client}
19
+ VALID_OPTIONS_KEYS = [
20
+ :remote_host,
21
+ :remote_port,
22
+ :local_host,
23
+ :local_port,
24
+ :auto_connect
25
+ ]
26
+
27
+ attr_accessor *VALID_OPTIONS_KEYS
28
+
29
+ # When this module is extended, set all configuration options to their default value
30
+ def self.extended(base)
31
+ base.reset
32
+ end
33
+
34
+ # Convenience method to allow configuration options to be set in a block
35
+ def configure
36
+ yield self
37
+ self
38
+ end
39
+
40
+ # Create a hash of options and their values
41
+ def options
42
+ options = {}
43
+ VALID_OPTIONS_KEYS.each {|k| options[k] = send(k)}
44
+ options
45
+ end
46
+
47
+ # Reset all configuration options to defaults
48
+ def reset
49
+ self.remote_host = DEFAULT_REMOTE_HOST
50
+ self.remote_port = DEFAULT_REMOTE_PORT
51
+ self.local_host = DEFAULT_LOCAL_HOST
52
+ self.local_port = DEFAULT_LOCAL_PORT
53
+ self.auto_connect = DEFAULT_AUTO_CONNECT
54
+ self
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ require 'fcs/request'
2
+
3
+ module FCS
4
+ class HashRequest < Request
5
+ def initialize(socket)
6
+ super(socket)
7
+ @command_type = ''
8
+ @command_value = ''
9
+ @params = {}
10
+ end
11
+
12
+ def generate_command
13
+ # TODO if app args is longer than 2048 octet limit, use alternate format
14
+ cmd = "#{@command_type} #{@command_value}\n"
15
+ cmd << @params.map do |k,v|
16
+ "#{k.gsub(/_/, '-')}: #{v}"
17
+ end.join("\n") if not @params.empty?
18
+ end
19
+
20
+ def update_command(method, *args, &block)
21
+ arg_string = args.map(&:to_s).join(' ') || ''
22
+
23
+ # assume the first call here is the command type and value
24
+ if @command_type.empty?
25
+ @command_type = method.to_s
26
+ @command_value = arg_string
27
+ else
28
+ @params[method.to_s] = arg_string
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ require 'fcs/response'
2
+
3
+ module FCS
4
+ class Request
5
+ VALID_HASH_COMMANDS = [:sendmsg, :sendevent]
6
+ VALID_STRING_COMMANDS = [:api, :bgapi, :event, :myevents, :divert_events,
7
+ :filter, :exit, :auth, :log, :nolog, :nixevent,
8
+ :noevents]
9
+ VALID_COMMANDS = VALID_HASH_COMMANDS + VALID_STRING_COMMANDS
10
+
11
+ def initialize(socket, response_class=Response)
12
+ @socket = socket
13
+ @response_class = response_class
14
+ end
15
+
16
+ def generate_command
17
+ # defined in the subclasses
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def update_command(method, *args, &block)
22
+ # defined in the subclasses
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def dispatch!
27
+ dispatch_raw!("#{generate_command}\n\n")
28
+ end
29
+
30
+ def dispatch_raw!(command)
31
+ @socket.write(command)
32
+ @response_class.new(@socket).parse_and_respond
33
+ end
34
+
35
+ def method_missing(method, *args, &block)
36
+ if @response_class.method_defined?(method)
37
+ dispatch!.send(method, *args, &block)
38
+ else
39
+ update_command(method, *args, &block)
40
+ self
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,72 @@
1
+ module FCS
2
+ class Response
3
+ attr_reader :headers, :content
4
+
5
+ def initialize(socket)
6
+ @socket = socket
7
+ @headers = ''
8
+ @content = ''
9
+ @content_length = 0
10
+ end
11
+
12
+ def parse_and_respond
13
+ parse_response
14
+ self
15
+ end
16
+
17
+ def parse_response
18
+ capture_headers
19
+ capture_content
20
+ puts self.inspect
21
+ end
22
+
23
+ def ok?
24
+ raw_reply.start_with?('+OK') or (@content_length and not error?)
25
+ end
26
+
27
+ def error?
28
+ raw_reply.start_with?('-ERR')
29
+ end
30
+
31
+ def reply
32
+ /^(?:\+OK|\-ERR)\s?(.*)/.match(raw_reply) ? Regexp.last_match(1) : raw_reply
33
+ end
34
+
35
+ private
36
+
37
+ def raw_headers=(raw_headers)
38
+ @headers = headers_2_hash(raw_headers.split("\n"))
39
+ @headers.each {|k,v| v.chomp! if v.respond_to? :chomp!}
40
+ end
41
+
42
+ def raw_content=(raw_content)
43
+ @content = raw_content.chomp!
44
+ end
45
+
46
+ def raw_reply
47
+ @content_length > 0 ? @content : @headers[:reply_text]
48
+ end
49
+
50
+ def capture_headers
51
+ self.raw_headers = @socket.readline("\n\n")
52
+ @content_length = @headers[:content_length].to_i
53
+ end
54
+
55
+ def capture_content
56
+ self.raw_content = @socket.read(@content_length) if @content_length > 0
57
+ end
58
+
59
+ # Stolen from eventmachine:
60
+ # https://github.com/eventmachine/eventmachine/blob/master/lib/em/protocols/header_and_content.rb
61
+ def headers_2_hash hdrs
62
+ hash = {}
63
+ hdrs.each do |h|
64
+ if /\A([^\s:]+)\s*:\s*/ =~ h
65
+ tail = $'.dup
66
+ hash[ $1.downcase.gsub(/-/,"_").intern ] = tail
67
+ end
68
+ end
69
+ hash
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,10 @@
1
+ require 'fcs/hash_request'
2
+
3
+ module FCS
4
+ class SendmsgRequest < HashRequest
5
+ # convenience method
6
+ def execute(app, *args)
7
+ self.call_command(:execute).execute_app_name(app).execute_app_arg(args.map(&:to_s).join(' '))
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require 'fcs/request'
2
+
3
+ module FCS
4
+ class StringRequest < Request
5
+ def initialize(socket)
6
+ super(socket)
7
+ @commands = []
8
+ end
9
+
10
+ def generate_command
11
+ @commands.join(' ')
12
+ end
13
+
14
+ def update_command(method, *args, &block)
15
+ @commands << method.to_s
16
+ @commands = @commands + args.map(&:to_s)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module FCS
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fcs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Quezada
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Library for interacting with FreeSWITCH via event socket.
15
+ email:
16
+ - mark@mirthlab.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - fcs.gemspec
27
+ - lib/fcs.rb
28
+ - lib/fcs/client.rb
29
+ - lib/fcs/config.rb
30
+ - lib/fcs/hash_request.rb
31
+ - lib/fcs/request.rb
32
+ - lib/fcs/response.rb
33
+ - lib/fcs/sendmsg_request.rb
34
+ - lib/fcs/string_request.rb
35
+ - lib/fcs/version.rb
36
+ homepage: https://github.com/mirthlab/fcs
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.11
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Library for interacting with FreeSWITCH via event socket.
60
+ test_files: []