From 525cda4223ee1792693da980513392b6da648137 Mon Sep 17 00:00:00 2001 From: "G. Gibson" Date: Thu, 7 Aug 2025 09:39:33 -0700 Subject: [PATCH] Federation Scaffolding --- README.md | 2 + gxg-framework.gemspec | 11 +- lib/gxg-framework.rb | 3 +- lib/gxg/gxg_augmented.rb | 44 +++++--- lib/gxg/gxg_events.rb | 226 ++++++++++++++++++++++++++++++++++++++- lib/gxg/net_clients.rb | 144 +++++++++++++++++++++++++ 6 files changed, 409 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 68ef06d..eba22fe 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,5 @@ jgem build ./gxg-framework.gemspec Installation: sudo jgem install GEMFILE + +Database prep: UPDATE mysql.user SET Super_Priv="Y" WHERE user='www-data' AND host='%'; \ No newline at end of file diff --git a/gxg-framework.gemspec b/gxg-framework.gemspec index 75144fd..46a742f 100644 --- a/gxg-framework.gemspec +++ b/gxg-framework.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'gxg-framework' - s.version = '0.0.59' + s.version = '0.0.63' s.licenses = ['HIPPOCRATIC 2.1', 'https://firstdonoharm.dev/version/2/1/license.html'] s.summary = "GxG Framework" s.description = "GxG Framework" @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'sys-cpu' s.add_runtime_dependency 'sys-proctable' s.add_runtime_dependency 'tzinfo-data' - s.add_runtime_dependency 'tzinfo', '~> 2.0' + s.add_runtime_dependency 'tzinfo', '~> 1.1' s.add_runtime_dependency 'bcrypt' s.add_runtime_dependency 'sequel' s.add_runtime_dependency 'ffi' @@ -24,11 +24,11 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'jrzmq' else # FIX : big hassle with windows - exclude for now - s.add_runtime_dependency 'ffi-rzmq' - s.add_runtime_dependency 'ezmq' + # s.add_runtime_dependency 'ffi-rzmq' + # s.add_runtime_dependency 'ezmq' end s.add_runtime_dependency 'nokogiri' - s.add_runtime_dependency 'mimemagic', '= 0.3.4' + s.add_runtime_dependency 'mimemagic' s.add_runtime_dependency 'rubysl-securerandom' s.add_runtime_dependency 'net-ssh' s.add_runtime_dependency 'net-scp' @@ -41,6 +41,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'matrix_sdk' s.add_runtime_dependency 'state_machines' s.add_runtime_dependency 'net-ldap' + s.add_runtime_dependency 'nextcloud' # ### Database Adapters: if ::RUBY_ENGINE == "jruby" s.add_runtime_dependency 'jdbc-sqlite3' diff --git a/lib/gxg-framework.rb b/lib/gxg-framework.rb index 9e87219..ab8e050 100644 --- a/lib/gxg-framework.rb +++ b/lib/gxg-framework.rb @@ -38,7 +38,7 @@ requirements.push({:requirement => "chronic", :gem => "chronic"}) # requirements.push({:requirement => "nokogiri", :gem => "nokogiri"}) requirements.push({:requirement => "rufus-scheduler", :gem => "rufus-scheduler"}) requirements.push({:requirement => "mimemagic", :gem => "mimemagic"}) -# requirements.push({:requirement => "mimemagic/overlay", :gem => "mimemagic"}) +#requirements.push({:requirement => "mimemagic/overlay", :gem => "mimemagic"}) requirements.push({:requirement => "bcrypt", :gem => "bcrypt"}) requirements.push({:requirement => "matrix_sdk", :gem => "matrix_sdk"}) requirements.push({:requirement => "state_machines", :gem => "state_machines"}) @@ -759,6 +759,7 @@ unless load_error ::GxG::LOG[:default] = ::Logger.new(STDOUT) ::GxG::LOG[:default].level = ::Logger::ERROR $Dispatcher.every("0.333 seconds") do + ::GxG::CHANNELS.update_channels ::GxG::LOG.process_messages() end # ### Defer long-runnign cpu info gather on Winderz diff --git a/lib/gxg/gxg_augmented.rb b/lib/gxg/gxg_augmented.rb index 46d16c0..558524c 100644 --- a/lib/gxg/gxg_augmented.rb +++ b/lib/gxg/gxg_augmented.rb @@ -110,6 +110,19 @@ class Time self.to_f.to_d end end +# +# class Set +# public +# def initialize_clone +# # TODO: flesh this out to return a copy Set +# @self +# end +# alias :initialize_dup :initialize_clone +# alias :dup :initialize_clone +# def clone() +# initialize_clone +# end +# end # IO/StringIO/File alterations # Fill in missing constants according to platform. # ### win32 stuff @@ -3204,20 +3217,23 @@ end # Alteration to Struct for support of structural processing: # class Struct - # - public - def initialize_clone - result = self.class.new - self.each_pair do |key, value| - result[(key)] = value.clone - end - result - end - alias :initialize_dup :initialize_clone - alias :dup :initialize_clone - def clone() - initialize_clone - end + # FIXME: dup collision on mail gem + # NoMethodError: private method 'dup' called for an instance of Net::IMAP::Config::AttrAccessors::Struct + # initialize_dup at /opt/jruby-gems/shared/gems/net-imap-0.5.9/lib/net/imap/config/attr_accessors.rb:69 + # public + # def initialize_clone + # result = self.class.new + # self.each_pair do |key, value| + # result[(key)] = value.clone + # end + # result + # end + # def clone() + # initialize_clone + # end + # private + # alias :initialize_dup :initialize_clone + # alias :dup :initialize_clone # def self.process(the_struct=::Struct.new(nil),&block) new_struct = ::Struct.new(nil) diff --git a/lib/gxg/gxg_events.rb b/lib/gxg/gxg_events.rb index 69cf575..b2db732 100644 --- a/lib/gxg/gxg_events.rb +++ b/lib/gxg/gxg_events.rb @@ -1,9 +1,172 @@ # module GxG # + GXG_FEDERATION = {:title => "Untitled", :uuid => nil, :available => {}, :connections => {}} + GXG_FEDERATION_SAFETY = Mutex.new + # + module Messages + class Channel + # + def initialize(the_uuid) + @uuid = the_uuid + @inbox = [] + @inbox_safety = ::Mutex.new + @outbox = [] + @outbox_safety = ::Mutex.new + @socket = nil + @remote = nil + @channel_secret = nil + self + end + # + def uuid() + @uuid + end + # + def socket() + @socket + end + # + def socket=(the_socket=nil) + @socket = the_socket + end + # + def remote() + @remote + end + # + def remote=(the_remote=nil) + @remote = the_remote + end + # + def secret() + @channel_secret + end + # + def secret=(the_secret=nil) + @channel_secret = the_secret + end + # + def inbox_size() + @inbox_safety.synchronize { @inbox.size } + end + # + def next_message() + @inbox_safety.synchronize { @inbox.unshift } + end + # + def outbox_size() + @outbox_safety.synchronize { @outbox.size } + end + # + def send_message(the_message) + @outbox_safety.synchronize { @outbox << the_message } + end + # + def read() + @outbox_safety.synchronize { @outbox.unshift } + end + # + def write(the_message) + @inbox_safety.synchronize { @inbox << the_message } + end + # + end + # + class ChannelManager + # + def update_channels + result = false + channels = [] + GXG_FEDERATION_SAFETY.synchronize { + GXG_FEDERATION[:connections].values.each do |the_channel| + channels << the_channel + end + } + channels.each do |the_channel| + the_message = the_channel.read() + while the_message do + self.dispatch_message(the_message) + the_message = the_channel.read() + end + end + if channels.size > 0 + # indicates that yes there were messages and yes they were sent. + result = true + end + result + end + # + def dispatch_message(the_message=nil) + result = false + if the_message.is_a?(::GxG::Events::Message) + destination = the_message[:to] + # uuid + if ::GxG::valud_uuid?(destination.to_s) + channel = self.fetch_channel(destination.to_s.to_sym) + if channel + channel.write(the_message) + result = true + end + end + # email address -- TODO: integrate sendmail functionality in pure ruby. ::GxG::Networking::SmtpClient.new(::URI::parse("smtp://username:password@hostname.org:587"), {:use_ssl => true, :ignore_ssl_errors => true}) + # + end + result + end + # + def fetch_channel(the_uuid) + GXG_FEDERATION_SAFETY.synchronize { GXG_FEDERATION[:connections][(the_uuid)] } + end + # + def create_channel(the_uuid) + GXG_FEDERATION_SAFETY.synchronize { GXG_FEDERATION[:connections][(the_uuid)] = ::GxG::Messages::Channel.new(the_uuid)} + end + # + def destroy_channel(the_uuid) + channel = self.fetch_channel(the_uuid) + if channel + channel.outbox_size.times do |indexer| + the_message = channel.read() + if the_message + self.dispatch_message(the_message) + end + end + end + GXG_FEDERATION_SAFETY.synchronize { GXG_FEDERATION[:connections].delete(the_uuid) } + end + # + def next_message(the_uuid) + result = nil + channel = self.fetch_channel(the_uuid) + if channel + result = channel.next_message() + end + result + end + # + def send_message(the_uuid, the_message) + channel = self.fetch_channel(the_uuid) + if channel + channel.send_message(the_message) + end + end + # + def initialize + self + end + # + end + end + module Events # class Message + def self.import(the_data=nil) + the_message = new_message {} + the_message.import(the_data) + the_message + end # def initialize(*args) # MUST provide a hash with at least :sender message field set @@ -139,6 +302,33 @@ module GxG def set_at_path(*args) @data.set_at_path(*args) end + # + def import(the_data=nil) + # Requires gxg_export+JSON String or gxg_export Hash + unless the_data.is_any?(::String, ::Hash) + raise Exception.new("You MUST supply a JSON string or a Hash, you supplied: #{the_data.class.inspect}") + end + result = false + if the_data.is_a?(::String) + the_data = JSON.parse(the_data, {:symbolize_names => true}) + end + if the_data.is(::Hash) + @data.merge(::Hash::gxg_import(the_data)) + result = true + end + result + end + # + def export() + @data.gxg_export.to_json.to_s + end + def to_s() + self.export.to_s + end + def to_json + self.to_s + end + # end # class LoggerDB @@ -771,4 +961,38 @@ module GxG end LOG = ::GxG::Events::LogRing.new() end -# \ No newline at end of file +# +module GxG + CHANNELS = ::GxG::Messages::ChannelManager.new +end +class Object + # + def send_message(the_message) + unless @uuid + @uuid = ::GxG::uuid_generate.to_s.to_sym + ::GxG::CHANNELS.create_channel(@uuid) + end + ::GxG::CHANNELS.send_message(@uuid, the_message) + true + end + # + def next_message() + unless @uuid + @uuid = ::GxG::uuid_generate.to_s.to_sym + ::GxG::CHANNELS.create_channel(@uuid) + end + ::GxG::CHANNELS.next_message(@uuid) + end + # + def post(the_message) + unless @uuid + @uuid = ::GxG::uuid_generate.to_s.to_sym + ::GxG::CHANNELS.create_channel(@uuid) + end + channel = ::GxG::CHANNELS.fetch_channel(@uuid) + if channel + channel.write(the_message) + end + true + end +end \ No newline at end of file diff --git a/lib/gxg/net_clients.rb b/lib/gxg/net_clients.rb index 6c61c78..e9a68dd 100644 --- a/lib/gxg/net_clients.rb +++ b/lib/gxg/net_clients.rb @@ -12,6 +12,7 @@ require 'net/imap' require 'gmail_xoauth' require 'mail' require 'matrix_sdk' +require 'nextcloud' # GxG: module GxG # @@ -3863,6 +3864,124 @@ module GxG end ::GxG::Networking::DISPATCHER.register_client("gxg",::GxG::Networking::GxGApi) # + class GxGRemoteServer + def refresh_services + response = nil + begin + the_uri = ::URI::parse("https://#{@hostname}#{@endpoint}") + the_connector = ::GxG::Networking::HttpsClient.new(the_uri) + response = the_connector.get(the_uri) + rescue Exception => the_error + begin + @scheme = "http" + the_uri = ::URI::parse("http://#{@hostname}#{@endpoint}") + the_connector = ::GxG::Networking::HttpClient.new(the_uri) + response = the_connector.get(the_uri) + rescue Exception => the_error + raise Exception.new("Failed to establish session link at #{the_url.to_s}.") + end + end + if response + if response.code.to_i == 200 + provisions = JSON::parse(response.body.decode64, :symbolize_names => true)[:result] + if provisions + @services = {} + provisions.each do |the_record| + @services[(the_record[:provides].to_s.downcase.to_sym)] = the_record[:path] + end + end + end + end + end + # + def initialize(the_url=nil, options={}) + # URI Note: user:password@hostname/path/to/endpoint + the_uri = ::URI::parse(the_url.to_s) + @uuid = ::GxG::uuid_generate.to_s.to_sym + @scheme = "https" + @hostname = the_uri.hostname + @services = {} + self.refresh_services + @clients = {} + @services.each_pair do |service,endpoint| + @clients[(service)] = ::GxG::Networking::GxGApi.new("gxg://#{@hostname}#{endpoint}") + if service == :federation + @clients[(service)].login(the_uri.username, the_uri.password) + if @clients[(service)].respond_to_event?(:connect) + header = @clients[(service)].get({:connect => @uuid}) + if header.is_a?(::Hash) + @title = header[:title] + else + raise Exception.new("Could not connect") + end + end + end + end + self + end + # + def uuid + @uuid + end + # + def title + @title + end + # + def interface + result = {} + # + @services.keys.each do |service| + result[(service)] = @clients[(service)].interface + end + # + result + end + # + def login(username=nil, password=nil) + # + @services.keys.each do |service| + @clients[(service)].login(username, password) + end + # + true + end + # + def logout + # + @services.keys.each do |service| + @clients[(service)].logout + end + # + true + end + # + def keys + @services.keys + end + # + def [](selector=:unspecified) + @clients[(selector)] + end + # + def send_message(the_message) + @clients[:federation].put({:send_message => {:uuid => @uuid, :message => the_message}})[:result] + end + # + def next_message() + @clients[:federation].get({:next_message => @uuid})[:result] + end + end + ::GxG::Networking::DISPATCHER.register_client("federation",::GxG::Networking::GxGRemoteServer) + # + class NextcloudClient + # TODO: complete class code + def initialize(the_url=nil, options={}) + self + end + end + ::GxG::Networking::DISPATCHER.register_client("nextcloud",::GxG::Networking::NextcloudClient) + # class MatrixID def initialize(the_id=nil) if the_id.is_a?(::MatrixSdk::MXID) @@ -4967,3 +5086,28 @@ module GxG CLIENTS = GxG::Networking::DISPATCHER # end +# +class Object + private + def remote_server(specifier=nil) + result = nil + if specifier or ::GxG::valid_uuid?(specifier) + ::GxG::GXG_FEDERATION_SAFETY.synchronize { + ::GxG::GXG_FEDERATION[:available].each_pair do |server_uuid, server| + if specifier.is_a?(::String) + if specifier == server.title + result = server + break + end + else + if specifier == server_uuid + result = server + break + end + end + end + } + end + result + end +end