gxg-framework/lib/gxg/net_clients.rb
2021-11-16 08:41:53 -08:00

4797 lines
169 KiB
Ruby

require 'net/http'
require 'net/ftp'
require 'net/ssh'
require 'net/scp'
require 'net/sftp'
require 'xmlrpc/client'
require 'handsoap'
require 'rest-client'
require 'net/pop'
require 'net/smtp'
require 'net/imap'
require 'gmail_xoauth'
require 'mail'
require 'matrix_sdk'
# GxG:
module GxG
#
module Networking
#
class ClientDispatcher
def initialize()
@thread_safety = ::Mutex.new
@client_database = {}
end
#
def new_client(the_url=nil, options={})
result = nil
if the_url.is_any?(::String, ::Symbol, ::URI::Generic)
if the_url.is_any?(::String, ::Symbol)
the_url = ::URI.parse(the_url.to_s)
else
the_url = the_url.clone
end
client_scheme = the_url.scheme.to_s
client_class = @thread_safety.synchronize {
if @client_database[(client_scheme.to_sym)].is_a?(::Hash)
@client_database[(client_scheme.to_sym)][:client]
else
nil
end
}
if client_class
if the_url.scheme.to_s.include?("-")
the_url.scheme = the_url.scheme.to_s.split("-")[1]
end
if client_class.respond_to?(:connect)
result = client_class::connect(the_url, options)
else
result = client_class.new(the_url, options)
end
else
log_warn("Unable to find a client for protocol: #{client_scheme}")
end
else
log_warn("Unable to find a client for protocol: #{the_url.inspect}")
end
result
end
#
def register_client(the_scheme=nil, client_class=nil)
result = false
if the_scheme.is_any?(::Symbol, ::String) && client_class.is_a?(::Class)
if the_scheme.to_s.include?("-")
protocol = the_scheme.to_s.split("-")[0]
service = the_scheme.to_s.split("-")[1]
else
protocol = the_scheme.to_s
service = protocol
end
#
::GxG::SYSTEM.service_ports_register_client(service, the_scheme.to_s)
@thread_safety.synchronize {
@client_database[(the_scheme.to_s.to_sym)] = {:client => client_class}
}
#
result = true
end
result
end
#
def unregister_client()
end
end
DISPATCHER = ::GxG::Networking::ClientDispatcher.new
# To Generate OAuth2 Tokens, See: https://github.com/google/gmail-oauth2-tools
# IMAP Classes:
class ImapClient
# ???
def connector()
@client
end
#
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
@user = the_url.user
@password = the_url.password
@client = nil
@host = the_url.hostname
if options[:use_ssl] == true || the_url.scheme.to_s == "imaps"
@port = (the_url.port || 993)
else
@port = (the_url.port || 143)
end
#
@capability = []
#
@mounted = nil
@mount_mode = :read
#
if options[:use_ssl] == true
if options[:ignore_ssl_errors] == true
@client = Net::IMAP.new(@host, @port, true, nil, false)
else
@client = Net::IMAP.new(@host, @port, true)
end
else
@client = Net::IMAP.new(@host, @port)
end
@capability = @client.capability
#
self
end
#
def inspect()
"<ImapClient: User: #{@user.inspect} Host: #{@host.inspect} Connected: #{! @client.disconnected?}>"
end
#
def capability()
@capability.clone
end
#
def login(user_id=nil, password=nil, options={:method => :auth})
result = false
if user_id.is_a?(::Hash)
options = user_id
user_id = nil
password = nil
end
begin
if @client
unless @client.disconnected?
unless user_id
user_id = @user
unless user_id
raise ArgumentError, "You MUST provide a USER id, like this: <user_id>,<password>,(<options>)"
end
end
unless password
password = @password
unless password
raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: <user_id>,<password/oauth-token>,(<options>)"
end
end
case options[:method]
when :auth
@client.login(user_id, password)
when :login
@client.authenticate("LOGIN", user_id, password)
when :cram_md5
@client.authenticate("CRAM-MD5", user_id, password)
when :oauth2
@client.authenticate("XOAUTH2", user_id, password)
else
raise ArgumentError, "Authentication method #{options[:method].inspect} not supported. Choose one of these: [:auth, :login, :cram_md5, :oauth2]"
end
#
result = true
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:user => user_id, :options => options}})
end
result
end
#
def logout()
result = false
if @client
begin
@client.logout
@client.disconnect
result = @client.disconnected?
@client = nil
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
end
result
end
#
def mounted?
result = false
if @client
if @mounted
result = true
end
end
result
end
#
def mount_mode()
if self.mounted?
@mount_mode
else
:unmounted
end
end
#
def mount(mailbox=nil,mode=:read_write)
result = false
if @client
begin
unless [:read, :read_write].include?(mode)
raise ArgumentError, "Mode #{mode.inspect} is invalid. Choose one of these: [:read, :read_write]."
end
if self.mounted?
if @mounted == mailbox
if self.mount_mode() == :read && mode == :read_write
response = @client.select(mailbox)
if response.is_a?(::Net::IMAP::TaggedResponse)
if response.name == "OK"
@mounted = mailbox
@mount_mode = :read_write
result = true
else
raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write."
end
else
raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write."
end
else
result = true
end
else
raise Exception, "You must unmount mailbox #{@mounted.inspect} first, then mount #{mailbox.inspect}."
end
else
# mount
if mode == :read_write
response = @client.select(mailbox)
if response.is_a?(::Net::IMAP::TaggedResponse)
if response.name == "OK"
@mounted = mailbox
@mount_mode = :read_write
result = true
else
raise Exception, "Failed to mount #{mailbox.inspect} as :read_write."
end
else
raise Exception, "Failed to mount #{mailbox.inspect} as :read_write."
end
else
# mount :read only
response = @client.examine(mailbox)
if response.is_a?(::Net::IMAP::TaggedResponse)
if response.name == "OK"
@mounted = mailbox
@mount_mode = :read
result = true
else
raise Exception, "Failed to mount #{mailbox.inspect} as :read."
end
else
raise Exception, "Failed to mount #{mailbox.inspect} as :read."
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox, :mode => mode}})
end
end
result
end
#
def unmount()
result = false
if self.mounted?()
@mounted = nil
@mount_mode = :read
result = true
end
result
end
#
def status(mailbox=nil)
result = {:connected => false, :unseen => 0, :recent => 0, :messages => 0}
if @client
begin
result[:connected] = (! @client.disconnected?)
the_mailbox = (mailbox || @mounted)
unless the_mailbox
raise ArgumentError, "You must either MOUNT an inbox, or specify one to get status."
end
if result[:connected] == true
raw_response = @client.status(the_mailbox, ["UNSEEN","MESSAGES", "RECENT"])
if raw_response.is_a?(::Hash)
result[:unseen] = raw_response["UNSEEN"]
result[:recent] = raw_response["RECENT"]
result[:messages] = raw_response["MESSAGES"]
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
end
end
result
end
#
def list(reference=nil, mailbox=nil)
result = []
if @client
begin
unless reference
raise ArgumentError, "You MUST sepcify a REFERENCE to list."
end
unless mailbox
raise ArgumentError, "You MUST sepcify a MAILBOX to list."
end
raw_list = @client.list(reference, mailbox)
if raw_list
raw_list.each do |entry|
if entry.is_a?(::Net::IMAP::MailboxList)
result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
end
end
result
end
#
def xlist(reference=nil, mailbox=nil)
result = []
if @client
begin
unless reference
raise ArgumentError, "You MUST sepcify a REFERENCE to list."
end
unless mailbox
raise ArgumentError, "You MUST sepcify a MAILBOX to list."
end
raw_list = @client.xlist(reference, mailbox)
if raw_list
raw_list.each do |entry|
if entry.is_a?(::Net::IMAP::MailboxList)
result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
end
end
result
end
#
def upload_message(mailbox=nil, gxg_message=nil, flags=[:Seen], timestamp=::Time.now)
result = false
if @client
begin
if mailbox.is_a?(::GxG::Events::Message)
if gxg_message.is_any?(::Symbol,::Array)
flags = gxg_message
end
gxg_message = mailbox
mailbox = nil
end
unless mailbox
mailbox = @mounted
unless @mount_mode == :read_write
raise Exception, "You should re-mount #{@mounted.inspect} as :read_write, or select another MAILBOX."
end
end
unless mailbox
raise ArgumentError, "You MUST sepcify a MAILBOX to append the message to."
end
if flags.is_a?(::Symbol)
flags = [(flags)]
end
if flags.is_a?(::Array)
unless flags.include?(:Seen)
flags << :Seen
end
end
unless gxg_message.is_a?(::GxG::Events::Message)
raise ArgumentError, "You MUST provide a ::GxG::Events::Message object as the message. See: <SmtpClient>.message_template()"
end
# Build suitable mail message
message = Mail.new
the_fields = []
gxg_message[:header].each_pair do |the_key, the_value|
if the_value.is_a?(::Array)
the_value.each do |the_field_value|
the_fields << "#{the_key.to_s}: #{the_field_value.to_s}"
end
else
the_fields << "#{the_key.to_s}: #{the_value.to_s}"
end
end
message.header = the_fields.join("\r\n")
message.from = gxg_message[:sender]
message.reply_to = gxg_message[:sender]
if gxg_message[:id].to_s.include?("@")
message.message_id = gxg_message[:id].to_s
else
message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
end
message.to = gxg_message[:to]
if gxg_message[:cc]
message.cc = gxg_message[:cc]
end
if gxg_message[:bcc]
message.bcc = gxg_message[:bcc]
end
message.subject = gxg_message[:subject]
message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s)
if gxg_message[:attachments].size > 0
multipart = true
else
part_count = 0
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
part_count += 1
end
end
if part_count > 1
multipart = true
else
multipart = false
end
end
if multipart
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
type_info = "Content-Type: #{part[:content_type][:type]}"
if part[:content_type][:parameters].keys.size > 0
part[:content_type][:parameters].each_pair do |the_key, the_value|
type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'"
end
end
new_part = Mail::Part.new(type_info)
new_part.body = part[:content]
message.add_part(new_part)
end
end
# Add file attachments:
gxg_message[:attachments].each do |attachment|
if attachment[:content].size == 0
file_size = ::File.size(attachment[:path])
file_handle = ::File.open(attachment[:path],"rb")
::GxG::apportioned_ranges(file_size,65536).each do |the_range|
file_handle.seek(the_range.first)
attachment[:content] << file_handle.read(the_range.size)
end
file_handle.close
end
message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s})
end
else
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
message.body = part[:content]
break
end
end
end
# Send it
@client.append(mailbox, message.to_s, flags, timestamp)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox, :message => gxg_message, :flags => flags}})
end
end
result
end
#
def fetch_message(uid=nil)
result = nil
if @client
begin
unless self.mounted?
raise Exception, "You MUST mount a Mailbox prior to calling this operation."
end
unless uid.is_a?(::Integer)
raise ArgumentError, "You MUST provide an Integer to specify the Message UID Number."
end
parse_message = Proc.new do |the_raw_message|
header = {}
body = []
attachments = []
parser = ::Mail.new(the_raw_message)
parser.header_fields.each do |field|
the_key = field.name.to_s.to_sym
the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!("\r\n","")
if header[(the_key)]
unless header[(the_key)].is_a?(::Array)
header[(the_key)] = [(header[(the_key)])]
end
header[(the_key)] << the_data
else
header[(the_key)] = the_data
end
end
if parser.multipart?
link_db = []
body << {:preamble => parser.body.preamble}
parser.parts.each do |part|
if part.multipart?
link_db << part
else
if part.attachment?
attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
else
body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
end
end
while link_db.size > 0 do
part = link_db.shift
if part.multipart?
part.parts.each do |the_sub_part|
link_db << the_sub_part
end
else
if part.attachment?
attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
else
body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
end
end
body << {:epilogue => parser.body.epilogue}
else
body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
mime_type = "text/plain"
parameters = {"charset" => "UTF-8"}
body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)
if body_text.xml?
mime_type = "text/xml"
end
if body_text.html?
mime_type = "text/html"
end
body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text}
body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
gxg_message = new_message
gxg_message[:sender] = parser.from
gxg_message[:id] = parser.message_id
gxg_message[:to] = parser.to
gxg_message[:cc] = parser.cc
gxg_message[:bcc] = parser.bcc
gxg_message[:subject] = parser.subject
gxg_message[:date] = parser.date
gxg_message[:header] = header
gxg_message[:body] = body
gxg_message[:attachments] = attachments
gxg_message
end
#
raw_response = @client.uid_fetch(uid,"BODY[]")
if raw_response.is_a?(::Array)
if raw_response.size > 0
result = parse_message.call(raw_response[0].attr()["BODY[]"])
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:unique_id => uid}})
end
end
result
end
#
def set_message_attributes(uid=nil, attributes=nil, flags=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID to set an ATTRIBUTE."
end
unless attributes.is_a?(::String)
raise ArgumentError, "You MUST specify what ATTRIBUTE to set."
end
unless flags.is_a?(::Array)
raise ArgumentError, "You MUST specify what FLAGS to set/unset."
end
@client.uid_store(uid, attributes, flags)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid, :attributes => attributes, :flags => flags}})
end
end
result
end
#
def add_message_flags(uid=nil, flags=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID name to set FLAGS."
end
unless flags.is_a?(::Array)
raise ArgumentError, "You MUST specify what FLAGS to set."
end
self.set_message_attributes(uid, "+FLAGS", flags)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}})
end
end
result
end
#
def remove_message_flags(uid=nil, flags=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID name to clear FLAGS."
end
unless flags.is_a?(::Array)
raise ArgumentError, "You MUST specify what FLAGS to clear."
end
self.set_message_attributes(uid, "-FLAGS", flags)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}})
end
end
result
end
#
def remove_messages(uid=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID to remove."
end
self.add_message_flags(uid, [:Deleted])
@client.expunge
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid}})
end
end
result
end
#
def copy_to_mailbox(uid=nil, mailbox=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID to copy the message."
end
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX to copy the message to."
end
self.uid_copy(uid, mailbox)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}})
end
end
result
end
#
def move_to_mailbox(uid=nil, mailbox=nil)
result = false
if @client
begin
unless uid.is_any?(::Integer, ::Array)
raise ArgumentError, "You MUST specify a UID to move the message."
end
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX to move the message to."
end
self.uid_move(uid, mailbox)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}})
end
end
result
end
#
def fetch_manifest(uid_set=[])
result = []
if @client
begin
unless self.mounted?
raise Exception, "You MUST mount a Mailbox prior to calling this operation."
end
if uid_set.is_a?(::Integer)
uid_set = [(uid_set)]
end
unless uid_set.is_a?(::Array)
raise ArgumentError, "You MUST provide an Array of Message UID Integers."
end
#
parse_header = Proc.new do |the_raw_message|
header = {}
parser = ::Mail.new(the_raw_message)
parser.header_fields.each do |field|
the_key = field.name.to_s.to_sym
the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!("\r\n","")
if header[(the_key)]
unless header[(the_key)].is_a?(::Array)
header[(the_key)] = [(header[(the_key)])]
end
header[(the_key)] << the_data
else
header[(the_key)] = the_data
end
end
header
end
#
uid_set.each do |uid|
record = {:uid => uid, :date => nil, :from => "", :to => "", :subject => ""}
raw_response = @client.uid_fetch(uid,"BODY[HEADER]")
if raw_response.is_a?(::Array)
if raw_response.size > 0
header = parse_header.call(raw_response[0].attr()["BODY[HEADER]"])
record[:date] = ::DateTime.parse(header[:Date].to_s)
record[:from] = header[:From]
record[:to] = header[:To]
record[:subject] = header[:Subject]
result << record
end
end
#
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:uid_set => uid_set}})
end
end
result
end
#
def uid_list(criteria=["NEW"])
result = []
if @client
begin
unless self.mounted?
raise Exception, "You MUST mount a Mailbox prior to calling this operation."
end
unless criteria.is_a?(::Array)
raise ArgumentError, "You MUST provide a search criteria in the form of an Array of Strings."
end
result = @client.uid_search(criteria)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:criteria => criteria}})
end
end
result
end
#
def theaded_list(algorithm=nil,search_keys=nil, charset="UTF-8")
result = []
if @client
begin
unless self.mounted?
raise Exception, "You MUST mount a Mailbox prior to calling this operation."
end
unless ["orderedsubject","references"].include?(algorithm.to_s.downcase)
raise ArgumentError, "The algorithm you specified is invalid. Choose one of these: ['ORDEREDSUBJECT', 'REFERENCES']"
end
algorithm = algorithm.to_s.upcase
unless search_keys.is_a?(::Array)
raise ArgumentError, "You MUST provide search keys in the form of an Array of Strings."
end
unless charset
raise ArgumentError, "You MUST provide a CHARSET specifier. Try one of these: ['US-ASCII', 'UTF-8']"
end
result = @client.uid_thread(algorithm, search_keys, charset)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:algorithm => algorithm, :search_keys => search_keys, :charset => charset}})
end
end
result
end
#
def sort(sort_keys=["DATE"], search_keys=["ALL"], charset="UTF-8")
result = []
if @client
begin
unless self.mounted?
raise Exception, "You MUST mount a Mailbox prior to calling this operation."
end
unless @mount_mode == :read_write
raise Exception, "You need to re-mount #{@mounted.inspect} as :read_write to peform this operation."
end
result = @client.uid_sort(sort_keys, search_keys, charset)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:sort_keys => sort_keys, :search_keys => search_keys, :charset => charset}})
end
end
result
end
#
def create_mailbox(mailbox=nil)
result = []
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name to create it."
end
result = @client.create(mailbox)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
end
end
result
end
#
def remove_mailbox(mailbox=nil)
result = []
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name to delete it."
end
result = @client.delete(mailbox)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
end
end
result
end
#
def rename_mailbox(mailbox=nil, new_name=nil)
result = false
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name to rename it."
end
unless new_name
raise ArgumentError, "You MUST specify a NEW_NAME name to rename the MAILBOX."
end
@client.rename(mailbox, new_name)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox, :new_name => new_name}})
end
end
result
end
#
def subscribe(mailbox=nil)
result = false
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name to subscribe to."
end
@client.subscribe(mailbox)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
end
end
result
end
#
def unsubscribe(mailbox=nil)
result = false
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name to unsubscribe from."
end
@client.unsubscribe(mailbox)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
end
end
result
end
#
def list_subscriptions(reference=nil, mailbox=nil)
result = []
if @client
begin
raw_list = @client.lsub(reference, mailbox)
if raw_list
raw_list.each do |entry|
if entry.is_a?(::Net::IMAP::MailboxList)
result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
end
end
result
end
#
def get_quota(mailbox=nil)
result = nil
if @client
begin
unless mailbox
raise ArgumentError, "You MUST specify a MAILBOX name."
end
raw_response = @client.getquotaroot(mailbox)[1]
if raw_response.is_a?(::Net::IMAP::MailboxQuota)
result = {:usage => raw_response.usage.to_i, :quota => raw_response.quota.to_i}
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
end
end
result
end
#
end
#
::GxG::Networking::DISPATCHER.register_client("imap", ::GxG::Networking::ImapClient)
::GxG::Networking::DISPATCHER.register_client("imaps", ::GxG::Networking::ImapClient)
# SMTP Classes:
class SmtpClient
#
def initialize(the_url=nil,options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
@user = the_url.user
@password = the_url.password
@client = nil
@host = the_url.hostname
if options[:use_ssl] == true || the_url.scheme == "ssmtp"
@port = (the_url.port || 465)
if options[:ssl_method]
# Valid: [:TLSv1, :TLSv1_server, :TLSv1_client, :TLSv1_1, :TLSv1_1_server, :TLSv1_1_client, :TLSv1_2, :TLSv1_2_server, :TLSv1_2_client, :SSLv23, :SSLv23_server, :SSLv23_client]
unless OpenSSL::SSL::SSLContext::METHODS.include?(options[:ssl_method])
raise ArgumentError, "Encryption method #{options[:ssl_method].inspect} is not valid. Choose one of these: #{OpenSSL::SSL::SSLContext::METHODS.inspect}"
end
openssl_context = OpenSSL::SSL::SSLContext.new(options[:ssl_method])
else
openssl_context = ::Net::SMTP.default_ssl_context
end
if options[:ignore_ssl_errors] == true
openssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
else
openssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
else
@port = (the_url.port || 25)
openssl_context = nil
end
@client = ::Net::SMTP.new(@host, @port)
if openssl_context
@client.enable_ssl(openssl_context)
end
#
self
end
#
def host()
@host.clone
end
#
def port()
@port.clone
end
#
def login(user_id=nil, password=nil, options={:authentication_method=> :plain})
result = false
if user_id.is_a?(::Hash)
options = user_id
user_id = nil
password = nil
end
unless options.is_a?(::Hash)
raise ArgumentError, "You MUST provide a options as a Hash"
end
authentication_method = (options[:authentication_method] || :plain)
if @client
begin
unless user_id
user_id = @user
unless user_id
raise ArgumentError, "You MUST provide a USER id, like this: <user_id>,<password/oauth-token>,(<options>)"
end
end
unless password
password = @password
unless password
raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: <user_id>,<password/oauth-token>,(<options>)"
end
end
unless @client.started?
unless [:plain, :login, :cram_md5, :oauth2].include?(authentication_method.to_s.to_sym)
raise ArgumentError, "Authentication method #{authentication_method.inspect} is invalid. Choose one of these: [:plain, :login, :cram_md5, :oauth2]"
end
# Internal cosmetics for interface consistency.
if authentication_method == :oauth2
authentication_method = :xoauth2
end
@client.start(@host, user_id, password, authentication_method)
end
result = true
rescue Exception => the_error
# Internal cosmetics for interface consistency.
if authentication_method == :xoauth2
authentication_method = :oauth2
end
log_error({:error => the_error, :parameters => {:user => user_id, :authentication => authentication_method}})
end
end
result
end
#
def logout()
if @client
begin
@client.finish
@client = nil
true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
false
end
else
false
end
end
#
def attachment_template(the_path="", content_parameters={"name" => ""})
result = nil
begin
unless the_path.to_s.valid_path?
raise ArgumentError, "You MUST provide a valid file system path to the file."
end
unless content_parameters.is_a?(::Hash)
raise ArgumentError, "You MUST provide content_parameters as a Hash."
end
unless content_parameters["name"].to_s.size > 0
content_parameters["name"] = ::File.basename(the_path)
end
discovered_mime = ::MimeMagic.by_magic(::File.open(the_path,"rb"))
if discovered_mime
discovered_mime = discovered_mime.type
else
discovered_mime = "application/octet-stream"
end
result = {:content_type => {:type => discovered_mime, :parameters => content_parameters}, :filename => (::File.basename(the_path)), :path => the_path, :content => ::GxG::ByteArray.new}
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:path => the_path, :content_parameters => content_parameters}})
end
result
end
#
def message_template(mime_type="text/plain", content_parameters={"charset"=>"UTF-8"})
result = nil
begin
unless ['text/plain', 'text/html', 'text/xhtml', 'text/xml'].include?(mime_type.to_s)
raise ArgumentError, "Content Type specified was invalid. Choose one of these: ['text/plain', 'text/html', 'text/xhtml', 'text/xml']"
end
unless content_parameters.is_a?(::Hash)
raise ArgumentError, "You MUST provide content_parameters as a Hash."
end
gxg_message = new_message
gxg_message[:sender] = ""
gxg_message[:id] = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
gxg_message[:to] = ""
gxg_message[:cc] = nil
gxg_message[:bcc] = nil
gxg_message[:subject] = ""
gxg_message[:date] = ::DateTime.now
gxg_message[:header] = {}
gxg_message[:body] = [{:prologue => ""},{:content_type => {:type => mime_type.to_s, :parameters => content_parameters}, :content => ""},{:epilogue => ""}]
gxg_message[:attachments] = []
result = gxg_message
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:content_type => mime_type, :content_parameters => content_parameters}})
end
result
end
#
def send_message(gxg_message=nil)
result = false
if @client
begin
unless @client.started?
raise Exception, "SMTP session not started yet."
end
unless gxg_message.is_a?(::GxG::Events::Message)
raise ArgumentError, "You MUST provide a valid message object of class ::GxG::Events::Message. Suggested: <SmtpClient>.message_template(<Mime-Type-String>)"
end
# Build suitable mail message
message = Mail.new
the_fields = []
gxg_message[:header].each_pair do |the_key, the_value|
if the_value.is_a?(::Array)
the_value.each do |the_field_value|
the_fields << "#{the_key.to_s}: #{the_field_value.to_s}"
end
else
the_fields << "#{the_key.to_s}: #{the_value.to_s}"
end
end
message.header = the_fields.join("\r\n")
message.from = gxg_message[:sender]
message.reply_to = gxg_message[:sender]
if gxg_message[:id].to_s.include?("@")
message.message_id = gxg_message[:id].to_s
else
message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
end
message.to = gxg_message[:to]
if gxg_message[:cc]
message.cc = gxg_message[:cc]
end
if gxg_message[:bcc]
message.bcc = gxg_message[:bcc]
end
message.subject = gxg_message[:subject]
message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s)
if gxg_message[:attachments].size > 0
multipart = true
else
part_count = 0
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
part_count += 1
end
end
if part_count > 1
multipart = true
else
multipart = false
end
end
if multipart
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
type_info = "Content-Type: #{part[:content_type][:type]}"
if part[:content_type][:parameters].keys.size > 0
part[:content_type][:parameters].each_pair do |the_key, the_value|
type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'"
end
end
new_part = Mail::Part.new(type_info)
new_part.body = part[:content]
message.add_part(new_part)
end
end
# Add file attachments:
gxg_message[:attachments].each do |attachment|
if attachment[:content].size == 0
file_size = ::File.size(attachment[:path])
file_handle = ::File.open(attachment[:path],"rb")
::GxG::apportioned_ranges(file_size,65536).each do |the_range|
file_handle.seek(the_range.first)
attachment[:content] << file_handle.read(the_range.size)
end
file_handle.close
end
message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s})
end
else
gxg_message[:body].each do |part|
unless part[:prologue] || part[:epilogue]
message.body = part[:content]
break
end
end
end
# Send it
@client.send_message(message.to_s, gxg_message[:sender], message.destinations)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:message => gxg_message}})
end
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("smtp", ::GxG::Networking::SmtpClient)
::GxG::Networking::DISPATCHER.register_client("ssmtp", ::GxG::Networking::SmtpClient)
# POP3 Classes:
class Pop3Client
#
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
@user = the_url.user
@password = the_url.password
@client = nil
@host = the_url.hostname
if options[:use_ssl] == true || the_url.scheme.to_s == "pop3s"
@port = (port || 995)
else
@port = (port || 110)
end
@client = ::Net::POP3.new(@host, @port, (options[:use_apop] || false))
if options[:use_ssl] == true
if options[:ignore_ssl_errors] == true
@client.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
else
@client.enable_ssl(OpenSSL::SSL::VERIFY_PEER)
end
end
#
self
end
#
def login(user_id=nil,password=nil,options={})
if @client
unless user_id
user_id = @user
end
unless password
password = @password
end
unless password
raise ArgumentError, "You MUST provide a PASSWORD, like this: <user_id>,<password>,(<options>)"
end
@client.start(user_id, password)
true
else
false
end
#
end
#
def logout()
if @client
@client.finish
@client = nil
true
end
end
#
def message_count()
# Return the number of emails on the server.
if @client
@client.n_mails()
else
0
end
end
#
def retrieve_messages(options={:read_only=>false},&block)
result = []
begin
if @client
if @client.started?
if @client.active?
parse_message = Proc.new do |the_raw_message|
header = {}
body = []
attachments = []
parser = ::Mail.new(the_raw_message.pop)
parser.header_fields.each do |field|
the_key = field.name.to_s.to_sym
the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!(" ","")
the_data.gsub!("\r\n","")
if header[(the_key)]
unless header[(the_key)].is_a?(::Array)
header[(the_key)] = [(header[(the_key)])]
end
header[(the_key)] << the_data
else
header[(the_key)] = the_data
end
end
if parser.multipart?
link_db = []
body << {:preamble => parser.body.preamble}
parser.parts.each do |part|
if part.multipart?
link_db << part
else
if part.attachment?
attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
else
body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
end
end
while link_db.size > 0 do
part = link_db.shift
if part.multipart?
part.parts.each do |the_sub_part|
link_db << the_sub_part
end
else
if part.attachment?
attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
else
body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
end
end
body << {:epilogue => parser.body.epilogue}
else
body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
mime_type = "text/plain"
parameters = {"charset" => "UTF-8"}
body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)
if body_text.xml?
mime_type = "text/xml"
end
if body_text.html?
mime_type = "text/html"
end
body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text}
body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)}
end
gxg_message = new_message
gxg_message[:sender] = parser.from
gxg_message[:id] = parser.message_id
gxg_message[:to] = parser.to
gxg_message[:cc] = parser.cc
gxg_message[:bcc] = parser.bcc
gxg_message[:subject] = parser.subject
gxg_message[:date] = parser.date
gxg_message[:header] = header
gxg_message[:body] = body
gxg_message[:attachments] = attachments
gxg_message
end
@client.each_mail do |raw_message|
if block.respond_to?(:call)
block.call(parse_message.call(raw_message))
else
result << parse_message.call(raw_message)
end
unless options[:read_only] == true
raw_message.delete
end
end
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("pop3", ::GxG::Networking::Pop3Client)
::GxG::Networking::DISPATCHER.register_client("pop3s", ::GxG::Networking::Pop3Client)
# REST Classes:
class RestClient
#
def self.set_system_proxy(the_url=nil)
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid proxy URI."
end
::RestClient.proxy = the_url.to_s
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url}})
end
result
end
#
def self.clear_system_proxy()
result = false
begin
::RestClient.proxy = nil
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def intitialize(the_url=nil, options={})
# REST Servers to test with:
# http://services.groupkt.com/country/get/all
# http://services.groupkt.com/state/get/{countryCode}/all
# https://httpbin.org/get
#
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
@url = the_url.clone
if options[:query].is_a?(::Hash)
@url.query = ::URI.encode_www_form(options[:query])
end
unless options[:follow_redirects] == false
options.merge({:follow_redirects => true})
end
#
if @url.scheme.downcase == "https"
options[:use_ssl] = true
end
if the_url.user.to_s.size > 0
@authenticated = true
@user = the_url.user
@password = the_url.password
else
@authenticated = false
@user = nil
@password = nil
end
@pem_option = nil
if options[:pem_source].is_any?(::File, ::GxG::Database::PersistedArray)
pem_data = nil
if options[:pem_source].is_a?(::File)
options[:pem_source].rewind
pem_data = options[:pem_source].read()
else
buffer = ::GxG::ByteArray.new
options[:pem_source].each_index do |segment|
buffer << options[:pem_source][(segment)].to_s
options[:pem_source].unload(segment)
end
pem_data = buffer.to_s
end
if pem_data
@pem_option = {:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER}
end
end
@proxy_option = nil
if options[:proxy].is_a?(::URI::Generic)
@proxy_option = options[:proxy]
end
# Review : update initialization parameters to track client changes.
# ????
if the_url.scheme.downcase.to_s == "https"
@client = ::GxG::Networking::HttpsClient.new
else
@client = ::GxG::Networking::HttpClient.new
end
@host = the_url.hostname
#
@cookies = nil
self
end
#
def clear_cache()
@cookies = nil
true
end
#
def get(the_url=nil, options={}, &block)
result = nil
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
if options.is_a?(::Hash)
options = @options.merge(options)
else
options = @options
end
header_options = (options[:header_options] || {}).merge({:accept => :json})
if @cookies
unless header_options[:cookies]
header_options[:cookies] = @cookies
end
end
if @pem_option
header_options = header_options.merge(@pem_option)
end
if @proxy_option
header_options[:proxy] = @proxy_option
end
if options[:save_to].is_a?(::File, ::GxG::Database::PersistedArray)
client = ::GxG::Networking::HttpClient.new(the_url, options)
response = client.get(the_url, {:save_to => options[:save_to], :proxy => options[:proxy]})
if response == true
result = options[:save_to]
if block.respond_to?(:call)
block.call(result)
end
end
else
if options[:parameters].is_a?(::Hash)
response = ::RestClient.get(the_url.to_s, header_options.merge({:params => options[:parameters]}), &block)
else
response = ::RestClient.get(the_url.to_s, header_options, &block)
end
#
unless @cookies
if response.cookies.is_a?(::Hash)
if response.cookies.keys.size > 0
@cookies = response.cookies
end
end
end
result = response
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def put(the_url=nil, data=nil, options={}, &block)
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
unless data.is_a?(::Hash)
raise Exception, "You MUST provide data to PUT to the server (in the form of a Hash)."
end
if options.is_a?(::Hash)
options = @options.merge(options)
else
options = @options
end
if @cookies
unless header_options[:cookies]
header_options[:cookies] = @cookies
end
end
if @pem_option
header_options = header_options.merge(@pem_option)
end
if @proxy_option
header_options[:proxy] = @proxy_option
end
header_options = (options[:header_options] || {}).merge({:accept => :json})
if options[:to_json]
data = data.to_json()
header_options[:content_type] = :json
else
# Review : completely redo file uploads with PUT ????
# BufferedSegments
if options[:upload_file].is_any?(::File, ::GxG::Database::PersistedArray)
if options[:upload_file].is_a?(::GxG::Database::PersistedArray)
the_upload = ::GxG::Storage::BufferedSegments.new(options[:upload_file])
else
the_upload = options[:upload_file]
end
data[(::GxG::uuid_generate().to_sym)] = the_upload
else
data.each_pair do |the_key, the_value|
if the_value.is_a?(::GxG::Database::PersistedArray)
data[(the_key)] = ::GxG::Storage::BufferedSegments.new(the_value)
end
end
end
end
result = ::RestClient.put(the_url.to_s, data, header_options, &block)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def post(the_url=nil, form_data=nil, options={}, &block)
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
unless form_data.is_a?(::Hash)
raise Exception, "You MUST provide valid form data (in the form of a Hash)."
end
unless options.is_a?(::Hash)
options = {}
end
header_options = (options[:header_options] || {}).merge({:accept => :json})
if @cookies
unless header_options[:cookies]
header_options[:cookies] = @cookies
end
end
if @pem_option
header_options = header_options.merge(@pem_option)
end
if @proxy_option
header_options[:proxy] = @proxy_option
end
if options[:upload_file].is_any?(::File, ::GxG::Database::PersistedArray)
if the_value.is_a?(::GxG::Database::PersistedArray)
the_upload = ::GxG::Storage::BufferedSegments.new(the_value)
else
the_upload = options[:upload_file]
end
form_data[:file] = the_upload
form_data[:multipart] = true
else
form_data.each_pair do |the_key, the_value|
if the_value.is_a?(::GxG::Database::PersistedArray)
form_data[(the_key)] = ::GxG::Storage::BufferedSegments.new(the_value)
end
end
end
result = ::RestClient.post(the_url.to_s, form_data, header_options, &block)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def delete(the_url=nil, options={}, &block)
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
unless options.is_a?(::Hash)
options = {}
end
header_options = (options[:header_options] || {}).merge({:accept => :json})
if @cookies
unless header_options[:cookies]
header_options[:cookies] = @cookies
end
end
if @pem_option
header_options = header_options.merge(@pem_option)
end
if @proxy_option
header_options[:proxy] = @proxy_option
end
result = ::RestClient.delete(the_url.to_s, header_options, &block)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("rest-https", ::GxG::Networking::RestClient)
::GxG::Networking::DISPATCHER.register_client("rest-http", ::GxG::Networking::RestClient)
# SOAP Classes:
class SoapDriver < ::Handsoap::Service
# Example sites to test with:
# http://www.restfulwebservices.net/rest/WeatherForecastService.svc?wsdl
# http://www.restfulwebservices.net/wcf/WeatherForecastService.svc?wsdl
# https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl
# http://www.webservicex.com/CurrencyConvertor.asmx?wsdl
#
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
super()
@user = nil
@password = nil
@interface = []
@tns_url = nil
::Handsoap::http_driver = :net_http
::Handsoap::xml_query_driver = :rexml
if the_url.query.to_s.downcase.include?("wsdl") || (the_url.path.to_s.downcase.include?("wsdl"))
point = (the_url.scheme.to_s + "://")
if the_url.user
point << the_url.user
@user = the_url.user
end
if the_url.password
point << (":" << the_url.password)
@password = the_url.password
end
if the_url.hostname
if the_url.user
point << ("@" << the_url.hostname)
else
point << the_url.hostname
end
end
if the_url.path
point << the_url.path
end
# Process WSDL, determine actual version number from that.
http_tool = ::GxG::Networking::HttpClient.new(the_url, options)
response = http_tool.get(the_url)
if response
wsdl_data = response.body.transcode({:replace => "."},::Encoding::UTF_8)
if wsdl_data.include?("http://schemas.xmlsoap.org/soap/envelope/")
wsdl_version = 1
else
wsdl_version = 2
end
::GxG::Networking::SoapDriver::endpoint({:uri => (point), :version => (wsdl_version)})
# Build Interface Data:
document = ::Oga.parse_xml(wsdl_data)
nodes = document.css("definitions")
nodes.each do |node|
next unless node.is_a?(Oga::XML::Element)
node.attributes.each do |attribute|
if ["tns", "targetNamespace"].include?(attribute.name)
@tns_url = attribute.value
break
end
end
if @tns_url
break
end
end
unless @tns_url
raise Exception, "Failed to load namespace (required)."
end
nodes = document.css("portType")
base_names = []
nodes.each do |node|
next unless node.is_a?(::Oga::XML::Element)
node.children.each do |the_operation|
next unless the_operation.is_a?(::Oga::XML::Element)
basename = nil
the_operation.attributes.each do |attribute|
if attribute.name == "name"
basename = attribute.value
break
end
end
if basename
record = {:base => (basename), :message => (("tns:" << basename).to_sym), :input => nil, :output => nil}
the_operation.children.each do |the_io|
next unless the_io.is_a?(::Oga::XML::Element)
if ["input", "output"].include?(the_io.name)
the_io.attributes.each do |the_message|
if the_message.name == "message"
record[(the_io.name.to_sym)] = the_message.value
end
end
end
end
if record[:input] || record[:output]
base_names << record
end
end
end
end
messages = document.css("message")
base_names.each do |the_method|
record = {:method => (the_method[:message]), :input => nil, :output => nil}
messages.each do |the_message|
next unless the_message.is_a?(::Oga::XML::Element)
the_message.attributes.each do |the_attribute|
direction_key = nil
if the_method[:input].gsub(/^.+:/, "") == the_attribute.value
direction_key = :input
end
if the_method[:output].gsub(/^.+:/, "") == the_attribute.value
direction_key = :output
end
if direction_key
parameters = {}
the_message.children.each do |the_part|
next unless the_part.is_a?(::Oga::XML::Element)
if the_part.name == "part"
the_name = nil
the_type = nil
the_part.attributes.each do |the_parameter|
if the_parameter.name == "name"
the_name = the_parameter.value
end
if the_parameter.name == "type"
the_type = the_parameter.value
end
if the_parameter.name == "element"
the_type = the_parameter.value
end
if (the_name && the_type)
parameters[(the_name.to_sym)] = the_type
the_name = nil
the_type = nil
end
end
end
end
if parameters.keys.size > 0
record[(direction_key)] = parameters
end
end
end
end
if record[:input] || record[:output]
@interface << record
end
end
#
else
raise Exception, "Failed to retrieve WSDL data."
end
else
raise ArgumentError, "No WSDL endpoint detected (required)."
end
#
self
end
# Handsoap support hooks:
def on_create_document(the_document=nil)
if @tns_url
the_document.alias('tns', @tns_url)
end
end
#
def on_before_dispatch(the_document=nil)
#
end
#
def on_response_document(the_document=nil)
if @tns_url
the_document.add_namespace('ns', @tns_url)
end
end
#
def interface()
@interface
end
#
end
#
class SoapClient
# Standard:
def initialize(the_url=nil, options={})
@client = ::GxG::Networking::SoapDriver.new(the_url, options)
self
end
#
def login(the_url=nil, options={})
result = false
begin
# Review - investigate how to do basic-auth or other method on a SOAP server.
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def logout()
@client = nil
true
end
#
def closed?()
if @client
false
else
true
end
end
#
def callmethod(*args)
result = nil
begin
if args.size == 1
the_method = args[0].to_s
arguments = {}
else
if args.size > 1
the_method = args[0].to_s
unless args[1].is_any?(::Hash, ::Array)
raise ArgumentError, "You MUST provide a Hash or Array to specify parameters for this method."
end
arguments = args[1]
else
raise ArgumentError, "You MUST provide a valid SOAP method."
end
end
if @client
response_tag = (the_method.gsub(/^.+:/, "") + "Response")
response = @client.invoke(the_method) do |message|
if arguments.is_a?(::Hash)
arguments.each_pair do |the_key, the_value|
message.add(the_key.to_s, the_value)
end
end
if arguments.is_a?(::Array)
arguments.each do |the_value|
message.add("literal", the_value)
end
end
end
data = ::Oga::parse_xml(response.to_xml).css(response_tag)
if data
found_numbers = data.text.numeric_values
found_value = []
if found_numbers.is_a?(::Array)
found_numbers.each do |the_subarray|
if the_subarray.is_a?(::Array)
if the_subarray[0].is_a?(::Hash)
the_key = the_subarray[0].keys[0]
found_value << the_subarray[0][(the_key)]
end
end
end
end
result = {}
result[(response_tag.to_sym)] = data.text
# if found_value.size > 0
# if found_value.size == 1
# result[(response_tag.to_sym)] = found_value[0]
# else
# result[(response_tag.to_sym)] = found_value
# end
# else
# result[(response_tag.to_sym)] = data.text
# end
else
raise Exception, "Failed to interpret response data. Data: #{data.inspect}"
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}})
end
result
end
#
def interface()
if @client
@client.interface
end
end
#
end
::GxG::Networking::DISPATCHER.register_client("soap-https", ::GxG::Networking::SoapClient)
::GxG::Networking::DISPATCHER.register_client("soap-http", ::GxG::Networking::SoapClient)
# XMLRPC Classes:
class XmlrpcClient
#
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https'
client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 443)]
else
client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 80)]
end
if options[:proxy].is_a?(::URI::Generic)
client_parameters << options[:proxy].host
client_parameters << options[:proxy].port
else
client_parameters << nil
client_parameters << nil
end
if the_url.user && the_url.password
client_parameters << the_url.user
client_parameters << the_url.password
else
client_parameters << nil
client_parameters << nil
end
if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https'
client_parameters << true
else
client_parameters << nil
end
if options[:timeout].is_a?(::Numeric)
client_parameters << options[:timeout].to_i
else
client_parameters << 30
end
@client = ::XMLRPC::Client.new(*client_parameters)
@interface = nil
self.interface()
self
end
#
def login(the_url=nil, options={})
result = false
begin
# Review - research how/when;/if you do basic-auth or other method of authentication for XML-RPC.
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url}})
end
result
end
#
def logout()
@client = nil
true
end
#
def closed?()
if @client
false
else
true
end
end
#
def callmethod(the_method = nil, arguments = nil)
# The +method+ parameter is converted into a String and should
# be a valid XML-RPC method-name.
#
# Each parameter of +args+ must be of one of the following types,
# where Hash, Struct and Array can contain any of these listed _types_:
#
# * Fixnum, Bignum
# * TrueClass, FalseClass, +true+, +false+
# * String, Symbol
# * Float
# * Hash, Struct
# * Array
# * Date, Time, XMLRPC::DateTime
# * XMLRPC::Base64
# * A Ruby object which class includes XMLRPC::Marshallable
# (only if Config::ENABLE_MARSHALLABLE is +true+).
# That object is converted into a hash, with one additional key/value
# pair <code>___class___</code> which contains the class name
# for restoring that object later.
result = nil
begin
unless the_method.is_a?(::String)
raise ArgumentError, "You MUST provide a valid method String."
end
if @client
calldata = []
calldata << the_method
#
# TODO: scour arguments structure for type conversions to be done.
if arguments.is_a?(::Array)
arguments.each do |entry|
calldata << entry
end
else
if arguments
calldata << arguments
end
end
result = @client.call(*calldata)
# TODO: scour result structure for type conversions to gxg standard.
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}})
end
result
end
#
def interface()
begin
if @client
unless @interface
@interface = @client.call("interface")
end
unless @interface
@interface = @client.call("Interface")
end
end
rescue Exception => the_error
# log_error({:error => the_error, :parameters => {}})
end
@interface
end
#
end
::GxG::Networking::DISPATCHER.register_client("xmlrpc-https", ::GxG::Networking::XmlrpcClient)
::GxG::Networking::DISPATCHER.register_client("xmlrpc-http", ::GxG::Networking::XmlrpcClient)
# ### SSH Client
class SshClient
def initialize(he_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
if the_url.password
options[:password] = the_url.password
end
@client = ::Net::SSH.start(the_url.hostname, the_url.user, options)
self
end
#
def login(the_url=nil, options={})
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
if the_url.password
options[:password] = the_url.password
end
@client = ::Net::SSH.start(the_url.hostname, the_url.user, options)
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def send_command(the_command=nil)
result = nil
begin
if @client
unless the_command.is_a?(::String)
raise ArgumentError, "You MUST provide a command as a String."
end
result = @client.exec!(the_command)
end
#
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:command => the_command}})
end
result
end
#
def channel_operations(&block)
result = nil
begin
if @client
unless block.respond_to?(:call)
raise ArgumentError, "You MUST provide a valid Block/Proc to execute."
end
result = @client.open_channel(&block)
end
#
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:block => block}})
end
result
end
#
def logout()
result = false
begin
if @client
@client.exec!("exit")
@client = nil
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def closed?()
if @client
false
else
true
end
end
#
def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
result = false
begin
if @client
unless the_local_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
if progress_block.respond_to?(:call)
channel = @client.scp.upload(the_local_source, the_destination, options, &progress_block)
else
channel = @client.scp.upload(the_local_source, the_destination, options)
end
channel.wait
# True result means the request was sent, not that it completed.
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
end
result
end
#
def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
# options:
#:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote
# on the remote server.
#
#:preserve - the atime and mtime of the file should be preserved.
#
#:verbose - the process should result in verbose output on the server end (useful for debugging).
#
#:chunk_size - the size of each "chunk" that should be sent. Defaults to 2048. Changing this value may
# improve throughput at the expense of decreasing interactivity.
result = false
begin
if @client
unless the_local_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
if progress_block.respond_to?(:call)
result = @client.scp.upload!(the_local_source, the_destination, options, &progress_block)
else
result = @client.scp.upload!(the_local_source, the_destination, options)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
end
result
end
#
def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
result = false
begin
if @client
unless the_remote_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
if progress_block.respond_to?(:call)
channel = @client.scp.download(the_remote_source, the_destination, options, &progress_block)
else
channel = @client.scp.download(the_remote_source, the_destination, options)
end
channel.wait
# True result means the request was sent, not that it completed.
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
end
result
end
#
def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
# options:
#:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote
# on the remote server.
#
#:preserve - the atime and mtime of the file should be preserved.
#
#:verbose - the process should result in verbose output on the server end (useful for debugging).
#
result = false
begin
if @client
unless the_remote_source.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a local path (or file name) as a String."
end
if progress_block.respond_to?(:call)
result = @client.scp.download!(the_remote_source, the_destination, options, &progress_block)
else
result = @client.scp.download!(the_remote_source, the_destination, options)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("ssh", ::GxG::Networking::SshClient)
#
class SftpClient
# Review : make BufferedSegments compatible
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
@user = the_url.user
if the_url.password
options[:password] = the_url.password
end
if the_url.port
options[:port] = the_url.port
end
@client = ::Net::SFTP.start(the_url.hostname, the_url.user, options)
@client.connect!
if @client.closed?
@client = nil
raise Exception, "Failed to open a connection."
end
self
end
#
def login(the_url=nil, options={})
result = false
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
@user = the_url.user
if the_url.password
options[:password] = the_url.password
end
if the_url.port
options[:port] = the_url.port
end
@client = ::Net::SFTP.start(the_url.hostname, the_url.user, options)
@client.connect!
if @client.closed?
@client = nil
raise Exception, "Failed to open a connection."
else
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def closed?()
if @client
@client.closed?
else
true
end
end
#
def logout()
if @client
@client.close_channel
@client = nil
true
end
end
#
def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
# options:
#:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
# below.
#
#:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
# tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
# throughput. Reducing it will reduce throughput.
#
#:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
# improve throughput. It defaults to 32,000 bytes.
#
#:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
# to "<memory>".
result = false
begin
if @client
unless the_local_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
unless options[:name]
options[:name] = File.basename(the_local_source)
end
if progress_block.respond_to?(:call)
channel = @client.upload(the_local_source, the_destination, options, &progress_block)
else
channel = @client.upload(the_local_source, the_destination, options)
end
channel.wait
# True result means the request was sent, not that it completed.
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
end
result
end
#
def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
# options:
#:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
# below.
#
#:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
# tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
# throughput. Reducing it will reduce throughput.
#
#:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
# improve throughput. It defaults to 32,000 bytes.
#
#:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
# to "<memory>".
result = false
begin
if @client
unless the_local_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
unless options[:name]
options[:name] = File.basename(the_local_source)
end
if progress_block.respond_to?(:call)
result = @client.upload!(the_local_source, the_destination, options, &progress_block)
else
result = @client.upload!(the_local_source, the_destination, options)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
end
result
end
#
def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
# progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
# at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
#
# :requests - the number of pending SFTP requests to allow at any given time. When downloading an
# entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
# Reducing it will reduce throughput.
#
# :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
# improve throughput. It defaults to 32,000 bytes.
#
result = false
begin
if @client
unless the_remote_source.is_a?(::String)
raise ArgumentError, "You MUST provide a local path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
if progress_block.respond_to?(:call)
channel = @client.download(the_remote_source, the_destination, options, &progress_block)
else
channel = @client.download(the_remote_source, the_destination, options)
end
channel.wait
# True result means the request was sent, not that it completed.
result = true
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
end
result
end
#
def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
# progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
# at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
#
# :requests - the number of pending SFTP requests to allow at any given time. When downloading an
# entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
# Reducing it will reduce throughput.
#
# :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
# improve throughput. It defaults to 32,000 bytes.
#
result = false
begin
if @client
unless the_remote_source.is_a?(::String)
raise ArgumentError, "You MUST provide a remote path as a String."
end
unless the_destination.is_a?(::String)
raise ArgumentError, "You MUST provide a local path (or file name) as a String."
end
if progress_block.respond_to?(:call)
result = @client.download!(the_remote_source, the_destination, options, &progress_block)
else
result = @client.download!(the_remote_source, the_destination, options)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
end
result
end
# Remote File System Operations and Objects
def entries(the_path=nil, pattern=nil, flags=0, &block)
# List entries in a given remote path.
# If block is provided, resultant list is iterated over the block - (each_entry).
result = []
begin
if @client
# Validate the_path: /.*(?:\\|\/)(.+)$/ from: https://www.experts-exchange.com/questions/22088316/Extract-Filename-from-PATH-using-REGEX.html
unless (/.*(?:\\|\/)(.+)$/.match(the_path.to_s) || the_path.to_s.split("/").size > 0 || the_path == "/")
raise ArgumentError, "You MUST provide a valid remote file system path."
end
if pattern.is_a?(::String)
# use glob
unless flags.is_a?(::Numeric)
flags = 0
end
raw_list = @client.dir.glob(the_path, pattern, flags)
else
# use entries
raw_list = @client.dir.entries(the_path)
end
list = []
raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
#
raw_list.each do |details|
#
unless details.name == "." || details.name == ".."
#
record = {:title => (details.name), :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil}
permission_record = blank_permissions.clone
#
record = record.merge({:size => (details.attributes.size), :accessed => (Time.at(details.attributes.atime)).to_datetime, :modified => (Time.at(details.attributes.mtime)).to_datetime, :state_modified => (Time.at(details.attributes.mtime)).to_datetime, :user_id => (details.attributes.uid), :group_id => (details.attributes.gid)})
if details.directory?()
record[:type] = :directory
record[:owner_type] = :directory
end
if details.file?()
record[:type] = :file
record[:owner_type] = :file
end
if details.symlink?()
record[:type] = :symlink
record[:owner_type] = :symlink
end
# puts "Got: #{details.attributes.permissions.inspect}"
record[:permissions] = ::File.mode_permission_to_gxg(details.attributes.permissions.to_i)
current_groups = @client.session.exec!("groups").gsub("\n","").split(" ")
if current_groups.size > 0
effective_permissions = raw_permission.clone
if @user == details.attributes.owner
include_owner = true
else
include_owner = false
end
if current_groups.include?(details.attributes.group)
include_group = true
else
include_group = false
end
#
permissions_stack = []
if include_owner
permissions_stack << record[:permissions][:owner]
end
if include_group
permissions_stack << record[:permissions][:group]
end
permissions_stack << record[:permissions][:other]
permissions_stack.each do |entry|
entry.keys do |the_key|
if entry[(the_key)] == true
effective_permissions[(the_key)] = true
end
end
end
record[:permissions][:effective] = effective_permissions
if record[:permissions][:effective][:read]
record[:flags] << :read
end
if record[:permissions][:effective][:write]
record[:flags] << :write
end
end
list << record
end
#
end
#
if block.respond_to?(:call)
list.each do |entry|
block.call(entry)
end
end
result = list
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_path}})
end
result
end
#
def open_remote_file(the_path=nil, permissions={})
result = nil
begin
if @client
filename = ::File.basename(the_path)
entries = this().entries(::File.dirname(the_path))
open_only = false
flags = []
entries.each do |item|
if item[:title] == filename
open_only = true
flags = item[:flags]
break
end
end
if open_only
if flags.include?(:read)
if flags.include?(:write)
open_mode = "w+"
else
open_mode = "r"
end
else
if flags.include?(:write)
open_mode = "w"
else
open_mode = "r"
end
end
#open
result = @client.file.open(the_path.to_s, open_mode)
else
#create
if permissions.is_a?(::Hash)
if permissions[:owner].is_a?(::Hash) && permissions[:group].is_a?(::Hash) && permissions[:other].is_a?(::Hash)
permissions = ::File.gxg_permissions_to_mode(permissions)
end
end
if permissions.is_a?(::Numeric)
unless permissions > 0
permissions = 0644.to_i
end
else
permissions = 0644.to_i
end
result = @client.file.open(the_path.to_s, "w+", permissions)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:remote_path => the_path, :permissions => permissions}})
end
result
end
#
def change_ownership(the_path=nil, new_owners=nil, &block)
result = false
begin
unless the_path.is_a?(::String)
raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
end
unless new_owners.is_a?(::Hash)
raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
end
unless new_owners[:user].is_a?(::String)
raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
end
response = @client.setstat!(the_path, new_owners)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:path => the_path, :new_owners => new_owners}})
end
result
end
#
def change_permissions(the_path=nil, new_permissions=nil, &block)
result = false
begin
unless the_path.is_a?(::String)
raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
end
unless new_permissions
raise ArgumentError, "You MUST supply a new permission value. (GxG permission or Integer/Octal)."
end
if new_permissions.is_a?(::Hash)
new_permissions = ::File.gxg_permissions_to_mode(new_permissions)
end
response = @client.setstat!(the_path, {:permissions => new_permissions})
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:path => the_path, :new_permissions => new_permissions}})
end
result
end
#
def remove_remote_file(the_path=nil, &block)
result = false
begin
unless the_path.is_a?(::String)
raise ArgumentError, "You MUST supply a remote file path. (/path/to/file)."
end
response = @client.remove!(the_path)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:path => the_path}})
end
result
end
#
def make_link(new_link_path=nil, existing_path=nil, symlink=true, &block)
result = false
begin
unless new_link_path.is_a?(::String)
raise ArgumentError, "You MUST supply a new link path. (/path/to/new_link)."
end
unless existing_path.is_a?(::String)
raise ArgumentError, "You MUST supply an existing file path. (/path/to/file)."
end
unless symlink
symlink = true
end
response = @client.link!(new_link_path,existing_path,symlink)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:new_link_path => new_link_path, :existing_path => existing_path, :symlink => symlink}})
end
result
end
#
def rename(name=nil, new_name=nil,&block)
result = false
begin
unless name.is_a?(::String)
raise ArgumentError, "You MUST supply a remote file path. (/path/to/old_name)."
end
unless new_name.is_a?(::String)
raise ArgumentError, "You MUST supply a new file name path. (/path/to/new_name)."
end
response = @client.rename!(name,new_name,0x0004)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
end
result
end
#
def make_directory(the_path=nil, &block)
result = false
begin
unless the_path.is_a?(::String)
raise ArgumentError, "You MUST supply a new directory path. (/path/to/new_dir)."
end
response = @client.mkdir!(the_path)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
end
result
end
#
def remove_directory(the_path=nil, &block)
result = false
begin
unless the_path.is_a?(::String)
raise ArgumentError, "You MUST supply a directory path to remove. (/path/to/dir)."
end
response = @client.rmdir!(the_path)
if block.respond_to?(:call)
block.call(response)
end
result = response.ok?
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("sftp-ssh", ::GxG::Networking::SftpClient)
::GxG::Networking::DISPATCHER.register_client("ftp-ssh", ::GxG::Networking::SftpClient)
#
class FtpClient
# Review : make BufferedSegments compatible
def initialize(the_url=nil, options={})
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
# The available options are:
#
# port:: Port number (default value is 21)
if options[:port].is_a?(::Numeric)
login_options[:port] = options[:port].to_i
end
# ssl:: If options[:ssl] is true, then an attempt will be made
# to use SSL (now TLS) to connect to the server. For this
# to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
# extensions need to be installed. If options[:ssl] is a
# hash, it's passed to OpenSSL::SSL::SSLContext#set_params
# as parameters.
if options[:use_ssl] == true
login_options[:ssl] = true
end
# private_data_connection:: If true, TLS is used for data connections.
# Default: +true+ when options[:ssl] is true.
# username:: Username for login. If options[:username] is the string
# "anonymous" and the options[:password] is +nil+,
# "anonymous@" is used as a password.
login_options[:username] = the_url.user
# password:: Password for login.
login_options[:password] = the_url.password
# account:: Account information for ACCT.
if options[:account_info]
login_options[:account] = options[:account_info]
end
# passive:: When +true+, the connection is in passive mode. Default:
# +true+.
if options[:passive] == false
login_options[:passive] = false
end
# open_timeout:: Number of seconds to wait for the connection to open.
# See Net::FTP#open_timeout for details. Default: +nil+.
if options[:open_timeout].is_a?(::Numeric)
login_options[:open_timeout] = options[:open_timeout]
end
# read_timeout:: Number of seconds to wait for one block to be read.
# See Net::FTP#read_timeout for details. Default: +60+.
if options[:read_timeout].is_a?(::Numeric)
login_options[:read_timeout] = options[:read_timeout]
end
# ssl_handshake_timeout:: Number of seconds to wait for the TLS
# handshake.
# See Net::FTP#ssl_handshake_timeout for
# details. Default: +nil+.
if options[:ssl_handshake_timeout].is_a?(::Numeric)
login_options[:ssl_handshake_timeout] = options[:ssl_handshake_timeout]
end
#
@client = Net::FTP.new(the_url.hostname, login_options)
if @client.is_a?(Net::FTP)
@client.login
if @client.closed?
raise Exception, "Failed to log into FTP session"
end
else
raise Exception, "Failed to create FTP session"
end
self
end
#
def login(the_url=nil, options={})
result = false
login_options = {}
begin
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI"
end
# The available options are:
#
# port:: Port number (default value is 21)
if options[:port].is_a?(::Numeric)
login_options[:port] = options[:port].to_i
end
# ssl:: If options[:ssl] is true, then an attempt will be made
# to use SSL (now TLS) to connect to the server. For this
# to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
# extensions need to be installed. If options[:ssl] is a
# hash, it's passed to OpenSSL::SSL::SSLContext#set_params
# as parameters.
if options[:use_ssl] == true
login_options[:ssl] = true
end
# private_data_connection:: If true, TLS is used for data connections.
# Default: +true+ when options[:ssl] is true.
# username:: Username for login. If options[:username] is the string
# "anonymous" and the options[:password] is +nil+,
# "anonymous@" is used as a password.
login_options[:username] = the_url.user
# password:: Password for login.
login_options[:password] = the_url.password
# account:: Account information for ACCT.
if options[:account_info]
login_options[:account] = options[:account_info]
end
# passive:: When +true+, the connection is in passive mode. Default:
# +true+.
if options[:passive] == false
login_options[:passive] = false
end
# open_timeout:: Number of seconds to wait for the connection to open.
# See Net::FTP#open_timeout for details. Default: +nil+.
if options[:open_timeout].is_a?(::Numeric)
login_options[:open_timeout] = options[:open_timeout]
end
# read_timeout:: Number of seconds to wait for one block to be read.
# See Net::FTP#read_timeout for details. Default: +60+.
if options[:read_timeout].is_a?(::Numeric)
login_options[:read_timeout] = options[:read_timeout]
end
# ssl_handshake_timeout:: Number of seconds to wait for the TLS
# handshake.
# See Net::FTP#ssl_handshake_timeout for
# details. Default: +nil+.
if options[:ssl_handshake_timeout].is_a?(::Numeric)
login_options[:ssl_handshake_timeout] = options[:ssl_handshake_timeout]
end
#
@client = Net::FTP.new(the_url.hostname, login_options)
if @client.is_a?(Net::FTP)
@client.login
if @client.closed?
raise Exception, "Failed to log into FTP session"
else
result = true
end
else
raise Exception, "Failed to create FTP session"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def logout()
if @client
@client.close
if @client.closed?
true
else
false
end
end
end
#
def closed?()
if @client
@client.closed?
else
true
end
end
#
def status()
result = nil
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed connection."
else
result = @client.status()
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def system_info()
result = nil
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed connection."
else
result = @client.system()
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def help()
result = nil
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed connection."
else
result = @client.help()
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def send_account_info(acct_info=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed connection."
else
unless acct_info.is_a?(::String)
raise ArgumentError, "You MUST provide account information (as a String)."
end
result = @client.acct(acct_info)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:account_info => acct_info}})
end
result
end
#
def send_command(command=nil)
result = nil
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed connection."
else
unless command.is_a?(::String)
raise ArgumentError, "You must provide a String to issue FTP commands."
end
result = @client.sendcmd(command)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:command => command}})
end
result
end
#
def reopen()
result = false
begin
if @client
if @client.closed?
@client.connect()
if @client.closed?
raise Exception, "Failed to re-open connection."
end
else
result = true
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def get_file(file_name=nil, save_path=nil)
result = nil
begin
if @client
if save_path
result = @client.getbinaryfile(file_name, save_path, 1024)
else
result = ::GxG::ByteArray.new
@client.getbinaryfile(file_name, save_path) do |data_chunk|
result << data_chunk
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_name => file_name, :save_path => save_path}})
end
result
end
#
def put_file(local_path=nil, remote_file=nil)
result = false
begin
if @client
result = @client.putbinaryfile(local_path, remote_file, 1024)
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:local_path => local_path, :remote_file => remote_file}})
end
result
end
#
def rename_file(file_name=nil, new_file_name=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
unless file_name
raise ArgumentError, "You MUST provide a current file name to rename."
end
unless new_file_name
raise ArgumentError, "You MUST provide a new name to rename the file."
end
result = @client.rename(file_name, new_file_name)
unless result
raise Exception, "Failed to rename file."
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_name => file_name, :new_file_name => new_file_name}})
end
result
end
#
def remove_file(file_name=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
unless file_name
raise ArgumentError, "You MUST provide a file name to remove."
end
result = @client.delete(file_name)
unless result
raise Exception, "Failed to remove file."
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:file_name => file_name}})
end
result
end
#
def entries(dir_name=nil, filter=nil)
result = []
unless dir_name
dir_name = @client.pwd()
end
begin
if @client
present_dir = @client.pwd()
@client.chdir(dir_name)
if filter
list = @client.list(filter)
else
list = @client.list()
end
list.each do |entry|
# TODO: process entry text
data = entry.split(" ")
record = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil}
#
record[:permissions] = ::File.unix_permissions_to_gxg(data[0].to_s)
if record[:permissions][:other][:write]
record[:flags] << :write
end
# record[:owner] = data[2]
# record[:group] = data[3]
record[:size] = data[4].to_i
record[:modified] = ::DateTime::parse(data[5..7].join(" "))
record[:title] = data[8]
result << record
end
@client.chdir(present_dir)
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:dir_name => dir_name, :filter => filter}})
end
result
end
#
def present_directory()
result = nil
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
result = @client.pwd()
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {}})
end
result
end
#
def change_directory(dir_name=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
unless dir_name
raise ArgumentError, "You MUST provide a directory path to change to."
end
@client.chdir(dir_name)
if @client.pwd() != dir_name
raise Exception, "Failed to change directory."
else
result = true
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
end
result
end
#
def make_directory(dir_name=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
unless dir_name
raise ArgumentError, "You MUST provide a directory path to create."
end
result = @client.mkdir(dir_name)
unless result
raise Exception, "Failed to change directory."
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
end
result
end
#
def remove_directory(dir_name=nil)
result = false
begin
if @client
if @client.closed?
raise Exception, "Attempted to access a closed session"
else
unless dir_name
raise ArgumentError, "You MUST provide a directory name to remove."
end
result = @client.rmdir(dir_name)
unless result
raise Exception, "Failed to remove directory."
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
end
result
end
#
end
::GxG::Networking::DISPATCHER.register_client("ftp", ::GxG::Networking::FtpClient)
#
class HttpClient
#
def initialize(the_url=nil, options={})
@cookie = nil
if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
@url = the_url.clone
if options[:query].is_a?(::Hash)
@url.query = ::URI.encode_www_form(options[:query])
end
unless options[:follow_redirects] == false
options.merge({:follow_redirects => true})
end
#
if @url.scheme.downcase == "https"
options[:use_ssl] = true
end
# sets following values by its accessor.
# The keys are ca_file, ca_path, cert, cert_store, ciphers,
# close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout,
# ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
# If you set :use_ssl as true, you can use https and default value of
# verify_mode is set as OpenSSL::SSL::VERIFY_PEER.
client_parameters = [(the_url.hostname), (the_url.port)]
if options[:proxy].is_a?(::URI::Generic)
client_parameters << options[:proxy].host
client_parameters << options[:proxy].port
if options[:proxy].user && options[:proxy].password
client_parameters << options[:proxy].user
client_parameters << options[:proxy].password
else
client_parameters << nil
client_parameters << nil
end
else
client_parameters << nil
client_parameters << nil
client_parameters << nil
client_parameters << nil
end
if options.keys.size > 0
client_parameters << options
end
@options = options
@client = Net::HTTP.start(*client_parameters)
if options[:pem_source].is_any?(::File, ::GxG::Database::PersistedArray)
pem_data = nil
if options[:pem_source].is_a?(::File)
options[:pem_source].rewind
pem_data = options[:pem_source].read()
else
buffer = ::GxG::ByteArray.new
options[:pem_source].each_index do |segment|
buffer << options[:pem_source][(segment)].to_s
options[:pem_source].unload(segment)
end
pem_data = buffer.to_s
end
if pem_data
@client.use_ssl = true
@client.cert = OpenSSL::X509::Certificate.new(pem_data)
@client.key = OpenSSL::PKey::RSA.new(pem_data, options[:pem_password])
@client.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
end
else
raise ArgumentError, "You MUST use HTTP or HTTPS as a protcol here."
end
self
end
#
def clear_cache()
@cookie = nil
true
end
#
def cookies()
@cookie.clone
end
#
def get(the_url=nil, options=nil)
result = nil
begin
unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
the_url = @url
end
if options.is_a?(::Hash)
if options[:query].is_a?(::Hash)
the_url.query = ::URI.encode_www_form(options[:query])
end
options = @options.merge(options)
else
options = @options
end
if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
if @cookie
if options[:header].is_a?(::Hash)
request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie}.merge(options[:header]))
else
request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie})
end
else
if options[:header].is_a?(::Hash)
request = Net::HTTP::Get.new(the_url.request_uri, options[:header])
else
request = Net::HTTP::Get.new(the_url.request_uri)
end
end
if (the_url.user && the_url.password)
# basic auth
request.basic_auth(the_url.user, the_url.password)
end
response = @client.request(request)
@cookie = response.response['set-cookie']
response_code = response.code.to_i
if options[:follow_redirects]
if response_code >= 300 && response_code < 400
# Process Redirect
redirect_count = 1
redirect_limit = (options[:redirect_limit] || 9).to_i
location = response['location']
while location do
#
if @cookie
redirect_request = Net::HTTP::Get.new(location, {'Cookie' => @cookie})
else
redirect_request = Net::HTTP::Get.new(location)
end
redirect_response = @client.request(redirect_request)
if redirect_response.code.to_i == 200
response = redirect_response
response_code = 200
@cookie = response.response['set-cookie']
break
else
if redirect_response['location']
@cookie = redirect_response.response['set-cookie']
redirect_count += 1
# Review : allow option to set this limit higher :redirect_limit
if redirect_count > redirect_limit
raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)"
end
response_code = redirect_response.code.to_i
location = redirect_response['location']
else
break
end
end
#
end
end
end
if options[:save_to].is_any?(::File, ::GxG::Database::PersistedArray)
if options[:save_to].is_a?(::File)
options[:save_to].rewind
response.read_body do |data_chunk|
options[:save_to].write(data_chunk)
end
options[:save_to].rewind
else
response.read_body do |data_chunk|
options[:save_to] << ::GxG::ByteArray.new(data_chunk)
options[:save_to].save
options[:save_to].unload(options[:save_to].size - 1)
end
end
end
if response_code == 200 && options[:save_to] == nil
# Return raw response
result = response
else
if options[:save_to]
result = true
else
raise Exception, "Error occured: Code #{response.code}, Data: #{response.to_s}"
end
end
else
raise Exception, "You MUST provide a valid HTTP or HTTP URI"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
#
def put(the_url=nil, data=nil, options={})
#
result = false
begin
unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
the_url = @url
end
if options.is_a?(::Hash)
if options[:query].is_a?(::Hash)
the_url.query = ::URI.encode_www_form(options[:query])
end
options = @options.merge(options)
else
options = @options
end
if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
if @cookie
request = Net::HTTP::Put.new(the_url.request_uri,data, {'Cookie' => @cookie})
else
request = Net::HTTP::Put.new(the_url.request_uri,data)
end
if (the_url.user && the_url.password)
# basic auth
request.basic_auth(the_url.user, the_url.password)
end
response = @client.request(request)
@cookie = response.response['set-cookie']
response_code = response.code.to_i
#
if options[:follow_redirects]
if response_code >= 300 && response_code < 400
# Process Redirect
redirect_count = 1
redirect_limit = (options[:redirect_limit] || 9).to_i
location = response['location']
while location do
#
if @cookie
redirect_request = Net::HTTP::Put.new(location, {'Cookie' => @cookie})
else
redirect_request = Net::HTTP::Put.new(location)
end
redirect_response = @client.request(redirect_request)
if redirect_response.code.to_i == 200
response = redirect_response
response_code = 200
break
else
if redirect_response['location']
redirect_count += 1
if redirect_count > redirect_limit
raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)"
end
response_code = redirect_response.code.to_i
location = redirect_response['location']
else
break
end
end
#
end
end
end
if response.code.to_i == 200
result = true
else
raise Exception, "Error occured: Code #{response.code}, Data: #{response.to_s}"
end
else
raise Exception, "You MUST provide a valid HTTP URI"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :form_data => data, :options => options}})
end
result
end
#
def post(the_url=nil, form_data=nil, options={})
result = false
begin
unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
the_url = @url
end
if options.is_a?(::Hash)
if options[:query].is_a?(::Hash)
the_url.query = ::URI.encode_www_form(options[:query])
end
options = @options.merge(options)
else
options = @options
end
# Review : figure a way to use VFS objects in multipart Posts (file uploads)
if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
if @cookie
request = Net::HTTP::Post.new(the_url.request_uri, {'Cookie' => @cookie})
else
request = Net::HTTP::Post.new(the_url.request_uri)
end
if (the_url.user && the_url.password)
# basic auth
request.basic_auth(the_url.user, the_url.password)
end
request.set_form_data(form_data)
response = @client.request(request)
@cookie = response.response['set-cookie']
response_code = response.code.to_i
if options[:follow_redirects]
if response_code >= 300 && response_code < 400
# Process Redirect
redirect_count = 1
redirect_limit = (options[:redirect_limit] || 9).to_i
location = response['location']
while location do
#
if @cookie
redirect_request = Net::HTTP::Post.new(location, {'Cookie' => @cookie})
else
redirect_request = Net::HTTP::Post.new(location)
end
redirect_request.set_form_data(form_data)
redirect_response = @client.request(redirect_request)
if redirect_response.code.to_i == 200
response = redirect_response
response_code = 200
break
else
if redirect_response['location']
redirect_count += 1
if redirect_count > redirect_limit
raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)"
end
response_code = redirect_response.code.to_i
location = redirect_response['location']
else
break
end
end
#
end
end
end
if response.code.to_i == 200
result = true
else
raise Exception, "Error occured: Code #{response.code}"
end
else
raise Exception, "You MUST provide a valid HTTP URI"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :form_data => form_data, :options => options}})
end
result
end
#
def delete(the_url=nil, options={})
result = false
begin
unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
the_url = @url
end
if options.is_a?(::Hash)
if options[:query].is_a?(::Hash)
the_url.query = ::URI.encode_www_form(options[:query])
end
options = @options.merge(options)
else
options = @options
end
if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
if @cookie
request = Net::HTTP::Delete.new(the_url.request_uri, {'Cookie' => @cookie})
else
request = Net::HTTP::Delete.new(the_url.request_uri)
end
if (the_url.user && the_url.password)
# basic auth
request.basic_auth(the_url.user, the_url.password)
end
response = @client.request(request)
@cookie = response.response['set-cookie']
response_code = response.code.to_i
if options[:follow_redirects]
if response_code >= 300 && response_code < 400
# Process Redirect
redirect_count = 1
redirect_limit = (options[:redirect_limit] || 9).to_i
location = response['location']
while location do
#
if @cookie
redirect_request = Net::HTTP::Delete.new(location, {'Cookie' => @cookie})
else
redirect_request = Net::HTTP::Delete.new(location)
end
redirect_response = @client.request(redirect_request)
if redirect_response.code.to_i == 200
response = redirect_response
response_code = 200
break
else
if redirect_response['location']
redirect_count += 1
if redirect_count > redirect_limit
raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)"
end
response_code = redirect_response.code.to_i
location = redirect_response['location']
else
break
end
end
#
end
end
end
if response.code.to_i == 200
result = true
else
raise Exception, "Error occured: Code #{response.code}"
end
else
raise Exception, "You MUST provide a valid HTTP URI"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
end
::GxG::Networking::DISPATCHER.register_client("http",::GxG::Networking::HttpClient)
#
class HttpsClient
def initialize(the_url=nil, options={:follow_redirects => true, :use_ssl => true})
@client = ::GxG::Networking::HttpClient.new(the_url, options)
self
end
#
def cookies()
@client.cookies()
end
#
def clear_cache()
@client.clear_cache()
end
#
def get(the_url=nil, options={:follow_redirects => true, :use_ssl => true})
@client.get(the_url,options)
end
def put(the_url=nil, data=nil, options={})
@client.put(the_url,data,options)
end
def post(the_url=nil, form_data=nil, options={})
@client.post(the_url,form_data,options)
end
def delete(the_url=nil, options={})
@client.post(the_url,options)
end
end
::GxG::Networking::DISPATCHER.register_client("https",::GxG::Networking::HttpsClient)
#
class MatrixID
def initialize(the_id=nil)
if the_id.is_a?(::MatrixSdk::MXID)
@id = the_id
else
if the_id.is_any?(::String, ::Symbol)
@id = ::MatrixSdk::MXID.new(the_id.to_s)
end
end
self
end
#
def base_object()
@id
end
#
def domain()
@id.domain()
end
#
def localport()
@id.localport()
end
#
def port()
@id.port()
end
#
def sigil()
@id.sigil()
end
#
def event?()
@id.event?
end
#
def group?()
@id.group?
end
#
def homeserver()
@id.homeserver
end
#
def homeserver_suffix()
@id.homeserver_suffix
end
#
def room?()
@id.room?
end
#
def room_alias()
@id.room_alias
end
#
def room_id()
@id.room_id
end
#
def to_s()
@id.to_s
end
#
def type()
@id.type
end
#
def user?()
@id.user?
end
#
def valid?()
@id.valid?
end
end
#
class MatrixUser
def initialize(client=nil, id=nil, options={})
if client.is_a?(::MatrixSdk::Client)
unless id.is_a?(::String)
raise ArgumentError, "You MUST provide a user_id as a String."
end
@user = ::MatrixSdk::User.new(client, id, options)
else
if client.is_a?(::MatrixSdk::User)
@user = client
else
raise ArgumentError, "You MUST provide a MatrixSdk::Client or MatrixSdk::User as client parameter."
end
end
self
end
#
def base_object()
@user
end
#
def active?
@user.active?
end
#
def avatar_url()
@user.avatar_url()
end
#
def avatar_url=(the_url_string=nil)
if the_url_string.is_any?(::String, ::URI::Generic)
@user.avatar_url = the_url_string.to_s
else
nil
end
end
#
def device_keys()
@user.device_keys()
end
#
def display_name()
@user.display_name()
end
#
def friendly_name()
@user.friendly_name()
end
#
def inspect()
"<MatrixUser: #{@user.client.mxid()}>"
end
#
def last_active()
@user.last_active.to_datetime()
end
#
def presence()
@user.presence()
end
#
def presence=(the_status=nil)
if the_status.is_any?(::String, ::Symbol)
if [:online, :offline, :unavailable].include?(the_status.to_sym)
@user.presence = the_status.to_sym
end
end
end
#
def status_message()
@user.status_msg()
end
#
def status_message=(the_message=nil)
@user.status_msg = the_message.to_s
end
#
def events(symbol_array=[])
@user.events(*symbol_array)
end
#
def ignore_inspect(symbol_array=[])
@user.ignore_inspect(*symbol_array)
end
#
end
#
class MatrixRoom
def initialize(client=nil, room_id=nil, options={})
if client.is_a?(::GxG::Networking::MatrixClient)
if room_id.is_any?(::MatrixSdk::MXID, ::GxG::Networking::MatrixID, ::String, ::Symbol)
room_id = ::MatrixSdk::MXID.new(room_id.to_s)
else
raise ArgumentError, "You MUST provide a GxG::Networking::MatrixID as room_id parameter."
end
@room = ::MatrixSdk::Room.new(client.base_object(), room_id, options)
else
if client.is_a?(::GxG::Networking::MatrixRoom)
@room = client.base_object()
else
if client.is_a?(::MatrixSdk::Room)
@room = client
else
raise ArgumentError, "You MUST provide a MatrixSdk::Client or MatrixSdk::Room as client parameter."
end
end
end
# Parameters:
# client (Client) —
# The underlying connection
# room_id (MXID) —
# The room ID
# data (Hash) (defaults to: {}) —
# Additional data to assign to the room
# Options Hash (data):
# :name (String) —
# The current name of the room
# :topic (String) —
# The current topic of the room
# :canonical_alias (String, MXID) —
# The canonical alias of the room
# :aliases (Array(String, MXID)) —
# All non-canonical aliases of the room
# :join_rule (:invite, :public) —
# The join rule for the room
# :guest_access (:can_join, :forbidden) —
# The guest access setting for the room
# :world_readable (Boolean) —
# If the room is readable by the entire world
# :members (Array(User)) —
# The list of joined members
# :events (Array(Object)) —
# The list of current events in the room
# :members_loaded (Boolean) —
# If the list of members is already loaded
# :event_history_limit (Integer) — default: 10 —
# The limit of events to store for the room
# :avatar_url (String, URI) —
# The avatar URL for the room
# :prev_batch (String) —
# The previous batch token for backfill
@thread_safety = ::Mutex.new
@listening = nil
@invitations = []
@file_transfers = []
@inbox = []
self
end
#
def listening?()
if @thread_safety.synchronize { @listening }
true
else
false
end
end
#
def start_listening(options={})
listener = @room.on_event.add_handler { |the_event| on_message(the_event) }
@thread_safety.synchronize { @listening = listener }
true
end
#
def stop_listening()
if self.listening?
the_id = 0
@thread_safety.synchronize { the_id = @listening[:id] }
@room.on_event.remove_handler(the_id)
@thread_safety.synchronize { @listening = nil }
true
else
false
end
end
#
def on_message(the_event=nil)
if the_event
# ### Event Types:
# See : https://matrix.org/docs/spec/client_server/latest#room-events
# See : https://matrix.org/docs/spec/client_server/latest#modules
# if ['m.room.message', 'm.room.member'].include?(the_event.type) && @room.client.mxid != the_event.sender.to_s
# the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event})
# @thread_safety.synchronize { @inbox << the_message }
# end
# Note: invitation = event.type 'm.room.member' (with the membership value set to "invite")
# Note: ?? = event.type 'm.room.membership' (??)
# Note: room-message = event.type 'm.room.message'
# Note: enrypted-message = event.type 'm.room.encrypted'
# Note: room-topic = event.type 'm.room.topic'
# Note: call invitation = event.type 'm.call.invite'
# Note: call answer = event.type 'm.call.answer'
put "Room Got: #{the_event.inspect}"
if @room.client.mxid != the_event.sender.to_s
the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event})
# ### Event Routing:
case the_event.type.to_s
when 'm.room.message', 'm.room.encrypted'
@thread_safety.synchronize { @inbox << the_message }
when 'm.room.member'
# Invitation?
if the_event.msgtype.to_s.include?('call.invite')
@thread_safety.synchronize { @invitations << the_message }
end
end
#
end
end
end
#
def invitations()
result = []
@thread_safety.synchronize { @invitations.each {|the_invitation| result << the_invitation} }
result
end
#
def next_message()
@thread_safety.synchronize { @inbox.shift }
end
#
def base_object()
@room
end
#
def room_id()
::GxG::Networking::MatrixID.new(@room.id)
end
#
def add_alias(room_alias)
@room.add_alias(room_alias)
end
#
def add_tag(tag, data={})
@room.add_tag(tag, **data)
end
#
def all_members(**parameters)
@room.all_members(**parameters).map {|item| ::GxG::Networking::MatrixUser.new(item)}
end
#
def allow_guests=(enable=false)
@room.allow_guests = enable
end
#
def avatar_url()
@room.avatar_url()
end
#
def avatar_url=(the_url=nil)
unless the_url.is_a?(::URI::MATRIX)
if the_url.is_a?(::String)
the_url = ::URI.parse(the_url)
else
raise ArgumentError, "You MUST provide a String or URI::MATRIX as the url."
end
end
@room.avatar_url = the_url
end
#
def backfill_messages(reverse=false, limit=10)
@room.backfill_messages(reverse, limit)
end
#
def ban_user(user_id=nil, reason="")
if user_id.is_any?(::String, ::GxG::Networking::MatrixUser)
if user_id.is_a?(::GxG::Networking::MatrixUser)
user_id = user_id.base_object
end
@room.ban_user(user_id, reason)
else
false
end
end
#
def display_name()
@room.display_name()
end
#
def get_account_data(type=nil)
if type
@room.get_account_data(type)
else
{}
end
end
#
def guest_access?()
@room.guest_access?()
end
#
def inspect()
"<MatrixRoom: #{@room.display_name()}>"
end
#
def invite_only=(enable=false)
if enable == true
@room.invite_only = true
else
@room.invite_only = false
end
end
#
def invite_only?()
@room.invite_only?()
end
#
def invite_user(user_id=nil)
if user_id.is_any?(::String, ::GxG::Networking::MatrixUser)
if user_id.is_a?(::GxG::Networking::MatrixUser)
user_id = user_id.base_object
end
@room.invite_user(user_id)
else
false
end
end
#
def joined_members()
@room.joined_members().map {|item| ::GxG::Networking::MatrixUser.new(item)}
end
#
def kick_user(user_id=nil, reason="")
if user_id.is_any?(::String, ::GxG::Networking::MatrixUser)
if user_id.is_a?(::GxG::Networking::MatrixUser)
user_id = user_id.base_object
end
@room.kick_user(user_id, reason)
else
false
end
end
#
def leave()
@room.leave()
end
#
def modify_required_power_levels(events = nil, params = {})
@room.modify_required_power_levels(events, params)
end
#
def modify_user_power_levels(users = nil, users_default = nil)
@room.modify_user_power_levels(users, users_default)
end
#
def redact_message(event_id, reason = nil)
@room.redact_message(event_id, reason)
end
#
def reload!()
@room.reload!()
end
#
def reload_aliases!()
@room.reload_aliases!()
end
#
def reload_name!()
@room.reload_name!()
end
#
def reload_topic!()
@room.reload_topic!()
end
#
def remove_tag(tag=nil)
if tag
@room.remove_tag(tag)
else
false
end
end
#
def report_message(event_id, reason="", score = -100)
if event_id.is_any?(::String, ::GxG::Networking::MatrixID)
if event_id.is_a?(::GxG::Networking::MatrixID)
event_id = event_id.base_object()
end
@room.report_message(event_id, reason, score)
else
false
end
end
#
def send_audio(url, name, audio_info={})
if url.is_any?(::String, ::URI::Generic)
@room.send_audio(url, name, audio_info)
else
false
end
end
#
def send_emote(text=nil)
if text
@room.send_emote(text)
end
end
#
def send_file(url, name, file_info={})
if url.is_any?(::String, ::URI::Generic)
@room.send_file(url, name, file_info)
else
false
end
end
#
def send_html(html, body=nil, msgtype=nil, format=nil)
@room.send_html(html, body, msgtype, format)
end
#
def send_image(url, name, image_info={})
if url.is_any?(::String, ::URI::Generic)
@room.send_image(url, name, image_info)
else
false
end
end
#
def send_location(geo_uri, name, thumbnail_url=nil, thumbnail_info={})
if geo_uri.is_any?(::String, ::URI::Generic) && thumbnail_url.is_any?(::String, ::URI::Generic)
@room.send_location(geo_uri, name, thumbnail_url, thumbnail_info)
else
false
end
end
#
def send_notice(text=nil)
if text
@room.send_notice(text)
end
end
#
def send_text(text=nil)
# Review : waiting for bug fix --> https://github.com/jruby/jruby/issues/6453
if text
@room.send_text(text)
end
end
#
def send_video(url, name, video_info={})
if url.is_any?(::String, ::URI::Generic)
@room.send_video(url, name, video_info)
else
false
end
end
#
def set_account_data(type, account_data)
if type.is_a?(::String) && account_data.is_a?(::Hash)
@room.set_account_data(type, account_data)
else
false
end
end
#
def set_user_profile(display_name=nil, avatar_url=nil, reason=nil)
if avatar_url.is_any?(::String, ::URI::Generic)
@room.set_user_profile(display_name, avatar_url, reason)
else
false
end
end
#
def tags()
@room.tags()
end
#
def unban_user(user_id)
if user_id.is_any?(::String, ::GxG::Networking::MatrixUser)
if user_id.is_a?(::GxG::Networking::MatrixUser)
user_id = user_id.base_object()
end
@room.unban_user(user_id)
else
false
end
end
end
#
class MatrixClient
# Review : ensure module supports are provided
# See : https://matrix.org/docs/spec/client_server/latest#modules
#
# Federation Notes:
# you need to make sure other servers can reach your server, and that can be done in one of three ways:
#
# have the server running directly at port 8448 on the server_name you've configured in homeserver.yaml
#
# have a webserver running at that server_name serving a file at /.well-known/matrix/server that tells other servers where the actual server is running
# mkdir -p /var/www/<YourServerName.tld>/.well-known/matrix
# echo '{ "m.server": "<YourServerName.tld>:443" }' > /var/www/<YourServerName.tld>/.well-known/matrix/server (or /var/www/html/.well-known/matrix/server)
#
# have an SRV record in your DNS at _matrix._tcp.server_name.tld. that points to where your server is.
#
# Once you have one of these working (the federation tester can verify if it is working), you're ready to talk to other servers
# See : https://matrix.org/blog/2020/04/06/running-your-own-secure-communication-service-with-matrix-and-jitsi#synapse
# Note: you do NOT have to enable registration (you can keep closed registration) to federate a room.
#
def initialize(the_url=nil, options={})
if the_url.is_a?(::MatrixSdk::Client)
@client = the_url
else
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URI."
end
# See: https://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk/MatrixSdk/Client.html#initialize-instance_method
@url = the_url
the_url = the_url.clone
@user_id = the_url.user
the_url.user = nil
@password = the_url.password
the_url.password = nil
unless @user_id && @password
raise ArgumentError, "You MUST provide a user_id and password in the URI."
end
@options = options.clone
@options[:user_id] = @user_id
unless [:all, :some, :none].include?(@options[:client_cache])
@options[:client_cache] = :all
end
if @options[:proxy]
if @options[:proxy].is_a?(::URI::Generic)
@options[:proxy_uri] = @options.delete(:proxy)
else
if @options[:proxy].is_a?(::String)
@options[:proxy_uri] = ::URI.parse(@options.delete(:proxy).to_s)
end
end
end
@client = ::MatrixSdk::Client.new(the_url, **@options)
end
@mxid = ::GxG::Networking::MatrixID.new("@#{@user_id}:#{the_url.hostname}")
@api = @client.api()
@session = nil
@thread_safety = ::Mutex.new
@invitations = []
@rooms = []
@watcher = nil
@listening = nil
#
self
end
#
def base_object()
@client
end
#
def inspect()
"<MatrixClient: @#{@user_id}:#{@url.hostname}>"
end
#
def api()
@api
end
#
def listening?()
if @thread_safety.synchronize { @listening }
true
else
false
end
end
#
def start_listening(options={})
listener = @client.on_event.add_handler { |the_event| on_message(the_event) }
@thread_safety.synchronize { @listening = listener }
true
end
#
def stop_listening()
if self.listening?
the_id = 0
@thread_safety.synchronize { the_id = @listening[:id] }
@client.on_event.remove_handler(the_id)
@thread_safety.synchronize { @listening = nil }
true
else
false
end
end
#
def on_message(the_event=nil)
if the_event
# ### Event Types:
# See : https://matrix.org/docs/spec/client_server/latest#room-events
# See : https://matrix.org/docs/spec/client_server/latest#modules
# if ['m.room.message', 'm.room.member'].include?(the_event.type) && @room.client.mxid != the_event.sender.to_s
# the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event})
# @thread_safety.synchronize { @inbox << the_message }
# end
# Note: invitation = event.type 'm.room.member' (with the membership value set to "invite")
# Note: ?? = event.type 'm.room.membership' (??)
# Note: room-message = event.type 'm.room.message'
# Note: enrypted-message = event.type 'm.room.encrypted'
# Note: room-topic = event.type 'm.room.topic'
# Note: call invitation = event.type 'm.call.invite'
# Note: call answer = event.type 'm.call.answer'
put "Got: #{the_event.inspect}"
if @room.client.mxid != the_event.sender.to_s
the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event})
# ### Event Routing:
case the_event.type.to_s
when 'm.room.member'
# Invitation?
if the_event.state_key.to_s.include?('invite')
@thread_safety.synchronize { @invitations << the_message }
end
end
#
end
end
end
#
def invitations()
result = []
@thread_safety.synchronize { @invitations.each {|the_invitation| result << the_invitation} }
result
end
#
def add_listener(room)
# Review : deprecated (I don't think I need this.)
if room.is_a?(::GxG::Networking::MatrixRoom)
room.start_listening
true
else
false
end
end
#
def login(user_id=nil, password=nil, options={})
if @client.logged_in?
false
else
unless user_id.is_a?(::String)
user_id = @user_id
end
unless password.is_a?(::String)
password = @password
end
unless options.is_a?(::Hash)
options = {}
end
options = options.clone
#
unless options[:sync_timeout].is_a?(::Integer)
options[:sync_timeout] = 15
end
unless options[:full_state] == true
options[:full_state] = false
end
@client.login(user_id, password, **options)
::GxG::Engine::reserve_event_descriptor()
@client.start_listener_thread({})
@watcher = $Dispatcher.every("5 seconds") do
if self.logged_in? && (! self.listening?)
self.start_listener_thread({})
end
end
self.start_listening
self.rooms.each do |the_room|
the_room.start_listening
end
true
end
end
#
def login_with_token(user_id=nil, token=nil, options={})
if @client.logged_in?()
false
else
if user_id.is_a?(::String) && token == nil
token = user_id
user_id = nil
end
unless user_id.is_a?(::String)
user_id = @user_id
end
unless password.is_a?(::String)
password = @password
end
unless options.is_a?(::Hash)
options = {}
end
options = options.clone
#
unless options[:sync_timeout].is_a?(::Integer)
options[:sync_timeout] = 15
end
unless options[:full_state] == true
options[:full_state] = false
end
@client.login_with_token(user_id, token, **options)
::GxG::Engine::reserve_event_descriptor()
@client.start_listener_thread({})
@watcher = $Dispatcher.every("5 seconds") do
if self.logged_in? && (! self.listening?)
self.start_listener_thread({})
end
end
self.start_listening
self.rooms.each do |the_room|
the_room.start_listening
end
true
end
end
#
def session()
@session
end
#
def logout()
$Dispatcher.cancel_timer(@watcher)
@watcher = nil
self.stop_listening
@client.stop_listener_thread()
::GxG::Engine::release_event_descriptor()
@client.logout()
true
end
#
def mxid()
@mxid
end
#
def access_token()
@api.access_token()
end
#
def device_id()
@api.device_id()
end
#
def proxy()
@api.proxy_uri()
end
#
def protocols()
@api.protocols()
end
#
def transaction_id()
@api.transaction_id()
end
#
def upload_file(the_file=nil, cdn_server_url=nil)
# Review : add CDN upload to chosen server.
result = nil
if the_file.is_any?(::File, ::GxG::Storage::BufferedSegments)
mime_type = ::MimeMagic.by_magic(the_file)
if mime_type
content_type = mime_type.type
else
content_type = "application/octet-stream"
end
if @api
options = {:body_stream => the_file, :headers => {"content-type" => content_type}}
result = @api.request(:post, :media_r0, "/upload", **options)
end
end
result
# data = Net::HTTP.get_response(URI(url))
# mxc_url = client.api.media_upload(data.body, data.content_type)[:content_uri] if data.is_a? Net::HTTPOK
# api.request(:post,
# :media_r0,
# '/upload',
# body_stream: open('./file'),
# headers: { 'content-type' => 'image/png' })
# => { :content_uri => "mxc://example.com/AQwafuaFswefuhsfAFAgsw" }
# Parameters:{
# method (Symbol) —
# The method to use, can be any of the ones under Net::HTTP
# api (Symbol) —
# The API symbol to use, :client_r0 is the current CS one
# path (String) —
# The API path to call, this is the part that comes after the API definition in the spec
# options (Hash) —
# Additional options to pass along to the request
# Options Hash (**options):
# :query (Hash) —
# Query parameters to set on the URL
# :body (Hash, String) —
# The body to attach to the request, will be JSON-encoded if sent as a hash
# :body_stream (IO) —
# A body stream to attach to the request
# :headers (Hash) —
# Additional headers to set on the request
# :skip_auth (Boolean) — default: false —
# Skip authentication}
end
#
def create_room(room_alias=nil, options={})
if room_alias.is_a?(::String)
room = @client.create_room(room_alias, **options)
if room
room = ::GxG::Networking::MatrixRoom.new(room)
@thread_safety.synchronize { @rooms << room }
room.start_listening
true
else
false
end
else
false
end
end
#
def ensure_room(room_id=nil)
if room_id.is_any?(::String, ::GxG::Networking::MatrixID)
if room_id.is_a?(::String)
room_id = ::GxG::Networking::MatrixID.new(room_id.to_s)
end
room = @client.ensure_room(room_id.base_object())
if room
id_list = self.rooms.map {|the_room| the_room.base_object.mxid.to_s }
if id_list.include?(room.mxid.to_s)
self.rooms.each do |the_room|
if the_room.base_object.mxid.to_s == room.mxid.to_s
room = the_room
break
end
end
if room.is_a?(::GxG::Networking::MatrixRoom)
room
else
nil
end
else
room = ::GxG::Networking::MatrixRoom.new(room)
@thread_safety.synchronize { @rooms << room }
room.start_listening
room
end
else
nil
end
else
nil
end
end
#
def find_room(room_id=nil, only_canonical=false)
if room_id.is_any?(::String, ::GxG::Networking::MatrixID)
if room_id.is_a?(::String)
room_id = ::GxG::Networking::MatrixID.new(room_id.to_s)
end
room = @client.find_room(room_id.base_object(), only_canonical)
if room
self.rooms.each do |the_room|
if the_room.base_object.mxid.to_s == room.mxid.to_s
room = the_room
break
end
end
if room.is_a?(::GxG::Networking::MatrixRoom)
room
else
::GxG::Networking::MatrixRoom.new(room)
end
else
nil
end
else
nil
end
end
#
def get_user(user_id)
if user_id.is_any?(::String, ::GxG::Networking::MatrixID)
if user_id.is_a?(::String)
user_id = ::GxG::Networking::MatrixID.new(user_id.to_s)
end
user = @client.get_user(user_id.base_object())
if user
::GxG::Networking::MatrixUser.new(user)
else
nil
end
else
nil
end
end
#
def join_room(room=nil, options={:server_name => []})
if room.is_a?(::GxG::Networking::MatrixRoom)
room = room.room_id.to_s
end
if room.is_any?(::GxG::Networking::MatrixID, ::MatrixSdk::MXID)
room = room.to_s
end
if room.is_a?(::String)
room = @client.join_room(room, **options)
if room
new_room = ::GxG::Networking::MatrixRoom.new(room)
@thread_safety.synchronize { @rooms << new_room }
new_room.start_listening
true
else
false
end
else
false
end
end
#
def listen_forever(timeout=30, bad_sync_timeout=5, sync_interval=0, options={})
@client.listen_forever(timeout, bad_sync_timeout, sync_interval, **options)
end
#
def listening?()
@client.listening?()
end
#
def logged_in?()
@client.logged_in?()
end
#
def user()
::GxG::Networking::MatrixUser.new(@client.mxid)
end
#
def presence()
@client.presence()
end
#
def public_rooms()
@client.public_rooms.map {|item| ::GxG::Networking::MatrixRoom.new(item)}
end
#
def register_as_guest()
@client.register_as_guest()
end
#
def register_with_password(username=nil, password=nil, options={})
if username.is_a?(::String) && password.is_a?(::String)
begin
@client.register_with_password(username, password, **options)
true
rescue Exception => exception
false
end
else
false
end
end
#
def registered_3pids()
@client.registered_3pids()
end
#
def reload_rooms!()
@client.reload_rooms!()
end
#
def remove_room_alias(room_id=nil)
if room_id.is_any?(::GxG::Networking::MatrixID, ::MatrixSdk::MXID)
room_id = room_id.to_s
end
if room_id.is_a?(::String)
room = @client.remove_room_alias(room_id)
if room
true
else
false
end
else
false
end
end
#
def rooms()
result = []
id_list = []
@thread_safety.synchronize {
@rooms.each do |the_room|
id_list << the_room.room_id.base_object.to_s
result << the_room
end
}
# Review : find a way to include direct rooms in @room :
# direct = client.api.get_account_data(client.mxid, 'm.direct')
# room_id = direct.fetch mxid.to_s.to_sym
# room = client.ensure_room room_id
@client.rooms.each do |item|
unless id_list.include?(item.room_id.to_s)
new_entry = ::GxG::Networking::MatrixRoom.new(item)
@thread_safety.synchronize { @rooms << new_entry }
result << new_entry
end
end
result
end
#
def set_presence(status, message=nil)
if [:online, :offline, :unavailable].include?(status)
@client.set_presence(status, message)
true
else
false
end
end
#
def start_listener_thread(options)
@client.start_listener_thread(**options)
end
#
def stop_listener_thread()
@client.stop_listener_thread()
end
#
def listen_for_events(skip_store_batch=false, options={})
# See : https://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk/MatrixSdk/Client.html#create_room-instance_method
unless self.listening?
unless options[:skip_store_batch] == true
options[:skip_store_batch] = false
end
@client.sync(**options)
end
end
#
end
MatrixSdk.logger = ::GxG::LOG
::GxG::Networking::DISPATCHER.register_client("matrix-https",::GxG::Networking::MatrixClient)
::GxG::Networking::DISPATCHER.register_client("matrix-http",::GxG::Networking::MatrixClient)
end
#
CLIENTS = GxG::Networking::DISPATCHER
#
end