Initial Commit

This commit is contained in:
G. Gibson 2021-11-16 08:58:51 -08:00
commit 81daf6f387
283 changed files with 130672 additions and 1 deletions

301
gxg/web/gxg.rb Normal file
View file

@ -0,0 +1,301 @@
# GxG for Opal
# Place Holders:
module GxG
LOG = nil
DISPATCHER = nil
OBJECT_SPACE = {:registry => {}}
end
$object_space = {:registry => {}}
# Alterations to basic Object
class Object
private
#
def bytes(*args)
GxG::ByteArray::try_convert(args)
end
#
def new_message(*args)
# if args[0].is_a?(Hash)
# ::GxG::Events::Message.new({:sender => self, :subject => args[1], :body => nil}.merge(args[0]))
# else
::GxG::Events::Message.new({:sender => self, :subject => args[1], :body => args[0]})
# end
end
# logging hooks
def log_unknown(message = nil, progname = nil, &block)
# a.k.a 'unknown'
if ::GxG::LOG
::GxG::LOG.unknown(message, progname, self, &block)
end
end
alias :log_trace :log_unknown
def log_fatal(message = nil, progname = nil, &block)
if ::GxG::LOG
::GxG::LOG.fatal(message, progname, self, &block)
end
end
def log_error(message = nil, progname = nil, &block)
if ::GxG::LOG
::GxG::LOG.error(message, progname, self, &block)
end
end
def log_warn(message = nil, progname = nil, &block)
if ::GxG::LOG
::GxG::LOG.warn(message, progname, self, &block)
end
end
alias :log_warning :log_warn
def log_info(message = nil, progname = nil, &block)
if ::GxG::LOG
::GxG::LOG.info(message, progname, self, &block)
end
end
def log_debug(message = nil, progname = nil, &block)
if ::GxG::LOG
::GxG::LOG.debug(message, progname, self, &block)
end
end
#
def post_event(queue_name=:root,dispatcher=nil,&block)
result = false
if dispatcher.is_a?(::GxG::Events::EventDispatcher)
result = dispatcher.post_event(queue_name,&block)
else
if ::GxG::DISPATCHER
result = ::GxG::DISPATCHER.post_event(queue_name,&block)
else
log_error("No Event Dispatcher Defined")
end
end
result
end
#
def post_event_at(at_time=Time.now,dispatcher=nil,&block)
result = nil
if dispatcher.is_a?(::GxG::Events::EventDispatcher)
result = dispatcher.add_timer({:when => at_time},&block)
else
if ::GxG::DISPATCHER
result = ::GxG::DISPATCHER.add_timer({:when => at_time},&block)
else
log_error("No Event Dispatcher Defined")
end
end
result
end
#
def post_event_periodic(interval=0.333,dispatcher=nil,&block)
result = nil
if dispatcher.is_a?(::GxG::Events::EventDispatcher)
result = dispatcher.add_periodic_timer({:interval => interval},&block)
else
if ::GxG::DISPATCHER
result = ::GxG::DISPATCHER.add_periodic_timer({:interval => interval},&block)
else
log_error("No Event Dispatcher Defined")
end
end
result
end
#
def cancel_periodic(reference=nil,dispatcher=nil)
result = false
if reference.is_a?(Hash)
if dispatcher.is_a?(::GxG::Events::EventDispatcher)
result = dispatcher.cancel_timer(reference)
else
if ::GxG::DISPATCHER
result = ::GxG::DISPATCHER.cancel_timer(reference)
else
log_error("No Event Dispatcher Defined")
end
end
end
result
end
#
public
def is_any?(*args)
result = false
args.flatten.to_enum.each do |thing|
if thing.class == Class
if self.is_a?(thing)
result = true
break
end
end
end
result
end
end
#
module GxG
# Generic toolbox of methods
def self.uuid_generate()
SecureRandom.uuid()
end
#
def self.passes_needed(size_used=0, container_limit=0)
if size_used > 0 and 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
else
0
end
end
#
def self.apportioned_ranges(how_much_data=0, container_limit=0, original_offset=0)
result = []
the_count = ::GxG::passes_needed(how_much_data, container_limit)
if the_count > 0
offset = original_offset
the_count.times do
if (offset + (container_limit - 1)) <= (how_much_data - 1)
end_point = (offset + (container_limit - 1))
else
end_point = (how_much_data - 1)
end
result << ((offset)..(end_point))
offset = (end_point + 1)
end
end
result
end
#
def self.valid_uuid?(uuid=nil,strict=true)
if uuid.is_any?(::String, ::Symbol)
if strict == true
pattern = /[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[4][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/
else
pattern = /[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/
end
if uuid.to_s.match(pattern)
if uuid.to_s.size == 36
true
else
false
end
else
false
end
else
false
end
end
#
def self.replace_registry(new_registry={})
$object_space[:registry] = new_registry
true
end
#
def self.register(something=nil, parent=nil)
unless $object_space[:registry][(something.uuid.to_s.to_sym)]
if parent
ordinal = GxG::children_of(parent).size
else
ordinal = 0
end
$object_space[:registry][(something.uuid.to_s.to_sym)] = {:object => something, :parent => parent, :ordinal => ordinal}
end
true
end
#
def self.unregister(the_uuid=nil)
if $object_space[:registry][(the_uuid.to_s.to_sym)] && GxG::DISPLAY_DETAILS[:object].uuid.to_s.to_sym != the_uuid.to_s.to_sym
$object_space[:registry].delete(the_uuid.to_s.to_sym)
end
true
end
#
def self.register_uuid_list()
$object_space[:registry].keys
end
#
def self.record_by_uuid(the_uuid=nil)
result = nil
if $object_space[:registry][(the_uuid.to_s.to_sym)]
result = $object_space[:registry][(the_uuid.to_s.to_sym)]
end
result
end
#
def self.object_by_uuid(the_uuid=nil)
(GxG::record_by_uuid(the_uuid) || {})[:object]
end
#
def self.children_of(something=nil)
result = []
buffer = []
$object_space[:registry].values.each do |entry|
if entry[:parent].object_id == something.object_id
buffer << entry
end
end
buffer = buffer.sort { |record_a,record_b| record_a[:ordinal] <=> record_b[:ordinal] }
buffer.each do |the_record|
result << the_record[:object]
end
result
end
#
def self.set_ordinal(the_selector=nil,the_ordinal=0)
result = false
if the_selector.is_any?(String, Symbol)
the_object = GxG::object_by_uuid(the_selector.to_sym)
else
the_object = the_selector
end
parent = GxG::parent_of(the_object)
if parent
list = GxG::children_of(parent)
current = list.find_index(the_object)
if current
if the_ordinal >= (list.size - 1)
list << list.delete_at(current)
else
if the_ordinal < 0
list = list.unshift(list.delete_at(current))
else
list = list.insert(the_ordinal,list.delete_at(current))
end
end
#
list.each_with_index do |an_object, indexer|
record = GxG::record_by_uuid(an_object.uuid.to_sym)
record[:ordinal] = indexer
end
result = true
end
end
result
end
#
def self.parent_of(something=nil)
result = nil
$object_space[:registry].values.each do |entry|
if entry[:object].object_id == something.object_id
result = entry[:parent]
break
end
end
result
end
#
def self.lineage_of(something=nil)
result = []
the_parent = GxG::parent_of(something)
until the_parent == nil do
result.unshift(the_parent)
the_parent = GxG::parent_of(the_parent)
end
# first = base progenitor.
result
end
#
end
#

519
gxg/web/gxg_applications.rb Normal file
View file

@ -0,0 +1,519 @@
module GxG
# ----------------------------------------------------------------------------------------------------
class Application
def initialize(settings={})
# @process = server-side process uuid
@process = settings[:application]
# receives a :source PersistedHash defining the application internals.
@definition = settings[:source]
@title = "Untitled"
# @state is persisted as state-data on the server when altered.
@state = (settings[:state] || {})
# Gui component if any:
@viewports = {}
if settings[:viewport].is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(settings[:viewport].uuid.to_s.to_sym)] = settings[:viewport]
end
if settings[:viewports].is_a?(::Array)
settings[:viewports].each do |the_viewport|
if the_viewport.is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(the_viewport.uuid.to_s.to_sym)] = the_viewport
end
end
end
@windows = {}
if settings[:window].is_a?(::GxG::Gui::Window)
@windows[(settings[:window].uuid.to_s.to_sym)] = settings[:window]
end
if settings[:windows].is_a?(::Array)
settings[:windows].each do |the_window|
if the_window.is_a?(::GxG::Gui::Window)
@windows[(the_window.uuid.to_s.to_sym)] = the_window
end
end
end
@libraries = {}
# Event Supports:
@event_handlers = {}
@event_listeners = {}
# Runtime flags:
@credentialed = (settings[:credentialed] || false)
@unique = (settings[:unique] || true)
# Application location:
@location = settings[:location]
# Internal Heap:
@heap = {}
# ### Compile Source
if @definition.is_a?(::String)
if @definition.base64?
@definition = @definition.decode64
end
if @definition.json?
begin
@definition = ::JSON::parse(@definition,{:symbolize_names => true})
@definition = ::GxG::Database::process_import(@definition)
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:definition => @definition}})
end
else
# error - malformed source.
end
end
if @definition.is_a?(::GxG::Database::DetachedHash)
@title = @definition.title
# Question: should I overwrite the potentially passed settings options?
the_opts = @definition[:options].unpersist
if the_opts[:credentialed] == true
@credentialed = true
else
@credentialed = false
end
if the_opts[:unique] == true
@unique = true
else
@unique = false
end
end
# Load Libraries
# You can use ruby or javascript for the :native code portion which will be added to the page body.
# you can then also set a wrapper library script on the library for smooth integration.
# use get_library(uuid/title) to access the library in order to call methods.
if @definition.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedHash)
# libarary requirement format: {:library => "", :type => "", :minimum => 0.0, :maximum => nil}
load_list = []
if @definition[:requirements].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray)
@definition[:requirements].each do |the_requirement|
unless GxG::DISPLAY_DETAILS[:object].library_loaded?(the_requirement)
load_list << the_requirement
end
end
end
GxG::DISPLAY_DETAILS[:object].load_libraries(load_list) do |all_loaded|
if all_loaded == true
# Link to libraries required:
if @definition[:requirements].is_a?(::GxG::Database::PersistedArray)
@definition[:requirements].each do |the_requirement|
the_library = GxG::DISPLAY_DETAILS[:object].get_library(the_requirement)
if the_library
@libraries[(the_library.uuid)] = the_library
end
end
end
# Set Script:
if @definition[:script].size > 0
if @definition[:script].base64?
instance_eval(@definition[:script].to_s.decode64)
else
instance_eval(@definition[:script].to_s)
end
end
else
log_warn("Library load error - not processing main application script for #{@definition.title}.")
end
end
end
#
self
end
#
def get_resource(the_reference=nil)
result = nil
if @definition
@definition[:content].each do |the_object|
if (the_object.title == the_reference || the_object.uuid == the_refrence.to_s.to_sym)
result = the_object
break
end
end
end
result
end
#
def require_resource(the_reference=nil)
# call self.require_resource("script-name") to load content.script-name
result = false
if @definition
# content source record format: {:component => "script", :type => "text/ruby", :script => ""}
the_script = self.get_resource(the_reference)
if the_script
if the_script[:component].to_s == "script" && the_script[:type].to_s == "text/ruby"
if the_script[:script].size > 0
if the_script[:script].base64?
instance_eval(the_script[:script].to_s.decode64)
else
instance_eval(the_script[:script].to_s)
end
result = true
end
end
end
end
result
end
#
def title()
@title
end
#
def credentialed()
@credentialed
end
#
def unique()
@unique
end
#
def location()
@location
end
# Libraries:
def libraries()
@libraries
end
#
def state_data()
@state
end
#
def state_pull()
GxG::CONNECTION.app_state_pull({:application => @process.to_s}) do |response|
if response.is_a?(::Hash)
if response[:result].is_a?(::Hash)
@state = response[:result]
true
else
false
end
else
false
end
end
end
#
def state_push()
GxG::CONNECTION.app_state_push({:application => @process.to_s, :data => @state}) do |response|
if response.is_a?(::Hash)
if response[:result] == true
true
else
false
end
else
false
end
end
end
#
def get_library(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @libraries[(the_reference.to_s.to_sym)]
unless result
@libraries.values.each do |the_library|
if the_library.title == the_reference
result = the_library
break
end
end
end
end
result
end
#
# Viewport Supports:
def link_viewport(the_viewport=nil)
if the_viewport.is_a?(::GxG::Gui::ApplicationViewport)
@viewports[(the_viewport.uuid.to_s.to_sym)] = the_viewport
true
else
false
end
end
#
def unlink_viewport(the_reference=nil)
if the_reference.is_any?(::String, ::Symbol)
if @viewports[(the_reference.to_s.to_sym)]
@viewports.delete((the_reference.to_s.to_sym))
else
@viewports.values.each do |the_viewport|
if the_viewport.title == the_reference
@viewports.delete(the_viewport.uuid)
break
end
end
end
true
else
false
end
end
#
def viewports()
@viewports
end
#
def get_viewport(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @viewports[(the_reference.to_s.to_sym)]
unless result
@viewports.values.each do |the_viewport|
if the_viewport.title == the_reference
result = the_viewport
break
end
end
end
end
result
end
#
# Window Supports:
def link_window(the_window=nil)
if the_window.is_a?(::GxG::Gui::Window)
@windows[(the_window.uuid.to_s.to_sym)] = the_window
# Review: add link to window object to application ??
true
else
false
end
end
#
def unlink_window(the_reference=nil)
if the_reference.is_any?(::String, ::Symbol)
if @windows[(the_reference.to_s.to_sym)]
@windows.delete((the_reference.to_s.to_sym))
else
@windows.values.each do |the_window|
if the_window.title == the_reference
@windows.delete(the_window.uuid)
break
end
end
end
true
else
false
end
end
#
def windows()
@windows
end
#
def get_window(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
result = @windows[(the_reference.to_s.to_sym)]
unless result
@windows.values.each do |the_window|
if the_window.title == the_reference
result = the_window
break
end
end
end
end
result
end
#
def process_uuid()
@process
end
#
def source_uuid()
if @definition.is_a?(::GxG::Database::DetachedHash)
@definition.uuid()
else
nil
end
end
#
def search_resources(the_reference=nil)
result = nil
if the_reference.is_any?(::String, ::Symbol)
if @definition
@definition[:content].search do |item,selector,container|
if item.is_a?(::GxG::Database::DetachedHash)
if the_reference.to_s.to_sym == item.uuid().to_s.to_sym || the_reference.to_s == item.title().to_s
result = item
break
end
end
end
end
end
result
end
# Event Supports:
## Internal method on
def on(the_event_type=nil, captured=false, &block)
if the_event_type.is_any?(::String, ::Symbol)
if block.respond_to?(:call)
@event_handlers[(the_event_type.to_s.downcase.to_sym)] = block
@event_listeners[(the_event_type.to_s.downcase.to_sym)] = `#{self.method(:event_handler).to_proc}`
`#{self.element}.addEventListener(#{the_event_type.to_s.downcase}, #{@event_listeners[(the_event_type.to_s.downcase.to_sym)]}, #{captured})`
end
end
end
#
def remove_listener(the_event_type=nil)
if @event_handlers[(the_event_type.to_s.downcase.to_sym)] && @event_listeners[(the_event_type.to_s.downcase.to_sym)]
`#{self.element}.removeEventListener(#{the_event_type.to_s.downcase},#{@event_listeners[(the_event_type.to_s.downcase.to_sym)]});`
@event_handlers.delete(the_event_type.to_s.downcase.to_sym)
@event_listeners.delete(the_event_type.to_s.downcase.to_sym)
end
true
end
#
def event_handler(the_event)
# format the event record for travel in the system.
# See: https://www.w3schools.com/jsref/dom_obj_event.asp
# Setup event type groupings to glean appropriate details from the event.
the_type = `#{the_event}.type`.to_s.downcase.to_sym
event_record = {}
if @event_handlers[(the_type)]
genre = (GxG::Gui::EventHandlers::EVENT_GENRES[(the_type)] || :event)
if genre
fields = GxG::Gui::EventHandlers::EVENT_FIELDS[(genre)]
if fields
#
%x{
serializer = function thisOne (e) {
if (e) {
var o = {
eventName: e.toString(),
altKey: e.altKey,
bubbles: e.bubbles,
button: e.button,
buttons: e.buttons,
cancelBubble: e.cancelBubble,
cancelable: e.cancelable,
clientX: e.clientX,
clientY: e.clientY,
composed: e.composed,
ctrlKey: e.ctrlKey,
currentTarget: e.currentTarget ? e.currentTarget.outerHTML : null,
defaultPrevented: e.defaultPrevented,
detail: e.detail,
eventPhase: e.eventPhase,
fromElement: e.fromElement ? e.fromElement.outerHTML : null,
isTrusted: e.isTrusted,
layerX: e.layerX,
layerY: e.layerY,
metaKey: e.metaKey,
movementX: e.movementX,
movementY: e.movementY,
offsetX: e.offsetX,
offsetY: e.offsetY,
pageX: e.pageX,
pageY: e.pageY,
relatedTarget: e.relatedTarget ? e.relatedTarget.outerHTML : null,
returnValue: e.returnValue,
screenX: e.screenX,
screenY: e.screenY,
shiftKey: e.shiftKey,
sourceCapabilities: e.sourceCapabilities ? e.sourceCapabilities.toString() : null,
target: e.target ? e.target.outerHTML : null,
timeStamp: e.timeStamp,
toElement: e.toElement ? e.toElement.outerHTML : null,
type: e.type,
view: e.view ? e.view.toString() : null,
which: e.which,
x: e.x,
y: e.y
};
return o;
};
};
event_record = JSON.stringify(serializer(#{the_event}));
}
event_record = ::JSON::parse(event_record.to_s, {:symbolize_names => true})
# puts "Got: #{event_record.inspect}"
end
end
#
@event_handlers[(the_type)].call(event_record)
#
end
end
#
def run(data={})
# override this in your object script.
if data.is_a?(::Hash)
if data[:restore] == true
self.state_pull
end
end
end
#
def exitready?(data={})
# override this in your object script.
:ready
end
#
def before_exit(data={})
# override this in your object script.
true
end
def exit(data={})
# override this in your object script.
# GxG::APPLICATIONS[:processes]
GxG::CONNECTION.application_close({:application => @process}) do |response|
if response.is_a?(::Hash)
if response[:result] == true
GxG::APPLICATIONS[:processes].delete(@process)
else
log_warn("Could not close application #{@process.inspect} : #{response.inspect}")
end
else
log_warn("Malformed response: #{response.inspect}")
end
end
true
end
#
def window_close(details={})
# override this in your object script.
if details.is_a?(::Hash)
if GxG::valid_uuid?(details[:window])
the_window = GxG::DISPLAY_DETAILS[:object].get_window(details[:window])
if the_window
# TODO: re-think documents and such.
# Document data needs saving first ??
@viewports.keys.each do |the_reference|
the_viewport = the_window.find_child(the_reference)
if the_viewport
the_viewport.set_application(nil)
self.unlink_viewport(the_reference)
end
end
self.unlink_window(the_window.uuid)
GxG::DISPLAY_DETAILS[:object].window_close(the_window.uuid)
unless @windows.size > 0
self.exit()
end
end
else
log_warn("Invalid Argument passed: #{details.inspect}")
end
end
end
#
# Viewport Management:
def viewport_clear(the_reference=nil)
viewport = self.get_viewport(the_reference)
if viewport
viewport.gxg_each_child do |the_item|
unless the_item == viewport
the_item.destroy
end
end
true
else
false
end
end
#
end
end

1270
gxg/web/gxg_augments.rb Normal file

File diff suppressed because it is too large Load diff

1081
gxg/web/gxg_comm.rb Normal file

File diff suppressed because it is too large Load diff

2319
gxg/web/gxg_database.rb Normal file

File diff suppressed because it is too large Load diff

3193
gxg/web/gxg_datetime.rb Normal file

File diff suppressed because it is too large Load diff

1887
gxg/web/gxg_elements.rb Normal file

File diff suppressed because it is too large Load diff

567
gxg/web/gxg_events.rb Normal file
View file

@ -0,0 +1,567 @@
# Events Section:
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>, :post_process => <some-proc>, :error_handler => <some-object>/<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), :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
#
def body()
@data[:body]
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
raise ArgumentError, "Unknown event type: #{event_type}"
end
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
#
def initialize()
@uuid = ::GxG.uuid_generate().to_sym
@busy = false
@outlets = {}
@messages = []
end
#
def uuid()
@uuid
end
#
def keys()
@outlets.keys()
end
#
def [](the_key)
@outlets[(the_key)]
end
#
def []=(the_key,the_logger)
if the_logger.is_any?(::Logger, ::GxG::Events::LoggerDB)
@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
#
def process_messages()
unless @busy
@busy = true
#
the_message = @messages.shift
if the_message.is_a?(::GxG::Events::Message)
begin
if the_message[:subject] == :notification
if the_message[:body].is_a?(::Hash)
outlets = self.keys()
outlets.to_enum(:each).each do |the_outlet_key|
the_outlet = self[(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
@busy = false
# log_error({:error => the_error, :parameters => {:message => the_message}})
end
end
#
@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
# self.mailbox << ::GxG::Events::Message.new({:sender => (origin || self), :severity => severity, :message => message, :progname => progname})
the_message = new_message({:severity => severity, :message => message, :progname => progname}, :notification)
if origin
the_message[:sender] = origin
end
while @busy == true do
sleep 0.333
end
@busy = true
@messages << the_message
@busy = false
true
rescue Exception => the_error
@busy = false
# 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)
self.add(::Logger::UNKNOWN, message, progname, (origin || self), &block)
end
alias :trace :unknown
def fatal?()
true
end
def fatal(message = nil, progname = nil, origin = nil, &block)
self.add(::Logger::FATAL, message, progname, (origin || self), &block)
end
def error?()
true
end
def error(message = nil, progname = nil, origin = nil, &block)
self.add(::Logger::ERROR, message, progname, (origin || self), &block)
end
def warn?()
true
end
alias :warning? :warn?
def warn(message = nil, progname = nil, origin = nil, &block)
self.add(::Logger::WARN, message, progname, (origin || self), &block)
end
alias :warning :warn
def info?()
true
end
def info(message = nil, progname = nil, origin = nil, &block)
self.add(::Logger::INFO, message, progname, (origin || self), &block)
end
def debug?()
true
end
def debug(message = nil, progname = nil, origin = nil, &block)
self.add(::Logger::DEBUG, message, progname, (origin || self), &block)
end
end
#
class EventDispatcher
def initialize(interval=0.333)
super()
@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 = []
# JavaScript main pushing timer:
@js_interval = (interval.to_f * 1000.0).to_i
@host_timer = nil
# Register with global dispatcher pool:
self
end
#
def startup()
@running = true
unless @host_timer
@host_timer = `setInterval(#{method(:tick).to_proc}, #{@js_interval})`
end
if @host_timer
true
else
false
end
end
#
def shutdown()
@running = false
if @host_timer
if @host_timer.is_a?(Numeric)
`clearInterval(#{@host_timer})`
else
`clearInterval(#{@host_timer}._id)`
end
end
self.cancel_all_timers
@host_timer = nil
true
end
#
def tick()
if @running
unless @ticking
@ticking = true
events = []
#
if @timers.size > 0
triggered = []
current = ::Time.now.to_f
@timers.each_with_index do |timer,indexer|
if current >= timer[:when]
events << timer[:what]
if timer[:interval]
@timers[(indexer)][:when] = (current + timer[:interval].to_f)
else
triggered.unshift(indexer)
end
end
end
triggered.each do |indexer|
@timers.delete_at(indexer)
end
end
#
total_slots = 100
load_size = 0
queues = {}
@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
begin
the_event.call()
rescue Exception => the_error
#
end
end
end
#
@ticking = false
end
end
end
#
def inspect_timers()
@timers
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
def add_timer(options={},&block)
what_time = options[:when]
what = (options[:do] || block)
#
if what_time.is_a?(::Numeric)
# ms offset
what_time = (::Time::now.to_f + what_time.to_f)
else
if what_time.is_a?(::Time)
what_time = what_time.to_f
else
raise ArgumentError, "you must specify a :when => millisecond (float) offset, or a Time for the timer"
end
end
#
unless what.respond_to?(:call)
raise ArgumentError, "you must provide a :do => callable object (Proc) for the timer, or a block"
end
the_id = ::GxG.uuid_generate().to_sym
@timers << {:when => what_time, :what => what, :id => the_id}
{:timer => the_id}
end
#
def add_periodic_timer(options={},&block)
interval = options[:interval]
what = (options[:do] || block)
#
what_time = ::Time::now.to_f
if interval.is_a?(::Numeric)
# offset
what_time += interval.to_f
else
raise ArgumentError, "you must specify an :interval => Float (as you would with sleep) offset interval for the periodic timer"
end
#
unless what.respond_to?(:call)
raise ArgumentError, "you must provide a :do => callable object (Proc) for the timer, or a block"
end
the_id = ::GxG.uuid_generate().to_sym
@timers << {:when => what_time, :what => what, :interval => interval.to_f, :id => the_id}
{:timer => the_id}
end
#
def cancel_timer(params={})
the_id = params[:timer]
result = false
if the_id
@timers.each_index do |indexer|
if @timers[(indexer)][:id] == the_id
@timers.delete_at(indexer)
result = true
break
end
end
end
result
end
#
def cancel_all_timers()
while @ticking == true do
sleep 0.333
end
@timers = []
true
end
#
end
end
end
# Establish Console Logger for Applications:
module GxG
# setup LOG.
if const_defined?(:LOG)
remove_const(:LOG)
end
LOG = ::GxG::Events::LogRing.new()
end
::GxG::LOG[:default] = ::Logger.new(::Logger::WARN)
# Establish Dispatcher Service for Applications:
module GxG
# setup DISPATCHER & LOG timer.
if const_defined?(:DISPATCHER)
remove_const(:DISPATCHER)
end
DISPATCHER = ::GxG::Events::EventDispatcher.new(0.1)
DISPATCHER.startup
LOG_TIMER = DISPATCHER.add_periodic_timer({:interval => 1.0}) { ::GxG::LOG.process_messages }
def self.shutdown()
DISPATCHER.shutdown
end
end
#

8418
gxg/web/gxg_ferro.rb Normal file

File diff suppressed because it is too large Load diff

2130
gxg/web/gxg_foundation.rb Normal file

File diff suppressed because it is too large Load diff

1281
gxg/web/gxg_gui.rb Normal file

File diff suppressed because it is too large Load diff

2349
gxg/web/gxg_gui_old.rb Normal file

File diff suppressed because it is too large Load diff

483
gxg/web/gxg_iac.rb Normal file
View file

@ -0,0 +1,483 @@
#
module GxG
module Networking
class Channel
#
def initialize(the_connector=nil, settings={})
unless the_connector.is_a?(GxG::Networking::Connector)
raise ArgumentError, "You MUST provide a GxG::Networking::Connector to associate with."
end
@connector = the_connector
@uuid = (settings[:uuid] || GxG::uuid_generate().to_sym)
@remote_uuid = settings[:remote_uuid]
@remote_interface = []
@mounted_object = nil
@pending = {}
# messages from server
@buffer = []
# messages from objects
@inbox = []
# deferred messages to send (to object or server) ???
@outbox = []
#
@timer = post_event_periodic(0.333) do
self.process_messages
end
#
post_event(:communications) do
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = @remote_uuid
gxg_message[:body] = {:operation => "fetch_interface"}
@connector.transmit(gxg_message)
end
#
self
end
#
def closed?()
if @timer.is_a?(Hash)
false
else
true
end
end
#
def close()
result = false
if @timer.is_a?(Hash)
cancel_periodic(@timer)
@timer = nil
end
# send out close_channel operation to partner on server, set result = true
result
end
#
def uuid()
@uuid
end
#
def remote_uuid()
@remote_uuid
end
#
def mounted()
@mounted_object
end
#
def mount(something=nil)
@mounted_object = something
end
#
def process_messages()
# process @buffer items, add to @outbox
server_message = @buffer.shift
if server_message[:body].is_a?(Hash)
# puts "Channel Processing: #{server_message.inspect}"
# puts "Got channel message: #{server_message.inspect}"
if server_message[:body][:operation]
case server_message[:body][:operation]
when "define_interface"
if server_message[:body][:interface].is_a?(Array)
@remote_interface = []
server_message[:body][:interface].each do |entry|
@remote_interface << entry.to_s.to_sym
end
end
if @mounted_object
@mounted_object.set_remote_uuid(server_message[:body][:remote_uuid].to_s.to_sym)
end
when "fetch_interface"
if @mounted_object
method_list = []
@mounted_object.public_methods.each do |entry|
method_list << entry.to_s
end
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:operation => "define_interface", :remote_uuid => @mounted_object.uuid.to_s, :interface => method_list}
@connector.transmit(gxg_message)
end
when "mount_object"
# a) by registry uuid, b) create with params, c) ?
# GxG::OBJECT_SPACE[:registry][(the_uuid)]
# GxG::object_by_uuid(the_uuid)
# puts "Attempting to mount object"
if server_message[:body][:uuid]
@mounted_object = GxG::object_by_uuid(server_message[:body][:uuid].to_s.to_sym)
if @mounted_object
if server_message[:body][:remote_uuid]
@mounted_object.set_remote_uuid(server_message[:body][:remote_uuid].to_s.to_sym)
end
end
end
# ???
if server_message[:body][:type]
case server_message[:body][:type].to_s
when "GxG::Gui::Display"
@mounted_object = nil
# puts "Creating Display ..."
if server_message[:body][:parameters]
if server_message[:body][:parameters].is_a?(Array)
@mounted_object = ::GxG::Gui::Display.new(*server_message[:body][:parameters])
else
@mounted_object = ::GxG::Gui::Display.new(server_message[:body][:parameters])
end
else
@mounted_object = ::GxG::Gui::Display.new()
end
# BUG - FIX - Never gets to this line of code:
# puts "Mounted: #{@mounted_object.inspect} with Parameters: #{server_message[:body][:parameters].inspect}"
else
log_error({:error => (Exception.new("Unrecognized object type")), :parameters => {:message => server_message}})
end
#
end
#
if @mounted_object != nil
#
# puts "Object MOUNTED."
@mounted_object.set_channel(self)
method_list = []
@mounted_object.public_methods.each do |entry|
method_list << entry.to_s
end
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:operation => "define_interface", :remote_uuid => @mounted_object.uuid.to_s, :interface => method_list}
@connector.transmit(gxg_message)
else
log_error({:error => (Exception.new("Failed to mount object")), :parameters => {:mesaage => server_message}})
end
#
end
else
# RPC message
# Layer 3
rpc_message = new_message
server_message[:body].keys.each do |rpc_key|
rpc_message[(rpc_key)] = server_message[:body][(rpc_key)]
end
if rpc_message[:body][:response]
case rpc_message[:body][:response]
when "success"
responder = @pending.delete(rpc_message.id().to_s.to_sym)
if responder
if responder[:success].respond_to?(:call)
responder[:success].call(rpc_message)
end
end
when "fail"
responder = @pending.delete(rpc_message.id().to_s.to_sym)
if responder
if responder[:fail].respond_to?(:call)
responder[:fail].call(rpc_message)
end
end
end
else
if @mounted_object
# {:method => :nil, :parameters => []/{}/etc}
if rpc_message[:body][:method]
if @mounted_object.public_methods.include?(rpc_message[:body][:method].to_s.to_sym)
begin
if rpc_message[:body][:parameters]
if rpc_message[:body][:parameters].is_a?(Array)
result = @mounted_object.send(rpc_message[:body][:method].to_s.to_sym, *rpc_message[:body][:parameters])
else
result = @mounted_object.send(rpc_message[:body][:method].to_s.to_sym, rpc_message[:body][:parameters])
end
else
result = @mounted_object.send(rpc_message[:body][:method].to_s.to_sym)
end
# result success back.
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:sender => rpc_message[:to], :to => rpc_message[:sender], :id => rpc_message[:id], :subject => rpc_message[:subject], :body => {:response => "success", :result => result}}
@connector.transmit(gxg_message)
#
rescue Exception => the_error
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:sender => rpc_message[:to], :to => rpc_message[:sender], :id => rpc_message[:id], :subject => rpc_message[:subject], :body => {:response => "fail", :error => the_error}}
@connector.transmit(gxg_message)
#
end
else
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:sender => rpc_message[:to], :to => rpc_message[:sender], :id => rpc_message[:id], :subject => rpc_message[:subject], :body => {:response => "fail", :error => "remote method missing"}}
@connector.transmit(gxg_message)
#
end
end
else
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = server_message[:sender]
gxg_message[:body] = {:sender => rpc_message[:to], :to => rpc_message[:sender], :id => rpc_message[:id], :subject => rpc_message[:subject], :body => {:response => "fail", :error => "no mount available"}}
@connector.transmit(gxg_message)
#
end
end
#
end
end
# process next message in @inbox
end
#
def in_buffer(the_message)
@buffer << the_message
end
#
def transmit(the_message)
if the_message.is_a?(GxG::Events::Message)
@connector.transmit(the_message)
end
#
end
#
def remote_call(the_message)
# Layer 3
if @remote_interface.size > 0
if (the_message[:success].respond_to?(:call)) || (the_message[:fail].respond_to?(:call))
@pending[(the_message.id().to_s.to_sym)] = the_message
end
rpc_message = {}
the_message.keys.each do |the_key|
unless [:success, :fail].include?(the_key.to_sym)
rpc_message[(the_key)] = the_message[(the_key)]
end
end
gxg_message = new_message
gxg_message[:sender] = @uuid
gxg_message[:to] = @remote_uuid
gxg_message[:body] = rpc_message
@connector.transmit(gxg_message)
else
error_message = "No Remote Interface Loaded."
@pending.delete(the_message.id().to_s.to_sym)
if (the_message[:fail].respond_to?(:call))
the_message[:fail].call({:response => "fail", :error => error_message})
else
raise Exception, error_message
end
end
end
#
def remote_interface()
@remote_interface
end
#
end
#
class Connector
#
def transmit(the_message)
# Fully qualified GxG::Events::Message with all the data needed to send it out.
# Packet Frame Format: {:id => <uuid>, :routing => <array>, :index => <int>, :total => <int>, :subject => <string>, :payload => <Base64-JSON>}
if @active == true
begin
while @out_busy == true do
sleep 0.333
end
@out_busy = true
if the_message.is_a?(Hash)
if the_message[:operation]
@socket << the_message.to_json.encode64
end
else
unless the_message.is_a?(GxG::Events::Message)
raise ArgumentError, "You MUST provide a valid GxG::Events::Message message object."
end
if the_message[:body].is_a?(String)
if the_message[:body].base64?
payload = the_message[:body]
else
payload = the_message[:body].encode64
end
else
if the_message[:body].is_any?(Hash,Array)
payload = the_message[:body].to_json.encode64
else
raise ArgumentError, "You MUST supply a message body that is a Hash, an Array, or a Base64 String."
end
end
if the_message[:routing].is_a?(Array)
packet = {:id => the_message[:id], :routing => the_message[:routing], :index => 0, :total => 10000, :subject => the_message[:subject], :payload => ""}
else
packet = {:id => the_message[:id], :routing => [(the_message[:sender]), (the_message[:to])], :index => 0, :total => 10000, :subject => the_message[:subject], :payload => ""}
end
overhead = packet.to_json.size
ranges = GxG.apportioned_ranges(payload.size,(65536 - overhead))
packet[:total] = ranges.size
ranges.each_with_index do |the_range,indexer|
packet[:index] = indexer
packet[:payload] = payload[(the_range)]
@socket << packet.to_json.encode64
end
end
@out_busy = false
true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:message => the_message}})
@out_busy = false
false
end
end
end
#
def receive(data)
# Data will ALWAYS be: Base64 JSON
# Packet Frame Format: {:id => <uuid>, :routing => <array>, :index => <int>, :total => <int>, :subject => <string>, :payload => <Base64-JSON>}
# On Mulit-Packet transfers: :id, :routing, :total, :subject are the same on each packet - only :index and :payload differ.
# Routing: first=sender, last=recipient; Connectors are invisible and don't have an entry in the routing table. Channels operate as Pairs.
# :index (which packet of :total set)? 0-offset (Marshalling)
# recipient Channel is found and sent the completed data set when all packets are in. Further processing will happen in the Channel object.
begin
while @in_busy == true do
sleep 0.333
end
@in_busy = true
packet = JSON.parse(data.decode64,{:symbolize_names => true})
unless packet.is_a?(Hash)
raise Exception, "Malformed Packet received."
end
# puts "Message Received: #{packet.inspect}"
if packet[:operation]
# Connector Operation
case packet[:operation]
when "introduction"
@remote_uuid = packet[:uuid]
when "heartbeat_query"
self.transmit({:operation => "heartbeat_acknowledge"})
when "create_channel"
unless @channels[(packet[:uuid])]
@channels[(packet[:uuid])] = GxG::Networking::Channel.new(self,{:uuid => packet[:uuid], :remote_uuid => packet[:remote_uuid]})
end
end
else
# Data packet
unless (packet[:id] && packet[:routing] && packet[:index] && packet[:total] && packet[:payload])
raise Exception, "Malformed Packet received."
end
# puts "Routing: #{packet[:routing].inspect}"
sender = packet[:routing].first.to_sym
recipient = packet[:routing].last.to_sym
if packet[:total] > 1
# Multi-part Packet Set
unless @buffers[(recipient)].is_a?(Array)
@buffers[(recipient)] = []
packet[:total].times do
@buffers[(recipient)] << nil
end
end
@buffers[(recipient)][(packet[:index])] = packet
complete = true
@buffers[(recipient)].each_index do |indexer|
unless @buffers[(recipient)][(indexer)]
complete = false
break
end
end
if complete == true
buffer = ""
@buffers[(recipient)].each_index do |indexer|
buffer = (buffer + @buffers[(recipient)][(indexer)][:payload])
end
if buffer.base64?
begin
buffer = ::JSON.parse(buffer.decode64,{:symbolize_names => true})
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:payload => buffer}})
end
end
the_message = new_message
the_message[:sender] = sender
the_message[:to] = recipient
the_message[:id] = packet[:id]
the_message[:subject] = packet[:subject]
the_message[:body] = buffer
unless @channels[(recipient)]
@channels[(recipient)] = GxG::Networking::Channel.new(self,{:uuid => recipient, :remote_uuid => sender})
end
@channels[(recipient)].in_buffer(the_message)
@buffers.delete(recipient)
end
else
# Single Packet
if packet[:payload].is_a?(String)
if packet[:payload].base64?
begin
packet[:payload] = ::JSON.parse(packet[:payload].decode64,{:symbolize_names => true})
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:payload => packet[:payload]}})
end
end
end
the_message = new_message
the_message[:sender] = sender
the_message[:to] = recipient
the_message[:id] = packet[:id]
the_message[:subject] = packet[:subject]
the_message[:body] = packet[:payload]
unless @channels[(recipient)]
@channels[(recipient)] = GxG::Networking::Channel.new(self,{:uuid => recipient, :remote_uuid => sender})
end
@channels[(recipient)].in_buffer(the_message)
end
end
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:data => data}})
end
@in_busy = false
true
end
#
def close()
self.transmit({:operation => "disconnect"})
@socket.close(1000)
@active = false
end
# Channel supports:
def channels()
@channels
end
#
def initialize(the_url=nil)
unless the_url
raise ArgumentError, "You MUST provide a URL String to contact a host."
end
@uuid = GxG.uuid_generate().to_sym
@remote_uuid = nil
@active = false
@in_busy = false
@out_busy = false
@pending = {}
@channels = {}
@buffers = {}
@the_connector = self
@socket = Browser::Socket.new(the_url) do |the_socket|
the_socket.on :open do
@active = true
@the_connector.transmit({:operation => "introduction", :uuid => @uuid.to_s, :location => `window.location["href"]`})
end
the_socket.on :close do
@active = false
end
the_socket.on :message do |the_event|
@the_connector.receive(the_event.data)
end
end
# Set trap for page closure / refresh / unload / etc
`jQuery(window).bind("beforeunload", function() { Opal.eval("GxG::CONNECTION.close"); });`
self
end
end
end
end

140
gxg/web/gxg_layout.rb Normal file
View file

@ -0,0 +1,140 @@
module GxG
module Gui
#
# Layout System Supports:
def self.layout_refresh()
# get rectangle of item -> convert to page % offsets
# `window.innerHeight`.to_i
# `window.innerWidth`.to_i
# <element>.getClientRects()[0].left
page_width = `window.innerWidth`.to_i
# Calculating proper page height, See: https://stackoverflow.com/questions/1145850/how-to-get-height-of-entire-document-with-javascript
body = `document.body`
html = `document.documentElement`
page_bottom = 0
%x{
page_bottom = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
}
page_height = [(`window.innerHeight`.to_i),(page_bottom.to_i)].max
# bounding areas:
top_bound = 0.0
left_bound = 0.0
right_bound = 1.0
bottom_bound = 1.0
# Update object percentages
[:"top-left", :top, :"top-right", :right, :"bottom-right", :bottom, :"bottom-left", :left].each do |the_position|
::GxG::LAYOUT[(the_position)].each_pair do |the_uuid,the_record|
object = self.find_object(the_uuid)
if object
rectangle = {:top => 0, :left => 0, :right => 0, :bottom => 0}
rectangle[:top] = `#{object.element}.getBoundingClientRect().top`.to_i
rectangle[:left] = `#{object.element}.getBoundingClientRect().left`.to_i
rectangle[:right] = `#{object.element}.getBoundingClientRect().right`.to_i
rectangle[:bottom] = `#{object.element}.getBoundingClientRect().bottom`.to_i
percentages = {:top => 0.0, :left => 0.0, :right => 0.0, :bottom => 0.0}
percentages[:top] = (rectangle[:top].to_f / page_height.to_f)
percentages[:left] = (rectangle[:left].to_f / page_width.to_f)
percentages[:right] = (rectangle[:right].to_f / page_width.to_f)
percentages[:bottom] = (rectangle[:bottom].to_f / page_height.to_f)
::GxG::LAYOUT[(the_position.to_s.to_sym)][(the_uuid.to_s.to_sym)] = percentages
end
end
end
#
[:"top-left", :top, :"top-right", :right, :"bottom-right", :bottom, :"bottom-left", :left].each do |the_position|
::GxG::LAYOUT[(the_position)].values.each do |the_record|
case the_position
when :top
if the_record[:bottom] > top_bound
top_bound = the_record[:bottom]
end
when :left
if the_record[:right] > left_bound
left_bound = the_record[:right]
end
when :right
if the_record[:left] < right_bound
right_bound = the_record[:left]
end
when :bottom
if the_record[:top] < bottom_bound
bottom_bound = the_record[:top]
end
when :"top-left"
if the_record[:bottom] > top_bound
top_bound = the_record[:bottom]
end
if the_record[:right] > left_bound
left_bound = the_record[:right]
end
when :"top-right"
if the_record[:bottom] > top_bound
top_bound = the_record[:bottom]
end
if the_record[:left] < right_bound
right_bound = the_record[:left]
end
when :"bottom-right"
if the_record[:top] < bottom_bound
bottom_bound = the_record[:top]
end
if the_record[:left] < right_bound
right_bound = the_record[:left]
end
when :"bottom-left"
if the_record[:top] < bottom_bound
bottom_bound = the_record[:top]
end
if the_record[:right] > left_bound
left_bound = the_record[:right]
end
end
end
end
#
::GxG::LAYOUT_LIMITS[:page_width] = page_width.to_i
::GxG::LAYOUT_LIMITS[:page_height] = page_height.to_i
::GxG::LAYOUT_LIMITS[:top] = (top_bound * page_height.to_f).to_i
::GxG::LAYOUT_LIMITS[:top_percent] = top_bound * 100.0
::GxG::LAYOUT_LIMITS[:left] = (left_bound * page_width.to_f).to_i
::GxG::LAYOUT_LIMITS[:left_percent] = left_bound * 100.0
::GxG::LAYOUT_LIMITS[:right] = (right_bound * page_width.to_f).to_i
::GxG::LAYOUT_LIMITS[:right_percent] = right_bound * 100.0
::GxG::LAYOUT_LIMITS[:bottom] = (bottom_bound * page_height.to_f).to_i
::GxG::LAYOUT_LIMITS[:bottom_percent] = bottom_bound * 100.0
true
end
#
def self.layout_add_item(the_uuid=nil, the_position=nil, refresh=true)
# store by uuid in position's record
if [:"top-left", :top, :"top-right", :right, :"bottom-right", :bottom, :"bottom-left", :left].include?(the_position.to_s.to_sym)
#
::GxG::LAYOUT[(the_position.to_s.to_sym)][(the_uuid.to_s.to_sym)] = {:top => 0.0, :left => 0.0, :right => 0.0, :bottom => 0.0}
if refresh
::GxG::Gui::layout_refresh
end
#
true
else
false
end
end
#
def self.layout_remove_item(the_uuid=nil)
[:"top-left", :top, :"top-right", :right, :"bottom-right", :bottom, :"bottom-left", :left].each do |the_position|
if ::GxG::LAYOUT[(the_position.to_s.to_sym)][(the_uuid.to_s.to_sym)]
::GxG::LAYOUT[(the_position.to_s.to_sym)].delete((the_uuid.to_s.to_sym))
break
end
end
::GxG::Gui::layout_refresh
#
true
end
#
def layout_content_area()
::GxG::LAYOUT_LIMITS
end
#
end
end

207
gxg/web/gxg_libraries.rb Normal file
View file

@ -0,0 +1,207 @@
# ----------------------------------------------------------------------------------------------------
# Provided Module Space for libraries to declare their :global within.
module Libraries
end
# ----------------------------------------------------------------------------------------------------
module GxG
class Library
#
def initialize(the_definition=nil)
unless the_definition.is_a?(GxG::Database::PersistedHash)
raise ArgumentError, "You MUST provide a persisted source object."
end
#
@uuid = the_definition.uuid
@title = the_definition.title
@version = the_definition.version
@type = (the_definition[:type] || "text/javascript").to_s
#
load_list = []
if the_definition[:requirements].is_a?(::GxG::Database::PersistedArray)
the_definition[:requirements].each do |the_requirement|
unless GxG::DISPLAY_DETAILS[:object].library_loaded?(the_requirement)
load_list << the_requirement
end
end
end
GxG::DISPLAY_DETAILS[:object].load_libraries(load_list) do |all_loaded|
if all_loaded == true
#
@src = the_definition[:options][:src].to_s
unless @src.size > 0
@src = nil
end
@charset = (the_definition[:options][:charset] || "utf-8").to_s
@local = the_definition[:options][:local]
unless @local
@local = false
end
@global = the_definition[:options][:global].to_s
unless @global.size > 0
@global = nil
end
# Load native code portion if supplied.
# If the global is already defined, your wrapper code will still work.
unless self.loaded?()
unless @global.to_s.size > 0
if @type = "text/javascript"
@global = "Opal"
else
@global = "GxG"
end
end
if @src.is_a?(::String)
the_uuid = @uuid
the_type = @type
the_charset = @charset
the_src = @src
%x{
var the_element = document.createElement('script');
the_element.id = the_uuid;
the_element.type = the_type;
the_element.charset = the_charset;
the_element.src = the_src;
document.body.appendChild(the_element);
}
else
the_script = the_definition[:options][:native].to_s
if the_script.is_a?(::String)
if the_script.size > 0
if the_script.base64?
the_script = the_script.to_s.decode64
end
the_uuid = @uuid
the_type = @type
the_charset = @charset
%x{
var the_element = document.createElement('script');
the_element.id = the_uuid;
the_element.type = the_type;
the_element.charset = the_charset;
the_element.innerHTML = the_script;
document.body.appendChild(the_element);
}
end
end
end
end
@source = the_definition
self.set_script(@source[:script].to_s)
@source = nil
else
raise Exception, "Library load error - skipping library intitialization: #{@title}"
end
end
#
self
end
#
def uuid
@uuid
end
#
def title
@title
end
#
def version()
@version
end
#
def type()
@type
end
#
def local()
@local
end
#
def global()
@global
end
#
def set_script(the_script_body="")
result = false
if the_script_body.size > 0
#
begin
#
if the_script_body.base64?
the_script_body = the_script_body.decode64
end
#
if @local == true
instance_eval(the_script_body)
else
eval(the_script_body)
end
result = true
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:script => the_script_body}})
end
end
result
end
#
def require_resource(the_reference=nil)
# call self.require_resource("script-name") to load content.script-name
result = false
if @source
# content source record format: {:component => "script", :type => "text/ruby", :script => ""}
@source[:content].each do |the_script|
if (the_script.title == the_reference || the_script.uuid == the_refrence.to_s.to_sym) && the_script[:component].to_s == "script" && the_script[:type].to_s == "text/ruby"
self.set_script(the_script[:script].to_s)
result = true
break
end
end
end
result
end
#
def require_module_resource(the_reference=nil)
# call self.require_module_resource("script-name") to load content.script-name
result = false
if @source
# content source record format: {:component => "script", :type => "text/ruby", :script => ""}
@source[:content].each do |the_script|
if (the_script.title == the_reference || the_script.uuid == the_refrence.to_s.to_sym) && the_script[:component].to_s == "script" && the_script[:type].to_s == "text/ruby"
if the_script[:script].size > 0
if the_script[:script].base64?
eval(the_script[:script].to_s.decode64)
else
eval(the_script[:script].to_s)
end
result = true
break
end
end
end
end
result
end
#
def loaded?
# Opal/JS Note: by saying `!!<js-value>` it will coerse it into a ruby boolean.
if @type == "text/javascript"
if (`(window.hasOwnProperty(#{@global.to_s}))`)
true
else
false
end
else
if @global
if (eval("defined? #{@global.to_s}"))
true
else
false
end
else
false
end
end
end
end
#
end

File diff suppressed because it is too large Load diff

3786
gxg/web/gxg_uri.rb Normal file

File diff suppressed because it is too large Load diff

2673
gxg/web/gxg_vdom.rb Normal file

File diff suppressed because it is too large Load diff

2283
gxg/web/gxg_windows.rb Normal file

File diff suppressed because it is too large Load diff

741
gxg/web/gxg_xhr.rb Normal file
View file

@ -0,0 +1,741 @@
#
#require 'gxg_uri'
#
module GxG
module Networking
class Channel
#
end
#
class Connector
#
def remote_uuid()
@remote_uuid
end
#
def open?()
@active
end
#
def close()
the_message = new_message({:operation => "close", :display_path => @display_path})
the_message[:sender] = @uuid
the_message[:to] = @remote_uuid
self.pull(the_message,{:synchronous => true})
@active = false
@csrf = nil
if @heart_beat_timer.is_a?(::Hash)
GxG::DISPATCHER.cancel_timer(@heart_beat_timer)
end
end
#
def push(the_message=nil,options={})
if @active
# Uses PUT method, with data payload
if the_message.is_a?(::GxG::Events::Message)
payload = the_message.body.to_json.to_s
the_xhr = `new XMLHttpRequest`
`#{the_xhr}.open('PUT',#{@host_prefix},true)`
`#{the_xhr}.timeout = #{(options[:timeout] || 5000)}`
`#{the_xhr}.setRequestHeader('X-Requested-With','XMLHttpRequest')`
`#{the_xhr}.setRequestHeader('Accept','application/json')`
`#{the_xhr}.setRequestHeader('X-CSRF-Token', #{@csrf})`
`#{the_xhr}.setRequestHeader('Content-Type', 'application/json')`
# unless (`navigator.userAgent`).include?("Firefox") || (`navigator.userAgent`).include?("Chrome")
# `#{the_xhr}.setRequestHeader('Content-Length', #{payload.size})`
# end
failed = Proc.new do |reason=nil|
the_message.fail(reason)
end
succeeded = Proc.new do
response_code = (`#{the_xhr}.status`).to_i
if response_code < 300
the_message.succeed(::JSON.parse(`#{the_xhr}.response`,{:symbolize_names => true}))
else
failed.call(response_code)
end
end
`#{the_xhr}.addEventListener('load', #{succeeded})`
`#{the_xhr}.addEventListener('error', #{failed})`
`#{the_xhr}.addEventListener('timeout', #{failed})`
`#{the_xhr}.addEventListener('abort', #{failed})`
`#{the_xhr}.send(#{payload})`
#
true
else
# error?
false
end
end
#
end
#
def pull(the_message=nil,options={:synchronous => false})
if @active
# Uses GET method, with in-URL query specifiers (no data payload)
if the_message.is_a?(::GxG::Events::Message)
the_query = ""
the_message.body.keys.each do |the_key|
the_query = the_query + ("#{the_key.to_s}=#{the_message.body[(the_key)].to_s}")
unless the_key == the_message.body.keys.last
the_query = the_query + "&"
end
end
the_url = (@host_prefix.to_s + "?" + the_query)
the_xhr = `new XMLHttpRequest`
if options[:synchronous] == true
`#{the_xhr}.open('GET',#{the_url.to_s},false)`
else
`#{the_xhr}.open('GET',#{the_url.to_s},true)`
`#{the_xhr}.timeout = #{(options[:timeout] || 600000)}`
end
`#{the_xhr}.setRequestHeader('X-Requested-With','XMLHttpRequest')`
`#{the_xhr}.setRequestHeader('Accept','application/json')`
failed = Proc.new do |reason=nil|
the_message.fail(reason)
end
succeeded = Proc.new do
response_code = (`#{the_xhr}.status`).to_i
if response_code < 300
the_message.succeed(::JSON.parse(`#{the_xhr}.response`,{:symbolize_names => true}))
else
failed.call(response_code)
end
end
`#{the_xhr}.addEventListener('load', #{succeeded})`
`#{the_xhr}.addEventListener('error', #{failed})`
`#{the_xhr}.addEventListener('timeout', #{failed})`
`#{the_xhr}.addEventListener('abort', #{failed})`
`#{the_xhr}.send(null)`
#
true
else
# error?
false
end
end
#
end
#
def push_message(the_message=nil)
# TODO: establish a sensible routing scheme and make better use of message :sender and :to in that idea.
end
#
def pull_message(options={})
# Use to pull a message portion or look for application messages on the server.
end
#
def process_pending(options={})
#
end
# Support-level methods for pulling/pushing objects or object-deltas.
def push_object_portions(details={}, error_handler=nil, &the_handler)
# supply thread and index to send, if successful, will post event to keep chain going.
# Details: {:thread => "", :index => 0, :portions => 100}
end
# Search The Database(s)
def search_database(details={}, error_handler=nil, &the_handler)
# FOR NOW: limit 100 records returned at a time.
# Use: {:limit => 100, :offset => 0} || {:page => 1}
if details.is_a?(::Hash) && the_handler
self.pull_data({:operation => :search_database, :criteria => details},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(data)
end
end
true
else
false
end
end
# High-level methods for pulling/pushing objects or object-deltas.
def destroy_object(the_specifier=nil,error_handler=nil,&the_handler)
if the_specifier && the_handler
self.pull_data({:operation => :destroy_object, :location => the_specifier},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(data)
end
end
true
else
false
end
end
#
def push_data(details={}, error_handler=nil,&the_handler)
if details.is_a?(::Hash) && the_handler
content = details.to_json.to_s.encode64
msg_frame = {:sender => @uuid.to_s, :to => @remote_uuid.to_s, :display_path => @display_path, :operation => "put_portion", :thread => "00000000-0000-4000-0000-000000000000", :index => 99999, :total => 100000, :payload => ""}
portions = ::GxG::apportioned_ranges(content.size,(65536 - msg_frame.to_json.size))
#
if portions.size > 1
the_message = new_message({ :operation => "put_data", :display_path => @display_path})
the_message[:sender] = @uuid
the_message[:to] = @remote_uuid
the_message[:body][:thread] = the_message.id().to_s
#
if the_handler.respond_to?(:call)
the_message.on(:success) do |response|
@buffer.delete(the_message[:body][:thread])
the_handler.call(response)
end
end
if error_handler.respond_to?(:call)
the_message.on(:fail) do |response|
@buffer.delete(the_message[:body][:thread])
error_handler.call(response)
end
end
#
@buffer[(the_message[:body][:thread])] = []
#
portions.each_with_index do |the_range, indexer|
the_msg_portion = msg_frame.clone
the_msg_portion[:thread] = the_message[:body][:thread]
the_msg_portion[:index] = indexer
the_msg_portion[:total] = portions.size
the_msg_portion[:payload] = content[(the_range)]
@buffer[(the_message[:body][:thread])] << the_msg_portion
end
# -----------------------------------------------------
# Send portions
(0..(portions.size - 1)).each do |the_portion|
::GxG::DISPATCHER.post_event(:communications) do
if @buffer[(the_message[:body][:thread])].is_a?(::Array)
if @buffer[(the_message[:body][:thread])][(the_portion)].is_a?(::Hash)
#
portion_message = new_message(@buffer[(the_message[:body][:thread])][(the_portion)])
portion_message[:sender] = @uuid
portion_message[:to] = @remote_uuid
#
portion_message.on(:succeed) do |response|
if the_portion == (@buffer[(the_message[:body][:thread])].size - 1)
if @buffer[(the_message[:body][:thread])]
GxG::CONNECTION.push(the_message)
end
end
end
#
portion_message.on(:fail) do |response|
the_message.fail(response)
end
#
GxG::CONNECTION.push(portion_message)
end
end
end
end
#
else
# single message payload
the_message = new_message({ :operation => "put_data", :display_path => @display_path, :payload => content})
the_message[:sender] = @uuid
the_message[:to] = @remote_uuid
GxG::CONNECTION.push(the_message)
end
#
true
else
false
end
end
#
def push_object(details={}, error_handler=nil,&the_handler)
if details.is_a?(::Hash) && the_handler
self.push_data({:operation => :put_object, :data => details},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(data)
end
end
true
else
false
end
end
#
def pull_data(the_specifier={},error_handler=nil,&the_handler)
if the_specifier && the_handler
the_message = new_message({:operation => "get_data", :thread => nil, :display_path => @display_path, :details => the_specifier.to_json.encode64})
the_message[:sender] = @uuid
the_message[:to] = @remote_uuid
the_message[:body][:thread] = the_message.id().to_s
if error_handler.respond_to?(:call)
the_message.on(:fail,&error_handler)
end
the_message.on(:success) do |response|
if response.is_a?(::Hash)
if response[:result] == true
case response[:operation]
when "this_portion"
data = response[:payload]
if data.is_a?(::String)
# FIXME: <String>.base64? needs a proper working regex (or go brute force)
# data = Base64::strict_decode64(data)
if data.base64?
data = data.decode64
end
if data.json?
begin
data = ::JSON::parse(data,{:symbolize_names => true})
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:data => data}})
end
end
the_handler.call(data)
else
log_error({:error => Exception.new("Malformed Response Data"), :parameters => {:data => data}})
end
when "get_portions"
@pending[(response[:thread])] = the_message
@buffers[(response[:thread])] = []
the_messages = []
(0..(response[:total] - 1)).each do |indexer|
portion_message = new_message({:operation => "get_portion", :thread => (response[:thread]), :index => indexer, :display_path => @display_path})
portion_message[:sender] = @uuid
portion_message[:to] = @remote_uuid
#
portion_message.on(:success) do |reply|
@buffers[(response[:thread])] << reply
if @buffers[(response[:thread])].size == response[:total]
already = []
the_data = ""
until already.size == response[:total] do
(0..(response[:total] - 1)).each do |referencer|
unless already.index(referencer)
the_data = (the_data + (@buffers[(response[:thread])][(referencer)][:payload]))
already << referencer
break
end
end
end
signal_message = new_message({:operation => "got_portions", :thread => response[:thread], :display_path => @display_path})
signal_message[:sender] = @uuid
signal_message[:to] = @remote_uuid
GxG::CONNECTION.pull(signal_message)
#
@pending.delete(response[:thread])
@buffers.delete(response[:thread])
if the_data.size > 0
# FIXME: <String>.base64? needs a proper working regex (or go brute force)
# the_data = Base64::strict_decode64(the_data)
if the_data.base64?
the_data = the_data.decode64
end
if the_data.json?
begin
the_data = ::JSON::parse(the_data,{:symbolize_names => true})
rescue Exception => the_error
log_error({:error => the_error, :parameters => {:data => the_data}})
end
end
the_handler.call(the_data)
else
log_warn("Empty payload from portions.")
end
end
end
#
portion_message.on(:fail) do |reply|
log_warn("Cancelling Transfer: portion #{indexer} of thread #{response[:thread]}.")
@pending.delete(response[:thread])
@buffers.delete(response[:thread])
if error_handler.respond_to?(:call)
error_handler.call(reply)
end
end
#
# GxG::CONNECTION.pull(portion_message)
the_messages << portion_message
#
end
the_messages.each do |a_message|
GxG::CONNECTION.pull(a_message)
end
end
end
end
end
GxG::CONNECTION.pull(the_message)
end
end
#
def pull_object(the_specifier=nil,error_handler=nil,&the_handler)
# Use pull_data({:operation => :get_object, :location => the_specifier})
if the_specifier && the_handler
# GxG::DISPLAY_DETAILS[:object].set_busy(true)
self.pull_data({:operation => :get_object, :location => the_specifier},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(::GxG::Database::process_detached_import(data))
# GxG::DISPLAY_DETAILS[:object].set_busy(false)
end
end
true
else
false
end
end
#
def get_permissions(the_specifier=nil,error_handler=nil,&the_handler)
# See vfs: :action => 'set_permissions', :path => (location), :alterations => [{:credential => nil, :permissions => {...}}], :revocations => [(a-credential)]
if the_specifier && the_handler
# GxG::DISPLAY_DETAILS[:object].set_busy(true)
self.pull_data({:operation => :get_permissions, :location => the_specifier},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(data)
# GxG::DISPLAY_DETAILS[:object].set_busy(false)
end
end
true
else
false
end
end
#
# Libarary Loading Support:
def library_pull(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
self.pull_data({:operation => :get_library}.merge!(details),error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
# Format Support:
def format_pull(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_any?(::Array, ::Hash) && the_handler
self.pull_data({:operation => :format_pull, :criteria => details},error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
# TODO: format_push(data={})
# Admin:
def admin(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
self.pull_data(({:operation => :admin}.merge!(details)),error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
# VFS:
def vfs(details={}, error_handler=nil, &the_handler)
# Specify either a :location or :name
if self.open?
if details.is_a?(::Hash) && the_handler
self.pull_data(({:operation => :vfs}.merge!(details)),error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
# Application Support:
def app_state_push(details={}, error_handler=nil, &the_handler)
if details.is_a?(::Hash) && the_handler
self.push_data({:operation => :app_state_push, :application => details[:application], :data => details[:data]},error_handler) do |data|
if data.is_a?(::Hash)
the_handler.call(data)
end
end
true
else
false
end
end
#
def app_state_pull(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
self.pull_data({:operation => :app_state_pull, :application => details[:application]},error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
def application_open(details={}, error_handler=nil, &the_handler)
# Specify either a :location or :name
if self.open?
if details.is_a?(::Hash) && the_handler
if details[:name]
self.pull_data({:operation => :application_open, :restore => (details[:restore] || false), :name => details[:name]},error_handler) do |data|
the_handler.call(data)
end
else
self.pull_data({:operation => :application_open, :restore => (details[:restore] || false), :location => details[:location]},error_handler) do |data|
the_handler.call(data)
end
end
true
else
false
end
end
end
#
def application_close(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
self.pull_data({:operation => :application_close, :application => details[:application]},error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
def application_list(error_handler=nil, &the_handler)
if self.open?
if the_handler
self.pull_data({:operation => :application_list},error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
def application_menu(error_handler=nil, &the_handler)
if self.open?
if the_handler
self.pull_data({:operation => :application_menu},error_handler) do |data|
the_handler.call(data)
end
true
else
false
end
end
end
#
def entries(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
GxG::DISPLAY_DETAILS[:object].set_busy(true)
self.pull_data({:operation => :entries, :location => details[:location]},error_handler) do |data|
the_handler.call(data)
GxG::DISPLAY_DETAILS[:object].set_busy(false)
end
true
else
false
end
end
end
#
def heartbeat()
if @active
heartbeat_message = new_message({:operation => "heartbeat", :display_path => @display_path})
heartbeat_message[:sender] = @uuid.to_s
heartbeat_message[:to] = @remote_uuid.to_s
heartbeat_message.on(:success) do |response|
if response.is_a?(::Hash)
# FIXME: possible conflict with proper credential setting.
if response[:result][:status] == "credentialed"
# GxG::DISPLAY_DETAILS[:logged_in] = true
else
# GxG::DISPLAY_DETAILS[:logged_in] = false
end
GxG::DISPLAY_DETAILS[:server_status] = response[:result][:server].to_s.to_sym
end
end
GxG::CONNECTION.pull(heartbeat_message)
end
end
#
# Set Credential:
def update_credential(details={}, error_handler=nil, &the_handler)
if self.open?
if details.is_a?(::Hash) && the_handler
content = details.to_json.to_s.encrypt(@csrf.to_s).encode64
the_message = new_message({:operation => "update_credential", :display_path => @display_path, :payload => content})
the_message[:sender] = @uuid
the_message[:to] = @remote_uuid
the_message[:body][:thread] = the_message.id().to_s
the_message.on(:success) do |the_response|
if the_response.is_a?(::Hash)
if the_response[:status] == "credentialed"
GxG::DISPLAY_DETAILS[:logged_in] = true
end
end
the_handler.call(the_response)
end
if error_handler
the_message.on(:fail) do |the_response|
error_handler.call(the_response)
end
end
GxG::CONNECTION.push(the_message)
end
end
end
#
def downgrade_credential(&the_handler)
# ????
if @active
downgrade_message = new_message({:operation => "downgrade_credential", :display_path => @display_path})
downgrade_message[:sender] = @uuid.to_s
downgrade_message[:to] = @remote_uuid.to_s
downgrade_message.on(:success) do |response|
# var evt = new CustomEvent('credentialchanged', { detail: "state" }); ??
# window.dispatchEvent(evt);
if response.is_a?(::Hash)
# FIXME: possible conflict with proper credential setting.
if response[:status] == "uncredentialed"
GxG::DISPLAY_DETAILS[:logged_in] = false
if the_handler.respond_to?(:call)
the_handler.call(response)
end
new_event = `new CustomEvent('credentialchanged', { detail: #{response[:status].to_s} })`
`window.dispatchEvent(#{new_event})`
else
# GxG::DISPLAY_DETAILS[:logged_in] = true
end
# GxG::DISPLAY_DETAILS[:server_status] = response[:server].to_s.to_sym
end
end
GxG::CONNECTION.pull(downgrade_message)
end
end
#
def relative_url()
@relative_url
end
#
def query()
@query
end
#
def article()
@article
end
#
def initialize()
if (`window.location['href']`).include?("https://")
@host_prefix = ("https://" + `window.location['host']`)
else
@host_prefix = ("http://" + `window.location['host']`)
end
@relative_url = ""
# Find Relative URL
page_name = File::basename(`window.location['pathname']`)
subpath_array = (`window.location['pathname']`.split("/"))
found_at = subpath_array.index(page_name)
if found_at.is_a?(::Numeric)
if found_at > 1
@relative_url = subpath_array[(0..(found_at - 1))].join("/")
end
end
# Process Query if found
@article = nil
@query = {}
query_string = `window.location.href`.split("?")[1]
if query_string
@article = query_string.split("#")[1]
query_string = query_string.split("#")[0]
if query_string.include?("&amp;")
query_array = query_string.split("&amp;")
else
query_array = query_string.split("&")
end
query_array.each do |entry|
buffer = entry.split("=")
@query[(buffer[0].to_s.to_sym)] = buffer[1]
end
else
@article = `window.location.href`.split("#")[1]
end
#
@uuid = GxG.uuid_generate().to_sym
@remote_uuid = nil
@active = true
@display_path = nil
@in_busy = false
@out_busy = false
@pending = {}
@channels = {}
@buffers = {}
@csrf = nil
@the_connector = self
# Make introduction to host and aquire csrf token
the_introduction = new_message({:operation => "introduction", :introducing => @uuid.to_s})
the_introduction[:sender] = @uuid.to_s
the_introduction[:to] = @host_prefix.to_s
the_introduction.on(:success) do |response|
if response[:result] == "OK"
if response[:csrf]
@csrf = response[:csrf]
end
if response[:status] == "credentialed"
GxG::DISPLAY_DETAILS[:logged_in] = true
else
GxG::DISPLAY_DETAILS[:logged_in] = false
end
#
if response[:display] == "unavailable"
`alert('Display Resource Unavailable')`
else
@display_path = response[:display]
@active = true
# Are we logged in at the server and recovering from a crash ??
GxG::DISPATCHER.post_event(:display) do
until (`document.readyState`.to_s == "complete") do
sleep 0.5
end
::GxG::DISPLAY_DETAILS[:query] = @query
::GxG::DISPLAY_DETAILS[:article] = @article
::GxG::DISPLAY_DETAILS[:object] = ::GxG::Gui::Page.new(@display_path)
GxG::DISPATCHER.post_event(:display) do
sleep 1.0
::GxG::DISPLAY_DETAILS[:object].layout_refresh
end
end
#
end
#
end
end
the_introduction.on(:fail) do |response|
@active = false
log_error({:error => Exception.new("Communication Error"), :parameters => {:response => response}})
end
GxG::DISPATCHER.post_event(:communications) do
GxG::CONNECTION.pull(the_introduction)
@heart_beat_timer = GxG::DISPATCHER.add_periodic_timer({:interval => 60.0}) do
GxG::CONNECTION.heartbeat()
end
end
#
`window.addEventListener('unload', do_unload,false);function do_unload() { Opal.GxG.$eval("GxG::CONNECTION.close"); };`
#
self
end
#
end
end
end