Initial Commit

This commit is contained in:
G. Gibson 2021-11-16 08:41:53 -08:00
commit 4bb9f2f2ea
189 changed files with 135079 additions and 2 deletions

394
lib/gxg/bootstrap.rb Normal file
View file

@ -0,0 +1,394 @@
#!/usr/bin/env ruby
require "rubygems"
# determine the arch, platform with version, and ruby implementation with version
# store this in GxG::BOOTSTRAP
module GxG
if $GXGROOT
details = {:gxg_root => $GXGROOT}
else
details = {}
end
details[:architecture] = RUBY_PLATFORM.split("-")[0].downcase
details[:platform_version] = {}
# ### determine platform and environment details
if RUBY_PLATFORM == "java" || RUBY_PLATFORM == "java_64"
platform = nil
#
unless platform
raw_data = `uname -a`.downcase
# Attempt to determine the distro and version
[:freebsd, :openbsd, :netbsd, :dragonfly, :pcbsd].to_enum.each do |entry|
if raw_data.include?(entry.to_s)
platform = "bsd"
break
end
end
end
#
unless platform
if `uname -a`.downcase.include?("darwin")
platform = "darwin"
end
end
#
unless platform
if `uname -a`.downcase.include?("aix")
platform = "aix"
end
end
#
unless platform
if `uname -a`.downcase.include?("solaris")
platform = "solaris"
end
end
#
unless platform
raw_data = `uname -a`.downcase
# Attempt to determine the distro and version
[:debian, :redhat, :gentoo, :ubuntu, :linuxmint, :angstrom, :gobolinux, :caldera,
:suse, :mandrake, :fedora, :slackware, :united, :yellowdog, :sun, :puppy, :linux].to_enum.each do |entry|
if raw_data.include?(entry.to_s)
platform = "linux"
break
end
end
end
#
unless platform
raw_data = `ver`.to_s.gsub!("[","").to_s.gsub!("]","").to_s.split(" ")
if raw_data.include?("Windows")
if ([ -1 ].pack('l!').length == 8)
platform = "mswin64"
else
platform = "mswin32"
end
end
end
#
unless platform
platform = "unknown"
end
#
else
#
unless platform
raw_data = `uname -a`.downcase
# Attempt to determine the distro and version
[:debian, :redhat, :gentoo, :ubuntu, :linuxmint, :angstrom, :gobolinux, :caldera,
:suse, :mandrake, :fedora, :slackware, :united, :yellowdog, :sun, :puppy, :linux].to_enum.each do |entry|
if raw_data.include?(entry.to_s)
platform = "linux"
break
end
end
end
unless platform
if RUBY_PLATFORM.split("-")[1] == "unknown"
platform = RUBY_PLATFORM.split("-")[2]
else
if RUBY_ENGINE = "rbx"
platform = RUBY_PLATFORM.split("-")[2]
else
platform = RUBY_PLATFORM.split("-")[1]
end
end
end
end
case platform
# SOMEDAY: look into the legal aspects of solutions published in the public space.
# Attribution: http://www.unix.com/slackware/23652-determine-linux-version.html
# and: http://www.novell.com/coolsolutions/feature/11251.html
when /mswin32/
details[:platform] = :windows
details[:environment] = :mswin32
details[:environment_variant] = :standard
details[:environment_version] = nil
when /mswin64/
details[:platform] = :windows
details[:environment] = :mswin64
details[:environment_variant] = :standard
details[:environment_version] = nil
when /mingw32/
details[:platform] = :windows
details[:environment] = :mingw32
details[:environment_variant] = :standard
details[:environment_version] = nil
when /bccwin32/
details[:platform] = :windows
details[:environment] = :bccwin32
details[:environment_variant] = :standard
details[:environment_version] = nil
when /cygwin/
details[:platform] = :windows
# TODO: find out how deep cygwin goes (looks like a linux kernel??)
# using the kernel version as the platform version
details[:platform_version] = {:interpret => `uname -r`}
details[:environment] = :cygwin
details[:environment_variant] = :standard
details[:environment_version] = nil
when /solaris|sunos/
details[:platform] = :solaris
details[:environment] = :solaris
details[:environment_variant] = :standard
details[:environment_version] = nil
raw_text = `more /etc/release`
unless raw_text.downcase.include?("no such file")
details[:environment_version] = {:interpret => raw_text}
end
when /bsd/, /dragonfly/
details[:platform] = :bsd
# using the kernel version as the platform version
details[:platform_version] = {:interpret => `uname -r`}
details[:environment] = :bsd
temp_env = `uname -a`.downcase
# Attempt to determine the distro and version
[:freebsd, :openbsd, :netbsd, :dragonfly, :pcbsd].to_enum.each do |entry|
if temp_env.include?(entry.to_s)
details[:environment] = entry
break
end
end
details[:environment_variant] = :standard
details[:environment_version] = nil
when /darwin|mac os|macos/
details[:platform] = :darwin
# using the kernel version as the platform version
details[:platform_version] = {:interpret => `uname -r`}
if (::File.exists?("/System") && ::File.exists?("/Library") && ::File.exists?("/Volumes") && ::File.exists?("/Users"))
details[:environment] = :macos
else
details[:environment] = :darwin
end
details[:environment_variant] = :standard
details[:environment_version] = nil
when /linux/
# ###
details[:platform] = :linux
# using the kernel version as the platform version
details[:platform_version] = {:interpret => `uname -r`}
details[:environment] = :linux
temp_env = `uname -a`.downcase
# Attempt to determine the distro and version
[:debian, :redhat, :gentoo, :ubuntu, :linuxmint, :angstrom, :gobolinux, :caldera,
:suse, :mandrake, :fedora, :slackware, :united, :yellowdog, :sun, :puppy].to_enum.each do |entry|
if temp_env.include?(entry.to_s)
details[:environment] = entry
break
end
end
details[:environment_variant] = :standard
details[:environment_version] = nil
raw_text = ""
raw_version = {}
case details[:environment]
when :debian
raw_text = `more /etc/debian_version`
if raw_text.downcase.include?("no such file")
raw_text = `more /etc/debian_release`
end
when :ubuntu
raw_text = `more /etc/lsb-release | grep 'DISTRIB_RELEASE' | sed -e 's#[^0-9.]##g'`
# LATER: @determine :environment_variant : attempt to determine if :desktop or :server or :what?
# unless raw_text.downcase.include?("no such file")
# temp_text = `more /etc/lsb-release | grep 'DISTRIB_CODENAME' | sed -e 's#[^a-zA-Z=]##g'`.split("=")[1]
# details[:environment_variant] = temp_text.downcase.to_sym
#end
when :suse
raw_text = `more /etc/SuSE-version | grep 'VERSION' | sed -e 's#[^0-9.]##g'`
if raw_text.downcase.include?("no such file")
raw_text = `more /etc/SUSE-version | grep 'VERSION' | sed -e 's#[^0-9.]##g'`
end
when :united
raw_text = `more /etc/UnitedLinux-release`
when :slackware
raw_text = `more /etc/slackware-release`
if raw_text.downcase.include?("no such file")
raw_text = `more /etc/slackware-version`
end
when :redhat
the_file = "redhat-release"
temp_text = `more /etc/#{the_file}`
if temp_text.downcase.include?("no such file")
the_file = "redhat_version"
temp_text = `more /etc/#{the_file}`
end
unless temp_text.downcase.include?("no such file")
if temp_text.downcase.include?("scientific")
details[:environment_variant] = :scientific
raw_text = `more /etc/#{the_file} | sed -e 's#[^0-9.]##g' | cut -c1`
else
if temp_text.downcase.include?("enterprise")
details[:environment_variant] = :enterprise
else
if temp_text.downcase.include?("cern")
details[:environment_variant] = :cern
end
end
raw_text = `more /etc/#{the_file} | sed -e 's#[^0-9.]##g' -e 's#7[0-2.]#73.#'`
end
end
#
else
raw_text = `more /etc/#{details[:environment].to_s}-release`
end
#
if (raw_text.downcase.include?("no such file") && raw_text.size == 0)
if raw_version.keys.size > 0
details[:environment_version] = raw_version
end
else
details[:environment_version] = {:interpret => raw_text}.merge(raw_version)
end
#
when /aix/
details[:platform] = :aix
details[:environment] = :aix
details[:environment_variant] = :standard
details[:environment_version] = nil
else
# unknown (unsupported) platform
details[:platform] = RUBY_PLATFORM.split("-")[1].to_s.downcase.to_sym
details[:environment] = details[:platform]
details[:environment_variant] = :standard
details[:environment_version] = nil
end
# Arch detail determination: Special thanks to Jari Bakken (Jarib) at https://github.com/jarib/childprocess/blob/master/lib/childprocess.rb
if details[:architecture] == "java"
# TODO: get arch via other means
case details[:platform]
when :windows
if ([ -1 ].pack('l!').length == 8)
details[:architecture] = "x86_64"
else
details[:architecture] = "i686"
end
when :linux, :bsd, :darwin, :aix, :solaris
raw_data = `uname -a`.downcase
[:i686, :i586, :i486, :i386, :amd64, :x86_64, :ppc, :powerpc, :arm, :mips].to_enum(:each).each do |the_arch|
if raw_data.include?(the_arch.to_s)
details[:architecture] = the_arch.to_s
break
end
end
end
end
#
case details[:architecture]
when /i[3456]86/
if (details[:platform] == :darwin && ([ -1 ].pack('l!').length == 8))
details[:architecture] = :x86_64
else
# details[:architecture] = :i386
# Jarib's solution does not help folks deal with the differences between the 32bit arch flavors. I'll be more specific
details[:architecture] = details[:architecture].to_sym
end
when /amd64|x86_64/
details[:architecture] = :x86_64
when /ppc|powerpc/
if [ -1 ].pack('l!').length == 8
details[:architecture] = :powerpc_64
else
details[:architecture] = :powerpc
end
else
if [ -1 ].pack('l!').length == 8
details[:architecture] = (details[:architecture] + "_64").to_sym
else
details[:architecture] = details[:architecture].to_sym
end
end
#
# ### propose private gem and library paths for standalone support:
details[:common_gems] = "common"
details[:native_gems] = "#{details[:architecture].to_s}-#{details[:platform].to_s}-#{RUBY_ENGINE.to_s.downcase}-#{RUBY_VERSION.to_s.downcase}"
details[:native_libraries] = details[:native_gems]
# ### Gather CPU data by platform
# I would really prefer that ALL architectures use the *same* detail format for profiling a given CPU.
details[:processors] = []
case details[:platform]
when :windows
# Review : working with wmic on windows takes Sooooo long that I had to put this block in a thread at startup. (not pretty but it works)
# Review : waiting upon a bug fix for JRUBY under Windows --> Null pointer exception thrown.
# Review : See - https://github.com/jruby/jruby/issues/6440
# Sys::CPU.processors()
when :bsd
gxg_template = {
:processor=>0,
:vendor_id=>"",
:cpu_family=> -1,
:model=> -1,
:model_name=>"",
:stepping=> -1,
:microcode=>"",
:cpu_mhz=>0.0,
:cache_size=>0,
:physical_id=> -1,
:siblings=> -1,
:core_id=> -1,
:cpu_cores=> 0,
:apicid=> -1,
:initial_apicid=>0,
:fpu=>false,
:fpu_exception=>false,
:cpuid_level=> -1,
:wp=>false,
:flags=>[],
:bugs=>"",
:bogomips=> 0.0,
:tlb_size=>"",
:clflush_size=> -1,
:cache_alignment=> -1,
:address_sizes=>{
:physical_bits=> -1,
:virtual_bits=> -1
},
:power_management=>""
}
#
when :linux
# for now: work around for unworkable Struct behavioral variation in Sys::CPU :P
# Gather CPU(n) data
cpu_entry = {}
lastcpu = 0
parse_cpu_info = Proc.new do |raw_line|
line = raw_line.split(":")
if line[0].to_s.size > 1
key = line[0].to_s.downcase.strip.gsub(/\s+/,"_")
value = line[1].to_s.strip
#
if value.to_s.size > 0
if value.to_s.include?("yes")
value = true
end
if value.to_s.include?("no")
value = false
end
#
else
value = nil
end
cpu_entry[(key)] = value
else
if cpu_entry.keys.size > 0
details[:processors] << cpu_entry
cpu_entry = {}
end
next
end
#
end
::IO::foreach("/proc/cpuinfo").each {|line| parse_cpu_info.call(line)}
if cpu_entry.keys.size > 0
details[:processors] << cpu_entry
end
#
end
# cleanup
#
# return final result
BOOTSTRAP=details
# for testing innards
# puts details.inspect()
end
# reset rubygems paths if in stand-alone mode

4366
lib/gxg/gxg_augmented.rb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,964 @@
# ### GxG Communications Layer
# Note: MatrixClient --> 64K max message size (gross), less than 1K overhead --> so, 63K max payload size (64512 bytes).
module GxG
BRIDGES = {}
BRIDGES_AVAILABLE = {}
module Communications
BRIDGE_THREAD_SAFETY = ::Mutex.new
def self.bridge(the_uuid)
if ::GxG::valid_uuid?(the_uuid)
BRIDGE_THREAD_SAFETY.synchronize { ::GxG::BRIDGES[(the_uuid)] }
else
nil
end
end
#
def self.open_bridge(process=nil, the_url=nil, options=nil)
new_bridge = ::GxG::Communications::Bridge.new(process, the_url, options)
BRIDGE_THREAD_SAFETY.synchronize { ::GxG::BRIDGES[(new_bridge.uuid)] = new_bridge }
new_bridge.uuid
end
#
def self.close_bridge(the_uuid=nil)
if ::GxG::valid_uuid?(the_uuid)
the_bridge = BRIDGE_THREAD_SAFETY.synchronize { ::GxG::BRIDGES.delete(the_uuid) }
if the_bridge
the_bridge.close
timeout = ::Chronic::parse("30 seconds from now").to_f
while the_bridge.open? do
sleep 1.0
if Time.now.to_f >= timeout
break
end
end
end
true
else
false
end
end
# ### Adapters
class BridgeAdapter
# WARNING: Only instantiate subclasses
def self.protocol()
:unspecified
end
#
def self.abilities()
[]
end
#
def self.limits()
{}
end
#
def self.templates()
{}
end
#
def initialize(the_process=nil, the_url=nil, options=nil)
if the_url.is_a?(::String)
the_url = ::URI::parse(the_url)
end
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URL as a String or URI."
end
unless the_process
raise ArgumentError, "You MUST provide a Service or Application object to interact with."
end
@process = the_process
@url = the_url
@options = (options || {})
unless @options.is_a?(::Hash)
raise ArgumentError, "You must provide options as a Hash."
end
@active = false
@connector = nil
@thread_safety = ::Mutex.new
#
@uuid = ::GxG::uuid_generate.to_sym
self
end
#
def uuid()
@uuid
end
#
def my_id()
nil
end
#
def open?()
if @connector
if @connector.closed?
@active = false
else
@active = true
end
end
@active
end
#
def open()
unless self.open?
@active = @connector.login(@url, @options)
end
@active
end
#
def close()
if self.open?
@connector.logout()
@active = false
end
true
end
# ### Information Refresh
def notify(details=nil)
#
end
#
# ### Basic Entity Support
def entitiies()
[]
end
#
def entity_status(the_address=nil)
:online
end
# ### Basic Channel Support
def channels()
{}
end
end
# ### Matrix
class BridgeAdapterMatrix < ::GxG::Communications::BridgeAdapter
def self.protocol()
:"matrix-https"
end
#
def self.abilities()
[:message, :file_transfer, :request]
end
#
def self.limits()
{:message_size => 64512, :file_transfer_size => nil, :request_size => 64512}
end
#
def self.templates()
{}
end
#
private
def decode_message(the_message=nil)
result = message
if message.is_a?(::GxG::Events::Message)
fallback_key = nil
key = nil
message_sender = message[:sender].to_s
@thread_safety.synchronize {
if @keychain[(message_sender.to_s)]
fallback_key = @keychain[(message_sender.to_s)][:original]
key = @keychain[(message_sender.to_s)][:current]
end
}
new_body = nil
if key && fallback_key
if message[:body].is_a?(::String)
begin
if message[:body].base64?
new_body = message[:body].decode64.decrypt(key)
else
new_body = message[:body].decrypt(key)
end
new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true}))
rescue Exception => the_error
begin
if message[:body].base64?
new_body = message[:body].decode64.decrypt(fallback_key)
else
new_body = message[:body].decrypt(fallback_key)
end
new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true}))
rescue Exception => the_error
message[:bridge_unintelligible] = true
log_error({:error => the_error})
new_body = nil
end
end
end
else
if message[:body].is_a?(::String)
if message[:body].base64?
new_body = message[:body].decode64
else
new_body = message[:body]
end
begin
new_body = ::Hash::gxg_import(::JSON::parse(new_body, {:symbolize_names => true}))
rescue Exception => the_error
log_error({:error => the_error})
new_body = nil
end
end
end
if new_body.is_a?(::Hash)
message[:body] = new_body
end
end
result
end
#
public
#
def initialize(the_process=nil, the_url=nil, options={:sasl => :digest, :digest => true})
super(the_process, the_url, options)
unless @options[:sasl]
@options = {:sasl => :digest, :digest => true}
end
#
# Channels format: {<uuid> => {:channel => <XMPPConversation>, :entities => [<jid-string>, ...]}}
@channels = {}
# Keychain format: {:original => "", :current => ""}
@keychain = {}
# Messages format: {<uuid> => [<::GxG::Events::Message>, ...]}
@messages = {}
@messages_busy_semaphore = false
@invitations = {}
@file_transfers = {}
#
@connector = ::GxG::Networking::MatrixClient.new(the_url)
self.open
#
self
end
#
def my_id()
if @connector
@connector.my_jid.to_s
else
nil
end
end
# ### Information Refresh
def notify(details=nil)
# Review : totally re-write this --> VFS integration, Matrix-based approach (adapt)
if details.is_a?(::Hash)
if @process
if @process.respond_to?(:dispatcher) && @process.respond_to?(:respond_to_event?)
case details[:event]
when :new_invitation
active_list = {}
@connector.invitations.each do |the_invitation|
active_list[(the_invitation.uuid)] = {:invitation => the_invitation, :title => the_invitation[:title].to_s, :sender => the_invitation[:sender].to_s, :type => the_invitation[:type].to_s}
end
@thread_safety.synchronize { @invitations = active_list }
#
if @process.respond_to_event?(:new_invitation)
@process.dispatcher.post_event(:communications) do
@process.call_event({:new_invitation => {:invitation => details[:invitation], :sender => details[:sender], :at => ::DateTime.now}})
end
end
when :new_file_transfer
# First, update Bridge Manifest
active_list = {}
@connector.file_transfers.each do |the_transfer|
reference = the_transfer.reference
file_name = the_transfer.file_details[:filename]
file_size = the_transfer.file_details[:size]
sender = the_transfer.info[:sender]
# set download path
unless ::File.exists?(GxG::SYSTEM_PATHS[:temporary] + "/" + reference.to_s)
::FileUtils.mkpath(GxG::SYSTEM_PATHS[:temporary] + "/" + reference.to_s)
end
the_transfer.download_directory = (GxG::SYSTEM_PATHS[:temporary] + "/" + reference.to_s)
# store entry
active_list[(reference)] = {:transfer => the_transfer, :sender => sender, :file => file_name, :size => file_size, :path => ("/System/Temporary/" + reference.to_s + "/" + file_name)}
end
@thread_safety.synchronize { @file_transfers = active_list }
if @process.respond_to_event?(:new_file_transfer)
@process.dispatcher.post_event(:communications) do
@process.call_event({:new_file_transfer => {:transfer => details[:transfer], :sender => details[:sender], :at => ::DateTime.now}})
end
end
when :file_transfer_complete, :file_transfer_cancelled, :file_transfer_error
notification = {}
notification[(details[:event])] = {:transfer => details[:transfer], :at => ::DateTime.now}
#
active_list = {}
@connector.file_transfers.each do |the_transfer|
reference = the_transfer.reference
file_name = the_transfer.file_details[:filename]
file_size = the_transfer.file_details[:size]
sender = the_transfer.info[:sender]
# store entry
active_list[(reference)] = {:transfer => the_transfer, :sender => sender, :file => file_name, :size => file_size, :path => ("/System/Temporary/" + reference.to_s + "/" + file_name)}
end
@thread_safety.synchronize { @file_transfers = active_list }
#
if @process.respond_to_event?(details[:event])
@process.call_event(notification)
end
when :file_transfer_progress
if @process.respond_to_event?(:file_transfer_progress)
@process.call_event({:file_transfer_progress => {:transfer => details[:transfer], :progress => details[:progress], :at => ::DateTime.now}})
end
when :new_message, :new_private_message, :new_announcement, :new_error
# ### unless channel exists - add it
the_channel = @thread_safety.synchronize { @channels[(details[:conversation].to_sym)] }
unless the_channel
found = nil
@connector.conversations.each do |channel_object|
if details[:conversation].to_sym == channel_object.uuid
found = channel_object
break
end
end
if found
@thread_safety.synchronize { @channels[(details[:conversation].to_sym)] = {:channel => found, :title => found.title()}}
the_channel = found
end
end
# ### get messages
if the_channel
unless @thread_safety.synchronize { @messages[(the_channel.uuid)].is_a?(::Array) }
@thread_safety.synchronize { @messages[(the_channel.uuid)] = [] }
end
the_channel.process_received() do |the_message|
# Note: this will pick up other messages from the adapter now, but it is ok - a notification will be generated on EACH message.
the_message[:channel] = the_channel.uuid
@thread_safety.synchronize { @messages[(the_channel.uuid)] << the_message }
end
end
# Review : differentiate beteween private and open messages? How to do?
if @process.respond_to_event?(:new_message)
notification = {}
notification[:new_message] = {:channel => details[:conversation], :message => details[:message], :sender => details[:sender].to_s, :at => ::DateTime.now}
#
@process.dispatcher.post_event(:communications) do
@process.call_event(notification)
end
end
#
end
end
end
end
end
# ### Keychain
def set_key(recipient=nil, password=nil)
result = false
if recipient.to_s.valid_jid? && password.is_a?(::String)
@thread_safety.synchronize {
if @keychain[(recipient.to_s)]
@keychain[(recipient.to_s)][:current] = password
else
@keychain[(recipient.to_s)] = {:original => password, :current => password}
end
result = true
}
end
result
end
# ### Invitations
def accept_invitation(the_invitation_uuid=nil)
result = false
if ::GxG::valid_uuid?(the_invitation_uuid)
found = nil
@thread_safety.synchronize {
found = @invitations[(the_invitation_uuid.to_sym)]
}
if found
found[:invitation].accept()
result = true
end
end
result
end
#
def decline_invitation(the_invitation_uuid=nil)
result = false
if ::GxG::valid_uuid?(the_invitation_uuid)
found = nil
@thread_safety.synchronize {
found = @invitations[(the_invitation_uuid.to_sym)]
}
if found
found[:invitation].decline()
result = true
end
end
result
end
# ### File Transfers
def file_transfers()
result = {}
@thread_safety.synchronize {
@file_transfers.each_pair do |the_uuid, the_record|
result[(the_uuid)] = {:sender => the_record[:sender], :file => the_record[:file], :size => the_record[:size], :path => the_record[:path]}
end
}
result
end
#
def accept_file_transfer(the_transfer_uuid=nil)
result = false
if ::GxG::valid_uuid?(the_transfer_uuid)
found = nil
@thread_safety.synchronize {
found = @file_transfers[(the_transfer_uuid.to_sym)]
}
if found
found[:transfer].accept()
result = true
end
end
result
end
#
def decline_file_transfer(the_transfer_uuid=nil)
result = false
if ::GxG::valid_uuid?(the_transfer_uuid)
found = nil
@thread_safety.synchronize {
found = @file_transfers[(the_transfer_uuid.to_sym)]
}
if found
found[:transfer].decline()
if ::File.exists?(GxG::SYSTEM_PATHS[:temporary] + "/" + found[:transfer].reference.to_s)
::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s)
end
result = true
end
end
result
end
#
def cancel_file_transfer(the_transfer_uuid=nil)
result = false
if ::GxG::valid_uuid?(the_transfer_uuid)
found = nil
@thread_safety.synchronize {
found = @file_transfers[(the_transfer_uuid.to_sym)]
}
if found
found[:transfer].cancel()
if ::File.exists?(GxG::SYSTEM_PATHS[:temporary] + "/" + found[:transfer].reference.to_s)
::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s)
end
result = true
end
end
result
end
# ### Entities
def entities()
result = []
@connector.buddies().each do |record|
new_record = {:id => "", :title => "Untitled", :groups => [], :status => :offline}
new_record[:id] = record[:jid]
new_record[:title] = record[:title]
new_record[:groups] = record[:groups]
case record[:status]
when :chat, :normal, :available
new_record[:status] = :online
when :dnd
new_record[:status] = :busy
when :away
new_record[:status] = :away
when :xa
new_record[:status] = :extended_away
when :unavailable, :offline
new_record[:status] = :offline
when :error
new_record[:status] = :error
end
result << new_record
end
result
end
#
def entity_status(the_address=nil)
result = :invalid_address
if the_address.valid_jid?()
result = :unknown_address
self.entities.each do |the_record|
if the_record[:id] == the_address
result = the_record[:status]
break
end
end
end
result
end
#
def status()
result = :offline
case @adapter.get_status
when :chat, :normal, :available
result = :online
when :dnd
result = :busy
when :away
result = :away
when :xa
result = :extended_away
when :unavailable, :offline
result = :offline
when :error
result = :error
end
result
end
#
def status=(the_status=nil, message=nil)
result = :available
case the_status
when :online
result = :available
when :busy
result = :dnd
when :away
result = :away
when :extended_away
result = :xa
when :offline
result = :unavailable
when :error
result = :error
end
@adapter.set_status(result, message)
end
# ### Basic Channel Support
def channels()
result = {}
@thread_safety.synchronize {
@channels.each_pair do |the_uuid, the_record|
result[(the_uuid)] = the_record[:title]
end
}
result
end
#
def associate(recipient=nil)
result = false
if recipient.is_a?(::String)
if recipient.valid_jid?
@adapter.add_buddy(recipient)
result = true
end
end
result
end
#
def disassociate(recipient=nil)
result = false
if recipient.is_a?(::String)
if recipient.valid_jid?
@adapter.remove_buddy(recipient)
result = true
end
end
result
end
#
def open_channel(with_jid=nil, password=nil, configuration={})
result = nil
if with_jid.is_a?(::String)
if with_jid.valid_jid? && configuration.is_any?(::Hash, ::GxG::Database::PersistedHash)
# If channel already exists and they attempt a repeat opening action:
@thread_safety.synchronize {
@channels.each_pair do |the_uuid, the_record|
if with_jid.to_s == the_record.jid.to_s
result = the_record.uuid
break
end
end
}
unless result
# Channel needs to be constructed:
if @connector.create_conversation(with_jid, password, configuration) == true
existing_channels = self.channels()
found = nil
@thread_safety.synchronize {
@connector.conversations.each do |channel_object|
if existing_channels.keys.include?(channel_object.uuid)
next
else
found = channel_object
@channels[(channel_object.uuid)] = {:channel => found, :title => found.title()}
break
end
end
}
if found
result = channel_object.uuid
if password
if channel_object[:type] == :chat
# on :groupchat password is only used for access, not encryption.
self.set_key(with_jid, password)
end
end
end
#
end
end
end
end
result
end
#
def join_channel(channel_jid=nil, password=nil, options={})
result = nil
if channel_jid.is_a?(::String)
if channel_jid.valid_jid? && options.is_any?(::Hash, ::GxG::Database::PersistedHash)
if @connector.join_conversation(channel_jid, password, options) == true
existing_channels = self.channels()
found = nil
@thread_safety.synchronize {
@connector.conversations.each do |channel_object|
if existing_channels.keys.include?(channel_object.uuid)
next
else
found = channel_object
@channels[(channel_object.uuid)] = {:channel => found, :title => found.title()}
break
end
end
}
if found
result = channel_object.uuid
end
#
end
end
end
result
end
#
def close_channel(the_channel_uuid=nil)
if ::GxG::valid_uuid?(the_channel_uuid)
the_channel = nil
@thread_safety.synchronize {
if @channels[(the_channel_uuid.to_sym)].is_a?(::Hash)
the_channel = @channels.delete(the_channel_uuid.to_sym)[:channel]
end
}
if the_channel
the_channel.leave("{ \"exit_channel\":\"#{the_channel.my_jid.to_s}\" }")
true
else
false
end
else
false
end
end
# ### Messages
def send_message(the_channel_uuid=nil, message=nil, recipient=nil, use_fallback_key=false)
result = false
#
if ::GxG::valid_uuid?(the_channel_uuid)
the_channel = nil
@thread_safety.synchronize {
if @channels[(the_channel_uuid.to_sym)].is_a?(::Hash)
the_channel = @channels[(the_channel_uuid.to_sym)][:channel]
end
}
if the_channel
if message.is_any?(::String, ::Hash, ::GxG::Database::PersistedHash, ::GxG::Events::Message)
if message.is_a?(::String)
message = new_message({:sender => @connector.my_jid.to_s, :body => {:message => message}.gxg_export.to_json})
end
if message.is_a?(::Hash)
message = new_message({:sender => @connector.my_jid.to_s, :body => message.gxg_export.to_json})
end
if message.is_a?(::GxG::Database::PersistedHash)
message = new_message({:sender => @connector.my_jid.to_s, :body => message.sync_export.gxg_export.to_json})
end
if message.is_a?(::GxG::Events::Message)
if message[:body].is_a?(::Hash)
message[:body] = message[:body].gxg_export.to_json
end
if message[:body].is_a?(::GxG::Database::PersistedHash)
message[:body] = message[:body].sync_export.gxg_export.to_json
end
end
#
if message.is_a?(::GxG::Events::Message)
# Reset :sender if a ruby object is the current setting. (bridge addressing translation)
unless message[:sender].is_a?(::String)
message[:sender] = @connector.my_jid.to_s
end
# encrypt body?
key = nil
if recipient.is_a?(::String)
intended_recipient = recipient
else
intended_recipient = the_channel.jid()
end
@thread_safety.synchronize {
if @keychain[(intended_recipient.to_s)]
if use_fallback_key == true
key = @keychain[(intended_recipient.to_s)][:original]
else
key = @keychain[(intended_recipient.to_s)][:current]
end
end
}
if key.is_a?(::String)
message[:body] = message[:body].encrypt(key).encode64
else
message[:body] = message[:body].encode64
end
# send message
result = the_channel.say_something(message, recipient)
# Review : use this next little bit of code in the request/reply code:
# if result == true
# message.succeed(message)
# else
# message.fail(message)
# end
end
else
raise ArgumentError, "You MUST provide the message as a String, Hash, GxG::Database::PersistedHash, or GxG::Events::Message; NOT #{message.class} ."
end
else
raise ArgumentError, "Invalid Channel selector, the Channel was not found: #{the_channel.inspect} . Try opening the channel first."
end
else
raise ArgumentError, "Invalid Channel selector: #{the_channel.inspect} . You MUST provide a valid UUID."
end
result
end
#
def next_message(the_channel_uuid=nil)
result = nil
if ::GxG::valid_uuid?(the_channel_uuid)
@thread_safety.synchronize {
if @messages[(the_channel_uuid.to_sym)].is_a?(::Array)
result = @messages[(the_channel_uuid.to_sym)].shift
end
}
if result
result = decode_message(result)
end
end
result
end
#
def all_messages(the_channel_uuid=nil)
result = []
if ::GxG::valid_uuid?(the_channel_uuid)
@thread_safety.synchronize {
if @messages[(the_channel_uuid.to_sym)].is_a?(::Array)
@messages[(the_channel_uuid.to_sym)].size.times do
result << decode_message(@messages[(the_channel_uuid.to_sym)].shift)
end
end
}
end
result
end
#
def get_message(the_channel_uuid=nil, the_message_uuid=nil)
result = nil
if ::GxG::valid_uuid?(the_channel_uuid) && the_message_uuid
@thread_safety.synchronize {
if @messages[(the_channel_uuid.to_sym)].is_a?(::Array)
@messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index|
if the_message_uuid == the_message.id()
result = @messages[(the_channel_uuid.to_sym)].delete_at(the_index)
break
end
end
end
}
if result
result = decode_message(result)
end
end
result
end
#
def get_messages_by_context(the_channel_uuid=nil, the_context_uuid=nil)
result = []
if ::GxG::valid_uuid?(the_channel_uuid) && the_context_uuid
@thread_safety.synchronize {
if @messages[(the_channel_uuid.to_sym)].is_a?(::Array)
@messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index|
if the_context_uuid.to_s.to_sym == the_message[:context].to_s.to_sym
result << @messages[(the_channel_uuid.to_sym)].delete_at(the_index)
end
end
end
}
if result
result = decode_message(result)
end
end
result
end
#
def get_messages_by_sender(the_channel_uuid=nil, the_sender=nil)
result = []
if ::GxG::valid_uuid?(the_channel_uuid) && the_sender
@thread_safety.synchronize {
if @messages[(the_channel_uuid.to_sym)].is_a?(::Array)
@messages[(the_channel_uuid.to_sym)].each_with_index do |the_message, the_index|
if the_sender.to_s == the_message[:sender].to_s
result << @messages[(the_channel_uuid.to_sym)].delete_at(the_index)
end
end
end
}
if result
result = decode_message(result)
end
end
result
end
#
end
#
class Bridge
def initialize(process=nil, the_url=nil, options=nil)
# Note: process (required) is a Service or Application instance only
unless process.is_a?(::GxG::Services::Service)
raise ArgumentError, "You MUST provide a Service or Application instance to bind to."
end
# Ensure protocol supported
if the_url.is_a?(::String)
the_url = ::URI::parse(the_url)
end
unless the_url.is_a?(::URI::Generic)
raise ArgumentError, "You MUST provide a valid URL as a String or URI."
end
the_type = ::GxG::BRIDGES_AVAILABLE[(the_url.scheme.to_sym)]
unless the_type
raise ArgumentError, "Sorry, #{the_url.scheme.to_sym.inspect} is not a supported protocol."
end
# Variables
@process = process
@url = the_url
@adapter = the_type.new(self, @url, options)
@uuid = ::GxG::uuid_generate.to_sym
@interface = {}
self.on(:interface, "Available Commands") do
self.interface()
end
self
end
#
def uuid()
@uuid
end
# ### Command Interface
def on(the_event, description=nil, &block)
unless the_event.is_a?(::Symbol)
raise ArgumentError, "You must specify an event listener with a unique Symbol."
end
unless block.respond_to?(:call)
raise ArgumentError, "You must provide an event code block to execute."
end
unless description
description = "{ '#{the_event.to_s}': '(your_data_payload)' }"
end
@interface[(the_event)] = {:description => description, :procedure => block}
true
end
#
def call_event(operation_envelope=nil)
result = nil
if operation_envelope.is_a?(::Hash)
the_event = operation_envelope.keys[0]
if the_event
data = operation_envelope[(the_event)]
if @interface[(the_event)]
begin
result = {:result => @interface[(the_event)][:procedure].call(@process, self, data)}
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:data => data}})
result = {:result => nil, :error => the_error.to_s}
end
else
result = {:result => nil, :error => "Command #{the_event.inspect} Not Found"}
end
end
end
result
end
#
def interface()
result = {}
@interface.each_pair do |the_event, the_record|
result[(the_event)] = the_record[:description]
end
result
end
#
def respond_to_event?(the_event=nil)
result = false
if the_event.is_a?(::Symbol)
if @interface[(the_event)]
result = true
end
end
result
end
# ### Request/Reply Support
def request(the_channel_uuid=nil, request_body=nil, address=nil, options={})
result = nil
#
if ::GxG::valid_uuid?(the_channel_uuid) && request_body.is_a?(::GxG::Events::Message) && address
payload = nil
context = ::GxG::uuid_generate
if address.to_s.valid_jid?
# Point-to-Point Request
payload = new_message({:sender => @adapter.jid().to_s, :body => request_body})
payload[:context] = context.to_s
if payload
@adapter.send_message(the_channel_uuid, payload, address)
the_reply_list = @adapter.get_messages_by_context(the_channel_uuid, context)
if options.is_a?(::Hash)
if options[:timeout].is_a?(::Numeric)
timeout = Time.now.to_f + options[:timeout].to_f
else
timeout = Time.now.to_f + 30.0
end
else
timeout = Time.now.to_f + 30.0
end
until the_reply_list.size > 0 do
the_reply_list = @adapter.get_messages_by_context(the_channel_uuid, context)
sleep 0.5
if Time.now.to_f >= timeout
break
end
end
if the_reply_list.size > 0
result = the_reply_list[0]
end
end
end
end
#
result
end
#
def reply(the_channel_uuid=nil, reply_body=nil, address=nil)
result = {:result => false}
if ::GxG::valid_uuid?(the_channel_uuid) && reply_body.is_a?(::GxG::Events::Message) && address
@adapter.send_message(the_channel_uuid, reply_body, address)
result[:result] = true
end
result
end
end
#
end
end
# ### Register BridgeAdapters by protocol
::GxG::Communications::BridgeAdapter.descendants.each do |the_adapter_class|
::GxG::BRIDGES_AVAILABLE[((the_adapter_class)::protocol)] = the_adapter_class
end

5505
lib/gxg/gxg_database.rb Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

2820
lib/gxg/gxg_dbfs.rb Normal file

File diff suppressed because it is too large Load diff

2263
lib/gxg/gxg_elements.rb Normal file

File diff suppressed because it is too large Load diff

853
lib/gxg/gxg_engine.rb Normal file
View file

@ -0,0 +1,853 @@
# ---------------------------------------------------------------------------------------------------------------------
# GxG Engine interface: rational memory management, and quota supports
module GxG
#
module Engine
# ok... why?
# example: ::Process::getrlimit(:AS) => [<1Exabyte>,<1Exabyte>]
# Now that is the Amsterdam-style pull on a bong with stirrups - so far as dealing with real memory limits goes.
# Rather - I want my apps to be *able* to deal with reality as the system resources determine
# Eventually provides a *corrected* drop-in replacement for Process module in standard MRI 1.9.2
# See : http://www.ruby-doc.org/core-1.9.3/Process.html
#
@@details = {}
# running effectively in 32 or 64 bits? See: http://www.ruby-forum.com/topic/164583
@@details[:runtime_bits] = ([ -1 ].pack('l!').length * 8)
# default to 32 bit size:
if @@details[:runtime_bits] == 64
@@details[:runtime_rvalue_size] = 40
else
@@details[:runtime_rvalue_size] = 20
end
#
@@details[:files] = {:maximum_file_descriptors => 1024}
# FORNOW : Review : server claims 50% of all system threads
@@details[:events] = {:maximum_event_descriptors => (GxG::SYSTEM["kernel.threads_max"]["kernel.threads_max"].to_f * 0.5).to_i , :event_descriptors_threshold => 0.05, :event_descriptors_envelope => (20..100), :event_minimum_threshold => 20}
# Fix for Windows: per-process theads max is 2000.
if GxG::SYSTEM.platform[:platform] == :windows
@@details[:files][:maximum_file_descriptors] = 512
@@details[:events][:maximum_event_descriptors] = 2000
end
#
@@details[:runtime] = RUBY_ENGINE.to_sym
@@details[:runtime_version] = GxG::Version.new({:interpret => RUBY_VERSION, :patch_level => RUBY_PATCHLEVEL, :revision => RUBY_REVISION})
# determine the byte size of a Ruby RVALUE (a.k.a :slot_size)
# See: http://www.engineyard.com/blog/2010/mri-memory-allocation-a-primer-for-developers/
#
@@details[:memory_load] = {:high => false, :medium => false, :low => true, :percent => 0.0}
@@details[:engine_load] = {:high => false, :medium => false, :low => true, :percent => 0.0}
#
@@details[:key_map] = []
@@details[:key_map] << {:key => "files.max_descriptors", :path => "/:files/:maximum_file_descriptors"}
@@details[:key_map] << {:key => "events.max_descriptors", :path => "/:events/:maximum_event_descriptors"}
#
@@thread_safety = Mutex.new
@@event_descriptors = 0
#
def self.detail_mapping()
@@details[:key_map]
end
#
def self.[](the_path="")
# Public interface for accessing system keyed values.
# access cached or system-read values on /proc (sysctl-style) keys. (frankly missing from BSD, etc)
result = nil
reference = ::GxG::Engine::detail_mapping()
#
reference.to_enum.each do |mapping|
if mapping[:key] == the_path
@@thread_safety.synchronize {
result = @@details.get_at_path(mapping[:path]).clone
}
break
end
end
# unless result
# # attempt to fetch the data from the system somehow.
# result = get_system_data(the_path)
# unless result.keys.size > 0
# result = nil
# end
# end
#
result
end
#
def self.memory_load_high?()
@@thread_safety.synchronize {
@@details[:memory_load][:high]
}
end
#
def self.memory_load_medium?()
@@thread_safety.synchronize {
@@details[:memory_load][:medium]
}
end
#
def self.memory_load_low?()
@@thread_safety.synchronize {
@@details[:memory_load][:low]
}
end
#
def self.memory_load()
@@thread_safety.synchronize {
@@details[:memory_load][:percent]
}
end
#
def self.engine_load_high?()
@@thread_safety.synchronize {
@@details[:engine_load][:high]
}
end
#
def self.engine_load_medium?()
@@thread_safety.synchronize {
@@details[:engine_load][:medium]
}
end
#
def self.engine_load_low?()
@@thread_safety.synchronize {
@@details[:engine_load][:low]
}
end
#
def self.engine_load()
@@thread_safety.synchronize {
@@details[:engine_load][:percent]
}
end
#
def self.determine_memory_load()
current_load = ((GxG::Engine::memory_used()[:total].to_f / GxG::SYSTEM.memory_limits()[:process].first.to_f) * 100.0)
if (0.0..40.0).include?(current_load)
# low
@@thread_safety.synchronize {
@@details[:memory_load][:high] = false
@@details[:memory_load][:medium] = false
@@details[:memory_load][:low] = true
@@details[:memory_load][:percent] = current_load
}
else
if (40.0..70.0).include?(current_load)
# medium
@@thread_safety.synchronize {
@@details[:memory_load][:high] = false
@@details[:memory_load][:medium] = true
@@details[:memory_load][:low] = false
@@details[:memory_load][:percent] = current_load
}
else
if current_load > 70.0
# high
@@thread_safety.synchronize {
@@details[:memory_load][:high] = true
@@details[:memory_load][:medium] = false
@@details[:memory_load][:low] = false
@@details[:memory_load][:percent] = current_load
}
end
end
end
end
#
def self.determine_engine_load()
max_event_descriptors = ::GxG::Engine::maximum_event_descriptors()
available_events = ::GxG::Engine::available_event_descriptors()
current_load = (((max_event_descriptors - available_events).to_f / max_event_descriptors.to_f) * 100.0)
if (0.0..40.0).include?(current_load)
# low
@@thread_safety.synchronize {
@@details[:engine_load][:high] = false
@@details[:engine_load][:medium] = false
@@details[:engine_load][:low] = true
@@details[:memory_load][:percent] = current_load
}
else
if (40.0..70.0).include?(current_load)
# medium
@@thread_safety.synchronize {
@@details[:engine_load][:high] = false
@@details[:engine_load][:medium] = true
@@details[:engine_load][:low] = false
@@details[:engine_load][:percent] = current_load
}
else
if current_load > 70.0
# high
@@thread_safety.synchronize {
@@details[:engine_load][:high] = true
@@details[:engine_load][:medium] = false
@@details[:engine_load][:low] = false
@@details[:engine_load][:percent] = current_load
}
end
end
end
end
#
def self.engine_gear()
# inverse of load
result = :low
@@thread_safety.synchronize {
if @@details[:engine_load][:medium]
result = :medium
end
if @@details[:engine_load][:low]
result = :high
end
}
result
end
#
def self.memory_gear()
# inverse of load
result = :low
@@thread_safety.synchronize {
if @@details[:memory_load][:medium]
result = :medium
end
if @@details[:memory_load][:low]
result = :high
end
}
result
end
#
def self.gear()
"#{::GxG::Engine::engine_gear().to_s}_#{::GxG::Engine::memory_gear().to_s}".to_sym
end
#
def self.determine_loads()
mem_gear = ::GxG::Engine::memory_gear()
eng_gear = ::GxG::Engine::engine_gear()
{
:engine => {:load => ::GxG::Engine::determine_engine_load(), :gear => eng_gear},
:memory => {:load => ::GxG::Engine::determine_memory_load(), :gear => mem_gear},
:gear => "#{eng_gear.to_s}_#{mem_gear.to_s}".to_sym
}
end
#
include ::Process
# overrides
#
# [:exec, :fork, :spawn, :exit!, :exit, :abort, :kill, :wait, :wait2, :waitpid, :waitpid2, :waitall, :detach, :pid, :ppid,
# .
def self.exec(*args)
::Process::exec(*args)
end
#
def self.fork(*args)
::Process::fork(*args)
end
#
def self.spawn(*args)
::Process::spawn(*args)
end
#
def self.exit!(*args)
::Process::exit!(*args)
end
#
def self.exit(*args)
::Process::exit(*args)
end
#
def self.abort(*args)
::Process::abort(*args)
end
#
def self.kill(*args)
::Process::kill(*args)
end
#
def self.wait(*args)
::Process::wait(*args)
end
#
def self.wait2(*args)
::Process::wait2(*args)
end
#
def self.waitpid(*args)
::Process::waitpid(*args)
end
#
def self.waitpid2(*args)
::Process::waitpid2(*args)
end
#
def self.waitall(*args)
::Process::waitall(*args)
end
#
def self.detach(*args)
::Process::detach(*args)
end
#
def self.pid(*args)
::Process::pid(*args)
end
#
def self.ppid(*args)
::Process::ppid(*args)
end
#
# :getpgrp, :setpgrp, :getpgid, :setpgid, :setsid, :getpriority, :setpriority, :getrlimit, :setrlimit, :uid, :gid, :euid, :egid,
#
def self.getpgrp(*args)
::Process::getpgrp(*args)
end
#
def self.setpgrp(*args)
::Process::setpgrp(*args)
end
#
def self.getpgid(*args)
::Process::getpgid(*args)
end
#
def self.setpgid(*args)
::Process::setpgid(*args)
end
#
def self.setsid(*args)
::Process::setsid(*args)
end
#
def self.getpriority(*args)
::Process::getpriority(*args)
end
#
def self.setpriority(*args)
::Process::setpriority(*args)
end
#
def self.getrlimit(key)
result = nil
# FIX: GxG::Engine::getrlimit : Awaiting rbx bug #2112 fix : https://github.com/rubinius/rubinius/issues/2112
case ::GxG::Engine::profile()[:engine]
when :ruby
raw_data = ::Process::getrlimit(key)
case key
when :AS
if GxG::SYSTEM
memory_limits = GxG::SYSTEM.memory_limits()
result = [raw_data[0],(memory_limits[:process].max)]
else
result = raw_data
end
else
result = raw_data
end
when :jruby
memory_limits = GxG::SYSTEM.memory_limits()
result = [(memory_limits[:process].first),(memory_limits[:process].last)]
when :rbx
memory_limits = GxG::SYSTEM.memory_limits()
result = [(memory_limits[:process].first),(memory_limits[:process].last)]
end
result
end
#
def self.setrlimit(*args)
::Process::setrlimit(*args)
end
#
def self.uid(*args)
::Process::uid(*args)
end
#
def self.gid(*args)
::Process::gid(*args)
end
#
def self.euid(*args)
::Process::euid(*args)
end
#
def self.egid(*args)
::Process::egid(*args)
end
#
# :initgroups, :groups, :maxgroups, :daemon, :times]
#
def self.initgroups(*args)
::Process::initgroups(*args)
end
#
def self.groups(*args)
::Process::groups(*args)
end
#
def self.maxgroups(*args)
::Process::maxgroups(*args)
end
#
def self.daemon(*args)
::Process::daemon(*args)
end
#
def self.times(*args)
::Process::times(*args)
end
#
# extensions
#
def self.profile()
@@thread_safety.synchronize {
{:engine => @@details[:runtime], :version => @@details[:runtime_version], :bits => @@details[:runtime_bits], :slot_size => @@details[:runtime_rvalue_size]}.clone
}
end
#
def self.instance_process(the_class, &block)
if block.respond_to?(:call)
::ObjectSpace.each_object(Class) do |the_object|
if the_object.alive?()
if the_object.is_a?(the_class)
block.call(the_object)
end
end
end
end
end
#
def self.object_memory_used()
#
slots = 0
other = 0
::ObjectSpace.each_object(Class) do |the_object|
slots += 1
if the_object.alive?()
if the_object.is_a?(::String)
other += the_object.bytesize()
end
end
end
#
((::GxG::Engine::profile()[:slot_size].to_i * slots) + other)
end
#
def self.memory_used()
# ok... why in the fly'n F isn't this provided by core ruby?!?
# Attribution: http://stackoverflow.com/questions/7220896/get-current-ruby-process-memory-usage
result = {:actual => 0, :virtual => 0, :total => 0}
#
if ::GxG::SYSTEM
case ::GxG::SYSTEM.platform[:platform]
when :windows
# FORNOW: user object space enumeration
result[:actual] = ::GxG::Engine::object_memory_used()
when :bsd
pid_string = "#{::Process::pid}"
env = self.environment()
case env[:environment]
when :openbsd
# See: http://modman.unixdev.net/?sektion=1&page=procmap&manpath=OpenBSD-3.6
# use `procmap -p #{pid_string}`
# LATER: GxG::Engine::memory_used (OpenBSD) : determine how to cull procmap text data into :actual and :virtual memory usage.
# FORNOW: user object space enumeration
result[:actual] = ::GxG::Engine::object_memory_used()
#
when :freebsd, :dragonfly, :pcbsd
# TODO: GxG::Engine::memory_used: Test with FreeBSD, DragonFlyBSD, and PCBSD.
raw_data = `top -d 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[6] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (netbsd) : Find a reliable way to get result[:virtual] mem used.
break
end
end
when :netbsd
# TODO: GxG::Engine::memory_used: Test with NetBSD.
raw_data = `top -d 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[4] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (netbsd) : Find a reliable way to get result[:virtual] mem used.
break
end
end
when :darwin, :macos
# TODO: GxG::Engine::memory_used: Test with Darwin/MacOSX.
raw_data = `top -l 1`
raw_data.to_enum(:each_line).each do |the_line|
if the_line.split(" ")[0] == pid_string
result[:actual] = (the_line.split(" ")[9] << "B").numeric_values([:byte])[:byte].to_i
# TODO: GxG::Engine.memory_used() (darwin) : Find a reliable way to get result[:virtual] mem used.
break
end
end
end
when :linux, :solaris
# PS is universal, but unfortunately universally incorrect, See: http://stackoverflow.com/questions/131303/linux-how-to-measure-actual-memory-usage-of-an-application-or-process
# TODO: GxG::Engine::memory_used: create a non-toolbox version for bootstrap memory profiling.
# TODO: GxG::Engine::memory_used: Test with Solaris.
raw_data = `pmap -d #{::Process::pid} | tail -n 1`.split(" ")
result[:actual] = (raw_data[3].to_s << "B").numeric_values()[:byte].to_i
result[:virtual] = (raw_data[1].to_s << "B").numeric_values()[:byte].to_i
# result[:shared] = (raw_data[5] << "B").numeric_values()[:byte].to_i
end
end
result[:total] = (result[:actual] + result[:virtual])
result
end
#
def self.memory_available()
# Memory available for this process
result = {:actual => 0, :virtual => 0, :total => 0}
if ::GxG::SYSTEM
used_memory = ::GxG::Engine::memory_used()
free_memory = ::GxG::SYSTEM.memory()
limits = ::GxG::Engine::getrlimit(:AS)
latitude_total = (limits[0] - used_memory[:actual])
if latitude_total < 0
latitude_total = 0
end
# latitude_virtual = (limits[0] - used_memory[:actual])
# latitude_actual = (limits[0] - used_memory[:virtual])
# indicates available ram vs. paged swap first
if free_memory[:actual][:free].to_i > limits[0]
result[:actual] = latitude_total
result[:virtual] = 0
else
result[:actual] = free_memory[:actual][:free]
# vm calc is crack-monkey, but someplace close to reality.
if free_memory[:virtual][:free] > ((limits[0] - result[:actual]) - used_memory[:total])
result[:virtual] = ((limits[0] - result[:actual]) - used_memory[:total])
unless result[:virtual] > 0
result[:virtual] = 0
end
else
if ((free_memory[:virtual][:free] - result[:actual]) - used_memory[:total]) > 0
result[:virtual] = ((free_memory[:virtual][:free] - result[:actual]) - used_memory[:total])
else
result[:virtual] = 0
end
end
end
#
end
result[:total] = (result[:actual] + result[:virtual])
result
end
#
def self.engine_data_keys(other_platform=nil)
#
result = []
data = []
data << {:key => "files.max_descriptors", :platforms => [:linux], :operation => {:getconf => "OPEN_MAX", :format => :to_int}}
# TODO: Research maximum socket limit under zmq:
# See: http://news.ycombinator.com/item?id=1740823
# Also : http://stackoverflow.com/questions/651665/how-many-socket-connections-possible
# inventory the various platform variables that affect what threshold of max_socket_count a given system would end up with.
# TODO: find a way to ask the OS and ZMQ for: how many descriptors available to this process, and how many in use under zmq 2.x & 3.x respectively?
# ALSO: factor in passed preferences for these settings.
# update available_file_descriptors when all that becomes clear and is a bit settled.
# current_memory = ::GxG::Engine::memory_available()[:actual]
# TODO: GxG::Engine::engine_data_keys : determine precise socket size (32bit/64bit arch) for event zmq sockets used (exact type used).
# GxG::Engine::profile()[:bits]
# zmq_socket_size = 16384
#
# event_allocation = 0.5
#
# TODO: GxG::Engine::engine_data_keys : tie event allocation percentage into defaults / config.file loader.
data << {:key => "events.max_descriptors", :platforms => [:linux], :operation => {:event_allocation => 0.5, :format => :noop}}
# See: http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3
# Sysctl tuning: http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1
# LATER: develop a series of formulas for calculating approx. how many clients one can support under one 'cluster' controller (session manager).
data.to_enum.each do |entry|
if entry[:platforms].include?((other_platform || ::GxG::SYSTEM.platform()[:platform]))
result << entry
end
end
#
result
end
#
def self.get_engine_data(keys=[])
# Support for <SYSTEM>.refresh_data_for(a_path) (private_method)
# some sort of sysctl + config + etc for *all* platforms:
# "sysctl -n net.ipv4.tcp_sack" becomes: "/net/ipv4/tcp/selective_ack" (external use path)
# @details[:memory_limits][:buffers][:socket][:ipc] becomes colllection_point = @details.at_path(":memory_limits/:buffers/:socket/:ipc");collection_point[(key)]=value
# procs for gathering data, and formatting for use.
# by_platform key between sysctl/config-key and @details hash path.
# {:synonyms => ["external-path"], :sysctl => "key-string', :format => :proc-name, :location => "hash-path", :location_key => (:sym or int)}
#
unless keys.is_a?(::Array)
keys = [(keys)]
end
result = {}
#
format_noop = Proc.new do |the_data|
the_data
end
format_to_int = Proc.new do |the_string|
(the_string.numeric_values()[:integer] || 0)
end
format_to_float = Proc.new do |the_string|
(the_string.numeric_values()[:float] || 0.0)
end
format_zerofalse = Proc.new do |the_string|
( format_to_int.call(the_string) > 0 )
end
format_valid_with_initial = Proc.new do |the_string|
raw_data = the_string.split(" ")
{:initial => format_to_int.call(raw_data[1]), :valid => ((format_to_int.call(raw_data[0]))..(format_to_int.call(raw_data[2])))}
end
sysctl_read = Proc.new do |a_key,the_formatter|
( the_formatter.call(`sysctl -n #{a_key.to_s}`.split("\n")[0]) )
end
getconf_read = Proc.new do |a_key, the_formatter|
( the_formatter.call(`getconf #{a_key.to_s}`.split("\n")[0]) )
end
event_allocation = Proc.new do |the_allocation, the_formatter|
# more precise zmq socket byte size goes here.
zmq_socket_size = 16384
#
(((::GxG::Engine::memory_available()[:actual].to_f * the_allocation.to_f).to_i / zmq_socket_size) - 1)
end
#
reference = ::GxG::Engine::engine_data_keys()
#
fetch_data = Proc.new do |the_key|
data = nil
reference.to_enum.each do |entry|
if entry[:key] == the_key
case entry[:operation][:format]
when :noop
the_formatter = format_noop
when :zerofalse
the_formatter = format_zerofalse
when :to_int
the_formatter = format_to_int
when :to_float
the_formatter = format_to_float
when :valid_with_initial
the_formatter = format_valid_with_initial
else
the_formatter = format_noop
end
if entry[:operation][:sysctl]
data = sysctl_read.call(entry[:operation][:sysctl],the_formatter)
else
if entry[:operation][:getconf]
data = getconf_read.call(entry[:operation][:getconf],the_formatter)
else
if entry[:operation][:event_allocation]
data = event_allocation.call(entry[:operation][:event_allocation],the_formatter)
else
#
end
#
end
end
if data
break
end
end
end
data
end
#
keys.to_enum.each do |specifier|
if specifier.is_a?(::String)
# if element is String: serves as result Hash key to hold result value.
# single result with this result key
result[(specifier)] = fetch_data.call(specifier)
else
if specifier.is_a?(::Hash)
# if element is Hash: {ukey => hash_path}, {:key => <keystring>, :path => <hash_path>}
# if hash_path supplied, read the data; set the @details hash element, and return the formatted data
if specifier[:key]
# {:key => <keystring>, :path => <hash_path>}
result[(specifier[:key])] = fetch_data.call(specifier[:key])
if specifier[:path]
@@thread_safety.synchronize {
@@details.set_at_path(specifier[:path],result[(specifier[:key])])
}
end
else
# {ukey => hash_path}
subkeys = specifier.keys
subkeys.to_enum.each do |a_key|
result[(a_key)] = fetch_data.call(a_key)
@@thread_safety.synchronize {
@@details.set_at_path(specifier[(a_key)],result[(a_key)])
}
end
end
end
end
end
#
result
end
#
def self.set_engine_data(keys=[])
# LATER: GxG::Engine::set_engine_data : when you figure out system(sudo) authentication - use this for ops that require elevated privs to set system parameters.
# Define a set of universal_keys that affect what other keys; upon setting any one, unique-list a set of keys to read back in w/ hash paths.
result = nil
if nil
::GxG::Engine::get_engine_data(nil,nil)
# compare hash value to the_value; return true if successful
end
result
end
# ### Descriptors in general
def self.descriptor_limits()
{:events => @@details[:events][:maximum_event_descriptors], :files => @@details[:files][:maximum_file_descriptors]}.clone
end
#
def self.descriptor_heap_used()
result = {:total => 0}
slot_size = ::GxG::Engine::profile[:slot_size]
file_total = 0
GxG::Engine::file_descriptors().to_enum.each do |item|
file_total += item.heap_used()
end
@@thread_safety.synchronize {
result[:files] = file_total
unless ::GxG::Engine::descriptor_limits()[:events] == :files
# see available_file_descriptors : beware counting event descriptors twice (w/ file descriptors)
# TODO: GxG::Engine::descriptor_heap_used : sync w/ more precise event socket size determination
result[:events] = (@@event_descriptors * (slot_size + 4))
end
}
result[:total] = (result[:files].to_i + result[:events].to_i)
#
result
end
# ### File Descriptors
def self.maximum_file_descriptors()
::GxG::Engine::descriptor_limits()[:files]
end
#
def self.file_descriptors()
# Return an Array of currently opened file descriptors.
# About File Descriptors: http://stackoverflow.com/questions/848717/handling-more-than-1024-file-descriptors-in-c-on-linux
#
result = []
exclude_list = []
[::IO, ::IO::descendants()].flatten!.to_enum.each do |the_class|
unless exclude_list.include?(the_class)
::ObjectSpace.each_object(the_class) do |the_io|
unless (the_io.closed? || result.include?(the_io))
result << the_io
end
end
end
end
result
end
#
def self.available_file_descriptors()
(::GxG::Engine::maximum_file_descriptors() - ::GxG::Engine::file_descriptors().size)
end
#
# ### Event Descriptors
def self.maximum_event_descriptors()
::GxG::Engine::descriptor_limits()[:events]
end
#
def self.event_descriptors_threshold()
# Percentage of allocation that must be available for events to be released.
@@thread_safety.synchronize {
@@details[:events][:event_descriptors_threshold].clone
}
end
#
def self.event_minimum_threshold()
# Percentage of allocation that must be available for events to be released.
@@thread_safety.synchronize {
@@details[:events][:event_minimum_threshold].clone
}
end
#
def self.events_in_use()
@@thread_safety.synchronize {
@@event_descriptors
}
end
#
def self.available_event_descriptors()
result = 0
current = 0
@@thread_safety.synchronize {
current = (::GxG::Engine::maximum_event_descriptors() - @@event_descriptors)
}
if current > 0
result = current
end
result
end
#
def self.reserve_event_descriptor()
result = false
if ::GxG::Engine::available_event_descriptors() > 0
@@thread_safety.synchronize {
@@event_descriptors += 1
}
result = true
else
raise Errno::ENOMEM, "maximum number of event descriptors in use (#{GxG::Engine::maximum_event_descriptors().to_s})"
end
result
end
#
def self.release_event_descriptor()
result = false
@@thread_safety.synchronize {
if @@event_descriptors > 0
@@event_descriptors -= 1
end
}
result = true
result
end
#
def self.determine_event_allocations()
#
total = ::GxG::Engine::maximum_event_descriptors()
dispatchers = []
[::GxG::Events::EventDispatcher, ::GxG::Events::EventDispatcher::descendants()].flatten!.each do |the_class|
::ObjectSpace.each_object(the_class) do |the_dispatcher|
if the_dispatcher.alive?()
if the_dispatcher.running?()
dispatchers << the_dispatcher
end
end
end
end
# Note : a ghost-dispatcher representing all other system funcitons is added to the dispatcher count.
maximum_events = (total.to_f / (dispatchers.size + 1).to_f).to_i
threshold = (maximum_events.to_f * ::GxG::Engine::event_descriptors_threshold().to_f).to_i
unless threshold > 0
threshold = 1
end
@@thread_safety.synchronize {
@@details[:events][:event_minimum_threshold] = threshold
@@details[:events][:event_descriptors_envelope] = ((threshold)..(maximum_events))
}
true
end
#
def self.event_allocation_envelope()
result = (20..100)
@@thread_safety.synchronize {
result = @@details[:events][:event_descriptors_envelope]
}
result
end
#
end
# FIX / Review : this appears to set file and event max descriptors to NIL on Winderz. Do I need this?
# ::GxG::Engine::get_engine_data(::GxG::Engine::detail_mapping())
#
end
#

2169
lib/gxg/gxg_entities.rb Normal file

File diff suppressed because it is too large Load diff

774
lib/gxg/gxg_events.rb Normal file
View file

@ -0,0 +1,774 @@
#
module GxG
#
module Events
#
class Message
#
def initialize(*args)
# MUST provide a hash with at least :sender message field set
# {:sender => <some-object>, :body => <some-object>, :on_success => <some-proc>, :on_fail => <some-proc> }
unless args[0].is_a?(::Hash)
raise ArgumentError, "you must pass a Hash to create the message"
end
@data = {:sender => nil, :id => (::GxG::uuid_generate().to_s.to_sym), :ufs => :"org.gxg.message", :subject => args[1], :body => nil, :on_success => nil, :on_fail => nil}.merge(args[0])
unless @data[:sender]
raise ArgumentError, "you must set the :sender key in the argument Hash"
end
self
end
#
def inspect()
@data.inspect
end
#
def id()
@data[:id]
end
#
def sender()
@data[:sender]
end
#
def subject()
@data[:subject]
end
# ### Universal Format Specifier of the body attribute.
def ufs()
@data[:ufs]
end
#
def ufs=(the_format=nil)
if the_format.is_any(::String, ::Symbol)
@data[:ufs] = the_format.to_s.to_sym
end
end
#
def body()
@data[:body]
end
#
def compose_reply(&block)
if block.respond_to?(:call)
# Review : consider just cloning the @data variable and swapping sender/to.
reply_message = ::GxG::Events::Message.new({:sender => self[:to], :to => self[:sender], :ufs => self.ufs(), :context => self[:context], :subject => self[:subject], :body => self[:body]})
block.call(reply_message)
else
nil
end
end
#
def succeed(input=nil)
if @data[:on_success].respond_to?(:call)
@data[:on_success].call(input)
end
end
#
def fail(input=nil)
if @data[:on_fail].respond_to?(:call)
@data[:on_fail].call(input)
end
end
#
def on(event_type=nil,&block)
if [:success, :fail].include?(event_type)
if block.respond_to?(:call)
case event_type
when :success
@data[:on_success] = block
when :fail
@data[:on_fail] = block
end
else
raise ArgumentError, "You MUST provide a callable block."
end
else
# Custom Events:
@data[(("on_" + event_type.to_s.downcase).to_sym)] = block
end
end
#
def respond_to_event?(event_type=nil)
result = false
if event_type.is_any(::String, ::Symbol)
if @data[(("on_" + event_type.to_s.downcase).to_sym)]
result = true
end
end
result
end
#
def call_event(operation_envelope=nil)
result = nil
if operation_envelope.is_a?(::Hash)
the_event = operation_envelope.keys[0]
if self.respond_to?(the_event)
data = operation_envelope[(the_event)]
result = {:result => @data[(("on_" + event_type.to_s.downcase).to_sym)].call(data)}
else
result = {:result => nil, :error => "Command #{the_event.inspect} Not Found"}
end
end
result
end
# Hash-like support methods:
def keys()
@data.keys()
end
def [](the_key)
@data[(the_key)]
end
def []=(the_key, the_value)
@data[(the_key)] = the_value
end
def process(&block)
@data.process(&block)
end
def process!(&block)
@data.process!(&block)
end
def search(&block)
@data.search(&block)
end
def paths_to(*args)
@data.paths_to(*args)
end
def get_at_path(*args)
@data.get_at_path(*args)
end
def set_at_path(*args)
@data.set_at_path(*args)
end
end
#
class LoggerDB
#
end
class LogRing < ::Logger
#
def initialize(*args)
super(::STDOUT)
@thread_safety = ::Mutex.new
@busy = false
@messages = []
@outlets = {}
self
end
#
def keys()
@thread_safety.synchronize { @outlets.keys() }
end
def [](the_key)
@thread_safety.synchronize { @outlets[(the_key)] }
end
def []=(the_key,the_logger)
if the_logger.is_any?(::Logger, ::GxG::Events::LoggerDB)
@thread_safety.synchronize { @outlets[(the_key)] = the_logger }
the_logger
else
raise ArgumentError, "You must supply a Logger or GxG::Events::LoggerDB instance, you provided a #{the_logger.class}"
end
end
# ### Message handlers
def post(the_message=nil)
if the_message.is_a?(::GxG::Events::Message)
@thread_safety.synchronize { @messages << the_message }
true
else
false
end
end
#
def next_message()
@thread_safety.synchronize { @messages.shift }
end
#
def process_messages()
unless @thread_safety.synchronize { @busy == true }
@thread_safety.synchronize { @busy = true }
#
the_message = self.next_message()
if the_message.is_a?(::GxG::Events::Message)
begin
if the_message[:subject] == :notification
if the_message[:body].is_a?(::Hash)
outlets = this.keys()
outlets.to_enum(:each).each do |the_outlet_key|
the_outlet = this()[(the_outlet_key)]
if (the_message[:body][:severity] || ::Logger::UNKNOWN) >= the_outlet.level()
# output
if the_outlet.is_a?(::GxG::Events::LoggerDB)
# TODO: GxG::Events::LogRing : build out object-based event logging:
else
# conventional string based:
if (the_message[:body][:message].is_a?(::String) || the_message[:body][:message].is_a?(::Exception))
the_outlet.add(the_message[:body][:severity], the_message[:body][:message], the_message[:body][:progname])
else
if the_message[:body][:message].is_a?(::Hash)
#
if the_message[:body][:message][:error].is_a?(::Exception)
if the_message[:body][:message][:error].backtrace.is_a?(::Array)
if the_message[:body][:message][:parameters]
the_outlet.add(the_message[:body][:severity], ("\n #{the_message[:body][:message][:error].exception.class.to_s}: " + the_message[:body][:message][:error].to_s + "\n Parameters: " + the_message[:body][:message][:parameters].inspect + "\n Trace: " + the_message[:body][:message][:error].backtrace.join("\n") + "\n"), the_message[:body][:progname])
else
the_outlet.add(the_message[:body][:severity], ("\n #{the_message[:body][:message][:error].exception.class.to_s}: " + the_message[:body][:message][:error].to_s + "\n Trace: " + the_message[:body][:message][:error].backtrace.join("\n") + "\n"), the_message[:body][:progname])
end
else
if the_message[:body][:message][:parameters]
the_outlet.add(the_message[:body][:severity], ("\n #{the_message[:body][:message][:error].exception.class.to_s}: " + the_message[:body][:message][:error].to_s + "\n Parameters: " + the_message[:body][:message][:parameters].inspect + "\n"), the_message[:body][:progname])
else
the_outlet.add(the_message[:body][:severity], ("\n #{the_message[:body][:message][:error].exception.class.to_s}: " + the_message[:body][:message][:error].to_s + "\n"), the_message[:body][:progname])
end
end
else
the_outlet.add(the_message[:body][:severity], the_message[:body][:message].inspect, the_message[:body][:progname])
end
else
the_outlet.add(the_message[:body][:severity], the_message[:body][:message].inspect, the_message[:body][:progname])
end
end
end
end
#
end
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:message => the_message}})
end
end
#
@thread_safety.synchronize { @busy = false }
end
true
end
#
def add(severity = nil, message = nil, progname = nil, origin = nil, &block)
#
unless [::Logger::DEBUG, ::Logger::INFO, ::Logger::WARN, ::Logger::ERROR, ::Logger::FATAL, ::Logger::UNKNOWN].include?(severity)
severity = ::Logger::UNKNOWN
end
if progname
unless message
message = progname
progname = nil
end
end
begin
unless message
if block.respond_to?(:call)
message = block.call()
end
end
# this.mailbox << ::GxG::Events::Message.new({:sender => (origin || this), :severity => severity, :message => message, :progname => progname})
the_message = new_message({:sender => (origin || self), :ufs => :"org.gxg.message.notification", :severity => severity, :message => message, :progname => progname}, :notification)
self.post(the_message)
true
rescue => the_error
log_error({:error => the_error, :parameters => {:severity => severity, :message => message, :progname => progname, :block => block}})
false
end
end
def unknown?()
true
end
alias :trace? :unknown?
def unknown(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::UNKNOWN, message, progname, (origin || self), &block)
end
end
alias :trace :unknown
def fatal?()
true
end
def fatal(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::FATAL, message, progname, (origin || self), &block)
end
end
def error?()
true
end
def error(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::ERROR, message, progname, (origin || self), &block)
end
end
def warn?()
true
end
alias :warning? :warn?
def warn(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::WARN, message, progname, (origin || self), &block)
end
end
alias :warning :warn
def info?()
true
end
def info(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::INFO, message, progname, (origin || self), &block)
end
end
def debug?()
true
end
def debug(message = nil, progname = nil, origin = nil, &block)
if self.alive?()
self.add(::Logger::DEBUG, message, progname, (origin || self), &block)
end
end
end
#
# Provide a Cooperative Processing facility for sessions
class EventDispatcher
def initialize(interval=0.333, thread_reservation=100)
super()
@reserved_threads = (thread_reservation.to_i || 100)
@uuid = ::GxG::uuid_generate().to_sym
@event_queues = {:root => {:events => [], :settings => {:active => true, :portion => 100.0}}}
@running = false
@ticking = false
# Timer format: {:when => Time_Object.to_f, :what => event_frame, :interval => float, :id => uuid.sym}
@timers = []
@scheduler = Rufus::Scheduler.new(:max_work_threads => @reserved_threads)
def @scheduler.on_error(job, error)
log_error({:error => error, :parameters => {:job => job}})
end
@scheduler.pause
@tick_timer = nil
@tick_interval = interval
#
self
end
#
def running?()
@running
end
#
def startup()
@running = true
if @scheduler.paused?
@scheduler.resume
end
unless @tick_timer
@tick_timer = @scheduler.every(@tick_interval) do
tick
end
end
@reserved_threads.times do
begin
::GxG::Engine::reserve_event_descriptor()
rescue Exception => the_error
log_error({:error => the_error})
break
end
end
@running
end
#
def shutdown()
@running = false
if @tick_timer
self.cancel_timer(@tick_timer)
@tick_timer = nil
@scheduler.pause
@reserved_threads.times do
::GxG::Engine::release_event_descriptor()
end
end
true
end
#
def tick()
if @running
unless @ticking
@ticking = true
events = []
envelope = ::GxG::Engine::event_allocation_envelope()
if envelope.last >= envelope.first
prefetch = {}
@event_queues.keys.each do |the_queue|
if (@event_queues[(the_queue)][:settings][:active] && @event_queues[(the_queue)][:events].size > 0)
((@event_queues[(the_queue)][:settings][:portion].to_f / 100.0) * envelope.last.to_f).to_i.times do
op = @event_queues[(the_queue)][:events].shift
if op.respond_to?(:call)
(prefetch[(the_queue)] ||= []) << op
end
end
end
end
#
prefetch_count = 0
prefetch.keys.each do |the_queue|
prefetch_count += prefetch[(the_queue)].size
end
#
while prefetch_count > 0 do
prefetch.keys.each do |the_queue|
op = prefetch[(the_queue)].shift
if op
events << op
end
prefetch_count -= 1
end
end
#
end
# xxx old version:
# queues = {}
# load_size = 0
# @event_queues.keys.each do |the_queue|
# if (@event_queues[(the_queue)][:settings][:active] && @event_queues[(the_queue)][:events].size > 0)
# queues[(the_queue)] = {:portion => ((@event_queues[(the_queue)][:settings][:portion].to_f / 100.0) * total_slots.to_f).to_i}
# if queues[(the_queue)][:portion] == 0
# # Fudging a little here in case of real heavy loads and/or too many queues.
# queues[(the_queue)][:portion] = 1
# end
# queues[(the_queue)][:size] = @event_queues[(the_queue)][:events].size
# load_size += queues[(the_queue)][:size]
# end
# end
# queues.keys.each do |the_queue|
# # TODO: devise apportioned event dispatch
# queues[(the_queue)][:portion].times do
# op = @event_queues[(the_queue)][:events].shift
# if op.respond_to?(:call)
# events << op
# else
# break
# end
# end
# end
#
events.each do |the_event|
if the_event
Thread.new do
::GxG::Engine::reserve_event_descriptor()
begin
the_event.call()
rescue Exception => the_error
log_error({:error => the_error})
end
::GxG::Engine::release_event_descriptor()
end
end
end
#
@ticking = false
end
end
end
#
def inspect_timers()
@scheduler.jobs()
end
def inspect_queue(queue=:root)
@event_queues[(queue)]
end
#
def adjust_event_queue(queue=:root,settings={})
# RESEARCH: GxG::Events::EventDispatcher.adjust_event_queue : refactor :portion of each queue algo needed.
unless queue.to_s.to_sym == :root
if @event_queues[(queue.to_sym)]
# LATER: Eventually add :portion as valid setting to accept.
[:active].each do |the_setting|
if settings[(the_setting.to_sym)]
new_setting = {}
# portion adjustment algo here
new_setting[(the_setting.to_sym)] = settings[(the_setting.to_sym)]
#
@event_queues[(queue.to_sym)][:settings].merge!(new_setting)
end
end
else
puts "Warning: queue #{queue.inspect} does not exist"
# For Now: just ignore references to non-existent queues. (reconsider later)
# raise ArgumentError, "Event Queue :#{queue} does not exist to alter"
end
end
end
#
def create_event_queue(settings={})
unless settings.is_a?(Hash)
raise ArgumentError, "you must provide a Hash as a parameter set"
end
queue_name = settings.delete(:name).to_s.to_sym
if queue_name.to_s.size > 0
if @event_queues[(queue_name)]
raise ArgumentError, "Event Queue #{queue_name.inspect} already exists"
else
@event_queues[(queue_name)] = {:events => [], :settings => {:active => false, :portion => 0.0}}
# For Now: simply evenly divide the portions equally, only :root can be 100%.
even_portion = (100.0 / @event_queues.keys.size.to_f)
@event_queues.keys.each do |the_queue|
@event_queues[(the_queue)][:settings][:portion] = (even_portion)
end
self.adjust_event_queue(queue_name, settings.merge({:active => true}))
true
end
else
raise ArgumentError, ":name must be provided for Event Queue"
end
end
#
def delete_event_queue(queue=nil)
if queue.to_s.to_sym == :root
false
else
@event_queues.delete(queue)
# For Now: simply evenly divide the portions equally, only :root can be 100%.
even_portion = (@event_queues.keys.size.to_f / 100.0)
@event_queues.keys.each do |the_queue|
@event_queues[(the_queue)][:settings][:portion] = (even_portion)
end
true
end
end
#
def pause_event_queue(queue=nil)
unless queue.to_s.to_sym == :root
self.adjust_event_queue(queue,{:active => true})
end
end
#
def unpause_event_queue(queue=nil)
unless queue.to_s.to_sym == :root
self.adjust_event_queue(queue,{:active => false})
end
end
#
def post_to_event_queue(queue=:root,the_event=nil)
if the_event.respond_to?(:call)
if @event_queues[(queue)]
@event_queues[(queue)][:events] << the_event
else
# issue a warning, and post to :root queue anyways (for now). (link to logger, and output)
@event_queues[:root][:events] << the_event
end
end
end
#
def post_event(queue_name=:root,&block)
unless @event_queues[(queue_name.to_sym)]
self.create_event_queue({:name => queue_name.to_sym})
end
self.post_to_event_queue(queue_name.to_sym,block)
true
end
# Adding Timers
# See: https://stackoverflow.com/questions/235504/validating-crontab-entries-with-php
# Cron REGEX: /^((?:[1-9]?\d|\*)\s*(?:(?:[\/-][1-9]?\d)|(?:,[1-9]?\d)+)?\s*){5}$/
# Rufus Duration REGEX: /^(-?)([\d\.smhdwy]+)$/
# at, in, every, cron
#
def cancel_timer(timer_reference=nil)
@scheduler.jobs().each do |the_task|
if the_task.job_id == timer_reference
the_task.unschedule
break
end
end
true
end
#
def at(expression="",&block)
the_time = nil
case expression.class
when ::DateTime
the_time = expression.to_time()
when ::Time
the_time = expression
when ::String
the_time = ::Chronic::parse(expression)
unless the_time
raise ArgumentError, "Invalid time expression: #{expression.inspect}"
end
else
raise ArgumentError, "Invalid time expression: #{expression.inspect}"
end
if the_time
if block.respond_to?(:call)
event_frame = Proc.new {
GxG::Engine.reserve_event_descriptor()
block.call()
GxG::Engine.release_event_descriptor()
}
@scheduler.at(the_time.iso8601,&event_frame)
else
nil
end
else
nil
end
end
#
def in(expression="", &block)
if expression.is_a?(::Numeric)
interval = expression
else
if expression.include?(" ")
# parse to Rufus duration
duration_type = nil
interval = nil
expression.split(" ").each do |entry|
if duration_type && interval
break
else
if ["s","second", "seconds"].include?(entry)
duration_type = "s"
end
if ["m","minute", "minutes"].include?(entry)
duration_type = "m"
end
if ["h","hour", "hours"].include?(entry)
duration_type = "h"
end
if ["d","day", "days"].include?(entry)
duration_type = "d"
end
if ["w","week", "weeks"].include?(entry)
duration_type = "w"
end
if /[-+0-9.,]*/.match(entry).to_s.size > 0
if entry.include?(".")
interval = entry.to_f
else
interval = entry.to_i
end
end
end
end
interval = (interval.to_s + duration_type.to_s)
else
# Verify standard Rufus expression
duration_type = expression.split(/[-+0-9.,]*/)[1].to_s
if ["s","m","h","d","w"].include?(duration_type.downcase)
interval = /[-+0-9.,]*/.match(expression).to_s.to_i
if interval > 0
interval = (interval.to_s + duration_type.to_s)
else
raise ArgumentError, "Invalid interval expression: #{expression.inspect}"
end
else
raise ArgumentError, "Invalid interval expression: #{expression.inspect}"
end
end
end
if block.respond_to?(:call)
event_frame = Proc.new {
GxG::Engine.reserve_event_descriptor()
block.call()
GxG::Engine.release_event_descriptor()
}
@scheduler.in(interval,&event_frame)
else
nil
end
end
#
def every(expression="", &block)
if expression.is_a?(::Numeric)
interval = expression
else
if expression.include?(" ")
# parse to Rufus duration
duration_type = nil
interval = nil
expression.split(" ").each do |entry|
if duration_type && interval
break
else
if ["s","second", "seconds"].include?(entry)
duration_type = "s"
end
if ["m","minute", "minutes"].include?(entry)
duration_type = "m"
end
if ["h","hour", "hours"].include?(entry)
duration_type = "h"
end
if ["d","day", "days"].include?(entry)
duration_type = "d"
end
if ["w","week", "weeks"].include?(entry)
duration_type = "w"
end
if /[-+0-9.,]*/.match(entry).to_s.size > 0
if entry.include?(".")
interval = entry.to_f
else
interval = entry.to_i
end
end
end
end
the_interval = (interval.to_s + duration_type.to_s)
else
# Verify standard Rufus expression
duration_type = expression.split(/[-+0-9.,]*/)[1].to_s
if ["s","m","h","d","w"].include?(duration_type.downcase)
interval = /[-+0-9.,]*/.match(expression).to_s.to_i
if interval > 0
the_interval = (interval.to_s + duration_type.to_s)
else
raise ArgumentError, "Invalid interval expression: #{expression.inspect}"
end
else
raise ArgumentError, "Invalid interval expression: #{expression.inspect}"
end
end
end
if block.respond_to?(:call)
event_frame = Proc.new {
GxG::Engine.reserve_event_descriptor()
block.call()
GxG::Engine.release_event_descriptor()
}
@scheduler.every(the_interval,&event_frame)
else
nil
end
end
#
def cron(expression="",&block)
if /^((?:[1-9]?\d|\*)\s*(?:(?:[\/-][1-9]?\d)|(?:,[1-9]?\d)+)?\s*){5}$/.match(expression)
if block.respond_to?(:call)
event_frame = Proc.new {
GxG::Engine.reserve_event_descriptor()
block.call()
GxG::Engine.release_event_descriptor()
}
@scheduler.cron(expression,&event_frame)
else
nil
end
else
raise ArgumentError, "Invalid CRON expression: #{expression.inspect}"
end
end
#
end
#
end
#
module EventManager
#
def self.manager_running?()
result = false
if $Dispatcher
result = $Dispatcher.running?
end
result
end
end
# ### Re-define the LOG Constant
if const_defined?(:LOG)
remove_const(:LOG)
end
LOG = ::GxG::Events::LogRing.new()
end
#

2717
lib/gxg/gxg_io.rb Normal file

File diff suppressed because it is too large Load diff

810
lib/gxg/gxg_net.rb Normal file
View file

@ -0,0 +1,810 @@
module GxG
module Networking
#
def self.buffer_limits(direction=:in, whatfor=nil)
limits = GxG::SYSTEM.memory_limits()[:buffers]
result = {:initial=>0, :valid=>0..0}
# unless whatfor.is_a?(::Symbol)
# # :unknown will play it safe with :socket buffer limits
# whatfor = :socket
# #
# if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
# whatfor = :socket
# else
# if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
# whatfor = :tcp
# else
# if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
# whatfor = :udp
# end
# end
# end
# end
case whatfor
when :socket
if direction == :in
result = limits[:socket][:ipc][:read].clone
else
result = limits[:socket][:ipc][:write].clone
end
when :tcp
if direction == :in
result = limits[:ipv4][:tcp][:read].clone
else
result = limits[:ipv4][:tcp][:write].clone
end
when :udp
if direction == :in
result = limits[:ipv4][:udp][:read].clone
else
result = limits[:ipv4][:udp][:write].clone
end
end
result
end
end
end
# WRONG Direction : there for pickings - eventually deleted.
## Tier 2 Augmentation: networking classes
##
#class BasicSocket
# #
# protected
# include ::GxG::Support::Library::Transcoding
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
# def self.open(*args,&block)
# if block.respond_to?(:call)
# block.call(new(*args))
# else
# new(*args)
# end
# end
# #
# # alias :original_method :method
# #
# def initialize(*args)
# #
# super(*args)
# end
# #
# def signature()
# @signature.dup
# end
# #
# def signature=(sig)
# if sig.is_a?(::String)
# sig = sig.hash
# end
# if sig.is_a?(::Numeric)
# @signature = sig.to_i
# else
# @signature = sig.to_s.hash
# end
# @signature.dup
# end
# #
# def unbind_reason()
# @unbind_reason
# end
# #
# def unbind_reason=(reason)
# @unbind_reason = reason
# end
# #
# def buffer_limits(direction=:in, whatfor=nil)
# limits = GxG::SYSTEM.memory_limits()[:buffers]
# result = {:initial=>0, :valid=>0..0}
# unless whatfor.is_a?(::Symbol)
# # :unknown will play it safe with :socket buffer limits
# whatfor = :socket
# #
# if self.is_any?(::UNIXServer, ::UNIXSocket, ::Socket)
# whatfor = :socket
# else
# if self.is_any?(::TCPServer, ::SOCKSocket, ::TCPSocket)
# whatfor = :tcp
# else
# if self.is_any?(::UDPServer, ::UDPSource, ::UDPSocket)
# whatfor = :udp
# end
# end
# end
# end
# case whatfor
# when :socket
# if direction == :in
# result = limits[:socket][:ipc][:read].dup
# else
# result = limits[:socket][:ipc][:write].dup
# end
# when :tcp
# if direction == :in
# result = limits[:ipv4][:tcp][:read].dup
# else
# result = limits[:ipv4][:tcp][:write].dup
# end
# when :udp
# if direction == :in
# result = limits[:ipv4][:udp][:read].dup
# else
# result = limits[:ipv4][:udp][:write].dup
# end
# end
# result
# end
# #
# def passes_needed(size_used=0, container_limit=0)
# needed_raw = size_used.to_f / container_limit.to_f
# overhang = needed_raw - needed_raw.to_i.to_f
# needed_raw = needed_raw.to_i.to_f
# if overhang > 0.0
# needed_raw += 1.0
# end
# needed_raw.to_i
# end
# #
# def receive_to_buffer(options={:propogate => false})
# unless self.closed?()
# unless options.is_a?(::Hash)
# options={:propogate => false}
# end
# if @halt_receive_to_buffer == true
# @halt_receive_to_buffer = false
# else
# data = self.recv().to_s
# if data.bytesize() > 0
# @in_buffer.string << data
# end
# while data.bytesize() > 0
# data = self.recv().to_s
# if data.bytesize() > 0
# @in_buffer.string << data
# end
# pause
# end
# if GxG::EventManager::manager_running?()[:result]
# if options[:propogate]
# if options[:interval].is_a?(::Numeric)
# ::GxG::EventManager::post_event(Proc.new { pause({:interval => options[:interval]});self.receive_to_buffer(options) })
# else
# ::GxG::EventManager::post_event(Proc.new { self.receive_to_buffer(options) })
# end
# end
# end
# end
# end
# end
# #
# def halt_receive_to_buffer()
# @halt_receive_to_buffer = true
# end
# #
#end
##
#class UNIXSocket
# #
# alias :original_recvfrom :recvfrom
# alias :original_recvfrom_nonblock :recvfrom
# alias :recvfrom_nonblock :recvfrom
# #
# # alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# # alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketIORecvFrom
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class UNIXServer
# #
# alias :original_recvfrom :recvfrom
# alias :original_recvfrom_nonblock :recvfrom
# alias :recvfrom_nonblock :recvfrom
# #
# # alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# # alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketIORecvFrom
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class Socket
# #
# # alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# # alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class IPSocket
# #
# # alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# # alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class TCPSocket
# #
# alias :original_recvfrom :recvfrom
# alias :original_recvfrom_nonblock :recvfrom
# alias :recvfrom_nonblock :recvfrom
# #
# alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketIORecvFrom
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class TCPServer
# #
# alias :original_recvfrom :recvfrom
# alias :original_recvfrom_nonblock :recvfrom
# alias :recvfrom_nonblock :recvfrom
# #
# alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketIORecvFrom
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class UDPSocket
# #
# alias :original_recvfrom :recvfrom
# alias :original_recvfrom_nonblock :recvfrom_nonblock
# #
# alias :original_recvmsg :recvmsg
# alias :original_recv :recv
# alias :original_sendmsg :sendmsg
# alias :original_send :send
# #
# include ::GxG::Support::Library::SocketIO
# # Since in binary mode, transcoding itself is useless, but other compatibility and object type-casting benefits if included.
# include ::GxG::Support::Library::TranscodingIO
# #
# include ::GxG::Support::Library::SocketIORecvFrom
# #
# include ::GxG::Support::Library::SocketRW
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
#class UDPServer < ::UDPSocket
# #
# def initialize(the_host_ip, the_port)
# #
# @interface = Addrinfo.udp(the_host_ip, the_port)
# if ::GxG::SYSTEM.network_port_used?(@interface.ip_address(),@interface.ip_port())
# raise Errno::EADDRINUSE, "#{@interface.ip_address()}:#{@interface.ip_port()} is already in use"
# end
# #
# if @interface.ipv6?()
# super(::Socket::AF_INET6)
# else
# super(::Socket::AF_INET)
# end
# self.bind(@interface.ip_address(),@interface.ip_port())
# #
# unless ::GxG::SYSTEM.network_port_used?(@interface.ip_address(),@interface.ip_port())
# raise Errno::EADDRNOTAVAIL, "#{@interface.ip_address()}:#{@interface.ip_port()} could not be bound properly"
# end
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
# end
#end
##
#class SOCKSocket
# #
# public
# #
# def self.new(*args)
# #
# allocate.instance_eval do
# @signature = nil
# # UUIDTools::UUID::random_create.to_s.hash
# #
# @in_buffer = ::StringIO.new("",::IO::RDWR)
# @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
# @out_buffer = ""
# @out_buffer.force_encoding(::Encoding::ASCII_8BIT)
# @unbind_reason = nil
# @remote_closed = false
# #
# @before_receive_data = nil
# @after_receive_data = nil
# @before_send_data = nil
# @after_send_data = nil
# #
# @halt_receive_to_buffer = false
# #
# initialize(*args)
# #
# # Socket IO *should* be set to non-blocking IO me thinks
# self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
# # post_init
# #
# unless @signature
# # define signature based upon IO.fileno, then add, based upon class, to connection list or acceptor list for servers.
# # ::EventMachine::gxg_add_connection / ::EventMachine::gxg_add_acceptor
# end
# self
# end
# end
# #
#end
##
## ### Networking Section of GxG
#module GxG
# #
# module Networking
# def self.buffer_limits(direction=:in, whatfor=nil)
# limits = GxG::SYSTEM.memory_limits()[:buffers]
# result = {:initial=>0, :valid=>0..0}
# case whatfor
# when :socket, :ipc
# if direction == :in
# result = limits[:socket][:ipc][:read].dup
# else
# result = limits[:socket][:ipc][:write].dup
# end
# when :inproc, :tcp, :tcp4, :pgm, :epgm
# if direction == :in
# result = limits[:ipv4][:tcp][:read].dup
# else
# result = limits[:ipv4][:tcp][:write].dup
# end
# when :tcp6
# # TODO: @GxG::Networking::ZMQSocket.buffer_limits : GxG::SYSTEM.memory_limits : for now route to ipv4, need to expand system key set to include ipv6
# if direction == :in
# result = limits[:ipv4][:tcp][:read].dup
# else
# result = limits[:ipv4][:tcp][:write].dup
# end
# when :udp, :udp4, :udp6
# if direction == :in
# result = limits[:ipv4][:udp][:read].dup
# else
# result = limits[:ipv4][:udp][:write].dup
# end
# end
# result
# end
# #
# def self.passes_needed(size_used=0, container_limit=0)
# needed_raw = size_used.to_f / container_limit.to_f
# overhang = needed_raw - needed_raw.to_i.to_f
# needed_raw = needed_raw.to_i.to_f
# if overhang > 0.0
# needed_raw += 1.0
# end
# needed_raw.to_i
# end
# #
# # networking classes
# class UNIXSocket < ::UNIXSocket
# # when gem is pushed, subclass from Celluloid::IO::UNIXSocket
# end
# #
# class BSDSocket < ::Socket
# #
# end
# #
# class UNIXServer < ::UNIXServer
# # when gem is pushed, subclass from Celluloid::IO::UNIXServer
# #
# # def accept()
# # ::GxG::Networking::UNIXSocket.new(self.path)
# # end
# # alias :accept_nonblock :accept
# #
# end
# #
# class TCPSocket < ::Celluloid::IO::TCPSocket
# #
# end
# #
# class TCPServer < ::Celluloid::IO::TCPServer
# #
# end
# #
# class UDPSocket < ::Celluloid::IO::UDPSocket
# #
# end
# #
# class UDPServer < ::UDPServer
# #
# end
# #
# class SOCKSocket < ::SOCKSocket
# #
# end
# #
# class DDPSocket
# #
# end
# #
# module Protocol
# #
# def register(*args)
# # register(:proto, MyProtoClassPath)
# end
# #
# end
# #
# # ### IPC Classes (via ZeroMQ)
# # require(::File.expand_path(::File.dirname(__FILE__) << "/gxg_ipc.rb"))
# #
# #
# end
# #
#end
##

1053
lib/gxg/gxg_transcode.rb Normal file

File diff suppressed because it is too large Load diff

122
lib/gxg/gxg_units.rb Normal file
View file

@ -0,0 +1,122 @@
# ---------------------------------------------------------------------------------------------------------------------
# Units and support element classes:
module GxG
module Units
class << self
@@interpreters = []
end
# this module is used for interpreting text into denominations and multipliers., and general support for various units of measure
def self.unit_interpreters(categories=:any)
unless categories == :any
unless categories.is_a?(Array)
categories = [(categories)]
end
end
unit_list = []
GxG::Units.constants.to_enum.each do |unit_handler|
unit_handler = GxG::Units.const_get(unit_handler)
if (unit_handler.is_any?([Class, Module]) && unit_handler.respond_to?(:interpret) && unit_handler.respond_to?(:categories))
if categories == :any
unit_list << unit_handler
else
categories.to_enum.each do |category|
if unit_handler::categories.include?(category)
unit_list << unit_handler
end
end
end
end
end
#
unit_list
end
#
def self.refresh_units_registry()
# SOMEDAY: Thread-safety?
@@interpreters = self.unit_interpreters()
end
#
def self.interpret_units(params={})
# {:text => quanta,:categories => categories,:locale => locale}
quanta_string = (params[:text] || "")
categories = (params[:categories] || :any)
locale = (params[:locale] || :en_US)
numeric_base = (params[:base] || 10)
unless categories == :any
unless categories.is_a?(Array)
categories = [(categories)]
end
end
results = []
@@interpreters.to_enum.each do |interpreter|
entry = nil
if categories == :any
entry = interpreter::interpret(quanta_string.to_s,locale,numeric_base)
else
categories.to_enum.each do |category|
if interpreter::categories.include?(category)
entry = interpreter.interpret(quanta_string.to_s,locale,numeric_base)
end
end
end
if entry
results << entry
end
end
{:result => results}
end
module Bits
#
end
module Bytes
def self.categories()
[:numeric, :byte, :memory, :storage, :computer]
end
def self.interpret(quanta_string="",interpret_for_locale=:en_US,interpret_for_numeric_base=10)
multiplier = nil
#
case quanta_string.to_s.downcase.to_sym
# Attribution: http://whatsabyte.com/
# Attribution: https://en.wikipedia.org/wiki/Exabyte
# Note: I am electing to count along the 1024 (to the power) rule. Hard drive manufacturers can kiss my rosey-red,
# I'll not bugger my enumeration standard for their bloody sales force. :P Thus:
# I've attempted to include the IS and IEC terms and abbreviations.
# LATER: add ability to distinguish case: Kb = kiloBIT, KB = KiloBYTE, etc
when :byte, :bytes
multiplier = 1
when :kb, :kib, :kilobyte, :kilobytes, :kibibyte, :kibibytes
multiplier = 1024 ** 1
when :mb, :mib, :megabyte, :megabytes, :mebibyte, :mebibytes
multiplier = 1024 ** 2
when :gb, :gib, :gigabyte, :gigabytes, :gibibyte, :gibibytes
multiplier = 1024 ** 3
when :tb, :tib, :terabyte, :terabytes, :tebibyte, :tebibytes
multiplier = 1024 ** 4
when :pb, :pib, :petabyte, :petabytes, :pebibyte, :pebibytes
multiplier = 1024 ** 5
when :eb, :eib, :exobyte, :exobytes, :exbibyte, :exbibytes
multiplier = 1024 ** 6
when :zb, :zib, :zettabyte, :zettabytes, :zebibyte, :zebibytes
multiplier = 1024 ** 7
when :yb, :yib, :yottabyte, :yottabytes, :yobibyte, :yobibytes
multiplier = 1024 ** 8
when :bb, :bib, :brontobyte, :brontobytes
multiplier = 1024 ** 9
when :geopbyte, :geopbytes
# TODO: GeopByte: need to find missing SI and IEC abbreviations
multiplier = 1024 ** 10
end
#
if multiplier
{:quantum => {:byte => multiplier}, :attributes => nil}
else
nil
end
end
end
end
end
# Initial setup:
::GxG::Units::refresh_units_registry()
#

1220
lib/gxg/gxg_zmq.rb Normal file

File diff suppressed because it is too large Load diff

4797
lib/gxg/net_clients.rb Normal file

File diff suppressed because it is too large Load diff

46
lib/gxg/net_tools.rb Normal file
View file

@ -0,0 +1,46 @@
#
# ### Networking Section of GxG
module GxG
#
module Networking
def self.wget(the_url=nil, options={})
result = false
begin
unless options.is_a?(::Hash)
options = {}
end
if the_url.is_a?(::String)
the_url = ::URI::parse(the_url)
end
if the_url.is_a?(::URI::Generic)
if the_url.scheme.to_s.downcase.include?("https") || options[:use_ssl] == true
client = ::GxG::Networking::HttpsClient.new(the_url, options)
else
client = ::GxG::Networking::HttpClient.new(the_url, options)
end
response = client.get(the_url, options.merge({:raw_response => true}))
if response
if response.is_a?(::File)
result = response
else
if options[:raw_response]
result = response
else
result = response.body
end
end
else
raise Exception, "Failed to get a response."
end
else
raise ArgumentError, "You MUST specify a valid URI"
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
end
result
end
end
#
end

252
lib/gxg/tag_helpers.rb Normal file
View file

@ -0,0 +1,252 @@
#
require 'securerandom'
module Padrino
module Helpers
module TagHelpers
# See: https://github.com/padrino/padrino-framework/blob/master/padrino-helpers/lib/padrino-helpers/tag_helpers.rb
def accordian_tag(the_id=nil, sections=nil)
# id => <uuid>, :sections => [{:heading => "", :content => ""}]
if the_id.is_a?(::Array)
sections = the_id
the_id = ::SecureRandom::uuid.to_s
end
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output = SafeBuffer.new
output.safe_concat "<div id='#{the_id.to_s}'>"
sections.each do |the_section|
output.safe_concat "<h3>#{the_section[:heading].to_s}</h3>"
output.safe_concat "<div>"
output.safe_concat the_section[:content].to_s
output.safe_concat "</div>"
end
output.safe_concat "</div><script>$('##{the_id.to_s}').accordion();</script>"
output
end
#
def autocomplete_tag(the_id=nil, the_terms=nil)
if the_id.is_a?(::Array)
the_terms = the_id
the_id = ::SecureRandom::uuid.to_s
end
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output = SafeBuffer.new
output.safe_concat( input_tag(:text, :name => the_id.to_s, :id => the_id.to_s) )
# output.safe_concat "<input type='text' id='#{the_id.to_s}'>"
output.safe_concat "<script>var terms#{the_id.to_s.gsub("-","")} = ["
the_terms.each do |the_term|
output.safe_concat ("'#{the_term.to_s}'")
unless the_term == the_terms.last
output.safe_concat(",")
end
end
output.safe_concat "];"
output.safe_concat "$( '##{the_id.to_s}' ).autocomplete({source: terms#{the_id.to_s.gsub("-","")}});</script>"
end
#
def gxg_button_tag(the_id=nil, options={})
# Options: :title => <string>,
# :javascript => <string>,
# :image => {:position => :full,:top,:left,:right,:bottom, :source => <path> :height => <int>, :width => <int>}
if the_id.is_a?(::Hash)
options = the_id
the_id = ::SecureRandom::uuid.to_s
end
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output = SafeBuffer.new
if options[:image].is_a?(::Hash)
output.safe_concat("<div id='#{the_id.to_s}'>")
img_opts = {}
if options[:image][:height]
img_opts[:height] = options[:image][:height]
end
if options[:image][:width]
img_opts[:width] = options[:image][:width]
end
if options[:image][:position] == :full
output.safe_concat("<table><tr><td>" + image_tag(options[:image][:source], img_opts) + "</td></tr></table>")
else
# top, left, right, bottom of title text?
case options[:image][:position]
when :top
output.safe_concat("<table><tr><td>" + image_tag(options[:image][:source], img_opts) + "</td></tr><tr><td>#{options[:title]}</td></tr></table>")
when :bottom
output.safe_concat("<table><tr><td>#{options[:title]}</td></tr><tr><td>" + image_tag(options[:image][:source], img_opts) + "</td></tr></table>")
when :left
output.safe_concat("<table><tr><td>" + image_tag(options[:image][:source], img_opts) + "</td><td>#{options[:title]}</td></tr></table>")
when :right
output.safe_concat("<table><tr><td>#{options[:title]}</td><td>" + image_tag(options[:image][:source], img_opts) + "</td></tr></table>")
end
end
output.safe_concat("</div>")
else
output.safe_concat("<div id='#{the_id.to_s}'>#{options[:title].to_s}</div>")
end
output.safe_concat("<script>$('##{the_id.to_s}').button();</script>")
if options[:javascript].is_a?(::String)
output.safe_concat("<script>#{options[:javascript].to_s}</script>")
end
output
end
#
def radio_tag(the_id=nil,title=nil)
output = SafeBuffer.new
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output.safe_concat("<label for='#{the_id}'>#{title}</label>")
output.safe_concat("<input type='radio' name='#{the_id}' id='#{the_id}'>")
output
end
#
def radio_group(legend=nil,button_names=nil)
# Waiting on Fix: https://bugs.jqueryui.com/ticket/15308#comment:1
output = SafeBuffer.new
output.safe_concat("<fieldset><legend>#{legend.to_s}</legend>")
button_names.each do |the_name|
output.safe_concat(radio_tag(nil,the_name))
end
output.safe_concat("</fieldset>")
output.safe_concat("<script>$('input[type=radio]').checkboxradio();</script>")
output
end
#
def control_group(content="", options={:vertical => false}, &block)
#
output = SafeBuffer.new
if options[:vertical]
output.safe_concat("<div class='controlgroup-verticle'>")
else
output.safe_concat("<div class='controlgroup'>")
end
if block.respond_to?(:call)
output.safe_concat(block.call())
else
output.safe_concat(content)
end
output.safe_concat("</div>")
if options[:vertical]
output.safe_concat("<script>$('.controlgroup-verticle').controlgroup({'direction': 'vertical'});</script>")
else
output.safe_concat("<script>$('.controlgroup').controlgroup();</script>")
end
output
end
#
def datepicker_tag(the_id=nil,title="")
output = SafeBuffer.new
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output.safe_concat("#{title.to_s}<input type='text' id='#{the_id.to_s}'>")
output.safe_concat("<script>$('##{the_id.to_s}').datepicker();</script>")
output
end
#
def dialog_tag(the_id=nil, title="", content="", &block)
output = SafeBuffer.new
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output.safe_concat("<div title='#{title.to_s}' id='#{the_id.to_s}'>")
if block.respond_to?(:call)
output.safe_concat(block.call())
else
output.safe_concat(content)
end
output.safe_concat("</div>")
output.safe_concat("<script>$('##{the_id.to_s}').dialog();</script>")
output
end
#
def slider_tag(the_id=nil, options={})
output = SafeBuffer.new
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output.safe_concat("<div id='#{the_id.to_s}'>")
if options[:content].is_a?(::String)
output.safe_concat(options[:content].to_s)
end
output.safe_concat("</div>")
if options[:javascript].is_a?(::String)
output.safe_concat("<script>$('##{the_id.to_s}').slider(#{options[:javascript].to_s});</script>")
else
output.safe_concat("<script>$('##{the_id.to_s}').slider();</script>")
end
output
end
#
def spinner_tab(the_id=nil, options={})
output = SafeBuffer.new
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output.safe_concat("<input id='#{the_id.to_s}'")
if options[:name].is_a?(::String)
output.safe_concat(" name='#{options[:name].to_s}'")
end
if options[:value]
output.safe_concat(" name='#{options[:value].to_s}'")
end
output.safe_concat(">")
if options[:javascript].is_a?(::String)
output.safe_concat("<script>$('##{the_id.to_s}').spinner(#{options[:javascript].to_s});</script>")
else
output.safe_concat("<script>$('##{the_id.to_s}').spinner();</script>")
end
output
end
#
def tabs_tag(the_id=nil, sections=nil, options={})
# id => <uuid>, :sections => [{:heading => "", :content => ""}]
if the_id.is_a?(::Array)
sections = the_id
options = sections
the_id = ::SecureRandom::uuid.to_s
end
unless the_id
the_id = ::SecureRandom::uuid.to_s
end
output = SafeBuffer.new
section_details = {}
output.safe_concat "<div id='#{the_id.to_s}'>"
sections.each do |the_section|
section_id = ::SecureRandom::uuid.to_sym
section_details[(section_id)] = {:heading => the_section[:heading], :content => the_section[:content]}
end
output.safe_concat "<ul>"
section_details.each_key do |the_uuid|
output.safe_concat "<li><a href='##{the_uuid.to_s}'>#{section_details[(the_uuid)][:heading].to_s}</a></li>"
end
output.safe_concat "</ul>"
section_details.each_key do |the_uuid|
output.safe_concat "<div id='#{the_uuid.to_s}'>#{section_details[(the_uuid)][:content].to_s}</div>"
end
output.safe_concat "</div>"
if options[:javascript].is_a?(::String)
output.safe_concat("<script>$('##{the_id.to_s}').tabs(#{options[:javascript].to_s});</script>")
else
output.safe_concat("<script>$('##{the_id.to_s}').tabs();</script>")
end
output
end
#
def currency_string(amount=0.0)
result = ""
if amount.to_s.split(".")[1].size < 2
result = ("$" << amount.to_s << "0")
else
result = ("$" << amount.to_s)
end
result
end
#
end
end
end