diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..504a1b4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,31 @@ +GxG Framework Copyright (2021) (COPYRIGHT G. GIBSON)(“Licensor”) + +Hippocratic License Version Number: 2.1. + +Purpose. The purpose of this License is for the Licensor named above to permit the Licensee (as defined below) broad permission, if consistent with Human Rights Laws and Human Rights Principles (as each is defined below), to use and work with the Software (as defined below) within the full scope of Licensor’s copyright and patent rights, if any, in the Software, while ensuring attribution and protecting the Licensor from liability. + +Permission and Conditions. The Licensor grants permission by this license (“License”), free of charge, to the extent of Licensor’s rights under applicable copyright and patent law, to any person or entity (the “Licensee”) obtaining a copy of this software and associated documentation files (the “Software”), to do everything with the Software that would otherwise infringe (i) the Licensor’s copyright in the Software or (ii) any patent claims to the Software that the Licensor can license or becomes able to license, subject to all of the following terms and conditions: + +Acceptance. This License is automatically offered to every person and entity subject to its terms and conditions. Licensee accepts this License and agrees to its terms and conditions by taking any action with the Software that, absent this License, would infringe any intellectual property right held by Licensor. + +Notice. Licensee must ensure that everyone who gets a copy of any part of this Software from Licensee, with or without changes, also receives the License and the above copyright notice (and if included by the Licensor, patent, trademark and attribution notice). Licensee must cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software. For clarity, although Licensee is free to create modifications of the Software and distribute only the modified portion created by Licensee with additional or different terms, the portion of the Software not modified must be distributed pursuant to this License. If anyone notifies Licensee in writing that Licensee has not complied with this Notice section, Licensee can keep this License by taking all practical steps to comply within 30 days after the notice. If Licensee does not do so, Licensee’s License (and all rights licensed hereunder) shall end immediately. + +Compliance with Human Rights Principles and Human Rights Laws. + +Human Rights Principles. + +(a) Licensee is advised to consult the articles of the United Nations Universal Declaration of Human Rights and the United Nations Global Compact that define recognized principles of international human rights (the “Human Rights Principles”). Licensee shall use the Software in a manner consistent with Human Rights Principles. + +(b) Unless the Licensor and Licensee agree otherwise, any dispute, controversy, or claim arising out of or relating to (i) Section 1(a) regarding Human Rights Principles, including the breach of Section 1(a), termination of this License for breach of the Human Rights Principles, or invalidity of Section 1(a) or (ii) a determination of whether any Law is consistent or in conflict with Human Rights Principles pursuant to Section 2, below, shall be settled by arbitration in accordance with the Hague Rules on Business and Human Rights Arbitration (the “Rules”); provided, however, that Licensee may elect not to participate in such arbitration, in which event this License (and all rights licensed hereunder) shall end immediately. The number of arbitrators shall be one unless the Rules require otherwise. + +Unless both the Licensor and Licensee agree to the contrary: (1) All documents and information concerning the arbitration shall be public and may be disclosed by any party; (2) The repository referred to under Article 43 of the Rules shall make available to the public in a timely manner all documents concerning the arbitration which are communicated to it, including all submissions of the parties, all evidence admitted into the record of the proceedings, all transcripts or other recordings of hearings and all orders, decisions and awards of the arbitral tribunal, subject only to the arbitral tribunal’s powers to take such measures as may be necessary to safeguard the integrity of the arbitral process pursuant to Articles 18, 33, 41 and 42 of the Rules; and (3) Article 26(6) of the Rules shall not apply. + +Human Rights Laws. The Software shall not be used by any person or entity for any systems, activities, or other uses that violate any Human Rights Laws. “Human Rights Laws” means any applicable laws, regulations, or rules (collectively, “Laws”) that protect human, civil, labor, privacy, political, environmental, security, economic, due process, or similar rights; provided, however, that such Laws are consistent and not in conflict with Human Rights Principles (a dispute over the consistency or a conflict between Laws and Human Rights Principles shall be determined by arbitration as stated above). Where the Human Rights Laws of more than one jurisdiction are applicable or in conflict with respect to the use of the Software, the Human Rights Laws that are most protective of the individuals or groups harmed shall apply. + +Indemnity. Licensee shall hold harmless and indemnify Licensor (and any other contributor) against all losses, damages, liabilities, deficiencies, claims, actions, judgments, settlements, interest, awards, penalties, fines, costs, or expenses of whatever kind, including Licensor’s reasonable attorneys’ fees, arising out of or relating to Licensee’s use of the Software in violation of Human Rights Laws or Human Rights Principles. + +Failure to Comply. Any failure of Licensee to act according to the terms and conditions of this License is both a breach of the License and an infringement of the intellectual property rights of the Licensor (subject to exceptions under Laws, e.g., fair use). In the event of a breach or infringement, the terms and conditions of this License may be enforced by Licensor under the Laws of any jurisdiction to which Licensee is subject. Licensee also agrees that the Licensor may enforce the terms and conditions of this License against Licensee through specific performance (or similar remedy under Laws) to the extent permitted by Laws. For clarity, except in the event of a breach of this License, infringement, or as otherwise stated in this License, Licensor may not terminate this License with Licensee. + +Enforceability and Interpretation. If any term or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, then such invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction; provided, however, subject to a court modification pursuant to the immediately following sentence, if any term or provision of this License pertaining to Human Rights Laws or Human Rights Principles is deemed invalid, illegal, or unenforceable against Licensee by a court of competent jurisdiction, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and Licensee. Upon a determination that any term or provision is invalid, illegal, or unenforceable, to the extent permitted by Laws, the court may modify this License to affect the original purpose that the Software be used in compliance with Human Rights Principles and Human Rights Laws as closely as possible. The language in this License shall be interpreted as to its fair meaning and not strictly for or against any party. + +Disclaimer. TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR AND ANY OTHER CONTRIBUTOR SHALL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY KIND OF LEGAL CLAIM. diff --git a/README.md b/README.md index 03e9512..68ef06d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# gxg-framework -GxG Framework +GxG Framework for JRuby + +Building: + +jgem build ./gxg-framework.gemspec + +Installation: + +sudo jgem install GEMFILE diff --git a/doc/Addrinfo.html b/doc/Addrinfo.html new file mode 100644 index 0000000..e14bd84 --- /dev/null +++ b/doc/Addrinfo.html @@ -0,0 +1,283 @@ + + + + + + +class Addrinfo - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Addrinfo +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ parse(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1752
+def self.parse(*args)
+  # Attempts to create an address object for the given URI/URL
+  address = nil
+  if args[0].is_a?(::URI::Generic)
+    raw_uri = args[0]
+  else
+    raw_uri = ::URI::parse(*args)
+  end
+  #
+  the_protocol = nil
+  the_port = nil
+  service_details = ::GxG::SYSTEM.service_ports_search({:scheme => raw_uri.scheme()})
+  if service_details.keys().size > 0
+    service_details = service_details[(service_details.keys()[0])]
+    if service_details[:usage_preference]
+      if service_details[:usage][(service_details[:usage_preference])].is_a?(::Array)
+        the_port = service_details[:usage][(service_details[:usage_preference])][0]
+        if the_port.is_a?(::Range)
+          the_protocol = (service_details[:usage_preference])
+          the_port = the_port.first
+        end
+      end
+    else
+      if service_details[:usage][(service_details[:usage].keys()[0])].is_a?(::Array)
+        the_port = service_details[:usage][(service_details[:usage].keys()[0])][0]
+        if the_port.is_a?(::Range)
+          the_protocol = (service_details[:usage].keys()[0])
+          the_port = the_port.first
+        end
+      end
+    end
+  end
+  unless the_protocol
+    the_protocol = raw_uri.scheme().to_sym
+  end
+  #
+  case the_protocol
+  when :inproc
+    #
+  when :ipc
+    # LATER: Anything to be done for IPC stuff here??? (Not available on winderz)
+  when :pgm
+    #
+  when :epgm
+    #
+  when :unix, :socket
+    if (raw_uri.hostname().to_s.size == 0)
+      # Unix socket
+      address = ::Addrinfo.unix(raw_uri.path())
+    else
+      # tcp used by default for ipc
+      if raw_uri.hostname().to_s.size > 0
+        address = ::Addrinfo.tcp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+      else
+        address = ::Addrinfo.tcp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+      end
+    end
+  when :tcp, :tcp4, :tcp6
+    if raw_uri.hostname().to_s.size > 0
+      address = ::Addrinfo.tcp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+    else
+      address = ::Addrinfo.tcp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+    end
+  when :udp, :udp4, :udp6
+    if raw_uri.hostname().to_s.size > 0
+      address = ::Addrinfo.udp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+    else
+      address = ::Addrinfo.udp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*"))
+    end
+  end
+  #
+  address
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ uri_info() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1826
+def uri_info()
+  result = nil
+  if self.ip?()
+    case self.protocol
+    when ::Socket::IPPROTO_TCP
+      result = ::URI::parse("tcp://#{self.ip_address()}:#{(self.ip_port() || '*')}")
+    when ::Socket::IPPROTO_UDP
+      result = ::URI::parse("udp://#{self.ip_address()}:#{(self.ip_port() || '*')}")
+    end
+  else
+    if self.unix?()
+      result = ::URI::parse("unix://#{self.unix_path()}")
+    else
+      # not sure what to do here
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Array.html b/doc/Array.html new file mode 100644 index 0000000..6369631 --- /dev/null +++ b/doc/Array.html @@ -0,0 +1,868 @@ + + + + + + +class Array - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Array +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ gxg_import(the_exported_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3997
+def self.gxg_import(the_exported_record=nil)
+  result = nil
+  if the_exported_record.is_a?(Hash)
+    if the_exported_record[:type] == "Hash"
+      result = ::Hash::gxg_import(the_exported_record)
+    else
+      import_value = Proc.new do |type,value|
+        if value.is_a?(String)
+          begin
+            the_class = eval(type)
+            if the_class.respond_to?(:parse)
+              value = the_class.parse(value)
+            else
+              if the_class.respond_to?(:new)
+                value = the_class.new(value)
+              else
+                if the_class.respond_to?(:try_convert)
+                  value = the_class.try_convert(value)
+                end
+              end
+            end
+          rescue Exception => the_error
+          end
+        end
+        value
+      end
+      if the_exported_record[:type] == "Array"
+        result = []
+        import_db = [{:parent => nil, :parent_selector => nil, :object => result, :record => the_exported_record}]
+        while import_db.size > 0 do
+          entry = import_db.shift
+          if entry[:record][:content].is_a?(Hash)
+            entry[:record][:content].each_pair do |selector, value|
+              if value[:type] == "Hash"
+                entry[:object][(selector)] = {}
+                import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+              else
+                if value[:type] == "Array"
+                  entry[:object][(selector)] = []
+                  import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                else
+                  entry[:object][(selector)] = import_value.call(value[:type], value[:content])
+                end
+              end
+            end
+          else
+            if entry[:record][:content].is_a?(Array)
+              entry[:record][:content].each_with_index do |value, selector|
+                if value[:type] == "Hash"
+                  entry[:object][(selector)] = {}
+                  import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                else
+                  if value[:type] == "Array"
+                    entry[:object][(selector)] = []
+                    import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                  else
+                    entry[:object][(selector)] = import_value.call(value[:type], value[:content])
+                  end
+                end
+              end
+            end
+          end
+        end
+      else
+        result = import_value.call(the_exported_record[:type], the_exported_record[:content])
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(the_array=[],&block) + + click to toggle source + +
+ + +
+ +

Interesting research on extending ruby classes '|' for instance: verboselogging.com/2011/05/06/simple-ruby-pipes Also: stackoverflow.com/questions/4234119/ruby-pipes-how-do-i-tie-the-output-of-two-subprocesses-together And a really nice one: jstorimer.com/2012/03/18/a-unix-shell-in-ruby-pipes.html#Shell%20Pipelines%20Demystified

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3704
+def self.process(the_array=[],&block)
+  new_array = []
+  if block.respond_to?(:call)
+    if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+      new_array = the_array.process(&block)
+    else
+      raise ArgumentError, "you must pass a Array, a Hash, or a ByteArray"
+    end
+  end
+  new_array
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ average() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 4068
+def average()
+  # See: https://stackoverflow.com/questions/1341271/how-do-i-create-an-average-from-a-ruby-array
+  self.instance_eval { reduce(:+).to_f / size.to_f } 
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_at_path(the_path="/") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3880
+def get_at_path(the_path="/")
+  # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric
+  # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu
+  #
+  # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/"))
+  result = nil
+  if the_path == "/"
+    result = self
+  else
+    object_stack = [(self)]
+    path_stack = the_path.split("/")
+    path_stack.to_enum.each do |path_element|
+      element = nil
+      if path_element.size > 0
+        if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          element = path_element.to_i
+        else
+          element = path_element
+          element.gsub!("%2f","/")
+          if element[0] == ":"
+            element = element[(1..-1)].to_sym
+          end
+        end
+      end
+      if element
+        result = object_stack.first[(element)]
+        if result.is_a?(NilClass)
+          break
+        else
+          object_stack.unshift(result)
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ gxg_export() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3951
+def gxg_export()
+  result = {:type => "Array", :content => []}
+  export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+  children_of = Proc.new do |the_parent=nil|
+    list = []
+    export_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  export_record = Proc.new do |the_value|
+    if the_value.is_any?(Integer, Float, String)
+      {:type => (the_value.class.to_s), :content => the_value}
+    else
+      {:type => (the_value.class.to_s), :content => the_value.to_s}
+    end
+  end
+  # Build up export_db:
+  self.search do |the_value, the_selector, the_container|
+    if the_value.is_a?(::Hash)
+      export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Hash", :content => {}}}
+    else
+      if the_value.is_a?(::Array)
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Array", :content => []}}
+      else
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => export_record.call(the_value)}
+      end
+    end
+  end
+  # Collect children export content:
+  link_db =[(export_db[0])]
+  while link_db.size > 0 do
+    entry = link_db.shift
+    children_of.call(entry[:object]).each do |the_child|
+      entry[:record][:content][(the_child[:parent_selector])] = the_child[:record]
+      if the_child[:object].is_any?(Hash, Array)
+        link_db << the_child
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ iterative(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3728
+def iterative(&block)
+  result = []
+  visit = Proc.new do |the_node=nil, accumulator=[]|
+    node_stack = []
+    if the_node
+      node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)})
+      while (node_stack.size > 0) do
+        a_node = node_stack.shift
+        #
+        if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash)
+          a_node[:object].each_pair do |the_key, the_value|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value})
+          end
+        end
+        if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray)
+          a_node[:object].each_with_index do |the_value, the_index|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value})
+          end
+        end
+        #
+        accumulator << a_node
+      end
+    end
+    accumulator
+  end
+  #
+  children_of = Proc.new do |the_db=[], the_parent=nil|
+    list = []
+    the_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  #
+  begin
+    database = visit.call(self,[])
+    link_db = children_of.call(database, self)
+    if block.respond_to?(:call)
+      while (link_db.size > 0) do
+        entry = link_db.shift
+        unless entry[:object].object_id == self.object_id
+          # calls with parameters: the_value, the_key/the_index (the_selector), the_container
+          raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent])
+          if raw_result
+            result << raw_result
+          end
+        end
+        if entry[:object].object_id != nil.object_id
+          children = children_of.call(database, entry[:object])
+          children.each do |child|
+            link_db << child
+          end
+        end
+      end
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(the_object=nil,base_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3813
+def paths_to(the_object=nil,base_path="")
+  # new idea here:
+  search_results = []
+  unless base_path[0] == "/"
+    base_path = ("/" << base_path)
+  end
+  if base_path.size > 1
+    path_stack = base_path.split("/")[1..-1].reverse
+  else
+    path_stack = []
+  end
+  origin = self.get_at_path(base_path)
+  container_stack = [{:selector => nil, :container => origin}]
+  find_container = Proc.new do |the_container|
+    result = nil
+    container_stack.each_with_index do |entry, index|
+      if entry[:container] == the_container
+        result = entry
+        break
+      end
+    end
+    result
+  end
+  last_container = origin
+  found = false
+  # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}}
+  if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    origin.process! do |the_value, selector, container|
+      if last_container.object_id != container.object_id
+        container_record = find_container.call(container)
+        if container_record
+          path_stack = container_record[:prefix].split("/").reverse
+          if path_stack.size == 0
+            path_stack << ""
+          end
+        end
+        last_container = container
+      end
+      if selector.is_a?(Symbol)
+        safe_key = (":" + selector.to_s)
+      else
+        safe_key = selector.to_s
+      end
+      safe_key.gsub!("/","%2f")
+      path_stack.unshift(safe_key)
+      # compare the_value
+      found = false
+      if (the_value == the_object)
+        found = true
+      end
+      if found
+        search_results << ("/" << path_stack.reverse.join("/"))
+      end
+      #
+      if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))})
+      end
+      path_stack.shift
+      #
+      nil
+    end
+  else
+    search_results << ("/" << path_stack.join("/"))
+  end
+  search_results
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3793
+def process(&block)
+  result = self.clone
+  result.iterative(&block)
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3799
+def process!(&block)
+  self.iterative(&block)
+  self
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ set_at_path(the_path="/",the_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3920
+def set_at_path(the_path="/",the_value=nil)
+  result = nil
+  if the_path != "/"
+    container = self.get_at_path(::File::dirname(the_path))
+    if container
+      raw_selector = ::File::basename(the_path)
+      selector = nil
+      if raw_selector.size > 0
+        if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          selector = raw_selector.to_i
+        else
+          selector = raw_selector
+          selector.gsub!("%2f","/")
+          if selector[0] == ":"
+            selector = selector[(1..-1)].to_sym
+          end
+        end
+      end
+      if selector
+        container[(selector)] = the_value
+        result = container[(selector)]
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+      #
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather.html b/doc/Blather.html new file mode 100644 index 0000000..89c6985 --- /dev/null +++ b/doc/Blather.html @@ -0,0 +1,99 @@ + + + + + + +module Blather - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Blather +

+ +
+ +

### MUC extentions for Blather: See: github.com/maccman/blather and github.com/adhearsion/blather/issues/81

+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Blather/Client.html b/doc/Blather/Client.html new file mode 100644 index 0000000..602ae40 --- /dev/null +++ b/doc/Blather/Client.html @@ -0,0 +1,158 @@ + + + + + + +class Blather::Client - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Client +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ unbind() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 37
+def unbind
+    # Fix bug where closing connection stops EM for ALL Client Instances.
+    # call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop)
+    call_handler_for(:disconnected, nil)
+    true
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/DSL.html b/doc/Blather/DSL.html new file mode 100644 index 0000000..251feb8 --- /dev/null +++ b/doc/Blather/DSL.html @@ -0,0 +1,97 @@ + + + + + + +module Blather::DSL - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Blather::DSL +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Blather/DSL/MultiUserChat.html b/doc/Blather/DSL/MultiUserChat.html new file mode 100644 index 0000000..6e43756 --- /dev/null +++ b/doc/Blather/DSL/MultiUserChat.html @@ -0,0 +1,809 @@ + + + + + + +class Blather::DSL::MultiUserChat - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::DSL::MultiUserChat +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(client, jid, nickname = nil, password = nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 47
+def initialize(client, jid, nickname = nil, password = nil)
+  @client   = client
+  @room     = ::Blather::JID.new(jid)
+  @nickname = nickname || @room.resource
+  @password = password
+  @room.strip!
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ configuration(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: get_configuration +
+ +
+ + +
+ +
+ configuration=(values, &block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: set_configuration +
+ +
+ + +
+ +
+ destroy(reason = nil) + + click to toggle source + +
+ + +
+ +

<iq from='crone1@shakespeare.lit/desktop'

+ +
  id='begone'
+  to='heath@chat.shakespeare.lit'
+  type='set'>
+<query xmlns='http://jabber.org/protocol/muc#owner'>
+  <destroy jid='darkcave@chat.shakespeare.lit'>
+    <reason>Macbeth doth come.</reason>
+  </destroy>
+</query>
+ +

</iq>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 120
+def destroy(reason = nil)
+  destroy = ::Blather::MUC::Owner::Destroy.new(@room)
+  
+  destroy.reason = reason
+  write destroy
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_configuration() { |stana| ... } + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 127
+def get_configuration(&block)
+  get_configure = ::Blather::MUC::Owner::Configure.new(:get, @room)
+  write_with_handler(get_configure) do |stana|
+    yield stana
+  end
+end
+
+ +
+ + +
+ Also aliased as: configuration +
+ + + +
+ + +
+ +
+ invite(jid, reason = nil) + + click to toggle source + +
+ + +
+ +

<message to='room@service'>

+ +
<x xmlns='http://jabber.org/protocol/muc#user'>
+  <invite to='jid'>
+    <reason>comment</reason>
+  </invite>
+</x>
+ +

</message>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 81
+def invite(jid, reason = nil)
+  message = ::Blather::Stanza::Message.new(@room, nil, nil)
+  message << ::Blather::MUC::Invite.new(jid, reason, @password)
+  
+  write message
+end
+
+ +
+ + + + +
+ + +
+ +
+ join(reason = nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 55
+def join(reason = nil)
+  join          = ::Blather::Stanza::Presence::MUCJoin.new
+  join.to       = "#{@room}/#{@nickname}"
+  join.password = @password
+  write join
+end
+
+ +
+ + + + +
+ + +
+ +
+ leave() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 106
+def leave
+  self.status = :unavailable
+end
+
+ +
+ + + + +
+ + +
+ +
+ members(&block) + + click to toggle source + +
+ + +
+ +

<iq from='crone1@shakespeare.lit/desktop'

+ +
  id='member3'
+  to='darkcave@chat.shakespeare.lit'
+  type='get'>
+<query xmlns='http://jabber.org/protocol/muc#admin'>
+  <item affiliation='member'/>
+</query>
+ +

</iq>

+ +

<iq from='darkcave@chat.shakespeare.lit'

+ +
  id='member3'
+  to='crone1@shakespeare.lit/desktop'
+  type='result'>
+<query xmlns='http://jabber.org/protocol/muc#admin'>
+  <item affiliation='member'
+        jid='hag66@shakespeare.lit'
+        nick='thirdwitch'
+        role='participant'/>
+</query>
+ +

</iq>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 173
+def members(&block)
+  members = ::Blather::MUC::Admin::Members.new(@room)
+  
+  write_with_handler(members, &block)
+end
+
+ +
+ + + + +
+ + +
+ +
+ say(msg, xhtml = nil) + + click to toggle source + +
+ + +
+ +

<message to='room@service' type='groupchat'>

+ +
<body>foo</body>
+ +

</message>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 91
+def say(msg, xhtml = nil)
+  message = ::Blather::Stanza::Message.new(@room, msg, :groupchat)
+  message.xhtml = xhtml if xhtml
+  write message
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_configuration(values, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 135
+def set_configuration(values, &block)
+  set_configure = ::Blather::MUC::Owner::Configure.new(:set, @room)
+  set_configure.data = values
+  write_with_handler(set_configure, &block)
+end
+
+ +
+ + +
+ Also aliased as: configuration= +
+ + + +
+ + +
+ +
+ set_default_configuration() { || ... } + + click to toggle source + +
+ + +
+ +
<iq type='set' id='purple52b37aa2' to='test3@conference.macbook.local'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'/></query>
+ +

</iq>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 146
+def set_default_configuration(&block)
+  set_configuration(:default) do
+    yield if block_given?
+  end
+end
+
+ +
+ + +
+ Also aliased as: unlock +
+ + + +
+ + +
+ +
+ status=(state) + + click to toggle source + +
+ + +
+ +

<presence

+ +
  from='wiccarocks@shakespeare.lit/laptop'
+  to='darkcave@chat.shakespeare.lit/oldhag'>
+<show>available</show>
+ +

</presence>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 67
+def status=(state)
+  status       = ::Blather::Stanza::Presence::Status.new
+  status.state = state
+  status.to    = @room
+  write status
+end
+
+ +
+ + + + +
+ + +
+ +
+ subject=(body) + + click to toggle source + +
+ + +
+ +

<message to='room@service' type='groupchat'>

+ +
<subject>foo</subject>
+ +

</message>

+ + + + +
+
# File lib/gxg/net_clients.rb, line 100
+def subject=(body)
+  message = Blather::Stanza::Message.new(@room, nil, :groupchat)
+  message.subject = body
+  write message
+end
+
+ +
+ + + + +
+ + +
+ +
+ unlock(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: set_default_configuration +
+ +
+ + +
+ +
+ write(stanza) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 179
+def write(stanza)
+  @client.write(stanza)
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_with_handler(stanza, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 183
+def write_with_handler(stanza, &block)
+  @client.write_with_handler(stanza, &block)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC.html b/doc/Blather/MUC.html new file mode 100644 index 0000000..aafbf80 --- /dev/null +++ b/doc/Blather/MUC.html @@ -0,0 +1,99 @@ + + + + + + +module Blather::MUC - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Blather::MUC +

+ +
+ +

Support for MUC Presense

+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Blather/MUC/Admin.html b/doc/Blather/MUC/Admin.html new file mode 100644 index 0000000..3cc5233 --- /dev/null +++ b/doc/Blather/MUC/Admin.html @@ -0,0 +1,162 @@ + + + + + + +class Blather::MUC::Admin - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Admin +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(type = nil, to = nil) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 396
+def self.new(type = nil, to = nil)
+  node           = super(type)
+  node.to        = to
+  node
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Admin/Members.html b/doc/Blather/MUC/Admin/Members.html new file mode 100644 index 0000000..233f49e --- /dev/null +++ b/doc/Blather/MUC/Admin/Members.html @@ -0,0 +1,220 @@ + + + + + + +class Blather::MUC::Admin::Members - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Admin::Members +

+ +
+ +

<iq from='crone1@shakespeare.lit/desktop'

+ +
  id='member3'
+  to='darkcave@chat.shakespeare.lit'
+  type='get'>
+<query xmlns='http://jabber.org/protocol/muc#admin'>
+  <item affiliation='member'/>
+</query>
+ +

</iq>

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + Blather::MUC::Admin::new +
+ + + +
+
# File lib/gxg/net_clients.rb, line 411
+def self.new(*args)
+  query = super(:get, *args)
+  query.create_item
+  query
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ create_item() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 417
+def create_item # @private
+  unless create_item = query.find_first('ns:item', :ns => self.class.registered_ns)
+    query << (create_item = XMPPNode.new('item', self.document))
+    create_item[:affiliation] = 'member'
+  end
+  create_item
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Invite.html b/doc/Blather/MUC/Invite.html new file mode 100644 index 0000000..7b74070 --- /dev/null +++ b/doc/Blather/MUC/Invite.html @@ -0,0 +1,623 @@ + + + + + + +class Blather::MUC::Invite - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Invite +

+ +
+ +

<x xmlns='jabber.org/protocol/muc#user'>

+ +
<invite to='jid'>
+  <reason>comment</reason>
+</invite>
+ +

</x>

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ import(node) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 222
+def self.import(node)
+    self.new(node.element_name).inherit(node)
+end
+
+ +
+ + + + +
+ + +
+ +
+ new(jid = nil, reason = nil, password = nil) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 234
+def self.new(jid = nil, reason = nil, password = nil)
+    invite = super :x
+    invite.invite_to = jid
+    invite.reason    = reason
+    invite.password  = password
+    invite
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ inherit(node) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 226
+def inherit(node)
+    create_invite.remove
+    self.from = node[:from]
+    invite = node.find_first('ns:x/ns:invite', :ns => self.class.registered_ns)
+    self << invite
+    self
+end
+
+ +
+ + + + +
+ + +
+ +
+ invite_from() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 250
+def invite_from
+create_invite[:from]
+end
+
+ +
+ + + + +
+ + +
+ +
+ invite_to() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 246
+def invite_to
+    ::Blather::JID.new(create_invite[:to])
+end
+
+ +
+ + + + +
+ + +
+ +
+ invite_to=(jid) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 242
+def invite_to=(jid)
+    create_invite[:to] = JID.new(jid)
+end
+
+ +
+ + + + +
+ + +
+ +
+ password() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 268
+def password
+    create_password.content
+end
+
+ +
+ + + + +
+ + +
+ +
+ password=(password) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 263
+def password=(password)
+    return if password.blank?
+    create_password.content = password
+end
+
+ +
+ + + + +
+ + +
+ +
+ reason() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 259
+def reason
+    create_reason.content
+end
+
+ +
+ + + + +
+ + +
+ +
+ reason=(reason) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 254
+def reason=(reason)
+    return if reason.blank?
+    create_reason.content = reason
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Protected Instance Methods

+
+ + +
+ +
+ create_invite() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 280
+def create_invite
+    unless create_invite = find_first('ns:invite', :ns => self.class.registered_ns)
+    self << (create_invite = XMPPNode.new('invite', self.document))
+    end
+    create_invite
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_password() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 273
+def create_password
+    unless create_password = find_first('ns:password', :ns => self.class.registered_ns)
+    self << (create_password = XMPPNode.new('password', self.document))
+    end
+    create_password
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_reason() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 287
+def create_reason
+    unless create_reason = create_invite.find_first('ns:reason', :ns => self.class.registered_ns)
+    self.create_invite << (create_reason = XMPPNode.new('reason', self.document))
+    end
+    create_reason
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Join.html b/doc/Blather/MUC/Join.html new file mode 100644 index 0000000..022b670 --- /dev/null +++ b/doc/Blather/MUC/Join.html @@ -0,0 +1,252 @@ + + + + + + +class Blather::MUC::Join - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Join +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(password = nil) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 195
+def self.new(password = nil)
+  join = super :x
+  join.password = password
+  join
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ password=(password) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 201
+def password=(password)
+  return if password.blank?
+  create_password.content = password
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Protected Instance Methods

+
+ + +
+ +
+ create_password() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 207
+def create_password
+    unless create_password = find_first('ns:password', :ns => self.class.registered_ns)
+        self << (create_password = XMPPNode.new('password', self.document))
+    end
+  create_password
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Owner.html b/doc/Blather/MUC/Owner.html new file mode 100644 index 0000000..72f9baf --- /dev/null +++ b/doc/Blather/MUC/Owner.html @@ -0,0 +1,162 @@ + + + + + + +class Blather::MUC::Owner - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Owner +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(type = nil, to = nil) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 304
+def self.new(type = nil, to = nil)
+  node           = super(type)
+  node.to        = to
+  node
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Owner/Configure.html b/doc/Blather/MUC/Owner/Configure.html new file mode 100644 index 0000000..2e0373b --- /dev/null +++ b/doc/Blather/MUC/Owner/Configure.html @@ -0,0 +1,274 @@ + + + + + + +class Blather::MUC::Owner::Configure - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Owner::Configure +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
DATA_NAMESPACE + +
+ + +
+
+ + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ data() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 333
+def data
+  items = create_data.find('//ns:field', :ns => self.class.registered_ns)
+  items.inject({}) do |hash, item|
+    key       = item[:var]
+    value     = item.find_first('ns:value', :ns => self.class.registered_ns)
+    value     = value.content
+    hash[key] = value
+    hash
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ data=(data = :default) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 313
+def data=(data = :default)
+  create_data[:type] = 'submit'
+  unless data.blank? || data == :default
+    raise "Invalid data format" unless data.is_a?(Hash)
+    data.each {|key, value|
+      create_field = XMPPNode.new('field', self.document)
+      create_field[:var] = key
+      
+      create_field_value = XMPPNode.new('value', self.document)
+      if [TrueClass, FalseClass].include?(value.class)
+        value = value ? 1 : 0
+      end
+      create_field_value.content = value.to_s
+      
+      create_field << create_field_value
+      create_data  << create_field
+    }      
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Protected Instance Methods

+
+ + +
+ +
+ create_data() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 345
+def create_data
+  unless create_data = query.find_first('ns:x', :ns => DATA_NAMESPACE)
+    query << (create_data = XMPPNode.new('x', self.document))
+    create_data.namespace = DATA_NAMESPACE
+  end
+  create_data
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/MUC/Owner/Destroy.html b/doc/Blather/MUC/Owner/Destroy.html new file mode 100644 index 0000000..9d5fb14 --- /dev/null +++ b/doc/Blather/MUC/Owner/Destroy.html @@ -0,0 +1,295 @@ + + + + + + +class Blather::MUC::Owner::Destroy - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::MUC::Owner::Destroy +

+ +
+ +

<iq from='crone1@shakespeare.lit/desktop'

+ +
  id='begone'
+  to='heath@chat.shakespeare.lit'
+  type='set'>
+<query xmlns='http://jabber.org/protocol/muc#owner'>
+  <destroy jid='darkcave@chat.shakespeare.lit'>
+    <reason>Macbeth doth come.</reason>
+  </destroy>
+</query>
+ +

</iq>

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + Blather::MUC::Owner::new +
+ + + +
+
# File lib/gxg/net_clients.rb, line 366
+def self.new(*args)
+  query = super(:set, *args)
+  query.create_destroy
+  query
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ create_destroy() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 384
+def create_destroy # @private
+  unless create_destroy = query.find_first('ns:destroy', :ns => self.class.registered_ns)
+    query << (create_destroy = XMPPNode.new('destroy', self.document))
+  end
+  create_destroy
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_reason() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 377
+def create_reason # @private
+  unless create_reason = create_destroy.find_first('ns:reason', :ns => self.class.registered_ns)
+    create_destroy << (create_reason = XMPPNode.new('reason', self.document))
+  end
+  create_reason
+end
+
+ +
+ + + + +
+ + +
+ +
+ reason=(reason) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 372
+def reason=(reason)
+  return if reason.blank?
+  create_reason.content = reason
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/Roster.html b/doc/Blather/Roster.html new file mode 100644 index 0000000..81572cd --- /dev/null +++ b/doc/Blather/Roster.html @@ -0,0 +1,193 @@ + + + + + + +class Blather::Roster - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Roster +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ add(elem, send = true, groups = ["Buddies"]) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: push +
+ +
+ + +
+ +
+ push(elem, send = true, groups = ["Buddies"]) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 27
+def push(elem, send = true, groups = ["Buddies"])
+    jid = elem.respond_to?(:jid) && elem.jid ? elem.jid : JID.new(elem)
+    @items[key(jid)] = node = RosterItem.new(elem)
+    @items[key(jid)].subscription = :both
+    @items[key(jid)].groups = groups
+    @stream.write(node.to_stanza(:set)) if send
+end
+
+ +
+ + +
+ Also aliased as: add +
+ + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/Stanza.html b/doc/Blather/Stanza.html new file mode 100644 index 0000000..0628486 --- /dev/null +++ b/doc/Blather/Stanza.html @@ -0,0 +1,104 @@ + + + + + + +class Blather::Stanza - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Stanza +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Blather/Stanza/Presence.html b/doc/Blather/Stanza/Presence.html new file mode 100644 index 0000000..09fad79 --- /dev/null +++ b/doc/Blather/Stanza/Presence.html @@ -0,0 +1,104 @@ + + + + + + +class Blather::Stanza::Presence - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Stanza::Presence +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Blather/Stanza/Presence/MUCJoin.html b/doc/Blather/Stanza/Presence/MUCJoin.html new file mode 100644 index 0000000..00ff833 --- /dev/null +++ b/doc/Blather/Stanza/Presence/MUCJoin.html @@ -0,0 +1,205 @@ + + + + + + +class Blather::Stanza::Presence::MUCJoin - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Stanza::Presence::MUCJoin +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
MUC_NS + +
+ + +
+
+ + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ password() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 436
+def password
+  create_muc.content_from :password
+  create_password.content
+end
+
+ +
+ + + + +
+ + +
+ +
+ password=(value) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 441
+def password=(value)
+  create_muc.set_content_for :password
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/Stanza/Presence/MUCUser.html b/doc/Blather/Stanza/Presence/MUCUser.html new file mode 100644 index 0000000..7423e24 --- /dev/null +++ b/doc/Blather/Stanza/Presence/MUCUser.html @@ -0,0 +1,344 @@ + + + + + + +class Blather::Stanza::Presence::MUCUser - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Stanza::Presence::MUCUser +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
MUC_NS + +
+ + +
+
+ + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ affiliation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 476
+def affiliation
+  create_ia[:affiliation]
+end
+
+ +
+ + + + +
+ + +
+ +
+ affiliation=(val) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 480
+def affiliation=(val)
+  create_ia[:affiliation] = val
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 492
+def jid
+  create_ia[:jid]
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid=(val) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 496
+def jid=(val)
+  create_ia[:jid] = val
+end
+
+ +
+ + + + +
+ + +
+ +
+ role() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 484
+def role
+  create_ia[:role]
+end
+
+ +
+ + + + +
+ + +
+ +
+ role=(val) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 488
+def role=(val)
+  create_ia[:role] = val
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Blather/Stanza/Presence/MUCUser/Status.html b/doc/Blather/Stanza/Presence/MUCUser/Status.html new file mode 100644 index 0000000..bded291 --- /dev/null +++ b/doc/Blather/Stanza/Presence/MUCUser/Status.html @@ -0,0 +1,240 @@ + + + + + + +class Blather::Stanza::Presence::MUCUser::Status - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Blather::Stanza::Presence::MUCUser::Status +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(code) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 457
+def self.new(code)
+  new_node = super :status
+  new_node.code = code
+  new_node
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ code() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 463
+def code
+  read_attr :code
+end
+
+ +
+ + + + +
+ + +
+ +
+ code=(var) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 467
+def code=(var)
+  write_attr :code, var
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Class.html b/doc/Class.html new file mode 100644 index 0000000..da32387 --- /dev/null +++ b/doc/Class.html @@ -0,0 +1,166 @@ + + + + + + +class Class - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Class +

+ +
+
+ +

Additional alternations to Standard element classes: expanded functionality (Tier 1 Augmentation)

+ +

Class extensions:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ descendants() + + click to toggle source + +
+ + +
+ +

Attribution : stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 23
+def descendants
+  result = []
+  ObjectSpace.each_object(::Class) do |the_class|
+    if the_class < self
+      result << the_class
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/EZMQ.html b/doc/EZMQ.html new file mode 100644 index 0000000..fd1cad9 --- /dev/null +++ b/doc/EZMQ.html @@ -0,0 +1,99 @@ + + + + + + +module EZMQ - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module EZMQ +

+ +
+ +

Patches to EZMQ:

+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/EZMQ/Publisher.html b/doc/EZMQ/Publisher.html new file mode 100644 index 0000000..f322710 --- /dev/null +++ b/doc/EZMQ/Publisher.html @@ -0,0 +1,156 @@ + + + + + + +class EZMQ::Publisher - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class EZMQ::Publisher +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ send(message="", topic='', **options) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 7
+def send(message="", topic='', **options)
+  message = "#{ topic } #{ (options[:encode] || @encode).call message }"
+  @socket.send_string message
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Fcntl.html b/doc/Fcntl.html new file mode 100644 index 0000000..13cb478 --- /dev/null +++ b/doc/Fcntl.html @@ -0,0 +1,116 @@ + + + + + + +module Fcntl - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Fcntl +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
O_BINARY + +
+ + +
O_TEXT + +
+ + +
+
+ + + + + +
+
+ + + + diff --git a/doc/File.html b/doc/File.html new file mode 100644 index 0000000..933ba32 --- /dev/null +++ b/doc/File.html @@ -0,0 +1,813 @@ + + + + + + +class File - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class File +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
DUPLEX + +
+ + +
SETENC_BY_BOM + +

Note: need to verify ::IO::SETENC_BY_BOM value before using:

+ + +
TEXT + +

What is the flag value under Win32?

+ + +
TTY + +
+ + +
WSPLIT + +
+ + +
WSPLIT_INITIALIZED + +
+ + +
+
+ + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ find_file_mimetype(the_file=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 847
+def self.find_file_mimetype(the_file=nil)
+  if the_file.is_a?(::String)
+    if (/.*(?:\\|\/)(.+)$/.match(the_file.to_s) || the_file.to_s.split("/").size > 0 || the_file == "/")
+      if ::File.directory?(the_file)
+        raise ArgumentError, "You tried to look at a directory, not a File. (#{the_file.inspect})."
+      else
+        the_file = ::File.open(the_file, "r")
+      end
+    else
+      raise ArgumentError, "You MUST provide a valid File path."
+    end
+  end
+  if the_file.is_a?(::File)
+    the_file.file_type()
+  else
+    raise ArgumentError, "You MUST provide a valid File object."
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ gxg_permissions_to_mode(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1120
+def self.gxg_permissions_to_mode(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file)
+  result = 0
+  if permissions.is_a?(::Hash)
+    octet = 0
+    octet_string = "0"
+    unix_permissions = ::File.gxg_permissions_to_unix(permissions,type)
+    #
+    unix_permissions.each_char_with_index do |character, index|
+      #
+      if (1..3).include?(index)
+        case index
+        when 1
+          unless character == "-"
+            octet += 4
+          end
+        when 2
+          unless character == "-"
+            octet += 2
+          end
+        when 3
+          unless character == "-"
+            octet += 1
+          end
+        end
+        if index == 3
+          octet_string << octet.to_s
+          octet = 0
+        end
+      end
+      #
+      if (4..6).include?(index)
+        case index
+        when 4
+          unless character == "-"
+            octet += 4
+          end
+        when 5
+          unless character == "-"
+            octet += 2
+          end
+        when 6
+          unless character == "-"
+            octet += 1
+          end
+        end
+        if index == 6
+          octet_string << octet.to_s
+          octet = 0
+        end
+      end
+      #
+      if (7..9).include?(index)
+        case index
+        when 7
+          unless character == "-"
+            octet += 4
+          end
+        when 8
+          unless character == "-"
+            octet += 2
+          end
+        when 9
+          unless character == "-"
+            octet += 1
+          end
+        end
+        if index == 9
+          octet_string << octet.to_s
+          octet = 0
+        end
+      end
+      #
+      result = octet_string.to_i(base=8)
+    end
+    #
+  else
+    raise ArgumentError, "You MUST provide a Hash with :owner, :group, and :other permission sub-hashes of true/false flags"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ gxg_permissions_to_unix(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1027
+def self.gxg_permissions_to_unix(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file)
+  result = "----------"
+  if permissions.is_a?(::Hash)
+    accumulator = ""
+    raw_data = "----------"
+    write_permissions = [:write, :create, :destroy, :rename, :move]
+    case type
+    when :directory
+      accumulator << "d"
+    when :symlink
+      accumulator << "l"
+    when :file
+      accumulator << "-"
+    else
+      raise ArgumentError, "You MUST provide a valid file system entity type: :file, :directory or :symlink."
+    end
+    raw_data.each_char_with_index do |character, index|
+      case index
+      when 1
+        if permissions[:owner][:read]
+          accumulator << "r"
+        else
+          accumulator << character
+        end
+      when 2
+        write_permissions.each do |the_perm|
+          if permissions[:owner][(the_perm)]
+            accumulator << "w"
+            break
+          end
+        end
+        if accumulator[-1] != "w"
+          accumulator << character
+        end
+      when 3
+        if permissions[:owner][:execute]
+          accumulator << "x"
+        else
+          accumulator << character
+        end
+      when 4
+        if permissions[:group][:read]
+          accumulator << "r"
+        else
+          accumulator << character
+        end
+      when 5
+        write_permissions.each do |the_perm|
+          if permissions[:group][(the_perm)]
+            accumulator << "w"
+            break
+          end
+        end
+        if accumulator[-1] != "w"
+          accumulator << character
+        end
+      when 6
+        if permissions[:group][:execute]
+          accumulator << "x"
+        else
+          accumulator << character
+        end
+      when 7
+        if permissions[:other][:read]
+          accumulator << "r"
+        else
+          accumulator << character
+        end
+      when 8
+        write_permissions.each do |the_perm|
+          if permissions[:other][(the_perm)]
+            accumulator << "w"
+            break
+          end
+        end
+        if accumulator[-1] != "w"
+          accumulator << character
+        end
+      when 9
+        if permissions[:other][:execute]
+          accumulator << "x"
+        else
+          accumulator << character
+        end
+      end
+    end
+    result = accumulator
+  else
+    raise ArgumentError, "You MUST provide a Hash with :owner, :group, and :other permission sub-hashes of true/false flags"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mode_permission_to_gxg(the_mode=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 866
+def self.mode_permission_to_gxg(the_mode=0)
+  result = {}
+  if the_mode.is_a?(::Numeric)
+    raw_mode = the_mode.to_s(base=8)
+    set_user_id = false
+    set_group_id = false
+    set_sticky_bit = false
+    while raw_mode.size < 4
+      raw_mode = ("0" << raw_mode)
+    end
+    if raw_mode.size > 4
+      raw_mode = raw_mode[((raw_mode.size - 4)..-1)].to_s
+    end
+    #
+    if (raw_mode[0].to_i & 4) == 4
+      set_user_id = true
+    end
+    if (raw_mode[0].to_i & 2) == 2
+      set_group_id = true
+    end
+    if (raw_mode[0].to_i & 1) ==  1
+      set_sticky_bit = true
+    end
+    raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
+    result = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+    #
+    # puts "using: #{raw_mode.inspect}"
+    # owner permissions
+    if (raw_mode[1].to_i & 4) == 4
+      result[:owner][:read] = true
+    end
+    if (raw_mode[1].to_i & 2) == 2
+      result[:owner][:write] = true
+      result[:owner][:rename] = true
+      result[:owner][:move] = true
+      result[:owner][:destroy] = true
+      result[:owner][:create] = true
+    end
+    if (raw_mode[1].to_i & 1) == 1
+      result[:owner][:execute] = true
+    end
+    # group permissions
+    if (raw_mode[2].to_i & 4) == 4
+      result[:group][:read] = true
+    end
+    if (raw_mode[2].to_i & 2) == 2
+      result[:group][:write] = true
+      result[:group][:rename] = true
+      result[:group][:move] = true
+      result[:group][:destroy] = true
+      result[:group][:create] = true
+    end
+    if (raw_mode[2].to_i & 1) == 1
+      result[:group][:execute] = true
+    end
+    # other permissions
+    if (raw_mode[3].to_i & 4) == 4
+      result[:other][:read] = true
+    end
+    if (raw_mode[3].to_i & 2) == 2
+      result[:other][:write] = true
+      result[:other][:rename] = true
+      result[:other][:move] = true
+      result[:other][:destroy] = true
+      result[:other][:create] = true
+    end
+    if (raw_mode[3].to_i & 1) == 1
+      result[:other][:execute] = true
+    end
+  else
+    raise ArgumentError, "You MUST provide an Integer"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mode_permission_to_unix(the_mode=0, type = :file) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 941
+def self.mode_permission_to_unix(the_mode=0, type = :file)
+  result = "----------"
+  if the_mode.is_a?(::Numeric)
+    accumulator = ""
+    raw_mode = the_mode.to_s(base=8)
+    if raw_mode.size < 7
+      raw_mode = ("0" << raw_mode)
+    end
+    set_user_id = false
+    set_group_id = false
+    set_sticky_bit = false
+    if (raw_mode[3].to_i & 4) == 4
+      set_user_id = true
+    end
+    if (raw_mode[3].to_i & 2) == 2
+      set_group_id = true
+    end
+    if (raw_mode[3].to_i & 1) ==  1
+      set_sticky_bit = true
+    end
+    #
+    case type.to_s.to_sym
+    when :directory
+      accumulator << "d"
+    when :symlink
+      accumulator << "l"
+    when :file
+      accumulator << "-"
+    else
+      raise ArgumentError, "You MUST provide a valid file system entity type: :file, :directory or :symlink."
+    end
+    # owner permissions
+    if (raw_mode[1].to_i & 4) == 4
+      accumulator << "r"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[1].to_i & 2) == 2
+      accumulator << "w"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[1].to_i & 1) == 1
+      accumulator << "x"
+    else
+      accumulator << "-"
+    end
+    # group permissions
+    if (raw_mode[2].to_i & 4) == 4
+      accumulator << "r"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[2].to_i & 2) == 2
+      accumulator << "w"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[2].to_i & 1) == 1
+      accumulator << "x"
+    else
+      accumulator << "-"
+    end
+    # other permissions
+    if (raw_mode[3].to_i & 4) == 4
+      accumulator << "r"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[3].to_i & 2) == 2
+      accumulator << "w"
+    else
+      accumulator << "-"
+    end
+    if (raw_mode[3].to_i & 1) == 1
+      accumulator << "x"
+    else
+      accumulator << "-"
+    end
+    result = accumulator
+  else
+    raise ArgumentError, "You MUST provide an Integer"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ unix_permissions_to_gxg(permission_string="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1201
+def self.unix_permissions_to_gxg(permission_string="")
+  raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
+  blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+  permission_record = blank_permissions.clone
+  #
+  if permission_string.is_a?(::String)
+    permission_string.each_char_with_index do |character, index|
+      #
+      case index
+      when 1
+        if character == "r"
+          permission_record[:owner][:read] = true
+        end
+      when 2
+        if character == "w"
+          permission_record[:owner][:write] = true
+          permission_record[:owner][:rename] = true
+          permission_record[:owner][:move] = true
+          permission_record[:owner][:destroy] = true
+          permission_record[:owner][:create] = true
+        end
+      when 3
+        if character == "x"
+          permission_record[:owner][:execute] = true
+        end
+      when 4
+        if character == "r"
+          permission_record[:group][:read] = true
+        end
+      when 5
+        if character == "w"
+          permission_record[:group][:write] = true
+          permission_record[:group][:rename] = true
+          permission_record[:group][:move] = true
+          permission_record[:group][:destroy] = true
+          permission_record[:group][:create] = true
+        end
+      when 6
+        if character == "x"
+          permission_record[:group][:execute] = true
+        end
+      when 7
+        if character == "r"
+          permission_record[:other][:read] = true
+        end
+      when 8
+        if character == "w"
+          permission_record[:other][:write] = true
+          permission_record[:other][:rename] = true
+          permission_record[:other][:move] = true
+          permission_record[:other][:destroy] = true
+          permission_record[:other][:create] = true
+        end
+      when 9
+        if character == "x"
+          permission_record[:other][:execute] = true
+        end
+      end
+      # No way to calculate Effective Permissions - it will be nil consequently.
+      #
+    end
+    result = permission_record
+  else
+    result = nil
+    raise ArgumentError, "You MUST supply a valid unix permission String"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ file_type() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1270
+def file_type()
+  # TODO: return GxG-UFS value as well.
+  type = ::MimeMagic.by_path(self.path)
+  {:mime_type => type.type(), :subtype => type.subtype(), :mediatype => type.mediatype(), :extensions => type.extensions(), :comment => type.comment(), :ufs => nil}
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Float.html b/doc/Float.html new file mode 100644 index 0000000..d041a5b --- /dev/null +++ b/doc/Float.html @@ -0,0 +1,284 @@ + + + + + + +class Float - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Float +

+ +
+ +

class FalseClass

+ +
public
+def initialize_clone
+  false
+end
+alias :initialize_dup :initialize_clone
+alias :dup :initialize_clone
+def clone()
+  initialize_clone
+end
+
+ +

end

+ +

class TrueClass

+ +
public
+def initialize_clone
+  true
+end
+alias :initialize_dup :initialize_clone
+alias :dup :initialize_clone
+def clone()
+  initialize_clone
+end
+
+ +

end

+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 80
+def clone()
+  initialize_clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 75
+def initialize_clone
+  (self + 0.0)
+end
+
+ +
+ + +
+ Also aliased as: initialize_dup, dup +
+ + + +
+ + +
+ +
+ initialize_dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG.html b/doc/GxG.html new file mode 100644 index 0000000..b5c95a1 --- /dev/null +++ b/doc/GxG.html @@ -0,0 +1,629 @@ + + + + + + +module GxG - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG +

+ +
+ +
---------------------------------------------------------------------------------------------------------------------
+ +

Various preliminary patches to dependencies:

+
+ +

Various preliminary patches to standard classes: <none>

+
+ +

main module declaration:

+
+ +

Set GxG Version and instantiate GxG::SYSTEM object:

+ +

First attempt at object serialization/reconstitution:

+ +

determine the arch, platform with version, and ruby implementation with version store this in GxG::BOOTSTRAP

+ +

To overcome load-order issues:

+ +

GxG Communications Layer

+ +

PeristedArray Code:

+ +

PersistedHash Code:

+
+ +

Various data element classes:

+
+ +

GxG Engine interface: rational memory management, and quota supports

+
+ +

Preliminary Entity supports

+ +

IO Support Library Module & IO Classes ############################################

+ +

Auto-Transcoding Support Library Module ############################################

+
+ +

Units and support element classes:

+ +

gxg_ipc is there to pick the bones, but will be discarded : use this file.

+ +

GxG:

+ +

Networking Section of GxG

+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
BOOTSTRAP + +

cleanup

+ +

return final result

+ + +
BRIDGES + +
+ + +
BRIDGES_AVAILABLE + +
+ + +
DB + +
+ + +
LOG + +

LOG = $LOG

+ + +
RUBY_ENGINE + +
+ + +
SYSTEM + +
+ + +
VERSION + +

setup constants, dispose of BOOTSTRAP data.

+ + +
VFS + +
+ + +
+
+ + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ apportioned_ranges(how_much_data=0, container_limit=0, original_offset=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 530
+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
+
+ +
+ + + + +
+ + +
+ +
+ passes_needed(size_used=0, container_limit=0) + + click to toggle source + +
+ + +
+ +

Generic toolbox of methods

+ + + + +
+
# File lib/gxg.rb, line 516
+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
+
+ +
+ + + + +
+ + +
+ +
+ reconstitute(raw_data="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 472
+def self.reconstitute(raw_data="")
+  # See: http://stackoverflow.com/questions/5758464/ruby-how-do-i-check-if-a-class-is-defined
+  #
+  if raw_data.is_a?(::String)
+    if raw_data.serialized?()
+      result = nil
+      if raw_data.include?("marshal:")
+        begin
+          result = ::Marshal.load(raw_data[(8..-1)].decode64())
+        rescue Exception
+          # Question : what to do here.
+        end
+      else
+        if raw_data.include?("structure:")
+          data = ::Marshal.load(raw_data[(10..-1)].decode64())
+          if data.is_any?(::Array, ::Hash, ::Set, ::Struct)
+            result = data.process do |entry, selector|
+              if entry.is_a?(::String)
+                if entry.serialized?()
+                  ::GxG::reconstitute(entry)
+                  # entry.unserialize()
+                else
+                  entry
+                end
+              else
+                entry
+              end
+            end
+          end
+        else
+          raise Exception, "unrecognized serialization format"
+        end
+      end
+    else
+      result = raw_data
+    end
+  else
+    result = raw_data
+  end
+  result
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ shutdown() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 465
+def self.shutdown()
+  $Dispatcher.shutdown()
+  ::GxG::Networking::ZMQ::zmq_default_context.terminate()
+end
+
+ +
+ + + + +
+ + +
+ +
+ sql_statement?(the_string) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 569
+def self.sql_statement?(the_string)
+  # See: https://larrysteinle.com/2011/02/20/use-regular-expressions-to-detect-sql-code-injection/
+  if /('(''|[^'])*')|(;)|(\\x08(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})\\x08)/.match(the_string)
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid_generate() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 91
+def self.uuid_generate()
+  ::SecureRandom::uuid.to_s
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_domain_name?(the_string) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 578
+def self.valid_domain_name?(the_string)
+  if /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/.match(the_string)
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_uuid?(uuid=nil,strict=true) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 548
+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
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ find_local(the_key) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 6
+def find_local(the_key)
+  nil
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/ByteArray.html b/doc/GxG/ByteArray.html new file mode 100644 index 0000000..1264db2 --- /dev/null +++ b/doc/GxG/ByteArray.html @@ -0,0 +1,5768 @@ + + + + + + +class GxG::ByteArray - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::ByteArray +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ [](*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 538
+def self.[](*args)
+  GxG::ByteArray.new(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ new(*args,&block) + + click to toggle source + +
+ + +
+ +

instance methods:

+ + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_elements.rb, line 571
+  def initialize(*args,&block)
+          # Integer(how_many), <Acceptable-data-type>, <Max-size>, <Pad-value>, <Outer-value>
+  # --OR--
+  # Integer(how_many), <Max-size>, <Pad-value>, <Outer-value> &block
+@data = ""
+@data.force_encoding(Encoding::ASCII_8BIT)
+#
+memory_limits = GxG::SYSTEM.memory_limits()
+                  @max_size = nil
+                  @pad_value = 0
+@outer = nil
+# *should* create a 'self' to call upon.
+super()
+#
+          if args.size > 0
+  if block.respond_to?(:call)
+    if args[0].is_a?(Numeric)
+      args[0] = args[0].to_i
+      if args[0] >= 0
+        if args[0] > memory_limits[:process].first
+          args[0] = memory_limits[:process].first
+        end
+        if args[1].is_a?(Numeric)
+          self.maxsize = args[1].to_i
+        end
+        if args[2].is_a?(Numeric)
+          self.padvalue = args[1].to_i
+        end
+        if args[3].is_a?(Numeric)
+          self.outervalue = args[1].to_i
+        end
+        #
+        (0..(args[0] - 1)).to_enum.each do |index|
+          raw_data_array = [(block.call(index))].flatten
+          raw_data_array.to_enum.each do |raw_data|
+            #
+            raw_data = (self.filter_data(raw_data) || "")
+            #
+            if raw_data.bytesize > 0
+              if (args[0] * raw_data.bytesize) > memory_limits[:process].first
+                # Attempts to project whether or not maximum memory limits will be exceeded.
+                # LATER: ByteArray.new: Might get false positives on variable return data from block (re-think this)
+                raise NoMemoryError.new("This operation will exceed available per-process memory limits")
+              end
+              @data << raw_data
+            end
+          end
+          #
+        end
+        #
+        if @max_size
+          if @data.bytesize > @max_size
+            @max_size = @data.bytesize
+          end
+        end
+        #
+      end
+    end
+  else
+    #
+    if args[0].is_a?(Numeric)
+      how_many = args[0].to_i
+      if args[1]
+        raw_data_array = [(args[1])].flatten
+      else
+        raw_data_array = [(@pad_value.to_i.chr)]
+      end
+      if args[2].is_a?(Numeric)
+        self.maxsize = args[2].to_i
+      end
+      if args[3].is_a?(Numeric)
+        self.padvalue = args[3].to_i
+      end
+      if args[4].is_a?(Numeric)
+        self.outervalue = args[4].to_i
+      end
+    else
+      how_many = 1
+      raw_data_array = [(args[0])].flatten
+      if args[1].is_a?(Numeric)
+        self.maxsize = args[1].to_i
+      end
+      if args[2].is_a?(Numeric)
+        self.padvalue = args[2].to_i
+      end
+      if args[3].is_a?(Numeric)
+        self.outervalue = args[3].to_i
+      end
+    end
+    #
+    new_data = ""
+    new_data.force_encoding(Encoding::ASCII_8BIT)
+    raw_data_array.to_enum.each do |raw_data|
+      new_data << (self.filter_data(raw_data) || "")
+    end
+    if new_data.bytesize > 0
+      if (how_many * new_data.bytesize) > memory_limits[:process].first
+        raise NoMemoryError.new("This operation will exceed available per-process memory limits")
+      end
+      how_many.times do
+        @data << new_data
+      end
+    end
+    #
+    if @max_size
+      if @data.bytesize > @max_size
+        @max_size = @data.bytesize
+      end
+    end
+    #
+  end
+                          #
+                  end
+                  self
+          end
+
+ +
+ + + + +
+ + +
+ +
+ process(the_array=GxG::ByteArray.new,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 542
+def self.process(the_array=GxG::ByteArray.new,&block)
+  new_array = GxG::ByteArray.new
+  if block.respond_to?(:call)
+    if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Set, ::Struct)
+      new_array = the_array.process(&block)
+    else
+      raise ArgumentError, "you must pass an Array, a ByteArray, or a Hash"
+    end
+  end
+  new_array
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ try_convert(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 530
+def self.try_convert(*args)
+  begin
+    GxG::ByteArray.new(1,[(args)])
+  rescue Exception
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ &(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 994
+          def &(*args)
+result = GxG::ByteArray.new
+                  if args.size > 0
+  other = GxG::ByteArray::try_convert(args)
+  if other
+    result_scrap = ""
+    result_scrap.force_encoding(Encoding::ASCII_8BIT)
+    self.each do |the_byte|
+      if other.include?(the_byte)
+        result_scrap << the_byte.chr
+      end
+    end
+    result = GxG::ByteArray.new(result_scrap)
+  end
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ *(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1012
+          def *(*args)
+result = GxG::ByteArray.new
+if self.size > 0
+  if args.size > 0
+    if args[0].is_a?(::String)
+      result = self.join(args[0])
+    else
+      if args[0].is_a?(Numeric)
+        scrap_size = ((args[0].to_f % args[0].to_i.to_f) * self.size.to_f).to_i
+        args[0] = args[0].to_i
+        if (args[0] * self.size) <= GxG::SYSTEM.memory_limits()[:process].first
+          args[0].times do
+            result << self.data().clone
+          end
+          if scrap_size > 0
+            result << self.data()[0..(scrap_size)]
+          end
+        else
+          raise NoMemoryError.new("This operation will exceed per-process memory limits")
+        end
+      else
+        raise ArgumentError.new("a String or Numeric is required, you provied #{args[0].inspect}")
+      end
+    end
+  else
+    raise TypeError.new("Can't convert nil to ByteArray")
+  end
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ +(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1043
+          def +(*args)
+result = GxG::ByteArray.new
+                  if args.size > 0
+  other = GxG::ByteArray::try_convert(args)
+  if other
+    if other.size > 0
+      result = GxG::ByteArray.new(1,[(self.data().clone),(other.data())])
+    end
+  end
+                  else
+                          raise TypeError.new("Can't convert nil to ByteArray")
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ -(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1058
+          def -(*args)
+result = GxG::ByteArray.new
+if args.size > 0
+  scrap = GxG::ByteArray::try_convert(args)
+  if scrap
+    result_scrap = ""
+    result_scrap.force_encoding(Encoding::ASCII_8BIT)
+    self.each do |the_byte|
+      unless scrap.include?(the_byte)
+        result_scrap << the_byte.chr
+      end
+    end
+    result = GxG::ByteArray.new(result_scrap)
+  end
+end
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ <<(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1076
+        def <<(*args)
+                if args.size > 0
+value = GxG::ByteArray::try_convert(args)
+if value
+  if @max_size
+    if (@data.bytesize + value.size) > @max_size
+      raise RangeError.new("Operation exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+    end
+  end
+  @data << value.data()
+end
+                end
+                self
+        end
+
+ +
+ + + + +
+ + +
+ +
+ <=>(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1091
+          def <=>(*args)
+result = 1
+                  if args.size > 0
+  if args[0].is_a?(::GxG::ByteArray)
+    result = (self.data() <=> args[0].data())
+  else
+    # Allows sorting against std. Array or String that *would* sort normal if it were a ByteArray (byte comparison)
+    other = GxG::ByteArray::try_convert(args)
+    if other
+      result = (self.data() <=> other.data())
+    end
+  end
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ ==(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1107
+          def ==(*args)
+result = false
+                  if args.size > 0
+  if args[0].is_a?(::GxG::ByteArray)
+    result = (self.data() == args[0].data())
+  else
+    # kinda cool, but might be a bridge too far for strict equivalence.
+    # other = GxG::ByteArray::try_convert(args)
+    # if other
+    #  result = (self.data() == other.data())
+    #end
+  end
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ [](indexer=0, length=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 886
+          def [](indexer=0, length=nil)
+self.slice(indexer,length)
+          end
+
+ +
+ + + + +
+ + +
+ +
+ []=(indexer=0, length=nil, value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 890
+          def []=(indexer=0, length=nil, value=nil)
+#
+if length
+  unless value
+    value = length
+    length = nil
+  end
+end
+value = self.filter_data(value)
+error = nil
+result = nil
+#
+if value
+  if value.bytesize > 0
+    #
+    the_range = self.filter_parameters(indexer,length)
+    #
+    unless the_range
+      if length
+        error = ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer")
+      else
+        error = ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer")
+      end
+    end
+    if error
+      raise error
+    end
+    # check overall compliance with maxsize
+    if @max_size
+      if (the_range.count > @max_size || the_range.first > (@max_size - 1) || the_range.last > (@max_size - 1))
+        error = IndexError.new("Index exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+      end
+      if (the_range.first + (value.bytesize - 1)) > (@max_size - 1)
+        error = RangeError.new("Data size at specified Index exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+      end
+      #
+      if indexer.is_a?(Integer)
+        if length
+          # replace operation
+          if (the_range.first + (the_range.count - 1)) > (@max_size - 1)
+            error = RangeError.new("Range exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+          else
+            if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1)
+              error = RangeError.new("Value size exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+            end
+          end
+        else
+          # insert operation
+          if (@data.size - 1 + value.bytesize) > @max_size
+            error = RangeError.new("Operation exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+          end
+          if (the_range.first + (value.bytesize - 1)) > (@max_size - 1)
+            error = RangeError.new("Value size exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+          end
+        end
+      else
+        if the_range.count > @max_size
+          error = RangeError.new("Range exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+        else
+          if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1)
+            error = RangeError.new("Value size exceeds #{@max_size} limit.  You might need to adjust maxsize.")
+          end
+        end
+      end
+      if error
+        raise error
+      end
+    end
+    #
+    if the_range.first > (@data.bytesize - 1)
+      (the_range.first - (@data.bytesize - 1)).times do
+        @data << @pad_value.chr
+      end
+    end
+    #
+    if length
+      @data[(the_range.first),(the_range.count)] = value
+    else
+      if indexer.is_a?(Numeric)
+        @data[(the_range.first)] = value
+      else
+        @data[(the_range)] = value
+      end
+    end
+    #
+  end
+  result = GxG::ByteArray.new(value)
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ assoc(key=nil) + +
+ + +
+ +

Method Aliases

+ + + + +
+ + + + +
+ Alias for: rassoc +
+ +
+ + +
+ +
+ at(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1123
+def at(*args)
+        if args[0].is_a?(Float)
+                args[0] = args[0].to_i
+        end
+        if args[0].is_a?(Integer)
+                self[(args[0])]
+        else
+                raise TypeError.new("Expected Integer or Float.  You provided #{args[0].class}")
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ clear() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1134
+def clear()
+        @data.clear
+        self
+end
+
+ +
+ + + + +
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 882
+def clone()
+  initialize_dup()
+end
+
+ +
+ + + + +
+ + +
+ +
+ collect(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1139
+          def collect(&block)
+result = GxG::ByteArray.new
+if block.respond_to?(:call)
+  result_scrap = ""
+  result_scrap.force_encoding(Encoding::ASCII_8BIT)
+  self.each do |the_byte|
+    result_scrap << self.filter_data(block.call(the_byte))
+  end
+  result = GxG::ByteArray.new(result_scrap)
+else
+  result = self.to_enum(:each)
+end
+result
+          end
+
+ +
+ + +
+ Also aliased as: map +
+ + + +
+ + +
+ +
+ collect!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1154
+          def collect!(&block)
+if block.respond_to?(:call)
+  self.replace(self.collect(&block).data())
+  self
+else
+  self.to_enum(:each)
+end
+          end
+
+ +
+ + +
+ Also aliased as: map! +
+ + + +
+ + +
+ +
+ combination(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1163
+def combination(*args,&block)
+  # TODO: port c code to ruby (true-enumeration-as-bytearray support)
+  enumerator = @data.to_enum(:bytes).to_a.to_enum(:combination,*args)
+  if block.respond_to?(:call)
+    enumerator.each do |the_combination|
+      block.call(GxG::ByteArray.new(the_combination))
+    end
+    self
+  else
+    # Note: this form does not put bytearrays of each combination into the block, just arrays (please fix)
+    GxG::Enumerator.new(enumerator)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ compact() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1190
+def compact()
+        self
+end
+
+ +
+ + +
+ Also aliased as: compact! +
+ + + +
+ + +
+ +
+ compact!() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: compact +
+ +
+ + +
+ +
+ concat(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1194
+          def concat(*args)
+result = self
+                  if args.size > 0
+  other = GxG::ByteArray::try_convert(args)
+  if other
+    result = (self + other)
+  else
+    raise Exception.new("Could not convert #{args.inspect} to ByteArray")
+  end
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ count(match_value=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1207
+def count(match_value=nil,&block)
+  result = 0
+  if match_value.is_a?(Numeric)
+    self.each do |the_byte|
+      if the_byte == match_value.to_i
+        result += 1
+      end
+    end
+  else
+    if block.respond_to?(:call)
+      self.each do |the_byte|
+        if block.call(the_byte) == true
+          result += 1
+        end
+      end
+    else
+      result = @data.bytesize
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ cycle(iterations=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1229
+def cycle(iterations=nil,&block)
+  if @data.bytesize > 0
+    unless iterations.to_i < 0
+      if block.respond_to?(:call)
+        if iterations
+          iterations.to_i.times do 
+            self.each do |the_byte|
+              block.call(the_byte)
+            end
+          end
+          nil
+        else
+          while true do
+            self.each do |the_byte|
+              block.call(the_byte)
+            end
+          end
+        end
+      else
+        @data.to_enum(:bytes).to_a.to_enum(:cycle,iterations)
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ data() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 566
+def data()
+  @data
+end
+
+ +
+ + + + +
+ + +
+ +
+ dclone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 860
+          def dclone()
+                  result = GxG::ByteArray.new(@data.clone)
+                  result.padvalue = (self.padvalue())
+                  result.maxsize = (self.maxsize())
+result.outervalue = (self.outervalue())
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ delete(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1254
+          def delete(*args,&block)
+                  result = nil
+if args[0].is_a?(Numeric)
+  comparitor = self.filter_data(args[0].to_i)
+  if comparitor.bytesize > 1
+    comparitor = comparitor[0]
+  end
+  scrap = @data.delete(comparitor)
+  if scrap == @data
+    if block.respond_to?(:call)
+      result = block.call()
+    end
+  else
+    @data.replace(scrap)
+    result = args[0]
+  end
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ delete_at(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1274
+        def delete_at(*args)
+                result = nil
+                if args.size > 0
+the_range = self.filter_parameters(args)
+if the_range.last < self.size
+  result = @data.slice!(the_range)
+end
+                end
+                result
+        end
+
+ +
+ + + + +
+ + +
+ +
+ delete_if(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1285
+          def delete_if(&block)
+self.reject!(&block)
+          end
+
+ +
+ + + + +
+ + +
+ +
+ drop(length) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1289
+def drop(length)
+  self.slice((length.to_i - 1)..-1)
+end
+
+ +
+ + + + +
+ + +
+ +
+ drop_while(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1293
+def drop_while(&block)
+  accumulator = GxG::ByteArray.new
+  if block.respond_to?(:call)
+    self.each do |the_byte|
+      result = block.call(the_byte)
+      unless result
+        accumulator << the_byte
+      end
+    end
+    accumulator
+  else
+    # TODO: 5xMem efficiency fix for various engine run conditions
+    GxG::Enumeratior.new(@data.to_enum(:bytes).to_a.to_enum(:drop_while))
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_dup +
+ +
+ + +
+ +
+ each(&block) + + click to toggle source + +
+ + +
+ +

TODO: GxG::ByteArray.each_index/each_with_index: improve memory efficiency and speed significantly (!)

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 1311
+          def each(&block)
+if block.respond_to?(:call)
+  if @data.bytesize > 0
+    @data.to_enum(:bytes).each {|the_byte| block.call(the_byte)}
+  end
+  self
+else
+  self.to_enum(:each)
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ each_index(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1322
+          def each_index(&block)
+if block.respond_to?(:call)
+  if @data.bytesize > 0
+    (0..(@data.bytesize - 1)).to_enum.each {|index| block.call(index)}
+  end
+  self
+else
+  self.to_enum(:each_index)
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ each_with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1333
+          def each_with_index(offset=0,&block)
+if block.respond_to?(:call)
+  if @data.bytesize > 0
+    # bulky, but required to support self.reject!, etc :
+    @data.to_enum(:bytes).with_index(offset).each {|value,index| block.call(value,index)}
+    #old : (0..(@data.bytesize - 1)).to_enum.each {|index| block.call(@data[(index)].ord,index)}
+  end
+  self
+else
+  self.to_enum(:each_with_index,offset)
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ empty?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1346
+def empty?()
+        @data.bytesize == 0
+end
+
+ +
+ + + + +
+ + +
+ +
+ eql?(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1350
+          def eql?(*args)
+if args.size > 0
+  self == args[0]
+else
+  false
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ fetch(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1358
+          def fetch(*args,&block)
+#fetch(index) → obj click to toggle source
+#fetch(index, default ) → obj
+#fetch(index) {|index| block } → obj
+#
+#Tries to return the element at position index. If the index lies outside the array, the first form throws an IndexError exception,
+# the second form returns default, and the third form returns the value of invoking the block, passing in the index.
+# Negative values of index count from the end of the array.
+# LATER: ByteArray.fetch: check how to incorporate 'padded-edges' feature
+if args.size > 0
+  if args[0].is_a?(Numeric)
+    args[0] = self.filter_parameters(args[0].to_i).first
+    if args[0] >= self.size
+      if args[1]
+        if block.respond_to?(:call)
+          block.call(args[0])
+        else
+          args[1]
+        end
+      else
+        raise IndexError.new("Index specified lies outside the ByteArray")
+      end
+    else
+      self.at(args[0])
+    end
+  end
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ fill(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1387
+          def fill(*args,&block)
+#fill(obj) → ary click to toggle source
+#fill(obj, start [, length]) → ary
+#fill(obj, range ) → ary
+#fill {|index| block } → ary
+#fill(start [, length] ) {|index| block } → ary
+#fill(range) {|index| block } → ary
+#
+#The first three forms set the selected elements of self (which may be the entire array) to obj. A start of nil is equivalent to zero.
+#A length of nil is equivalent to self.length. The last three forms fill the array with the value of the block.
+#The block is passed the absolute index of each element to be filled. Negative values of start count from the end of the array.
+#a = [ "a", "b", "c", "d" ]
+#a.fill("x")              #=> ["x", "x", "x", "x"]
+#a.fill("z", 2, 2)        #=> ["x", "x", "z", "z"]
+#a.fill("y", 0..1)        #=> ["y", "y", "z", "z"]
+#a.fill {|i| i*i}         #=> [0, 1, 4, 9]
+#a.fill(-2) {|i| i*i*i}   #=> [0, 1, 8, 27]
+# Note: if ByteArray is empty, it will be filled to @max_size, otherwise only the used portion or specified range.
+result = nil
+result_scrap = ""
+result_scrap.force_encoding(Encoding::ASCII_8BIT)
+#
+if block.respond_to?(:call)
+  if args.size > 0
+    if args[0].is_a?(Numeric)
+      args[0] = args[0].to_i
+      if args[0] < 0
+        if args[1].is_a?(Numeric)
+          if args[1].to_i > 0
+            args = [((@data.bytesize + args[0])..(@data.bytesize + args[0] + (args[1].to_i - 1)))]
+          else
+            raise ArgumentError.new("length specified should be a positive Integer for Float")
+          end
+        else
+          args = [((@data.bytesize + args[0])..(@data.bytesize - 1))]
+        end
+      end
+    end
+    fillrange = self.filter_parameters(*args)
+  else
+    fillrange = (0..((@max_size || @data.bytesize) - 1))
+  end
+  (0..(@data.bytesize - 1)).to_enum.each do |index|
+    if fillrange.include?(index)
+      raw_value = self.filter_data(block.call(index))
+      if raw_value.size > 0
+        # Allow only single byte results to merge into fill
+        # since the concept is 'per-item' which in this case is a single byte only.
+        result_scrap << raw_value[0]
+      else
+        result_scrap << @pad_value.chr
+      end
+    else
+      result_scrap << @data[(index)]
+    end
+  end
+else
+  if args.size > 0
+    value = nil
+    case args.size
+    when 1
+      fillrange = (0..((@max_size || @data.bytesize) - 1))
+      value = self.filter_data(args[0])
+    when 2
+      fillrange = self.filter_parameters(args[1])
+      value = self.filter_data(args[0])
+    when 3
+      fillrange = self.filter_parameters(args[1],args[2])
+      value = self.filter_data(args[0])
+    else
+      fillrange = self.filter_parameters(args[1],args[2])
+      value = self.filter_data(args[0])
+    end
+    value = (value || @pad_value.chr)
+    #
+    (0..(@data.bytesize - 1)).to_enum.each do |index|
+      if fillrange.include?(index)
+        result_scrap << value
+      else
+        result_scrap << @data[(index)]
+      end
+    end
+  else
+    raise ArgumentError.new("wrong number of arguments (0 for 1..3)")
+  end
+end
+if result_scrap.size > 0
+  self.replace(result_scrap)
+end
+self
+          end
+
+ +
+ + + + +
+ + +
+ +
+ find_index(*args, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1542
+          def find_index(*args, &block)
+                  result = nil
+if block.respond_to?(:call)
+  found_at = 0
+  @data.to_enum(:bytes).each do |the_byte|
+    if block.call(the_byte) == true
+      result = found_at
+      break
+    end
+    found_at += 1
+  end
+else
+  if args.size > 0
+    if args[0].is_a?(Float)
+      args[0] = args[0].to_i
+    end
+    if args[0].is_a?(Integer)
+      if (0..255).include?(args[0])
+        if args[1]
+          # offset specified (addtional option vs standard Array)
+          result = @data.index(args[0].chr,args[1])
+        else
+          result = @data.index(args[0].chr)
+        end
+      end
+    else
+      raise TypeError.new("Expected Integer or Float.  You provided #{args[0].class}")
+    end
+  else
+    result = @data.to_enum(:bytes)
+  end
+end
+                  result
+          end
+
+ +
+ + +
+ Also aliased as: rindex, index +
+ + + +
+ + +
+ +
+ first(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1479
+    def first(*args)
+            if args.size > 0
+                    if args[0].is_a?(Float)
+                            args[0] = args[0].to_i
+                    end
+                    if args[0].is_a?(Integer)
+                            if args[0] > 0
+if @max_size
+  unless args[0] <= @max_size
+    raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
+  end
+end
+                                    self[(0..(args[0] - 1))]
+                            end
+                    else
+                            raise TypeError.new("Expected Integer.  You provided #{args[0].class}")
+                    end
+            else
+                    self[0]
+            end
+    end
+
+ +
+ + + + +
+ + +
+ +
+ flatten() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1501
+def flatten()
+        # ByteArray is always flat anyhow
+        self.dclone
+end
+
+ +
+ + + + +
+ + +
+ +
+ flatten!() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1506
+def flatten!()
+        # ByteArray is always flat anyhow
+        self
+end
+
+ +
+ + + + +
+ + +
+ +
+ freeze() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_elements.rb, line 1511
+def freeze()
+  @data.freeze
+  @max_size.freeze
+  @pad_value.freeze
+  @outer.freeze
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_at_path(the_path="/") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2191
+def get_at_path(the_path="/")
+  # TODO: question: would it serve much to allow ranges as structure-path specifier?  What would that involve?
+  # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric
+  # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu
+  #
+  # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/"))
+  result = nil
+  if the_path == "/"
+    result = self
+  else
+    object_stack = [(self)]
+    path_stack = the_path.split("/")
+    path_stack.to_enum.each do |path_element|
+      element = nil
+      if path_element.size > 0
+        if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          element = path_element.to_i
+        end
+      end
+      if element
+        result = object_stack.first[(element)]
+        if result.is_a?(NilClass)
+          break
+        else
+          object_stack.unshift(result)
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ hash() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1519
+def hash()
+        @data.hash
+end
+
+ +
+ + + + +
+ + +
+ +
+ include?(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1523
+def include?(*args)
+        if args.size > 0
+                if args[0].is_a?(Float)
+                        args[0] = args[0].to_i
+                end
+                if args[0].is_a?(Integer)
+                        if (0..255).include?(args[0])
+                                @data.include?(args[0].chr)
+                        else
+                                raise RangeError.new("Value out of range: range is 0 to 255.  You provided #{args[0]}")
+                        end
+                else
+                        raise TypeError.new("Expected Integer or Float.  You provided #{args[0].class}")
+                end
+        else
+                raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1).")
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ index(*args, &block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: find_index +
+ +
+ + +
+ +
+ indexes(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: values_at +
+ +
+ + +
+ +
+ indices(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: values_at +
+ +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 868
+def initialize_clone()
+  result = self.dclone
+  if self.frozen?
+    result.freeze
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ initialize_copy() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_dup +
+ +
+ + +
+ +
+ initialize_dup() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 876
+def initialize_dup()
+  # ByteArry is one-dimensional anyhow
+  self.dclone
+end
+
+ +
+ + +
+ Also aliased as: dup, initialize_copy +
+ + + +
+ + +
+ +
+ insert(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1577
+      def insert(*args)
+              if args.size > 1
+                      if args[0].is_a?(Float)
+                              args[0] = args[0].to_i
+                      end
+                      if args[0].is_a?(Integer)
+                              value = self.filter_data(args[(1..(args.size - 1))])
+if @max_size
+  unless (@data.bytesize + value.bytesize) <= @max_size
+    raise RangeError.new("Operation exceeds #{@max_size} limit.  You need to adjust maxsize.")
+  end
+end
+begin
+  @data.insert(args[0],value)
+rescue Exception => error
+  raise error.exception(error.message.gsub("string",(self.class.to_s)))
+end
+                      else
+                              raise TypeError.new("Expected Integer or Float.  You provided #{args[0].class}")
+                      end
+              else
+                      raise ArgumentError.new("Wrong number of arguments (#{args.size} for 2).")
+              end
+              self
+      end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1603
+          def inspect()
+result = "["
+@data.to_enum(:bytes).each do |the_byte|
+  if result.size > 1
+    result << ", "
+  end
+  if the_byte > 10
+    result << "0x#{the_byte.to_s(16).upcase}"
+  else
+    result << "0x0#{the_byte.to_s(16).upcase}"
+  end
+end
+result << "]"
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ join(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1619
+          def join(*args)
+result = ""
+if args[1].is_a?(Encoding)
+  # Extra optional encoding vs standard Array
+  result.force_encoding(args[1])
+else
+  result.force_encoding(Encoding::ASCII_8BIT)
+end
+@data.to_enum(:bytes).each do |the_byte|
+  if (args[0].is_a?(String) && result.size > 0)
+    result << (args[0] + the_byte.chr)
+  else
+    result << the_byte.chr
+  end
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ keep_if(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: select! +
+ +
+ + +
+ +
+ last(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1637
+    def last(*args)
+            if args.size > 0
+                    if args[0].is_a?(Float)
+                            args[0] = args[0].to_i
+                    end
+                    if args[0].is_a?(Integer)
+                            if args[0] > 0
+the_range = ((@data.bytesize - 1 - (args[0] - 1))..(@data.bytesize - 1))
+if @max_size
+  unless args[0] <= @max_size
+    raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
+  end
+end
+                                    self[(the_range)]
+                            else
+                                    raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).")
+                            end
+                    else
+                            raise TypeError.new("Expected Integer.  You provided #{args[0].class}")
+                    end
+            else
+                    self[(@data.bytesize - 1)]
+            end
+    end
+
+ +
+ + + + +
+ + +
+ +
+ length() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: size +
+ +
+ + +
+ +
+ map(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: collect +
+ +
+ + +
+ +
+ map!(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: collect! +
+ +
+ + +
+ +
+ match(regular_expression) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 791
+def match(regular_expression)
+  @data.match(regular_expression)
+end
+
+ +
+ + + + +
+ + +
+ +
+ maxsize() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 795
+def maxsize()
+  @max_size.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ maxsize=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 799
+          def maxsize=(*args)
+# Set @maxsize: Warning: setting @maxsize lower than the @data.bytesize will truncate the data
+                  if args.size > 0
+  if args[0]
+    if args[0].is_a?(Numeric)
+      args[0] = args[0].to_i
+    end
+    #
+    if args[0].is_a?(Integer)
+      if args[0] > GxG::SYSTEM.memory_limits()[:process].first
+        raise NoMemoryError.new("Setting maxsize this high exceeds per-process memory limits")
+      end
+      if args[0] > @max_size.to_i
+        @max_size = args[0]
+      else
+        if args[0] < @max_size.to_i
+          # truncate data if @max_size lowered below @data.bytesize
+          @max_size = args[0]
+          if @data.bytesize > args[0]
+            @data.replace(@data[0..(args[0] - 1)])
+          end
+        end
+      end
+    else
+      raise ArgumentError.new("Expected NilClass or Numeric class.  You provided #{args[0].class}")
+    end
+  else
+    @max_size = nil
+  end
+else
+  @max_size = nil
+                  end
+@max_size.clone
+          end
+
+ +
+ + + + +
+ + +
+ +
+ mime_type() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 687
+def mime_type()
+  @data.mime_type()
+end
+
+ +
+ + + + +
+ + +
+ +
+ nitems() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: size +
+ +
+ + +
+ +
+ outervalue() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 834
+def outervalue()
+  @outer.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ outervalue=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 838
+def outervalue=(*args)
+                    if args.size > 0
+    if args[0]
+      if args[0].is_a?(Float)
+        args[0] = args[0].to_i
+      end
+      if args[0].is_a?(Integer)
+        if (0..255).include?(args[0])
+          @outer = args[0]
+        end
+      else
+        raise TypeError.new("Expected a NilClass, a Float, or a Integer.  You provided #{args[0].class}")
+      end
+    else
+      @outer = nil
+    end
+  else
+    @outer = nil
+                    end
+  self.outervalue()
+end
+
+ +
+ + + + +
+ + +
+ +
+ pack(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1662
+          def pack(*args)
+result = ""
+                  if args.size >= 1
+                          args[0] = (args[0] || "")
+                          if args[0].is_a?(String)
+    if args[1].is_a?(Encoding)
+      # Extra optional encoding vs standard Array
+      result.force_encoding(args[1])
+    end
+    # LATER: ByteArray.pack: a more memory-efficient method is needed here long term.  For now: leverage Array.pack.
+    # Each byte will take up 4 to 8 bytes depending just to get packed into a string ... gotta fix this.
+                                  result << self.to_a.pack(args[0])
+                          else
+                                  raise TypeError.new("Expected String.  You provided #{args[0].class}")
+                          end
+                  else
+                          raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1+).")
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ padvalue() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 771
+def padvalue()
+  @pad_value.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ padvalue=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 775
+          def padvalue=(*args)
+                  if args.size > 0
+                          if args[0].is_a?(Float)
+                                  args[0] = args[0].to_i
+                          end
+                          if args[0].is_a?(Integer)
+                                  if (0..255).include?(args[0])
+      @pad_value = args[0]
+                                  end
+                          else
+                                  raise TypeError.new("Value type: expected Float or Integer.  You provided #{args[0].class}")
+                          end
+                  end
+@pad_value.clone
+          end
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(object=nil,base_path="") + + click to toggle source + +
+ + +
+ +

GxG Convenience Methods:

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 2177
+def paths_to(object=nil,base_path="")
+  # new idea here:
+  search_results = []
+  # TODO: if paths can use ranges, expand to use string or byte or int byte or byte-pattern matching to define returned valid path for get/set.
+  if object.is_a?(Numeric)
+    self.to_enum.each_with_index do |entry,index|
+      if entry == object.to_i
+        search_results << (base_path + "/" + index.to_s)
+      end
+    end
+  end
+  search_results
+end
+
+ +
+ + + + +
+ + +
+ +
+ pattern_range(the_pattern="", initial_offset=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 721
+def pattern_range(the_pattern="", initial_offset=0)
+  result = nil
+  if the_pattern.is_a?(::ByteArray)
+    the_pattern = the_pattern.to_s
+  end
+  if the_pattern.is_a?(::String)
+    if initial_offset.is_a?(::Integer)
+      if @max_size
+        if initial_offset > @max_size
+          raise ArgumentError, "Offset exceeds the maximum size."
+        end
+      end
+      if the_pattern.encoding != ::Encoding::ASCII_8BIT
+        the_pattern = the_pattern.clone
+        the_pattern.force_encoding(::Encoding::ASCII_8BIT)
+      end
+      offset = @data.index(the_pattern, initial_offset)
+      if offset.is_a?(::Integer)
+        result = ((offset)..(offset + (the_pattern.size - 1)))
+      end
+    else
+      raise ArgumentError, "You MUST provide the offset as an Integer."
+    end
+  else
+    raise ArgumentError, "You MUST provide a String to find."
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ pattern_ranges(the_pattern="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 750
+def pattern_ranges(the_pattern="")
+  result = []
+  offset = 0
+  if the_pattern.is_a?(::ByteArray)
+    the_pattern = the_pattern.to_s
+  end
+  last_found = self.pattern_range(the_pattern,offset)
+  while last_found != nil do
+    if last_found.is_a?(::Range)
+      result << last_found
+      if (last_found.last() + 1) <= @data.bytesize
+        offset = (last_found.last() + 1)
+      else
+        break
+      end
+    end
+    last_found = self.pattern_range(the_pattern,offset)
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ permutation(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1683
+def permutation(*args,&block)
+  # TODO: port c code to ruby (true-enumeration-as-bytearray support)
+  enumerator = @data.to_enum(:bytes).to_a.to_enum(:permutation,*args)
+  if block.respond_to?(:call)
+    enumerator.each do |the_permutation|
+      block.call(GxG::ByteArray.new(the_permutation))
+    end
+    self
+  else
+    GxG::Enumerator.new(enumerator)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ pop(how_many=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1709
+          def pop(how_many=nil)
+# LIFO style stack operation
+if how_many.is_a?(Numeric)
+  unless how_many.to_i > 0
+    raise ArgumentError.new("#{how_many} needs to be a positive Numeric")
+  end
+  self.slice!((how_many.to_i * -1),(how_many.to_i))
+else
+  self.slice!(-1,1)
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ process(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 691
+def process(&block)
+  result = self.clone
+  if block.respond_to?(:call)
+    result.each_with_index do |entry,index|
+      block.call(entry, index, self)
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 701
+def process!(&block)
+  if block.respond_to?(:call)
+    self.replace(self.process(&block).data())
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ product(*others,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1721
+def product(*others,&block)
+  if @data.bytesize > 0
+    others = GxG::ByteArray.new(others)
+    if block.respond_to?(:call)
+      self.each do |the_byte|
+        others.each do |other_byte|
+          block.call(GxG::ByteArray.new([(the_byte),(other_byte)]))
+        end
+      end
+      self
+    else
+      accumulator = GxG::ByteArray.new
+      self.each do |the_byte|
+        others.each do |other_byte|
+          accumulator << the_byte
+          accumulator << other_byte
+        end
+      end
+      accumulator
+    end
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ push(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1746
+          def push(*args)
+# LIFO style stack operation
+                  self << GxG::ByteArray.new(args)
+          end
+
+ +
+ + + + +
+ + +
+ +
+ rassoc(key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1751
+def rassoc(key=nil)
+        nil
+end
+
+ +
+ + +
+ Also aliased as: assoc +
+ + + +
+ + +
+ +
+ reject(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1755
+          def reject(&block)
+                  result = nil
+if block.respond_to?(:call)
+  result = GxG::ByteArray.new
+                          result.padvalue = (@pad_value)
+  if @max_size
+    result.maxsize = (@max_size)
+  end
+  self.to_enum.each do |the_byte|
+    result << the_byte
+  end
+else
+  result = self.to_enum
+end
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ reject!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1772
+          def reject!(&block)
+result = nil
+if block.respond_to?(:call)
+  purgelist = []
+  self.each_with_index do |value, index|
+    if block.call(value) == true
+      @data[(index)] = ""
+      purgelist << index
+    end
+  end
+  result = self
+else
+  result = self.to_a.to_enum(:reject!)
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ repeated_combination(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1177
+def repeated_combination(*args,&block)
+  # TODO: port c code to ruby (true-enumeration-as-bytearray support)
+  enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_combination,*args)
+  if block.respond_to?(:call)
+    enumerator.each do |the_combination|
+      block.call(GxG::ByteArray.new(the_combination))
+    end
+    self
+  else
+    GxG::Enumerator.new(enumerator)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ repeated_permutation(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1696
+def repeated_permutation(*args,&block)
+  # TODO: port c code to ruby (true-enumeration-as-bytearray support)
+  enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_permutation,*args)
+  if block.respond_to?(:call)
+    enumerator.each do |the_permutation|
+      block.call(GxG::ByteArray.new(the_permutation))
+    end
+    self
+  else
+    GxG::Enumerator.new(enumerator)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ replace(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1789
+          def replace(*args)
+# replace data payload while leaving max_size and pad_value untouched, fitting replacement data into in-place constraints if any.
+                  if args.size > 0
+  replacement = nil
+  if args[0].is_a?(::GxG::ByteArray)
+    replacement = args[0].data()
+  end
+  unless replacement.is_a?(::String)
+    replacement = GxG::ByteArray::try_convert(args)
+    if replacement
+      replacement = replacement.data()
+    end
+  end
+  if replacement
+    replacement.force_encoding(Encoding::ASCII_8BIT)
+    if @max_size
+      if replacement.bytesize <= @max_size
+        @data.replace(replacement)
+      else
+        @data.replace(replacement[(0..(@max_size - 1))])
+      end
+    else
+      @data.replace(replacement)
+    end
+  else
+    raise Exception.new("Could not convert #{args[0].class} to ByteArray")
+  end
+                  end
+                  self
+          end
+
+ +
+ + + + +
+ + +
+ +
+ reverse() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1820
+          def reverse()
+                  result = GxG::ByteArray.new(@data.reverse)
+                  result.padvalue = (@pad_value.clone)
+if @max_size
+  result.maxsize = (@max_size.clone)
+end
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ reverse!() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1829
+def reverse!()
+        @data.reverse!
+        self
+end
+
+ +
+ + + + +
+ + +
+ +
+ reverse_each(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1834
+          def reverse_each(&block)
+result = nil
+                  if block.respond_to?(:call)
+  # use duplicate of self, do not preserve frozen state.
+  result = self.dup
+  result.clear
+                          @data.reverse.to_enum(:bytes).each {|the_byte| result << block.call(the_byte)}
+                  else
+                          # ballons mem footprint x2: *ouch* - don't know a better way yet.
+  result = self.clone.reverse!.to_enum
+                  end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ rindex(*args, &block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: find_index +
+ +
+ + +
+ +
+ rotate(vector=1) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1848
+def rotate(vector=1)
+  unless vector.is_a?(Numeric)
+    raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}")
+  end
+  # from array.c : rotate_count
+  if vector < 0
+    vector = (self.size - (~vector % self.size) - 1).to_i
+  else
+    vector = (vector % self.size).to_i
+  end
+  if GxG::Engine.memory_load_high?
+    # trade speed for (2x) space
+    scrap = []
+    unless vector == 0
+      the_range = self.filter_parameters(((vector)..-1))
+      scrap << self[(the_range)]
+      if the_range.first > 0
+        scrap << self[(0..(the_range.first - 1))]
+      end
+    end
+    if scrap.size > 0
+      result = GxG::ByteArray.new(scrap)
+    else
+      result = self.dclone
+    end
+  else
+    # trade (5x) space for speed
+    result = GxG::ByteArray.new(self.to_a.rotate(vector))
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rotate!(vector=1) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1880
+          def rotate!(vector=1)
+if vector.is_a?(Numeric)
+  if vector.to_i != 0
+    new_data = self.rotate(vector).data()
+    unless @data.hash == new_data.hash
+      self.replace(new_data)
+    end
+  end
+else
+  raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}")
+end
+                  self
+          end
+
+ +
+ + + + +
+ + +
+ +
+ sample(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1894
+def sample(*args)
+  GxG::ByteArray.new(@data.to_enum(:bytes).to_a.sample(*args))
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ select(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1898
+          def select(&block)
+                  result = GxG::ByteArray.new
+                  result.maxsize = (@max_size)
+                  result.padvalue = (@pad_value)
+result.outervalue = (@outer)
+                  if block.respond_to?(:call)
+                          self.each do |value|
+                                  unless block.call(value) == false
+                                          result << value
+                                  end
+                          end
+else
+  self.to_enum(:each)
+                  end
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ select!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1915
+          def select!(&block)
+if block.respond_to?(:call)
+  state = @data.hash
+  self.replace(self.select(&block).data())
+  if state == @data.hash
+    nil
+  else
+    self
+  end
+else
+  self.to_enum(:each)
+end
+          end
+
+ +
+ + +
+ Also aliased as: keep_if +
+ + + +
+ + +
+ +
+ set_at_path(the_path="/",the_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2226
+def set_at_path(the_path="/",the_value=nil)
+  result = nil
+  if the_path != "/"
+    container = self
+    if container
+      raw_selector = ::File::basename(the_path)
+      selector = nil
+      if raw_selector.size > 0
+        if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          selector = raw_selector.to_i
+        end
+      end
+      if selector
+        container[(selector)] = the_value
+        if container[(selector)] == the_value
+          result = true
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+      #
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ shift() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1929
+          def shift()
+# FIFO style pop
+                  self.delete_at(0)
+          end
+
+ +
+ + + + +
+ + +
+ +
+ shuffle(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1934
+def shuffle(*args)
+  GxG::ByteArray.new(@data.to_enum(:bytes).to_a.shuffle(*args))
+end
+
+ +
+ + + + +
+ + +
+ +
+ shuffle!(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1938
+def shuffle!(*args)
+  self.replace(self.shuffle(*args))
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ size() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1943
+          def size()
+@data.bytesize
+          end
+
+ +
+ + +
+ Also aliased as: length, nitems +
+ + + +
+ + +
+ +
+ slice(indexer=0, length=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1947
+def slice(indexer=0, length=nil)
+  the_range = self.filter_parameters(indexer,length)
+  unless the_range
+    if length
+      raise ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer")
+    else
+      raise ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer")
+    end
+  end
+                    # check overall compliance with maxsize
+  if @max_size
+    if ((the_range.count > @max_size) || (the_range.first > (@max_size - 1)) || (the_range.last > (@max_size - 1)))
+      raise IndexError.new("Index exceeds #{@max_size} limit.  You need to adjust maxsize.")
+    end
+  end
+                    #
+                    result = nil
+  #
+  if the_range
+    if the_range.first == the_range.last
+      result = @data.byte_at(the_range.first)
+    else
+      result = @data.bytes_at(the_range)
+    end
+  end
+  #
+                    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ slice!(indexer=0, length=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 1976
+          def slice!(indexer=0, length=nil)
+replacement = self.slice(indexer,length)
+if replacement.is_a?(Numeric)
+  self.replace(replacement.chr)
+else
+  self.replace(replacement)
+end
+if self.size == 1
+  self.slice(0)
+else
+  self
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ sort(&block) + + click to toggle source + +
+ + +
+ +

BETA: ByteArray : see about self.to_a replacement (memory efficiency) in sort methods.

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 1992
+def sort(&block)
+        if block.respond_to?(:call)
+                GxG::ByteArray.new(self.to_a.sort {|a,b| pause;block.call(a,b)})
+        else
+                GxG::ByteArray.new(self.to_a.sort)
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ sort!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2000
+        def sort!(&block)
+                if block.respond_to?(:call)
+self.replace(self.sort(&block).data())
+                end
+                self
+        end
+
+ +
+ + + + +
+ + +
+ +
+ sort_by!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2007
+        def sort_by!(&block)
+                if block.respond_to?(:call)
+self.replace(GxG::ByteArray.new(self.to_a.sort_by! {|a,b| pause;block.call(a,b)}).data())
+self
+                else
+                        self.to_enum(:sort_by!)
+                end
+        end
+
+ +
+ + + + +
+ + +
+ +
+ take(length) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2016
+def take(length)
+  self.slice(0..(length.to_i - 1))
+end
+
+ +
+ + + + +
+ + +
+ +
+ take_while(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2020
+def take_while(&block)
+  if block.respond_to?(:call)
+    accumulator = GxG::ByteArray.new
+    self.each do |the_byte|
+      result = block.call(the_byte)
+      if result
+        accumulator << the_byte
+      else
+        break
+      end
+    end
+    accumulator
+  else
+    self.to_enum(:take_while)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_a() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2037
+def to_a()
+        result = []
+        if @data.bytesize > 0
+                (0..(@data.bytesize - 1)).to_enum.each {|index| result << self[(index)]}
+        end
+        result
+end
+
+ +
+ + +
+ Also aliased as: to_ary +
+ + + +
+ + +
+ +
+ to_ary() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: to_a +
+ +
+ + +
+ +
+ to_json() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2049
+def to_json()
+  (("binary:" + self.to_s).encode64)
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_s() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2045
+def to_s()
+        @data.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+ transpose() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2053
+def transpose()
+        self.dclone
+end
+
+ +
+ + + + +
+ + +
+ +
+ uniq(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2057
+          def uniq(&block)
+if block.respond_to?(:call)
+  harness = Proc.new do |element|
+    pause
+    block.call(element)
+  end
+  GxG::ByteArray.new(self.to_a.uniq(&harness))
+else
+  self
+end
+          end
+
+ +
+ + + + +
+ + +
+ +
+ uniq!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2069
+def uniq!(&block)
+        scrap = self.uniq(&block)
+        if scrap.size == self.size
+                nil
+        else
+                self.replace(scrap)
+                self
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ unshift(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2079
+          def unshift(*args)
+# FIFO style push
+                  if args.size > 0
+                          self.insert(0,args)
+                  end
+                  self
+          end
+
+ +
+ + + + +
+ + +
+ +
+ values_at(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2087
+        def values_at(*args)
+                result = GxG::ByteArray.new
+                if args.size > 0
+result_scrap = ""
+result_scrap.force_encoding(Encoding::ASCII_8BIT)
+                        args.to_enum.each do |selector|
+                                if selector.is_a?(Numeric) or selector.is_a?(Range)
+    unless selector.is_a?(Range)
+      selector = selector.to_i
+    end
+                                        scrap = self[(selector)]
+    if scrap.is_a?(Integer)
+      result_scrap << scrap.chr
+    else
+      result_scrap << scrap.data()
+    end
+                                else
+                                        raise TypeError.new("Expected Integer, Float or Range.  You provided #{selector.class}")
+                                end
+                        end
+result = GxG::ByteArray.new(result_scrap)
+                end
+                result
+        end
+
+ +
+ + +
+ Also aliased as: indices, indexes +
+ + + +
+ + +
+ +
+ zip(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 2112
+          def zip(*args,&block)
+                  # Converts any valid argument values to array, then merges elements of self with corresponding elements from each argument
+                  # This generates a sequence of self.size n-element ByteArrays as step buffers, where n is one more that the count of arguments
+                  # * Then flattened output as ByteArray *
+                  # If the size of any argument is less than enumObj.size, @pad_value is supplied
+                  # If a block given, it is invoked for each output array.
+                  # If block returns an array or ByteArray, it replaces the provided buffer and is appended to output.
+                  #
+result = nil
+if args.size > 0
+  result_scrap = ""
+  result_scrap.force_encoding(Encoding::ASCII_8BIT)
+  args.to_enum(:each_index).each do |index|
+    unless args[(index)].is_a?(GxG::ByteArray)
+      args[(index)] = GxG::ByteArray.new(args[(index)])
+    end
+  end
+  self.each_with_index do |byte,index|
+    #
+    buffer = ""
+    buffer.force_encoding(Encoding::ASCII_8BIT)
+    buffer << byte.chr
+    args.to_enum(:each_index).each do |arg_index|
+      if index >= args[(arg_index)].size
+        buffer << @pad_value.chr
+      else
+        buffer << args[(arg_index)].data()[(index)]
+      end
+    end
+    #
+    if block.respond_to?(:call)
+      # I want to differ from stock array.zip behavior:
+      # I want to have it possible to return the processed buffer back -
+      # to a result ByteArray if result of yield = array/ByteArray.
+      raw_new = block.call(GxG::ByteArray.new(buffer))
+      if raw_new
+        buffer = GxG::ByteArray.new(raw_new).data()
+      end
+    end
+    result_scrap << buffer
+  end
+  result = GxG::ByteArray.new(result_scrap)
+else
+  result = self.dclone
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ |(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 981
+          def |(*args)
+                  scrap = GxG::ByteArray::try_convert(args)
+                  result = self.dclone
+if scrap
+  scrap.each do |byte|
+    unless result.include?(byte)
+      result << byte
+    end
+  end
+end
+                  result
+          end
+
+ +
+ + + + +
+ + +
+ +
+
+

Protected Instance Methods

+
+ + +
+ +
+ filter_data(the_data="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 445
+def filter_data(the_data="")
+  #
+  error_message = ("ByteArray.new expects a String, a Integer or Float between 0 and 255, a nil ( counted as padvalue() ), a ByteArray, or an Array of any of those; you provided #{the_data.inspect}")
+  #
+  unless the_data.is_any?(::NilClass,::String,::Numeric, ::Array, ::GxG::ByteArray)
+    raise Exception.new(error_message)
+  end
+  if the_data.is_a?(::String)
+    the_data = the_data.slice_bytes((0..-1))
+  end
+  if the_data.is_a?(::Numeric)
+    the_data = the_data.to_i
+    if (0..255).include?(the_data)
+      the_data = the_data.chr
+      the_data.force_encoding(Encoding::ASCII_8BIT)
+    else
+      raise Exception.new(error_message)
+    end
+  end
+  if the_data.is_a?(::GxG::ByteArray)
+    the_data = the_data.data().clone
+  end
+  if the_data.is_a?(::Array)
+    temp_string = ""
+    temp_string.force_encoding(Encoding::ASCII_8BIT)
+    the_data.flatten.to_enum.each do |element|
+      temp_string << self.filter_data(element)
+    end
+    if temp_string.size > 0
+      the_data = temp_string
+    else
+      the_data = ""
+      the_data.force_encoding(Encoding::ASCII_8BIT)
+    end
+  end
+  unless the_data
+    the_data = @pad_value.to_i.chr
+    the_data.force_encoding(Encoding::ASCII_8BIT)
+  end
+  #
+  the_data
+end
+
+ +
+ + + + +
+ + +
+ +
+ filter_parameters(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 488
+def filter_parameters(*args)
+  the_range = nil
+  if args[0].is_a?(Numeric)
+    args[0] = args[0].to_i
+    if args[0] < 0
+      args[0] = @data.bytesize + args[0]
+    end
+    the_range = ((args[0])..(args[0]))
+    if args[1].is_a?(Numeric)
+      args[1] = args[1].to_i
+      if args[1] < 1
+        raise ArgumentError.new("Second parameter needs to be a Numeric greater than 0, you provided #{args[1].inspect}")
+      else
+        the_range = ((args[0])..(args[0] + (args[1] - 1)))
+      end
+    end
+  else
+    if args[0].is_a?(Range)
+      if args[0].first < 0
+        args[1] = @data.bytesize + args[0].first
+      else
+        args[1] = args[0].first
+      end
+      if args[0].last < 0
+        args[2] = @data.bytesize + args[0].last
+      else
+        args[2] = args[0].last
+      end
+      if args[1] <= args[2]
+        the_range = ((args[1])..(args[2]))
+      else
+        the_range = ((args[2])..(args[1]))
+      end
+    else
+      raise ArgumentError.new("First parameter needs to be a Numeric or a Range, you provided #{args[0].inspect}")
+    end
+  end
+  the_range
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Communications.html b/doc/GxG/Communications.html new file mode 100644 index 0000000..6f1c377 --- /dev/null +++ b/doc/GxG/Communications.html @@ -0,0 +1,253 @@ + + + + + + +module GxG::Communications - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Communications +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
BRIDGE_THREAD_SAFETY + +
+ + +
+
+ + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ bridge(the_uuid) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 7
+def self.bridge(the_uuid)
+    if ::GxG::valid_uuid?(the_uuid)
+        BRIDGE_THREAD_SAFETY.synchronize { ::GxG::BRIDGES[(the_uuid)] }
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ close_bridge(the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 21
+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
+
+ +
+ + + + +
+ + +
+ +
+ open_bridge(process=nil, the_url=nil, options=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 15
+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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Communications/Bridge.html b/doc/GxG/Communications/Bridge.html new file mode 100644 index 0000000..900f4dc --- /dev/null +++ b/doc/GxG/Communications/Bridge.html @@ -0,0 +1,509 @@ + + + + + + +class GxG::Communications::Bridge - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Communications::Bridge +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(process=nil, the_url=nil, options=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 823
+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
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ call_event(operation_envelope=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 869
+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
+
+ +
+ + + + +
+ + +
+ +
+ interface() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 890
+def interface()
+  result = {}
+  @interface.each_pair do |the_event, the_record|
+    result[(the_event)] = the_record[:description]
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ on(the_event, description=nil, &block) + + click to toggle source + +
+ + +
+ +

Command Interface

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 855
+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
+
+ +
+ + + + +
+ + +
+ +
+ reply(the_channel_uuid=nil, reply_body=nil, address=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 947
+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
+
+ +
+ + + + +
+ + +
+ +
+ request(the_channel_uuid=nil, request_body=nil, address=nil, options={}) + + click to toggle source + +
+ + +
+ +

Request/Reply Support

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 908
+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
+
+ +
+ + + + +
+ + +
+ +
+ respond_to_event?(the_event=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 898
+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
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 851
+def uuid()
+    @uuid
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Communications/BridgeAdapter.html b/doc/GxG/Communications/BridgeAdapter.html new file mode 100644 index 0000000..76cb3cc --- /dev/null +++ b/doc/GxG/Communications/BridgeAdapter.html @@ -0,0 +1,654 @@ + + + + + + +class GxG::Communications::BridgeAdapter - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Communications::BridgeAdapter +

+ +
+ +

Adapters

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ abilities() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 46
+def self.abilities()
+    []
+end
+
+ +
+ + + + +
+ + +
+ +
+ limits() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 50
+def self.limits()
+    {}
+end
+
+ +
+ + + + +
+ + +
+ +
+ new(the_process=nil, the_url=nil, options=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 58
+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
+
+ +
+ + + + +
+ + +
+ +
+ protocol() + + click to toggle source + +
+ + +
+ +

WARNING: Only instantiate subclasses

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 42
+def self.protocol()
+    :unspecified
+end
+
+ +
+ + + + +
+ + +
+ +
+ templates() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 54
+def self.templates()
+    {}
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ channels() + + click to toggle source + +
+ + +
+ +

Basic Channel Support

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 129
+def channels()
+    {}
+end
+
+ +
+ + + + +
+ + +
+ +
+ close() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 108
+def close()
+    if self.open?
+        @connector.logout()
+        @active = false
+    end
+    true
+end
+
+ +
+ + + + +
+ + +
+ +
+ entitiies() + + click to toggle source + +
+ + +
+ +

Basic Entity Support

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 121
+def entitiies()
+    []
+end
+
+ +
+ + + + +
+ + +
+ +
+ entity_status(the_address=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 125
+def entity_status(the_address=nil)
+    :online
+end
+
+ +
+ + + + +
+ + +
+ +
+ my_id() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 86
+def my_id()
+    nil
+end
+
+ +
+ + + + +
+ + +
+ +
+ notify(details=nil) + + click to toggle source + +
+ + +
+ +

Information Refresh

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 116
+def notify(details=nil)
+    #
+end
+
+ +
+ + + + +
+ + +
+ +
+ open() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 101
+def open()
+    unless self.open?
+        @active = @connector.login(@url, @options)
+    end
+    @active
+end
+
+ +
+ + + + +
+ + +
+ +
+ open?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 90
+def open?()
+    if @connector
+        if @connector.closed?
+            @active = false
+        else
+            @active = true
+        end
+    end
+    @active
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 82
+def uuid()
+    @uuid
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Communications/BridgeAdapterXMPP.html b/doc/GxG/Communications/BridgeAdapterXMPP.html new file mode 100644 index 0000000..3937edc --- /dev/null +++ b/doc/GxG/Communications/BridgeAdapterXMPP.html @@ -0,0 +1,1688 @@ + + + + + + +class GxG::Communications::BridgeAdapterXMPP - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Communications::BridgeAdapterXMPP +

+ +
+ +

XMPP

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ abilities() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 139
+def self.abilities()
+    [:message, :file_transfer, :request]
+end
+
+ +
+ + + + +
+ + +
+ +
+ limits() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 143
+def self.limits()
+    {:message_size => 1048576, :file_transfer_size => nil, :request_size => 65536}
+end
+
+ +
+ + + + +
+ + +
+ +
+ new(the_process=nil, the_url=nil, options={:sasl => :digest, :digest => true}) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + GxG::Communications::BridgeAdapter::new +
+ + + +
+
# File lib/gxg/gxg_communications.rb, line 213
+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::XmppClient.new({:notify => self})
+    self.open
+    #
+    self
+end
+
+ +
+ + + + +
+ + +
+ +
+ protocol() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 135
+def self.protocol()
+    :xmpp
+end
+
+ +
+ + + + +
+ + +
+ +
+ templates() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 147
+def self.templates()
+    {}
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ accept_file_transfer(the_transfer_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 402
+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
+
+ +
+ + + + +
+ + +
+ +
+ accept_invitation(the_invitation_uuid=nil) + + click to toggle source + +
+ + +
+ +

Invitations

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 362
+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
+
+ +
+ + + + +
+ + +
+ +
+ all_messages(the_channel_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 748
+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
+
+ +
+ + + + +
+ + +
+ +
+ associate(recipient=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 541
+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
+
+ +
+ + + + +
+ + +
+ +
+ cancel_file_transfer(the_transfer_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 435
+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::SERVER_PATHS[:temporary] + "/" + found[:transfer].reference.to_s)
+                ::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s)
+            end
+            result = true
+        end
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ channels() + + click to toggle source + +
+ + +
+ +

Basic Channel Support

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 531
+def channels()
+    result = {}
+    @thread_safety.synchronize {
+        @channels.each_pair do |the_uuid, the_record|
+            result[(the_uuid)] = the_record[:title]
+        end
+    }
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ close_channel(the_channel_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 637
+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
+
+ +
+ + + + +
+ + +
+ +
+ decline_file_transfer(the_transfer_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 417
+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::SERVER_PATHS[:temporary] + "/" + found[:transfer].reference.to_s)
+                ::GxG::VFS.rmdir("/System/Temporary/" + found[:transfer].reference.to_s)
+            end
+            result = true
+        end
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ decline_invitation(the_invitation_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 377
+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
+
+ +
+ + + + +
+ + +
+ +
+ disassociate(recipient=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 552
+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
+
+ +
+ + + + +
+ + +
+ +
+ entities() + + click to toggle source + +
+ + +
+ +

Entities

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 453
+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
+
+ +
+ + + + +
+ + +
+ +
+ entity_status(the_address=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 479
+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
+
+ +
+ + + + +
+ + +
+ +
+ file_transfers() + + click to toggle source + +
+ + +
+ +

File Transfers

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 392
+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
+
+ +
+ + + + +
+ + +
+ +
+ get_message(the_channel_uuid=nil, the_message_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 762
+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
+
+ +
+ + + + +
+ + +
+ +
+ get_messages_by_context(the_channel_uuid=nil, the_context_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 782
+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
+
+ +
+ + + + +
+ + +
+ +
+ get_messages_by_sender(the_channel_uuid=nil, the_sender=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 801
+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
+
+ +
+ + + + +
+ + +
+ +
+ join_channel(channel_jid=nil, password=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 609
+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
+
+ +
+ + + + +
+ + +
+ +
+ my_id() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 235
+def my_id()
+    if @connector
+        @connector.my_jid.to_s
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ next_message(the_channel_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 733
+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
+
+ +
+ + + + +
+ + +
+ +
+ notify(details=nil) + + click to toggle source + +
+ + +
+ +

Information Refresh

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 243
+def notify(details=nil)
+    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::SERVER_PATHS[:temporary] + "/" + reference.to_s)
+                            ::FileUtils.mkpath(GxG::SERVER_PATHS[:temporary] + "/" + reference.to_s)
+                        end
+                        the_transfer.download_directory = (GxG::SERVER_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
+
+ +
+ + + + +
+ + +
+ +
+ open_channel(with_jid=nil, password=nil, configuration={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 563
+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
+
+ +
+ + + + +
+ + +
+ +
+ send_message(the_channel_uuid=nil, message=nil, recipient=nil, use_fallback_key=false) + + click to toggle source + +
+ + +
+ +

Messages

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 656
+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
+
+ +
+ + + + +
+ + +
+ +
+ set_key(recipient=nil, password=nil) + + click to toggle source + +
+ + +
+ +

Keychain

+ + + + +
+
# File lib/gxg/gxg_communications.rb, line 347
+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
+
+ +
+ + + + +
+ + +
+ +
+ status() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 493
+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
+
+ +
+ + + + +
+ + +
+ +
+ status=(the_status=nil, message=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_communications.rb, line 512
+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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Database.html b/doc/GxG/Database.html new file mode 100644 index 0000000..bb5a085 --- /dev/null +++ b/doc/GxG/Database.html @@ -0,0 +1,194 @@ + + + + + + +module GxG::Database - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Database +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ connect(the_url, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 10
+def self.connect(the_url, options={})
+  # TODO: require :credential (access privs)
+  result = nil
+  begin
+    # check for URI ipv6 [format] compatibility
+    db_url = ::URI::parse(the_url.to_s)
+    the_path = db_url.path.to_s
+    if the_path == ""
+      the_path = db_url.hostname.to_s
+    end
+    if ::RUBY_ENGINE == "jruby"
+      case db_url.scheme.to_sym
+      when :sqlite, :sqlite3
+        if ["/memory", "memory"].include?(the_path)
+          result = Sequel.connect("jdbc:sqlite::memory:")
+        else
+          result = Sequel.connect("jdbc:sqlite:" + the_path)
+        end
+      else
+        result = Sequel.connect({:adapter => ("jdbc:" + db_url.scheme.to_s), :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
+      end
+      #
+    else
+      case db_url.scheme.to_sym
+      when :sqlite, :sqlite3
+        if ["/memory", "memory"].include?(the_path)
+          result = Sequel.connect("sqlite::memory:")
+        else
+          result = Sequel.connect("sqlite://" + the_path)
+        end
+      else
+        # auto-forward to mysql2 gem if mysql:// scheme
+        if db_url.scheme.to_s == "mysql"
+          #
+          result = Sequel.connect({:adapter => "mysql2", :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
+          #
+        else
+          #
+          result = Sequel.connect({:adapter => (db_url.scheme.to_s), :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)})
+          #
+        end
+      end
+    end
+    result = ::GxG::Database::Database.new(result, options.merge({:url => (db_url)}))
+  rescue => the_error
+    log_error({:error => the_error, :parameters => {:url => (the_url), :options => (options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Database/Database.html b/doc/GxG/Database/Database.html new file mode 100644 index 0000000..cbc6c54 --- /dev/null +++ b/doc/GxG/Database/Database.html @@ -0,0 +1,8853 @@ + + + + + + +class GxG::Database::Database - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Database::Database +

+ +
+ +

Database class proper

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ element_table_by_index(the_index=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 97
+def self.element_table_by_index(the_index=0)
+  (::GxG::Database::Database::element_tables().keys[(the_index)] || :unspecified)
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_table_for_instance(the_instance=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 101
+def self.element_table_for_instance(the_instance=nil)
+  # Review : rewrite - in use
+  result = :unspecified
+  #
+  table = ::GxG::Database::Database::element_tables()
+  table.keys.each do |the_key| 
+    unless the_key == :unspecified
+      if the_instance.is_any?(table[(the_key)])
+        result = the_key
+        break
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_table_index(the_key=:unspecified) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 93
+def self.element_table_index(the_key=:unspecified)
+  (::GxG::Database::Database::element_tables().keys.index(the_key.to_sym) || 0)
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_tables() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 74
+def self.element_tables()
+  {
+    :unspecified => nil,
+    :element_boolean => [::TrueClass, ::FalseClass, ::NilClass],
+    :element_integer => [::Integer],
+    :element_float => [::Float],
+    :element_bigdecimal => [::BigDecimal],
+    :element_datetime => [::DateTime],
+    :element_text => [::String],
+    :element_binary => [::GxG::ByteArray],
+    :element_array => [::Array],
+    :element_hash => [::Hash]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ new(connection = nil, options={}) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_database.rb, line 5111
+def initialize(connection = nil, options={})
+  # TODO: require :credential (access privs)
+  super()
+  @credential = nil
+  @url = options[:url]
+  @base_permissions = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+  #
+  if connection.is_a?(::Sequel::Database)
+    @connector = connection
+    #
+    @active = false
+    unless open?()
+      raise Exception, "Unable to connect with the database"
+    end
+    #
+    @scheme = @url.scheme.to_s.to_sym
+    case @scheme
+    when :sqlite, :sqlite3
+      @connector.run("PRAGMA page_size=65536")
+      @connector.run("PRAGMA max_page_count=281474976710656")
+      @connector.run("PRAGMA encoding='UTF-8'")
+    when :mysql, :mysql2
+      @connector.run("SET GLOBAL max_allowed_packet=17825792;")
+      @connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
+      @connector.run("SET collation_connection = 'utf8_general_ci';")
+    when :postgres
+      # place holder
+    end
+  else
+    raise Exception, "Invalid db connection"
+  end
+  # characterize basic effective permissions
+  begin
+    formatted = db_formatted?()
+    @base_permissions[:read] = true
+    @base_permissions[:execute] = true
+  rescue Exception => the_error
+    formatted = false
+    raise Exception, "Lack read permissions to the database"
+  end
+  #
+  unless options[:read_only]
+    if formatted
+      begin
+        dbid = @connector[:text_page].insert()
+      rescue Exception => the_error
+        dbid = nil
+      end
+      if dbid.is_a?(::Integer)
+        @connector[:text_page].filter({:dbid => (dbid)}).delete
+        @base_permissions[:write] = true
+        @base_permissions[:create] = true
+        @base_permissions[:destroy] = true
+        @base_permissions[:rename] = true
+        @base_permissions[:move] = true
+      end
+    else
+      begin
+        test_table_name = ::GxG::uuid_generate().gsub("-","")
+        while (@connector.table_exists?(test_table_name.to_sym)) do
+          test_table_name = ::GxG::uuid_generate().gsub("-","")
+        end
+        @connector.create_table(test_table_name.to_sym) do
+          primary_key :dbid
+        end
+        @connector.drop_table(test_table_name.to_sym)
+        @base_permissions[:write] = true
+        @base_permissions[:create] = true
+        @base_permissions[:destroy] = true
+        @base_permissions[:rename] = true
+        @base_permissions[:move] = true
+      rescue Exception => the_error
+        # Read-only Database
+      end
+    end
+  end
+  #
+  @base_permissions.freeze
+  #
+  unless formatted
+    if @base_permissions[:write]
+      db_format
+      unless db_formatted?()
+        @active = false
+        raise Exception, "Unable to format the database for use"
+      end
+    else
+      raise Exception, "Unable to format a read-only database"
+    end
+  end
+  #
+  @authority = nil
+  @db_list = []
+  if options[:authority].is_a?(::GxG::Database::Database)
+    @authority = options[:authority]
+    @authority.db_register(self)
+  end
+  #
+  unless @authority
+    unless self.group_exist?("Administrators")
+      if @base_permissions[:write]
+        if self.user_id_available?("root")
+          self.user_create("root","password")
+          temp_cred = self.user_credential("root","password")
+          temp_group = self.group_create({:title => "Administrators", :default_role => {:title => "Members"}})
+          self.role_add_user(temp_group[:default_role],temp_cred)
+          #
+          dev_group = self.group_create({:title => "Developers", :default_role => {:title => "Members"}})
+          dsn_group = self.group_create({:title => "Designers", :default_role => {:title => "Members"}})
+          #
+          self[:system_credentials] = {:administrator => temp_cred, :administrators => temp_group[:default_role], :developers => dev_group[:default_role], :designers => dsn_group[:default_role]}
+          self[:permission_policy] = [{:event => :extend, :credential => :any, :action => {:extend_permissions => temp_group[:default_role]}}]
+          #
+        end
+        if self.user_id_available?("public")
+          self.user_create("public","password", "00000000-0000-4000-0000-000000000000")
+        end
+      end
+    end
+  end
+  #
+  permission_policy_load
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_field_classes() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 89
+def self.valid_field_classes()
+  [::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray]
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](setting_key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3500
+def [](setting_key=nil)
+  result = nil
+  begin
+    if self.alive?()
+      if self.open?()
+        record = @connector[:settings].filter({:title => setting_key.to_s}).first
+        if record
+          content = ""
+          @connector[:text_page].select(:content).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
+            content << entry[:content]
+          end
+          if content.size > 0
+            result = content.unserialize()
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ []=(setting_key=nil, object=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3527
+def []=(setting_key=nil, object=nil)
+  result = object
+  begin
+    if self.alive?()
+      if self.open?()
+        unless self.db_permissions()[:write]
+          raise Exception, "Unable to write to database"
+        end
+        unless setting_key.is_a?(::Symbol)
+          raise ArgumentError, "You must supply a setting key as a Symbol"
+        end
+        unless setting_key.to_s.size > 0
+          raise ArgumentError, "You must supply a setting key whose length is greater than zero"
+        end
+        record = @connector[:settings].filter({:title => setting_key.to_s}).first
+        unless record
+          new_uuid = ::GxG::uuid_generate().to_s
+          while (@connector[:settings].filter({:uuid => (new_uuid)}).count > 0 or @connector[:formats].filter({:uuid => (new_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (new_uuid)}).count > 0)
+            new_uuid = ::GxG::uuid_generate().to_s
+          end
+          @connector[:settings].insert({:title => setting_key.to_s, :uuid => new_uuid.to_s})
+          record = @connector[:settings].filter({:uuid => (new_uuid)}).first
+        end
+        if record
+          @connector[:settings].filter({:dbid => (record[:dbid])}).update({:version => (record[:version] + 0.0001)})
+          content = object.serialize()
+          old_segments = []
+          @connector[:text_page].select(:dbid).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry|
+            old_segments << entry[:dbid]
+          end
+          if content.size == 0
+            if old_segments.size > 0
+              @connector[:text_page].filter({:parent_uuid => record[:uuid].to_s}).delete
+            end
+          else
+            new_segments = ::GxG::apportioned_ranges(content.size, 4096)
+            if old_segments.size > new_segments.size
+              the_count = old_segments.size
+            else
+              the_count = new_segments.size
+            end
+            manifest = []
+            (0..(the_count - 1)).each do |the_ordinal|
+              if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
+                #overwrite with new segment range
+                manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
+              else
+                if new_segments[(the_ordinal)]
+                  # create new segment at this ordinal
+                  manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
+                else
+                  # delete old segment
+                  manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
+                end
+              end
+            end
+            manifest.each do |action|
+              case action[:operation]
+              when :overwrite
+                @connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
+              when :create
+                @connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
+              when :delete
+                @connector[:text_page].filter({:dbid => (action[:dbid])}).delete
+              end
+            end
+            #
+          end
+        else
+          raise Exception, "Internal Error - could not retrieve setting record: #{setting_key.inspect}"
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:setting => setting_key, :object => object}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ assign_element_permission(table=:unspecified, dbid=0, credential=nil, permissions={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1502
+def assign_element_permission(table=:unspecified, dbid=0, credential=nil, permissions={})
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless dbid.is_a?(::Integer)
+      raise ArgumentError, "dbid must be specified with a Integer"
+    end
+    unless permissions.is_a?(::Hash)
+      raise ArgumentError, "permissions must be specified with a Hash"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    if table.is_a?(::Integer)
+      table = ::GxG::Database::Database::element_table_by_index(table)
+    end
+    # Review : change in permission policy
+    # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+    if [:element_array, :element_hash].include?(table)
+      record = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).first
+      if record
+        # update
+        the_update = {:execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if record[(the_permission)] == 1
+            the_update[(the_permission)] = 1
+          end
+        end
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if permissions[(the_permission)].is_a?(::TrueClass)
+            the_update[(the_permission)] = 1
+          end
+          if permissions[(the_permission)].is_a?(::FalseClass)
+            the_update[(the_permission)] = 0
+          end
+        end
+        the_update[:version] = record[:version] + 0.0001
+        @connector[:permissions].filter({:dbid => (record[:dbid])}).update(the_update)
+      else
+        # create
+        new_record = {:credential => (credential.to_s), :element_table => (table.to_s), :elementid => (dbid), :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if permissions[(the_permission)]
+            new_record[(the_permission)] = 1
+          end
+        end
+        @connector[:permissions].insert(new_record)
+      end
+      result = true
+    else
+      raise ArgumentError, "Invalid table specified"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential), :permissions => (permissions)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ byte_size_by_uuid(the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2514
+def byte_size_by_uuid(the_uuid=nil)
+  result = 0
+  begin
+    if self.alive?
+      if self.open?
+        found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+        if found
+          result += self.element_byte_size(:element_hash,(found[:dbid]))
+        else
+          found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+          if found
+            result += self.element_byte_size(:element_array,(found[:dbid]))
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ clear_all_element_locks() + + click to toggle source + +
+ + +
+ +

Utility methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 5086
+def clear_all_element_locks()
+  # for when boo-boos happen
+  if self.open?()
+    if self.db_permissions()[:write]
+      @connector[:element_locks].filter({}).delete
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 5107
+def clone()
+  initialize_clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ close() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 415
+def close()
+  # place holder for now.
+  if @authority
+    @authority.db_unregister(self)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ connector() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 422
+def connector()
+  @connector
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1767
+def create_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "file_id must be specified with a String"
+    end
+    unless permission.is_a?(::Hash)
+      raise ArgumentError, "permission must be specified with a Hash"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    #
+    record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
+    if record
+      update = true
+      dbid = record.delete(:dbid)
+    else
+      update = false
+      dbid = nil
+      record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
+    end
+    permission.keys.each do |the_permission_key|
+      if permission[(the_permission_key)] == true
+        record[(the_permission_key)] = 1
+      else
+        record[(the_permission_key)] = 0
+      end
+    end
+    if update == true
+      @connector[:file_permissions].filter({:dbid => dbid}).update(record)
+    else
+      @connector[:file_permissions].insert(record)
+    end
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_format(format_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 297
+def db_format(format_options={})
+  # MySql stuff:
+  # ALTER TABLE tbl_name MAX_ROWS=1000000000 AVG_ROW_LENGTH=nnn;
+  # See: https://dev.mysql.com/doc/refman/5.0/en/table-size-limit.html
+  result = true
+  #
+  begin
+    if @connector.is_a?(::Sequel::Database)
+      case @scheme
+      when :sqlite
+        @connector.run("PRAGMA page_size=65536")
+        @connector.run("PRAGMA max_page_count=281474976710656")
+        @connector.run("PRAGMA encoding='UTF-8'")
+      when :mysql, :mysql2
+        @connector.run("SET GLOBAL max_allowed_packet = 17825792;")
+        @connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';")
+        @connector.run("SET collation_connection = 'utf8_general_ci';")
+      when :postgres
+        # place holder
+      end
+      migration = db_migration()
+      migration.keys.each do |the_table|
+        unless @connector.table_exists?(the_table)
+            @connector.create_table(the_table) do
+            primary_key :dbid
+          end
+        end
+        used_bytes = 0
+        #
+        existing_columns = @connector[(the_table)].columns
+        migration[(the_table)].keys.each do |the_column|
+          used_bytes += (migration[(the_table)][(the_column)][:bytes]).to_i
+          if existing_columns.include?(the_column)
+            # TODO: figure out a way to inspect columns for attributes -> detailed verification
+          else
+            # collation for mysql
+            if @scheme == :mysql
+              # Note: might have to specify :collate first, use -> {:collate => ""}.merge(migration[(the_table)][(the_column)][:options])
+              if migration[(the_table)][(the_column)][:type] == :varchar
+                # migration[(the_table)][(the_column)][:options] = {:collate => "utf8_general_ci"}.merge(migration[(the_table)][(the_column)][:options])
+                migration[(the_table)][(the_column)][:options][:collate] = "utf8_general_ci"
+              end
+              if migration[(the_table)][(the_column)][:type] == :blob
+                # migration[(the_table)][(the_column)][:options] = {:collate => "binary"}.merge(migration[(the_table)][(the_column)][:options])
+                # Note: MySQL 5 doesn't like putting collation on blobs! (blows up)
+                # migration[(the_table)][(the_column)][:options][:collate] = "binary"
+              end
+            else
+            end
+            # do the deed
+            @connector.add_column((the_table), (the_column), (migration[(the_table)][(the_column)][:type]), (migration[(the_table)][(the_column)][:options]))
+            #
+          end
+        end
+        #
+        if @scheme == :mysql
+          @connector.run("ALTER TABLE #{the_table.to_s} MAX_ROWS=18446744073709551616 AVG_ROW_LENGTH=#{used_bytes.to_s};")
+        end
+        #
+      end
+    else
+      raise Exception, "Invalid db connection"
+    end
+  rescue Exception => the_error
+    result = false
+    log_error({:error => the_error, :parameters => format_options})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_formatted?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 368
+def db_formatted?()
+  result = true
+  #
+  begin
+    if @connector.is_a?(::Sequel::Database)
+      migration = db_migration()
+      migration.keys.each do |the_table|
+        unless @connector.table_exists?(the_table)
+          result = false
+          break
+        end
+        #columns
+        existing_columns = @connector[(the_table)].columns
+        migration[(the_table)].keys.each do |the_column|
+          unless existing_columns.include?(the_column)
+            result = false
+            break
+          end
+        end
+        #
+        unless (result)
+          break
+        end
+      end
+    else
+      raise Exception, "Invalid db connection"
+    end
+  rescue Exception => the_error
+    result = false
+    log_error({:error => the_error, :parameters => nil})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_list() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 455
+def db_list()
+  result = []
+  @db_list.each do |the_db|
+    result << the_db
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_migration() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 118
+def db_migration()
+  # Review : Consider adding support for a ::Symbol class as well. (ease-of-use implications later)
+  # Internal mapping of the database format
+  # Datatypes - See: http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html
+  # Column Options - See: http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-add_column
+  # See also : http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack
+  {
+    :element_text => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
+      },
+    :text_page => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :content => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
+      },
+    :element_binary => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
+      },
+    :binary_page => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :content => {:type => :blob, :options => {:size => 65536}, :bytes => 65536}
+      },
+    :element_array => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :constraint => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+      },
+    :array_elements => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
+      :element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
+      :element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
+      :time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
+    },
+    :element_hash => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+      },
+    :hash_properties => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :property => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259},
+      :element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256},
+      :element_datetime => {:type => :datetime, :options => {}, :bytes => 16},
+      :time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}
+    },
+    :element_locks => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :type => {:type => :varchar, :options => {:size => 40,:default => "", :null => false}, :bytes => 40},
+      :reservation => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
+    },
+    :trash => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}
+    },
+    :users => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :user_id => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
+      :password_hash => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :roles => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :group_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :seo => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :user_roles => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :user_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :role_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :groups => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :default_role => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :seo => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :permissions => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :file_permissions => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :file_id => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    },
+    :formats => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :type => {:type => :varchar, :options => {:size => 40, :null => false, :default => "structure"}, :bytes => 40},
+      :ufs => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099},
+      :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)},
+      :mime_types => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}
+    },
+    :settings => {
+      :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)},
+      :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40},
+      :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259},
+      :version => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}
+    }
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_permissions() + + click to toggle source + +
+ + +
+ +

Permission Handling methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 435
+def db_permissions
+  @base_permissions.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_register(the_db) + + click to toggle source + +
+ + +
+ +

Authority support

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 439
+def db_register(the_db)
+  if the_db.is_a?(::GxG::Database::Database)
+    unless @db_list.find_index(the_db)
+      @db_list << the_db
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_unregister(the_db) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 447
+def db_unregister(the_db)
+  if the_db.is_a?(::GxG::Database::Database)
+    if @db_list.find_index(the_db)
+      @db_list.delete_at(@db_list.find_index(the_db))
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ destroy_by_uuid(credential=nil, the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2457
+def destroy_by_uuid(credential=nil, the_uuid=nil)
+  # Review : rewrite - in use
+  # element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
+  # .effective_uuid_permission(data["location"].to_s.to_sym,@credential)
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:destroy] == true
+          if ::GxG::valid_uuid?(credential)
+            found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+            if found
+              self.element_destroy(credential, :element_hash,(found[:dbid]))
+              result = true
+            else
+              found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+              if found
+                self.element_destroy(credential, :element_array,(found[:dbid]))
+                result = true
+              end
+            end
+          else
+            raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:credential => (credential), :uuid => (the_uuid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ destroy_vfs_permission(file_id=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1861
+def destroy_vfs_permission(file_id=nil, credential=nil)
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "file_id must be specified with a String"
+    end
+    # ### Review : make more efficient
+    if credential
+      unless ::GxG::valid_uuid?(credential)
+        raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+      end
+      record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
+      if record
+        @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).delete
+      end
+    else
+      record = @connector[:file_permissions].filter({:file_id => file_id.to_s}).first
+      if record
+        @connector[:file_permissions].filter({:file_id => file_id.to_s}).delete
+      end
+    end
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ effective_element_permission(table=:unspecified, dbid=0, credential=nil) + + click to toggle source + +
+ + +
+ +

DB Objects

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 1895
+def effective_element_permission(table=:unspecified, dbid=0, credential=nil)
+  result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless dbid.is_a?(::Integer)
+      raise ArgumentError, "dbid must be specified with a Integer"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    if table.is_a?(::Integer)
+      table = ::GxG::Database::Database::element_table_by_index(table)
+    end
+    # Review : change in permission policy
+    # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+    if [:element_array, :element_hash].include?(table)
+      # is credential a user or role?
+      raw_permissions = []
+      # Include PUBLIC credential
+      credentials = ["00000000-0000-4000-0000-000000000000"]
+      #
+      if self.user_exist?(credential)
+        credentials << credential
+        role_list = self.user_roles(credential)
+        role_list.each do |entry|
+          credentials << entry[:credential]
+        end
+        #
+      else
+        if self.role_exist?(credential)
+          credentials << credential
+        else
+          raise ArgumentError, "Invalid credential.  It is neither a user nor a role UUID"
+        end
+      end
+      credentials.each do |entry|
+        manifest = @connector[:permissions].filter({:credential => (entry.to_s), :element_table => (table.to_s), :elementid => (dbid)})
+        manifest.each do |record|
+          new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+          [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+            if record[(the_permission)] == 1
+              new_permission[(the_permission)] = true
+            end
+          end
+          raw_permissions << new_permission
+        end
+      end
+      permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+      raw_permissions.each do |entry|
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if entry[(the_permission)]
+            permission[(the_permission)] = true
+          end
+        end
+      end
+      db_limits = self.db_permissions()
+      [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+        if db_limits[(the_permission)] and permission[(the_permission)]
+          result[(the_permission)] = true
+        end
+      end
+      #
+    else
+      raise ArgumentError, "Invalid table specified"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ effective_uuid_permission(uuid=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1971
+def  effective_uuid_permission(uuid=nil, credential=nil)
+  result = nil
+  begin
+    if self.alive?
+      if self.open?
+        address = nil
+        record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
+        if record
+          address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
+        else
+          record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
+          if record
+            address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
+          end
+        end
+        if address
+          result = self.effective_element_permission(address[:table], address[:dbid], credential)
+        else
+          # ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
+          already_checked = [(self)]
+          ::GxG::DB[:roles].each_pair do |the_db_role, the_database|
+            unless already_checked.include?(the_database)
+              record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
+              if record
+                address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
+                result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
+                break
+              else
+                record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
+                if record
+                  address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
+                  result = the_database.effective_element_permission(address[:table], address[:dbid], credential)
+                  break
+                end
+              end
+              already_checked << the_database
+            end
+          end
+          #
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ effective_vfs_permission(file_id=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1710
+def effective_vfs_permission(file_id=nil, credential=nil)
+  result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "file_id must be specified with a String"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    # ### VFS Permissions
+    raw_permissions = []
+    credentials = ["00000000-0000-4000-0000-000000000000"]
+    if self.user_exist?(credential)
+      credentials << credential
+      role_list = self.user_roles(credential)
+      role_list.each do |entry|
+        credentials << entry[:credential]
+      end
+      #
+    else
+      if self.role_exist?(credential)
+        credentials << credential
+      else
+        raise ArgumentError, "Invalid credential.  It is neither a user nor a role UUID"
+      end
+    end
+    credentials.each do |entry|
+      manifest = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => (entry.to_s)})
+      manifest.each do |record|
+        new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if record[(the_permission)] == 1
+            new_permission[(the_permission)] = true
+          end
+        end
+        raw_permissions << new_permission
+      end
+    end
+    raw_permissions.each do |entry|
+      [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+        if entry[(the_permission)]
+          result[(the_permission)] = true
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_byte_size(table=:unspecified, dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2490
+def element_byte_size(table=:unspecified, dbid=0)
+  #
+  result = 0
+  begin
+    if self.alive?
+      if self.open?
+        migration = self.db_migration()
+        elements = self.element_manifest(table, dbid, true)
+        elements.each do |entry|
+          table_definition = migration[(entry[:table])]
+          if table_definition
+            table_definition.keys.each do |the_field|
+              result += table_definition[(the_field)][:bytes].to_i
+            end
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_create(the_table=:unspecified, the_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2090
+def element_create(the_table=:unspecified, the_record=nil)
+  # Review : rewrite - in use
+  result = nil
+  begin
+    unless the_table.is_a?(::Symbol)
+      raise ArgumentError, "You MUST provide the table specifier as a Symbol."
+    end
+    unless the_record.is_a?(::Hash)
+      raise ArgumentError, "You MUST provide a record in the form of a Hash."
+    end
+    if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
+      # Review : UTF8 fix
+      begin
+        result = @connector[(the_table)].insert(the_record)
+      rescue Exception
+        if the_table == :text_page
+          the_record[:content] = the_record[:content].encode64
+        end
+        result = @connector[(the_table)].insert(the_record)
+      end
+    else
+      raise ArgumentError, "The table specified is unknown - Check your code."
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (the_table), :record => (the_record)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_destroy(credential=nil,table=:unspecified,dbid=0, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2401
+def element_destroy(credential=nil,table=:unspecified,dbid=0, options={})
+  # Review : rewrite for new arch.
+  # Review : rewrite - in use
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          # if credential has :destroy permission on this element?
+          if ::GxG::valid_uuid?(credential)
+            if [:persisted_array, :persisted_hash].include?(table.to_s.to_sym)
+              go_ahead = self.effective_element_permission(table, dbid, credential)[:destroy]
+            else
+              go_ahead = true
+            end
+            # xxx Quick fix : BETA: clean all this up!
+            if go_ahead
+              # Review : DB overhaul / rewrite - allow delete if object has write lock in place? I think not. (return false)
+              # Note: BETA - bring back read-locks? it was 9x slower with them ... not sure.
+              elements = self.element_manifest(table, dbid, false)
+              elements.each do |entry|
+                #
+                unless self.element_in_trash?(entry[:table], entry[:dbid])
+                  @connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+                end
+                #
+                #if @connector[:element_locks].filter({:type => "read", :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
+                  # put in trash
+                  #unless self.element_in_trash?(entry[:table], entry[:dbid])
+                    #@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+                  #end
+                #else
+                  # delete now
+                  #manifest = self.element_manifest(entry[:table], entry[:dbid], true)
+                  #manifest.each do |item|
+                    #@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
+                    #@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
+                  #end
+                #end
+              end
+              result = true
+            else
+              raise Exception, "You do not have permission to destory this element"
+            end
+          else
+            raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:credential => (credential), :table => (table), :dbid => (dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_exists?(the_table=:unspecified, the_dbid=0) + + click to toggle source + +
+ + +
+ +

Data Handling methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 2024
+def element_exists?(the_table=:unspecified, the_dbid=0)
+  result = false
+  if the_table.is_a?(::Integer)
+    the_table = ::GxG::Database::Database::element_table_by_index(the_table)
+  end
+  if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(the_table)
+    begin
+      record = @connector[(the_table)].select(:dbid).where({:dbid => (the_dbid)}).first
+      if record
+        result = true
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_in_trash?(table=:unspecified, dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3021
+def element_in_trash?(table=:unspecified, dbid=0)
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        if @connector[:trash].filter({:element_table => table.to_s, :elementid => dbid}).count > 0
+          result = true
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_in_use?(db_table=:unspecified, dbid = 0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2211
+def element_in_use?(db_table=:unspecified, dbid = 0)
+  # Review : rewrite - in use for now.
+  # Review : rewrite - todo (alter to reflect the new architecture : perhaps limit to structures only)
+  unless db_table.is_any?(::Integer, ::Symbol)
+    raise ArgumentError, "Table needs to be specified as a Symbol, or a table code Integer"
+  end
+  unless dbid.is_a?(::Integer)
+    raise ArgumentError, "Record ID needs to be specified with an Integer"
+  end
+  result = false
+  begin
+    if db_table == :element_hash || db_table == :element_array
+      # Review : make more efficient in later revs of this.
+      the_record = @connector[(db_table)].filter({:dbid => (dbid)}).select(:uuid).first
+      if the_record
+        if @connector[:hash_properties].filter({:element_hash_uuid => the_record[:uuid]}).count > 0 || @connector[:array_elements].filter({:element_array_uuid => the_record[:uuid]}).count > 0
+          result = true
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table_code => (db_table), :dbid => (dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_manifest(the_table=:unspecified, the_dbid=0, everything=false) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2263
+def element_manifest(the_table=:unspecified, the_dbid=0, everything=false)
+  # Review : rewrite - in use
+  # returns an Array of tables and dbid pairs and sub-item pairs if they exist
+  result = []
+  #
+  begin
+    unless the_dbid > 0
+      raise ArgumentError, "dbid specified must be greater than zero"
+    end
+    case the_table
+    when :element_text
+      the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
+      if the_record
+        result << {:table => :element_text, :dbid => the_record[:dbid]}
+        if everything
+          the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
+          the_pages.each do |entry|
+            result << {:table => :text_page, :dbid => entry[:dbid]}
+          end
+        end
+      end
+    when :element_binary
+      the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first
+      if the_record
+        result << {:table => :element_binary, :dbid => the_record[:dbid]}
+        if everything
+          the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
+          the_pages.each do |entry|
+            result << {:table => :binary_page, :dbid => entry[:dbid]}
+          end
+        end
+      end
+    when :element_array, :element_hash
+      record_db = [{:table => the_table, :dbid => the_dbid}]
+      while record_db.size > 0 do
+        item = record_db.shift
+        case item[:table]
+        when :element_text
+          the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
+          if the_record
+            result << {:table => :element_text, :dbid => the_record[:dbid]}
+            if everything
+              the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
+              the_pages.each do |entry|
+                result << {:table => :text_page, :dbid => entry[:dbid]}
+              end
+            end
+          end
+        when :element_binary
+          the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first
+          if the_record
+            result << {:table => :element_binary, :dbid => the_record[:dbid]}
+            if everything
+              the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal)
+              the_pages.each do |entry|
+                result << {:table => :binary_page, :dbid => entry[:dbid]}
+              end
+            end
+          end
+        when :element_array
+          result << {:table => item[:table], :dbid => item[:dbid]}
+          array_record = @connector[:element_array].select(:uuid).where({:dbid => item[:dbid]}).first
+          if array_record
+            @connector[:array_elements].filter({:parent_uuid => (array_record[:uuid])}).order(:ordinal).each do |entry|
+              if entry[:element_text_uuid].size > 0
+                the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_binary_uuid].size > 0
+                the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_array_uuid].size > 0
+                the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_hash_uuid].size > 0
+                the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              result << {:table => :array_elements, :dbid => entry[:dbid]}
+            end
+          end
+        when :element_hash
+          result << {:table => item[:table], :dbid => item[:dbid]}
+          hash_record = @connector[:element_hash].select(:uuid).where({:dbid => item[:dbid]}).first
+          if hash_record
+            @connector[:hash_properties].filter({:parent_uuid => (hash_record[:uuid])}).order(:ordinal).each do |entry|
+              if entry[:element_text_uuid].size > 0
+                the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_binary_uuid].size > 0
+                the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_array_uuid].size > 0
+                the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              if entry[:element_hash_uuid].size > 0
+                the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first
+                if the_temp_record
+                  record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]}
+                end
+              end
+              result << {:table => :hash_properties, :dbid => entry[:dbid]}
+            end
+          end
+        else
+          the_record = @connector[(item[:table])].select(:dbid).where({:dbid => (item[:dbid])}).first
+          if the_record
+            result << {:table => item[:table], :dbid => item[:dbid]}
+          end
+        end
+      end
+      #
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_parent_address(the_table=:unspecified, the_dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2237
+def element_parent_address(the_table=:unspecified, the_dbid=0)
+  result = nil
+  if self.element_in_use?(the_table,the_dbid)
+    begin
+      link = @connector[:hash_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
+      if link
+        record = @connector[:element_hash].select(:dbid).where({:uuid => link[:parent_uuid]}).first
+        if record
+          result = {:table => :element_hash, :dbid => (record[:dbid])}
+        end
+      else
+        link = @connector[:array_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first
+        if link
+          record = @connector[:element_array].select(:dbid).where({:uuid => link[:parent_uuid]}).first
+          if record
+            result = {:table => :element_array, :dbid => (record[:dbid])}
+          end
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_permissions(table=:unspecified, dbid=0, options={}) + + click to toggle source + +
+ + +
+ +

Raw Permission Handling methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 1345
+def element_permissions(table=:unspecified, dbid=0, options={})
+  result = []
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless dbid.is_a?(::Integer)
+      raise ArgumentError, "dbid must be specified with a Integer"
+    end
+    unless options.is_a?(::Hash)
+      raise ArgumentError, "options must be specified with a Hash"
+    end
+    credential = options[:credential]
+    if credential
+      unless ::GxG::valid_uuid?(credential)
+        raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
+      end
+    end
+    if table.is_a?(::Integer)
+      table = ::GxG::Database::Database::element_table_by_index(table)
+    end
+    # Review : change in permission policy
+    # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+    if [:element_array, :element_hash].include?(table)
+      if credential
+        list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)})
+      else
+        list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)})
+      end
+      list.each do |record|
+        # [:execute, :rename, :move, :destroy, :create, :write, :read]
+        entry = {:credential => (record[:credential].to_sym), :table => (record[:element_table].to_sym), :dbid => (record[:elementid]), :permissions => {}}
+        [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+          if record[(the_permission)] == 1
+            entry[:permissions][(the_permission)] = true
+          else
+            entry[:permissions][(the_permission)] = false
+          end
+        end
+        result << entry
+      end
+    else
+      raise ArgumentError, "Invalid table specified"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_permissions_by_uuid(the_uuid=nil,options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1458
+def element_permissions_by_uuid(the_uuid=nil,options={})
+  result = []
+  begin
+    if self.alive?
+      if self.open?
+        found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+        if found
+          result = self.element_permissions_manifest(:element_hash,(found[:dbid]),options)
+        else
+          found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+          if found
+            result = self.element_permissions_manifest(:element_array,(found[:dbid]),options)
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_permissions_manifest(table=:unspecified, dbid=0, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1431
+def element_permissions_manifest(table=:unspecified, dbid=0, options={})
+  result = []
+  self.element_permissions(table,dbid,options).each do |entry|
+    the_record = {:credential => nil, :permissions => {}, :details => {}}
+    #
+    the_record[:credential] = entry[:credential]
+    the_record[:permissions] = entry[:permissions]
+    if self.role_exist?(entry[:credential])
+      role = self.role_fetch({:uuid => entry[:credential].to_s})
+      group = self.group_fetch({:uuid => role[:group_uuid].to_s})
+      the_record[:details][:role_title] = role[:title]
+      the_record[:details][:group] = group[:uuid].to_s.to_sym
+      the_record[:details][:group_title] = group[:title]
+    else
+      if self.user_exist?(entry[:credential])
+        user = self.user_fetch({:uuid => entry[:credential].to_s})
+        the_record[:details][:user_title] = user[:user_id]
+      else
+        log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
+        the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
+      end
+    end
+    result << the_record
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_recover(table=:unspecified, dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3037
+def element_recover(table=:unspecified, dbid=0)
+  # recover an element from the trash
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          unless [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+            raise ArgumentError, "Invalid table specified"
+          end
+          unless dbid > 0
+            raise ArgumentError, "dbid must be greater than zero"
+          end
+          if self.element_in_trash?(table, dbid)
+            scrub_list = []
+            manifest = self.element_manifest(table, dbid)
+            manifest.each do |entry|
+              if @connector[:trash].filter({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).count > 0
+                record = @connector[:trash].select(:dbid).where({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).first
+                if record
+                  scrub_list << record[:dbid]
+                end
+              end
+            end
+            scrub_list.each do |entry|
+              @connector[:trash].filter({:dbid => entry}).delete
+            end
+            result = true
+          else
+            # it was never in the trash, so mission accomplished
+            result = true
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_retrieve(the_table=:unspecified, the_filter={}, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2042
+def element_retrieve(the_table=:unspecified, the_filter={}, options={})
+  result = []
+  begin
+    if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary,:binary_page, :element_array, :element_hash].include?(the_table)
+      #
+      if the_filter.is_a?(::Hash)
+        if options[:order]
+          raw_result = @connector[(the_table)].filter(the_filter).order(options[:order])
+        else
+          raw_result = @connector[(the_table)].filter(the_filter)
+        end
+        # Review : UTF-8 Encoding Error Fix
+        if options[:single_record] == true
+          result = raw_result.first
+          # utf8 fix
+          if the_table == :text_page
+            if result[:content].is_a?(::String)
+              if result[:content].base64?
+                result[:content] = result[:content].decode64
+              end
+            end
+          end
+          #
+        else
+          # utf8 fix
+          raw_result.each do |the_record|
+            if the_record[:content].is_a?(::String)
+              if the_record[:content].base64?
+                the_record[:content] = the_record[:content].decode64
+              end
+            end
+            result << the_record
+          end
+          # result = raw_result
+        end
+        #
+      else
+        raise ArguementError, "You MUST provide a record selection filter as a Hash. "
+      end
+    else
+      raise ArgumentError, "The table specified is unknown - Check your code."
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_update(the_table=:unspecified, the_filter={}, the_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2119
+def element_update(the_table=:unspecified, the_filter={}, the_record=nil)
+  result = false
+  begin
+    unless the_table.is_a?(::Symbol)
+      raise ArgumentError, "You MUST provide the table specifier as a Symbol."
+    end
+    unless the_filter.is_a?(::Hash)
+      raise ArgumentError, "You MUST provide a selection filter in the form of a Hash."
+    end
+    unless the_record.is_a?(::Hash)
+      raise ArgumentError, "You MUST provide a record in the form of a Hash."
+    end
+    if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table)
+      # Review : shim to eliminate UTF-8 encoding errors
+      begin
+        @connector[(the_table)].filter(the_filter).update(the_record)
+      rescue Exception
+        if the_table == :text_page
+          the_record[:content] = the_record[:content].encode64
+        end
+        @connector[(the_table)].filter(the_filter).update(the_record)
+      end
+      result = true
+    else
+      raise ArgumentError, "The table specified is unknown - Check your code."
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :record => the_record}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ empty_trash() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2986
+def empty_trash()
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          scrub_list = []
+          @connector[:trash].filter({}).each do |entry|
+            the_table = entry[:element_table].to_sym
+            the_dbid = entry[:elementid]
+            if @connector[:element_locks].filter({:type => "write", :element_table => the_table.to_s, :elementid => the_dbid}).count == 0
+              # delete full manifest
+              manifest = self.element_manifest(the_table, the_dbid, true)
+              manifest.each do |item|
+                @connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete
+                @connector[(item[:table])].filter({:dbid => item[:dbid]}).delete
+              end
+              scrub_list << entry[:dbid]
+            end
+          end
+          scrub_list.each do |entry|
+            @connector[:trash].filter({:dbid => entry}).delete
+          end
+          if @connector[:trash].filter({}).count == 0
+            result = true
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ enforce_permission_policy(event={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3679
+def enforce_permission_policy(event={})
+  # Event format: enforce_permission_policy({:action => :extend, :credential => nil, :source => nil, :destination => nil})
+  # Rule format: {:event => :create, :credential => :any, :action => {:extend_permissions => nil}}
+  the_policy = self.permission_policy()
+  result = false
+  begin
+    if self.alive?()
+      if self.open?()
+        actions = []
+        the_policy.each do |rule|
+          if event[:action] == rule[:event]
+            if rule[:credential] == event[:credential] || rule[:credential] == :any
+              actions << rule[:action]
+            end
+          end
+        end
+        actions.each do |action|
+          case action.keys[0]
+          when :extend_permissions
+            if action[:use_permission].is_a?(::Hash)
+              use_permission = action[:use_permission]
+            else
+              use_permission = nil
+            end
+            credential = event[:credential]
+            unless ::GxG::valid_uuid?(credential)
+              raise ArgumentError, "You MUST provide a valid UUID as :credential."
+            end
+            new_credential = action[:extend_permissions]
+            source = event[:source]
+            unless source.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              raise ArgumentError, "You MUST provide a persisted object as :source."
+            end
+            unless source.db_address
+              raise Exception, "You provided a Deactivated object as :source."
+            end
+            destination = event[:destination]
+            unless destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              raise ArgumentError, "You MUST provide a persisted object as :source."
+            end
+            unless destination.db_address
+              raise Exception, "You provided a Deactivated object as :destination."
+            end
+            #
+            permissions = self.element_permissions(source.db_address()[:table],source.db_address()[:dbid])
+            permission_db = []
+            if [:persisted_array, :persisted_hash].include?(destination.db_address()[:table])
+              search_db = [{:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}]
+              permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
+              while search_db.size > 0 do
+                entry = search_db.shift
+                self.element_manifest(entry[:table],entry[:dbid],false).each do |item|
+                  if [:persisted_array, :persisted_hash].include?(item[:table])
+                    search_db << item
+                    permission_db << item
+                  end
+                end
+              end
+              permission_db.each do |the_reference|
+                permissions.each do |record|
+                  self.assign_element_permission(the_reference[:table],the_reference[:dbid], credential, record[:permissions])
+                  if use_permission
+                    self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, use_permission)
+                  else
+                    self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, record[:permissions])
+                  end
+                end
+              end
+              # destination.search do |the_value, the_selector, the_container|
+              #   if [:persisted_array, :persisted_hash].include?(the_value.db_address()[:table])
+              #     permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
+              #   end
+              # end
+              #
+            end
+            # permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}
+            # if destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+            #   destination.search do |the_value, the_selector, the_container|
+            #     permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]}
+            #   end
+            # end
+            # permission_db.each do |item|
+            #   self.element_manifest(item[:table],item[:dbid],false).each do |entry|
+            #     permissions.each do |record|
+            #       self.assign_element_permission(entry[:table],entry[:dbid], credential, record[:permissions])
+            #       if use_permission
+            #         self.assign_element_permission(entry[:table],entry[:dbid], new_credential, use_permission)
+            #       else
+            #         self.assign_element_permission(entry[:table],entry[:dbid], new_credential, record[:permissions])
+            #       end
+            #     end
+            #   end
+            # end
+            #
+          end
+        end
+        result = true
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:event => event}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ extend_element_permissions(source_table=:unspecified, source_dbid=0, dest_table=:unspecified, dest_dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1565
+def extend_element_permissions(source_table=:unspecified, source_dbid=0, dest_table=:unspecified, dest_dbid=0)
+  permissions = self.element_permissions(source_table, source_dbid)
+  permissions.each do |entry|
+    if [:persisted_array, :persisted_hash].include?(dest_table)
+      self.assign_element_permission(dest_table, dest_dbid, entry[:credential], entry[:permissions])
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_create(format_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3244
+def format_create(format_record=nil)
+  result = false
+  begin
+    if self.alive?()
+      if self.open?
+        unless self.db_permissions()[:write]
+          raise Exception, "Unable to write to database"
+        end
+        if format_record.is_a?(::Hash)
+          unless ::GxG::valid_uuid?(format_record[:uuid])
+            raise Exception, "Invalid UUID passed for the format"
+          end
+          unless format_record[:type].is_any?([::String, ::Symbol])
+            raise Exception, "You must pass the format_record[:type] as a String or Symbol"
+          end
+          unless format_record[:ufs].is_any?([::String, ::Symbol])
+            raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
+          end
+          unless format_record[:title].is_a?(::String)
+            raise Exception, "You must pass the format_record[:title] as a String"
+          end
+          unless format_record[:version].is_a?(::Float)
+            raise Exception, "You must pass the format_record[:version] as a Float"
+          end
+          unless format_record[:mime_types].is_any?(::String, ::Array)
+            raise Exception, "You must pass the format_record[:mime_types] as a String"
+          end
+          unless format_record[:content].is_a?(::Hash)
+            raise Exception, "You must pass the format_record[:content] as a Hash"
+          end
+          unless self.persistable?(format_record[:content])
+            raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
+          end
+          if (@connector[:settings].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:element_text].filter({:uuid => format_record[:uuid].to_s}).count == 0)
+            new_record = {:uuid => (format_record[:uuid].to_s)}
+            new_record[:type] = format_record[:type].to_s
+            new_record[:ufs] = format_record[:ufs].to_s
+            new_record[:title] = format_record[:title].to_s[0..256]
+            new_record[:version] = format_record[:version]
+            if format_record[:mime_types].is_a?(::Array)
+              the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
+            else
+              the_mimes = format_record[:mime_types].to_s[0..4096]
+            end
+            new_record[:mime_types] = the_mimes
+            content = format_record[:content].gxg_export.to_json.encode64
+            new_dbid = @connector[:formats].insert(new_record)
+            if content.size > 0
+              ::GxG::apportioned_ranges(content.size, 4096).each_with_index do |portion, ordinal|
+                @connector[:text_page].insert({:parent_uuid => (new_record[:uuid].to_s), :ordinal => (ordinal), :length => (content[(portion)].size), :content => (content[(portion)])})
+              end
+            end
+            if new_dbid
+              result = true
+            end
+          else
+            raise Exception, "That format UUID is already in use, please generate another"
+          end
+          #
+        else
+          raise ArgumentError, "You must supply a valid format record as a Hash"
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_destroy(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3452
+def format_destroy(search_options={})
+  result = false
+  begin
+    if self.alive?()
+      if self.open?
+        unless self.db_permissions()[:write]
+          raise Exception, "Unable to write to database"
+        end
+        format_stub = self.format_list(search_options).first
+        if format_stub
+          @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).delete
+          if @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).count > 0
+            @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).delete
+          end
+          result = true
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_list(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3112
+def format_list(search_options={})
+  result = []
+  begin
+    if self.alive?
+      if self.open?
+        unless search_options.is_a?(::Hash)
+          raise ArgumentError, "You must specify search options as a Hash"
+        end
+        the_filter = {}
+        if search_options[:uuid]
+          the_filter[:uuid] = search_options[:uuid].to_s
+        end
+        if search_options[:type]
+          the_filter[:type] = search_options[:type].to_s
+        end
+        if search_options[:ufs]
+          the_filter[:ufs] = search_options[:ufs].to_s[0..4096]
+        end
+        if search_options[:title]
+          the_filter[:title] = search_options[:title].to_s[0..256]
+        end
+        if search_options[:version].is_a?(::Float)
+          the_filter[:version] = search_options[:version]
+        end
+        mime_type = search_options[:mime_type].to_s
+        if mime_type.size > 0
+          @connector[:formats].filter(the_filter).where(::Sequel.like(:mime_types, ("%" + mime_type + "%"))).order(::Sequel[:version].desc).each do |entry|
+            result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
+          end
+        else
+          @connector[:formats].filter(the_filter).order(::Sequel[:version].desc).each do |entry|
+            result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]}
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_load(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3158
+def format_load(search_options={})
+  result = nil
+  begin
+    if self.alive?()
+      if self.open?
+        format_stub = self.format_list(search_options).first
+        if format_stub
+          format_record = @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).first
+          if format_record
+            content = ""
+            @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).order(:ordinal).each do |entry|
+              content << entry[:content]
+            end
+            format_record.delete(:dbid)
+            format_record[:ufs] = format_stub[:ufs]
+            format_record[:type] = format_stub[:type]
+            if content.size > 0
+              if content.base64?
+                content = content.decode64
+              end
+              if content.json?
+                content = ::JSON::parse(content.to_s, {:symbolize_names => true})
+              end
+              if content[:type]
+                content = ::Hash.gxg_import(content)
+              end
+              format_record[:content] = content
+            else
+              format_record[:content] = nil
+            end
+            #
+            if format_record[:mime_types].size > 0
+              if format_record[:mime_types].include?(",")
+                format_record[:mime_types] = format_record[:mime_types].split(",")
+              else
+                format_record[:mime_types] = [(format_record[:mime_types])]
+              end
+            end
+            #
+            result = format_record
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_template(type=:structure) + + click to toggle source + +
+ + +
+ +

Format handing methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 3078
+def format_template(type=:structure)
+  result = nil
+  begin
+    if self.alive?
+      if self.open?
+        unless type.is_any?(::String, ::Symbol)
+          raise ArgumentError, "You must supply a type as a String or Symbol:  :structure or :binary"
+        end
+        the_uuid = ::GxG::uuid_generate()
+        while (@connector[:formats].filter({:uuid => (the_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (the_uuid)}).count > 0)
+          the_uuid = ::GxG::uuid_generate()
+        end
+        result = {:uuid => (the_uuid.to_sym), :type => (type.to_s), :ufs => "unspecified", :title => "", :version => 0.0, :mime_types => [], :content => nil}
+        case type
+        when :structure
+          result[:content] = {}
+        when :binary
+          result[:content] = {:sample_size => 0, :recognition => "", :import => "", :export => ""}
+        else
+          raise Exception, "Invalid format type specified: #{type.inspect}.  Use :structure or :binary"
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    result = nil
+    log_error({:error => the_error, :parameters => {:type => (type)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format_update(format_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3318
+def format_update(format_record=nil)
+  format_record = format_record.clone
+  result = false
+  begin
+    if self.alive?()
+      if self.open?
+        unless self.db_permissions()[:write]
+          raise Exception, "Unable to write to database"
+        end
+        if format_record.is_a?(::Hash)
+          unless ::GxG::valid_uuid?(format_record[:uuid])
+            raise Exception, "Invalid UUID passed for the format"
+          end
+          record = @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).first
+          if record
+            format_update = {}
+            record.keys.each do |the_key|
+              if [:type, :ufs, :title, :version, :mime_types].include?(the_key)
+                case the_key
+                when :type
+                  unless format_record[:type].is_any?(::String, ::Symbol)
+                    raise Exception, "You must pass the format_record[:type] as a String or Symbol"
+                  end
+                when :ufs
+                  unless format_record[:ufs].is_any?(::String, ::Symbol)
+                    raise Exception, "You must pass the format_record[:ufs] as a String or Symbol"
+                  end
+                when :title
+                  unless format_record[:title].is_a?(::String)
+                    raise Exception, "You must pass the format_record[:title] as a String"
+                  end
+                when :version
+                  unless format_record[:version].is_a?(::Float)
+                    raise Exception, "You must pass the format_record[:version] as a Float"
+                  end
+                when :mime_types
+                  unless format_record[:mime_types].is_any?(::String, ::Array)
+                    raise Exception, "You must pass the format_record[:mime_types] as a String"
+                  end
+                end
+                #
+                if the_key == :mime_types
+                  if format_record[:mime_types].is_a?(::Array)
+                    the_mimes = format_record[:mime_types].join(",").to_s[0..4096]
+                  else
+                    the_mimes = format_record[:mime_types].to_s[0..4096]
+                  end
+                  format_record[:mime_types] = the_mimes
+                end
+                #
+                if format_record[(the_key)] != record[(the_key)]
+                  format_update[(the_key)] = format_record[(the_key)]
+                end
+              end
+            end                  
+            if format_update.keys.size > 0
+              # FIXME: :type and :ufs are being passed as a Symbols, but DB requires them to be a strings:
+              if format_update[:type].is_a?(::Symbol)
+                format_update[:type] = format_update[:type].to_s
+              end
+              if format_update[:ufs].is_a?(::Symbol)
+                format_update[:ufs] = format_update[:ufs].to_s
+              end
+              @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update(format_update)
+            end
+            if format_record[:content].is_a?(::Hash)
+              unless self.persistable?(format_record[:content])
+                raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements"
+              end
+              old_segments = []
+              content = format_record[:content].gxg_export.to_json.encode64
+              @connector[:text_page].select(:dbid).where({:parent_uuid => format_record[:uuid].to_s}).order(:ordinal).each do |entry|
+                old_segments << entry[:dbid]
+              end
+              if content.size == 0
+                if old_segments.size > 0
+                  @connector[:text_page].filter({:parent_uuid => format_record[:uuid].to_s}).delete
+                end
+              else
+                new_segments = ::GxG::apportioned_ranges(content.size, 4096)
+                if old_segments.size > new_segments.size
+                  the_count = old_segments.size
+                else
+                  the_count = new_segments.size
+                end
+                manifest = []
+                (0..(the_count - 1)).each do |the_ordinal|
+                  if new_segments[(the_ordinal)] and old_segments[(the_ordinal)]
+                    #overwrite with new segment range
+                    manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
+                  else
+                    if new_segments[(the_ordinal)]
+                      # create new segment at this ordinal
+                      manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])}
+                    else
+                      # delete old segment
+                      manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])}
+                    end
+                  end
+                end
+                manifest.each do |action|
+                  case action[:operation]
+                  when :overwrite
+                    @connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
+                  when :create
+                    @connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])})
+                  when :delete
+                    @connector[:text_page].filter({:dbid => (action[:dbid])}).delete
+                  end
+                end
+                @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update({:version => (format_record[:version].to_f + 0.00001)})
+                #
+              end
+            end
+            #
+            result = true
+          else
+            result = self.format_create(format_record)
+          end
+        else
+          raise ArgumentError, "You must supply a valid format record as a Hash"
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:format_record => (format_record)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_create(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1177
+def group_create(options={})
+  result = nil
+  if @authority
+    result = @authority.group_create(options)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            unless options.is_a?(::Hash)
+              raise ArgumentError, "Options must be passed as a Hash"
+            end
+            #
+            title = (options[:title] || "Untitled Group")
+            seo = (options[:seo] || title.downcase.gsub(" ", "_"))
+            if options[:default_role].is_a?(::Hash)
+              role_title = (options[:default_role][:title] || (title + " Members"))
+              role_seo = (options[:default_role][:seo] || role_title.downcase.gsub(" ", "_"))
+            else
+              role_title = (title + " Members")
+              role_seo = role_title.downcase.gsub(" ", "_")
+            end
+            #
+            the_uuid = ::GxG::uuid_generate()
+            while (@connector[:groups].filter({:uuid => (the_uuid.to_s)}).count > 0)
+              the_uuid = ::GxG::uuid_generate()
+            end
+            @connector[:groups].insert({:uuid => (the_uuid.to_s), :title => (title.to_s), :seo => (seo.to_s)})
+            role_uuid = self.role_create(the_uuid, role_title, role_seo)
+            if role_uuid
+              @connector[:groups].filter({:uuid => (the_uuid.to_s)}).update({:default_role => (role_uuid.to_s)})
+              result = {:group => the_uuid.to_sym, :default_role => role_uuid.to_sym}
+            else
+              @connector[:groups].filter({:uuid => (the_uuid.to_s)}).delete
+              raise Exception, "Could not create default role for group.  Aborting."
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:options => (options)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_credential(details={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1308
+def group_credential(details={})
+  result = nil
+  if @authority
+    result = @authority.group_credential(details)
+  else
+    begin
+      unless self.alive?
+        raise Exception, "Database inactive"
+      end
+      unless self.open?
+        raise Exception, "Database not available"
+      end
+      unless details.is_any?(::Hash, ::GxG::Database::PersistedHash)
+        raise ArgumentError, "group and role specifiers must be provided within a Hash or PeristedHash"
+      end
+      #
+      if details[:group].is_a?(::String)
+        group_record = self.group_fetch({:title => details[:group]})
+        if group_record
+          role_title_specified = (details[:role] || (group_record[:title].to_s + " Members")).to_s
+          group_roles = self.group_roles(group_record[:uuid].to_sym)
+          group_roles.each do |role_profile|
+            if role_profile[:title] == role_title_specified
+              result = role_profile[:uuid].to_s.to_sym
+              break
+            end
+          end
+        end
+      end
+      #
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:details => (details)}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_delete(group_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1275
+def group_delete(group_uuid=nil)
+  result = false
+  if @authority
+    result = @authority.group_delete(group_uuid)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(group_uuid)
+              record = @connector[:groups].filter({:uuid => (group_uuid.to_s)}).first
+              if record
+                @connector[:groups].filter({:uuid => (group_uuid.to_s)}).update({:default_role => "__role_deleted__"})
+                role_list = @connector[:roles].select(:uuid).where({:group_uuid => (group_uuid.to_s)})
+                role_list.each do |role|
+                  self.role_destroy(role[:uuid].to_sym)
+                end
+                @connector[:groups].filter({:uuid => (group_uuid.to_s)}).delete
+              end
+              result = true
+            else
+              raise ArgumentError, "You must specify a valid group UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:group_uuid => (group_uuid)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_exist?(the_specifier=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1090
+def group_exist?(the_specifier=nil)
+  result = nil
+  if @authority
+    result = @authority.group_exist?(the_specifier)
+  else
+    if self.alive?
+      if self.open?
+        if GxG::valid_uuid?(the_specifier)
+          # uuid search
+          the_filter = {:uuid => the_specifier.to_s}
+        else
+          # title search
+          the_filter = {:title => the_specifier.to_s}
+        end
+        if @connector[:groups].filter(the_filter).count > 0
+          result = true
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_fetch(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1113
+def group_fetch(search_options={})
+  result = nil
+  if @authority
+    result = @authority.group_fetch(search_options)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          record = @connector[:groups].filter(search_options).first
+          if record
+            result = record
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:options => (search_options)}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_manifest() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1134
+def group_manifest()
+  result = {}
+  if @authority
+    result = @authority.group_manifest()
+  else
+    if self.alive?
+      if self.open?
+        # intitialize result tree
+        @connector[:groups].filter({:parent_uuid => ""}).each do |the_group|
+          # dbid, uuid, parent_uuid, default_role, seo, title, version
+          result[(the_group[:uuid].to_s.to_sym)] = {:title => the_group[:title], :seo => the_group[:seo], :version => the_group[:version], :groups => {}, :roles => {}}
+        end
+        # flesh out tree
+        build_list = result.keys
+        while build_list.size > 0 do
+          group_uuid = build_list.shift
+          if group_uuid
+            the_group = nil
+            result.search do |item, selector, container|
+              if selector == group_uuid
+                the_group = item
+                break
+              end
+            end
+            if the_group
+              @connector[:groups].filter({:parent_uuid => group_uuid.to_s}).each do |subgroup|
+                the_group[:groups][(subgroup[:uuid].to_s.to_sym)] = {:title => subgroup[:title], :seo => subgroup[:seo], :version => subgroup[:version], :groups => {}, :roles => {}}
+                build_list << subgroup[:uuid].to_s.to_sym
+              end
+              @connector[:roles].filter({:group_uuid => group_uuid.to_s}).each do |the_role|
+                the_group[:roles][(the_role[:uuid].to_s.to_sym)] = {:title => the_role[:title], :seo => the_role[:seo], :version => the_role[:version]}
+              end
+            end
+            #
+          end
+        end
+        #
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_roles(group_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1076
+def group_roles(group_uuid=nil)
+  result = []
+  if @authority
+      result = @authority.group_roles(group_uuid)
+  else
+      if ::GxG::valid_uuid?(group_uuid)
+          @connector[:roles].filter(:group_uuid => group_uuid).each do |the_entry|
+              result << {:role => (the_entry[:title]), :uuid => (the_entry[:uuid])}
+          end
+      end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ group_update(group_uuid=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1223
+def group_update(group_uuid=nil, options={})
+  result = false
+  if @authority
+    result = @authority.group_update(group_uuid, options)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(group_uuid)
+              unless options.is_a?(::Hash)
+                raise ArgumentError, "Options must be passed as a Hash"
+              end
+              title = options[:title]
+              seo = options[:seo]
+              if title.is_a?(::String) or seo.is_a?(::String) or ::GxG::valid_uuid?(options[:default_role]) or ::GxG::valid_uuid?(options[:parent_uuid])
+                the_update = {}
+                if title
+                  the_update[:title] = title.to_s[0..255]
+                end
+                if seo
+                  the_update[:seo] = seo.to_s[0..255]
+                end
+                if options[:default_role]
+                  the_update[:default_role] = options[:default_role].to_s[0..36]
+                end
+                if options[:parent_uuid]
+                  the_update[:parent_uuid] = options[:parent_uuid].to_s[0..36]
+                end
+                record = @connector[:groups].select(:version).where({:uuid => (group_uuid.to_s)}).first
+                if record
+                  the_update[:version] = (record[:version] + 0.0001)
+                else
+                  log_warn("Could not update version for group #{group_uuid}")
+                end
+                @connector[:groups].filter({:uuid => (group_uuid.to_s)}).update(the_update)
+                # Review : BE SURE to also update the title of the default "<group-title> Members" role.
+                result = true
+              end
+            else
+              raise ArgumentError, "You must specify a valid group UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:group_uuid => (group_uuid), :options => (options)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ import(credential=nil, the_record=nil) + + click to toggle source + +
+ + +
+ +

Import methods (deprecated in favor of sychronize_records)

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 3788
+def import(credential=nil, the_record=nil)
+  #
+  result = nil
+  if self.alive?
+    if self.open?
+      if self.db_permissions()[:write]
+        cleanup_manifest = []
+        begin
+          unless ::GxG::valid_uuid?(credential)
+            raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+          end
+          if the_record.is_a?(::Hash)
+            case the_record[:type]
+            when :element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime
+              new_dbid = @connector[(the_record[:type])].insert({:version => (the_record[:version]), :content => (the_record[:content])})
+              self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+              result = {:table => (the_record[:table]), :dbid => (new_dbid)}
+            when :element_text
+              the_uuid = ::GxG::uuid_generate()
+              while (@connector[:element_text].filter({:uuid => the_uuid}).count > 0)
+                the_uuid = ::GxG::uuid_generate()
+              end
+              new_dbid = @connector[:element_text].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
+              cleanup_manifest << {:table => :element_text, :dbid => (new_dbid)}
+              ::GxG::apportioned_ranges(the_record[:content].size, 4096).each_with_index do |the_range,the_ordinal|  
+                @connector[:text_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content][(the_range)].size), :content => (the_record[:content][(the_range)])})
+              end
+              self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+              result = {:table => :element_text, :dbid => (new_dbid)}
+            when :element_binary
+              the_uuid = ::GxG::uuid_generate()
+              while (@connector[:element_binary].filter({:uuid => the_uuid}).count > 0)
+                the_uuid = ::GxG::uuid_generate()
+              end
+              new_dbid = @connector[:element_binary].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)})
+              cleanup_manifest << {:table => :element_binary, :dbid => (new_dbid)}
+              ::GxG::apportioned_ranges(the_record[:content].size, 65536).each_with_index do |the_range,the_ordinal|  
+                @connector[:binary_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content].data()[(the_range)].size), :content => (the_record[:content].data()[(the_range)])})
+              end
+              self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+              result = {:table => :element_binary, :dbid => (new_dbid)}
+            when :element_array
+              # {:type => :element_array, :uuid => @uuid, :title => @title, :version => @version, :content => []}
+              existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
+              if existing
+                if the_record[:version] > existing[:version]
+                  unless self.element_destroy(credential, :element_array, existing[:dbid])
+                    raise Exception, "Error replacing array element - Aborting"
+                  end
+                  existing = nil
+                else
+                  log_warn("Import record is older than existing one - Skipping")
+                  new_dbid = existing[:dbid]
+                end
+              end
+              unless existing
+                new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version])})
+                cleanup_manifest << {:table => :element_array, :dbid => (new_dbid)}
+                self.assign_element_permission(:element_array, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                the_record[:content].each_with_index do |element, indexer|
+                  new_address = self.import(credential, (element))
+                  if new_address
+                    cleanup_manifest << new_address
+                    @connector[:array_links].insert({:parent_uuid => (the_record[:uuid].to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
+                  else
+                    raise Exception, "Error processing the import record - Aborting"
+                  end
+                end
+              end
+              result = {:table => :element_array, :dbid => (new_dbid)}
+            when :element_hash
+              # {:type => :element_hash, :uuid => @uuid, :title => @title, :version => @version, :format => @format, :content => {}}
+              existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first
+              if existing
+                if the_record[:version] > existing[:version]
+                  unless self.element_destroy(credential, :element_hash, existing[:dbid])
+                    raise Exception, "Error replacing hash element - Aborting"
+                  end
+                  existing = nil
+                else
+                  log_warn("Import record is older than existing one - Skipping")
+                  new_dbid = existing[:dbid]
+                end
+              end
+              unless existing
+                new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version]), :format => (the_record[:format].to_s)})
+                cleanup_manifest << {:table => :element_hash, :dbid => (new_dbid)}
+                self.assign_element_permission(:element_hash, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                the_record[:content].keys.each_with_index do |the_key, indexer|
+                  new_address = self.import(credential, (the_record[:content][(the_key)]))
+                  if new_address
+                    cleanup_manifest << new_address
+                    @connector[:hash_links].insert({:parent_uuid => (the_record[:uuid].to_s), :property => (the_key.to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]})
+                  else
+                    raise Exception, "Error processing the import record - Aborting"
+                  end
+                end
+              end
+              result = {:table => :element_hash, :dbid => (new_dbid)}
+            else
+              raise ArgumentError, "Invalid table specified"
+            end
+          else
+            raise ArgumentError, "You must supply an import record as a Hash"
+          end
+        rescue Exception => the_error
+          # clean up
+          cleanup_manifest.each do |entry|
+            self.element_destroy(credential, entry[:table], entry[:dbid])
+          end
+          log_error({:error => the_error, :parameters => {:the_record => (the_record)}})
+        end
+      else
+        log_warn("Cannot import records into a read-only Database - Aborting")
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ +

Comm objects cannot be cloned - returns nil

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 5102
+def initialize_clone
+  nil
+end
+
+ +
+ + +
+ Also aliased as: initialize_dup, dup +
+ + + +
+ + +
+ +
+ initialize_dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 431
+def inspect()
+  "<Database: #{this().to_uri()}>"
+end
+
+ +
+ + + + +
+ + +
+ +
+ iterative_persist(old_root=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2558
+def iterative_persist(old_root=nil, credential=nil)
+  # New PersistedArray, or PersistedHash interface:
+  # mount element: {:database => <database>, :uuid => <uuid>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+  # create element: {:database => <database>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+  #
+  result = nil
+  begin
+    # db open?
+    # write permissions on db?
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You MUST provide a valid UUID as a credential."
+    end
+    unless old_root.is_any?(::Array, ::Hash)
+      raise ArgumentError, "You MUST provide either an Array or a Hash."
+    end
+    unless self.persistable?(old_root)
+      raise ArgumentError, "The object you are attempting to persist contains a non-persistable item."
+    end
+    if old_root.is_a?(::Array)
+      original_partner = ::GxG::Database::PersistedArray.new({:database => self, :credential => credential})
+      original_partner.get_reservation()
+      self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
+      #
+    end
+    if old_root.is_a?(::Hash)
+      original_partner = ::GxG::Database::PersistedHash.new({:database => self, :credential => credential})
+      original_partner.get_reservation()
+      self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner})
+    end
+    if old_root.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
+      # TODO: support subsuming already persisted objects.
+    end
+    paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}]
+    children_of = Proc.new do |the_parent=nil|
+      list = []
+      paring_data.each do |node|
+        if node[:parent].object_id == the_parent.object_id
+          list << node
+        end
+      end
+      list
+    end
+    #
+    parent_of = Proc.new do |the_parent=nil|
+      output = nil
+      paring_data.each do |entry|
+        if entry[:object].object_id == the_parent.object_id
+          output = entry
+        end
+      end
+      output
+    end
+    find_partner = Proc.new do |the_object|
+      found_partner = nil
+      paring_data.each do |the_record|
+        if the_record.is_a?(::Hash)
+          if the_record[:object].object_id == the_object.object_id
+            found_partner = the_record[:partner]
+            break
+          end
+        end
+      end
+      found_partner
+    end
+    # build paring data:
+    delegate_permission = nil
+    old_root.search do |the_value, the_selector, the_container|
+      if the_value.is_a?(::Array)
+        paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedArray.new({:database => self, :credential => credential})}
+      else
+        if the_value.is_a?(::Hash)
+          paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedHash.new({:database => self, :credential => credential})}
+        else
+          paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value}
+        end
+      end
+    end
+    # Assign objects to structure in order by parent / parent_selector
+    link_db = [(paring_data[0])]
+    while link_db.size > 0
+      entry = link_db.shift
+      if entry.is_a?(::Hash)
+        if entry[:object].is_any?(::Array, ::Hash)
+          # get children and assign
+          children = children_of.call(entry[:object])
+          children.each do |child|
+            entry[:partner][(child[:parent_selector])] = child[:partner]
+            if child[:partner].is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash)
+              link_db << child
+            end
+          end
+        end
+      end
+    end
+    result = original_partner
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:object => old_root, :credential => credential}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ new_structure_from_format(credential=nil, search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3212
+def new_structure_from_format(credential=nil, search_options={})
+  result = nil
+  begin
+    if self.alive?()
+      if self.open?
+        unless ::GxG::valid_uuid?(credential)
+          raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+        end
+        format = self.format_load(search_options)
+        if format
+          # TODO: add support for :binary formats.
+          unless format[:type] == :structure
+            raise Exception, "Only formats of type :structure are supported"
+          end
+          result = self.try_persist(format[:content], credential)
+          if result.is_a?(::GxG::Database::PersistedHash)
+            result.set_format(format[:uuid].to_sym)
+            @connector[:element_hash].filter({:uuid => result.uuid.to_s}).update({:format => format[:uuid].to_s})
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:search_options => (search_options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ open?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 403
+def open?()
+  begin
+    @connector.synchronize do |connection|
+      @active = @connector.valid_connection?(connection)
+    end
+  rescue => the_error
+    @active = false
+    log_error({:error => the_error, :parameters => nil})
+  end
+  @active
+end
+
+ +
+ + + + +
+ + +
+ +
+ permission_policy() + + click to toggle source + +
+ + +
+ +

Permission policy support:

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 3611
+def permission_policy()
+  result = nil
+  begin
+    if self.alive?()
+      if self.open?()
+        if @authority
+          result = @authority.permission_policy()
+        else
+          result = @permission_policy
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ permission_policy_load() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3633
+def permission_policy_load()
+  result = false
+  begin
+    if self.alive?()
+      if self.open?()
+        if @authority
+          result = @authority.permission_policy_load()
+        else
+          @permission_policy = (self[:permission_policy] || [])
+          result = true
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ permission_policy_save() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 3656
+def permission_policy_save()
+  result = false
+  begin
+    if self.alive?()
+      if self.open?()
+        if @authority
+          result = @authority.permission_policy_save()
+        else
+          self[:permission_policy] = (@permission_policy || [])
+          result = true
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ persistable?(the_object) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2536
+def persistable?(the_object)
+  result = true
+  unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    if the_object.is_any?(::Hash, ::Array)
+      the_object.search do |item, selector, container|
+        if result
+          if ::GxG::Database::Database::element_table_for_instance(item) == :unspecified
+            result = false
+            break
+          end
+        end
+        nil
+      end
+    else
+      if ::GxG::Database::Database::element_table_for_instance(the_object) == :unspecified
+        result = false
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ release_element_locks(reservation_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2878
+def release_element_locks(reservation_uuid=nil)
+  result = false
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(reservation_uuid)
+          raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to release locks"
+        end
+        if self.db_permissions()[:write]
+          @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).delete
+        end
+        # on read-only db, no reservation was made so mission accomplished
+        result = true
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_element_permissions(table=:unspecified, dbid=0, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1574
+def remove_element_permissions(table=:unspecified, dbid=0, options={})
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless dbid.is_a?(::Integer)
+      raise ArgumentError, "dbid must be specified with a Integer"
+    end
+    unless options.is_a?(::Hash)
+      raise ArgumentError, "permissions must be specified with a Hash"
+    end
+    credential = options[:credential]
+    if credential
+      unless ::GxG::valid_uuid?(credential)
+        raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+      end
+    end
+    if table.is_a?(::Integer)
+      table = ::GxG::Database::Database::element_table_by_index(table)
+    end
+    # Review : change in permission policy
+    # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+    if [:element_array, :element_hash].include?(table)
+      if credential
+        @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
+      else
+        @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)}).delete
+      end
+      result = true
+    else
+      raise ArgumentError, "Invalid table specified"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ reservation_add_element(reservation_uuid=nil, table=:unspecified, dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2903
+def reservation_add_element(reservation_uuid=nil, table=:unspecified, dbid=0)
+  result = false
+  #
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(reservation_uuid)
+          raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to add an element to the reserve locks"
+        end
+        record = @connector[:element_locks].select(:type, :reservation, :credential).where({:reservation => reservation_uuid.to_s}).first
+        if record
+          type = record[:type].to_sym
+          credential = record[:credential].to_sym
+          manifest = self.element_manifest(table, dbid, false)
+          if type == :write
+            if self.db_permissions()[:write]
+              #
+              manifest.each do |entry|
+                if @connector[:element_locks].filter({:type => (type.to_s), :reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count == 0
+                  @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+                end
+              end
+              result = true
+            end
+          else
+            # read
+            if self.db_permissions()[:write]
+              #
+              manifest.each do |entry|
+                @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+              end
+              result = true
+            end
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ reservation_remove_element(reservation_uuid=nil, table=:unspecified, dbid=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2951
+def reservation_remove_element(reservation_uuid=nil, table=:unspecified, dbid=0)
+  result = false
+  #
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(reservation_uuid)
+          raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to remove element from reserve locks"
+        end
+        record = @connector[:element_locks].select(:reservation).where({:reservation => reservation_uuid.to_s}).first
+        if record
+          manifest = self.element_manifest(table, dbid, false)
+          if self.db_permissions()[:write]
+            #
+            manifest.each do |entry|
+              if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
+                @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).delete
+              end
+            end
+            result = true
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ reserve_element_locks(credential=nil, manifest=[], type=:read) + + click to toggle source + +
+ + +
+ +

Element Write-Lock Handling methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 2816
+def reserve_element_locks(credential=nil, manifest=[], type=:read)
+  result = nil
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(credential)
+          raise ArgumentError, "You must supply a valid credential UUID to reserve locks"
+        end
+        unless manifest.is_a?(::Array)
+          raise ArguementError, "You must supply an Array of tables and record ids to reserve for writing"
+        end
+        unless [:write, :read].include?(type)
+          raise ArgumentError, "You must specify a type of reservation: either :write or :read are supported"
+        end
+        if manifest.size > 0
+          reservation_uuid = ::GxG::uuid_generate()
+          while @connector[:element_locks].filter({:reservation => (reservation_uuid.to_s)}).count > 0
+            reservation_uuid = ::GxG::uuid_generate()
+          end
+          if type == :write
+            if self.db_permissions()[:write]
+              # Write-lock must be unique to the elements
+              manifest.each do |entry|
+                if @connector[:element_locks].filter({:type => (type.to_s), :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0
+                  self.release_element_locks(reservation_uuid)
+                  # raise Exception, "A reservation already exists for element #{entry[:table]}:#{entry[:dbid]}"
+                  # Note - just silently fail and return Nil - no reservation for you.
+                  break
+                else
+                  @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+                end
+              end
+              # if write-reservation was made
+              if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).count > 0
+                result = reservation_uuid.to_sym
+              end
+            end
+          else
+            # Read-lock reservation - can share elements
+            if self.db_permissions()[:write]
+              manifest.each do |entry|
+                @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]})
+                #
+              end
+            end
+            # Note - on a read-only db it will act as though it has a read-reservation since no deletions can be made.
+            result = reservation_uuid.to_sym
+          end
+        end
+        #
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:credential => (credential), :manifest => (manifest), :type => (type)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ retrieve_by_address(the_address={}, credential=nil, delegate=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2687
+def retrieve_by_address(the_address={}, credential=nil, delegate=nil)
+  result = nil
+  begin
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+    end
+    if self.alive?
+      if self.open?
+        address = {}
+        if the_address[:database]
+          unless the_address[:database] == self
+            raise ArgumentError, "Database mismatch.  Call this method on database #{the_address[:database].inspect}"
+          end
+          address[:database] = the_address[:database]
+        else
+          address[:database] = self
+        end
+        unless ((the_address[:table]) and (the_address[:dbid].is_a?(::Integer)))
+          raise ArgumentError, "Invalid database address: #{the_address.inspect}"
+        end
+        if the_address[:table].is_a?(::Integer)
+          address[:table] = ::GxG::Database::Database::element_table_by_index(the_address[:table])
+        else
+          address[:table] = the_address[:table]
+        end
+        address[:dbid] = the_address[:dbid]
+        unless self.element_in_trash?(address[:table], address[:dbid])
+          if [:persisted_array, :persisted_hash].include?(address[:table])
+            the_permission = address[:database].effective_element_permission(address[:table], address[:dbid], credential)
+          else
+            the_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => true}
+          end
+          # Review ????
+          # puts "Effective Permission: #{the_permission.inspect} for #{address[:table].inspect},#{address[:dbid].inspect}"
+          if the_permission[:read] == true
+            case address[:table]
+            when :element_array
+              record = address[:database].connector()[(address[:table].to_s.to_sym)].select(:uuid).where({:dbid => (address[:dbid])}).first
+              if record
+                result = ::GxG::Database::PersistedArray.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
+              else
+                raise Exception, "Unable to retrieve PersistedArray"
+              end
+            when :element_hash
+              record = address[:database].connector()[:element_hash].select(:uuid, :format).where({:dbid => (address[:dbid])}).first
+              if record
+                result = ::GxG::Database::PersistedHash.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)})
+                if result
+                  if record[:format].to_s.size > 0
+                    result.set_format(record[:format].to_sym)
+                  end
+                end
+              else
+                raise Exception, "Unable to retrieve PersistedHash"
+              end
+            else
+              raise Exception, "Invalid table specifier: #{address[:table].inspect}"
+            end
+          else
+            # At this point the idea is that if you don't have permissions to read it, you never see it
+            # raise Exception, "You don't have permission to retrieve this element"
+          end
+          #
+        end
+        #
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:address => (the_address), :credential => (credential), :delegate => (delegate)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ retrieve_by_uuid(uuid=nil, credential=nil, delegate=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2764
+def retrieve_by_uuid(uuid=nil, credential=nil, delegate=nil)
+  result = nil
+  begin
+    if self.alive?
+      if self.open?
+        address = nil
+        record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
+        if record
+          address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])}
+        else
+          record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first
+          if record
+            address = {:database => self, :table => :element_array, :dbid => (record[:dbid])}
+          end
+        end
+        if address
+          result = self.retrieve_by_address(address, credential, delegate)
+        else
+          # ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures.
+          already_checked = [(self)]
+          ::GxG::DB[:roles].each_pair do |the_db_role, the_database|
+            unless already_checked.include?(the_database)
+              record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first
+              if record
+                address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])}
+                result = the_database.retrieve_by_address(address, credential, delegate)
+                break
+              else
+                record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first
+                if record
+                  address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])}
+                  result = the_database.retrieve_by_address(address, credential, delegate)
+                  break
+                end
+              end
+              already_checked << the_database
+            end
+          end
+          #
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Database not available (defunct)"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential), :delegate => (delegate)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ revoke_element_permissions(table=:unspecified, dbid=0, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1398
+def revoke_element_permissions(table=:unspecified, dbid=0, credential=nil)
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless dbid.is_a?(::Integer)
+      raise ArgumentError, "dbid must be specified with a Integer"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
+    end
+    if table.is_a?(::Integer)
+      table = ::GxG::Database::Database::element_table_by_index(table)
+    end
+    # Review : change in permission policy
+    # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table)
+    if [:element_array, :element_hash].include?(table)
+      @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete
+      result = true
+    else
+      raise ArgumentError, "Invalid table specified"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ revoke_permissions_by_uuid(the_uuid=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1480
+def revoke_permissions_by_uuid(the_uuid=nil, credential=nil)
+  result = []
+  begin
+    if self.alive?
+      if self.open?
+        found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+        if found
+          result = self.revoke_element_permissions(:element_hash,(found[:dbid]),credential)
+        else
+          found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first
+          if found
+            result = self.revoke_element_permissions(:element_array,(found[:dbid]),credential)
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_add_user(role_uuid=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 930
+def role_add_user(role_uuid=nil, credential=nil)
+  result = false
+  if @authority
+    result = @authority.role_add_user(role_uuid, credential)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(role_uuid)
+              if ::GxG::valid_uuid?(credential)
+                if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count == 0
+                  @connector[:user_roles].insert({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)})
+                end
+                result = true
+              else
+                raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
+              end
+            else
+              raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_create(group_uuid=nil, title="", seo="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 808
+def role_create(group_uuid=nil, title="", seo="")
+  result = nil
+  if @authority
+    result = @authority.role_create(title,seo)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(group_uuid)
+              #
+              if title.to_s.size <= 256
+                #
+                if title.to_s.size == 0
+                  title = "Member"
+                end
+                unless seo.to_s.size <= 256
+                  raise ArgumentError, "Oversized SEO attempted (256 Character Limit): #{seo}"
+                end
+                if seo.to_s.size == 0
+                  seo = title.downcase.gsub(" ","_")[0..255]
+                end
+                the_uuid = ::GxG::uuid_generate()
+                while (@connector[:roles].filter({:uuid => (the_uuid.to_s)}).count > 0)
+                  the_uuid = ::GxG::uuid_generate()
+                end
+                @connector[:roles].insert({:uuid => (the_uuid.to_s), :group_uuid => (group_uuid.to_s), :title => (title.to_s), :seo => (seo.to_s)})
+                result = the_uuid.to_sym
+              else
+                raise ArgumentError, "Oversized title attempted (256 Character Limit): #{title}"
+              end
+            else
+              raise ArgumentError, "You must specify a valid group UUID to link to (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:group_uuid => (group_uuid), :title => (title), :seo => (seo)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_destroy(role_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 888
+def role_destroy(role_uuid=nil)
+  result = false
+  if @authority
+    result = @authority.role_destroy(role_uuid)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(role_uuid)
+              # cannot destroy if still linked as group's default role
+              record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
+              if record
+                group_record = @connector[:groups].filter({:uuid => (record[:group_uuid].to_s)}).first
+                if group_record
+                  if role_uuid.to_s != group_record[:default_role].to_s
+                    the_list = self.db_list()
+                    the_list << (self)
+                    the_list.each do |the_db|
+                      the_db.connector()[:permissions].filter({:credential => (role_uuid.to_s)}).delete
+                      the_db.connector()[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).delete
+                    end
+                    @connector[:roles].filter({:uuid => (role_uuid.to_s)}).delete
+                    result = true
+                  else
+                    log_warn("You cannot destroy a group's default role")
+                  end
+                end
+              end
+            else
+              raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_exist?(credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 792
+def role_exist?(credential=nil)
+  result = nil
+  if @authority
+    result = @authority.role_exist?(credential)
+  else
+    if self.alive?
+      if self.open?
+        if @connector[:roles].filter({:uuid => (credential.to_s)}).count > 0
+          result = true
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_fetch(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1062
+def role_fetch(search_options={})
+  result = nil
+  if @authority
+    result = @authority.role_fetch(search_options)
+  else
+    if self.alive?
+      if self.open?
+        result = @connector[:roles].filter(search_options).first
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_group(role_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1040
+def role_group(role_uuid=nil)
+  result = nil
+  if @authority
+    result = @authority.role_group(role_uuid)
+  else
+    if self.alive?
+      if self.open?
+        if GxG::valid_uuid?(role_uuid)
+          record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
+          if record
+            group = self.group_fetch({:uuid => record[:group_uuid].to_s})
+            if group
+              result = group
+            end
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_member?(role_uuid=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 992
+def role_member?(role_uuid=nil, credential=nil)
+  result = false
+  if @authority
+    result = @authority.role_member?(role_uuid, credential)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if ::GxG::valid_uuid?(role_uuid)
+            if ::GxG::valid_uuid?(credential)
+              if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
+                result = true
+              end
+            else
+              raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
+            end
+          else
+            raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_members(role_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1020
+def role_members(role_uuid=nil)
+  result = []
+  if @authority
+    result = @authority.role_members(role_uuid)
+  else
+    if self.alive?
+      if self.open?
+        if GxG::valid_uuid?(role_uuid)
+          @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).each do |the_record|
+            unless result.include?(the_record[:user_uuid].to_s.to_sym)
+              result << the_record[:user_uuid].to_s.to_sym
+            end
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_remove_user(role_uuid=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 961
+def role_remove_user(role_uuid=nil, credential=nil)
+  result = false
+  if @authority
+    result = @authority.role_remove_user(role_uuid, credential)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(role_uuid)
+              if ::GxG::valid_uuid?(credential)
+                if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0
+                  @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).delete
+                end
+                result = true
+              else
+                raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)"
+              end
+            else
+              raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ role_update(role_uuid=nil, data={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 852
+def role_update(role_uuid=nil, data={})
+  # data: :title, :seo
+  result = false
+  if @authority
+    result = @authority.role_update(role_uuid,data)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            unless data.is_a?(::Hash)
+              raise Exception, "Update data must be passed in a Hash"
+            end
+            if ::GxG::valid_uuid?(role_uuid)
+              the_title = (data[:title] || "Member")
+              the_seo = (data[:seo] || the_title.downcase.gsub(" ", "_"))
+              record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first
+              if record
+                @connector[:roles].filter({:uuid => (role_uuid.to_s)}).update({:title => (the_title), :seo => (the_seo), :version => (record[:version] + 0.0001)})
+                result = true
+              else
+                raise Exception, "Attempted to update a non-existent role: #{role_uuid}"
+              end
+            else
+              raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :data => (data)}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ search_database(credential=nil, criteria={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 4468
+def search_database(credential=nil, criteria={})
+  # Usage: search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]})
+  #
+  # criteria[:properties] :
+  # For all
+  # {:property_name => {:equals => value}}
+  # For :element_integer, :element_float, :element_bigdecimal
+  # {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
+  # For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
+  # {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
+  # For Text, Binary
+  # {:property_name => {:include => value}}
+  #
+  # criteria[:element] :
+  # similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
+  #
+  result = []
+  criterium = []
+  criteria_error = false
+  unless criteria.is_a?(::Hash)
+    criteria_error = true
+  end
+  unless criteria_error
+    common_criteria = criteria.clone
+    properties = (common_criteria.delete(:properties) || [])
+    unless properties.is_a?(::Array)
+      criteria_error = true
+    end
+    elements = (common_criteria.delete(:elements) || [])
+    unless elements.is_a?(::Array)
+      criteria_error = true
+    end
+  end
+  #
+  unless criteria_error
+    if properties.size > 0 && elements.size > 0
+      criteria_error = true
+      log_warn("You MUST select EITHER :properites OR :elements, not both.")
+    end
+  end
+  #
+  unless criteria_error
+    # Prepare criterium
+    if common_criteria[:select].is_a?(::Array)
+      unless common_criteria[:select].include?(:uuid)
+        common_criteria[:select] << :uuid
+      end
+    else
+      common_criteria[:select] = [:uuid]
+    end
+    #
+    if common_criteria[:ufs]
+      criterium << {:ufs => common_criteria[:ufs]}
+    end
+    if common_criteria[:format]
+      criterium << {:format => common_criteria[:format]}
+    end
+    #
+    if properties.size > 0
+      properties.each do |the_property_search|
+        criterium << ((common_criteria.clone).merge({:property => the_property_search}))
+      end
+    end
+    if elements.size > 0
+      elements.each do |the_element_search|
+        criterium << ((common_criteria.clone).merge({:element => the_element_search}))
+      end
+    end
+  end
+  #
+  result_sets = []
+  # if properties.size == 0 && elements.size == 0
+  #   result_sets << self.uuid_list(credential, criteria)
+  # else
+    criterium.each do |the_criteria|
+      result_sets << self.uuid_list(credential, the_criteria)
+    end
+  # end
+  scores = {}
+  result_sets.each do |the_result|
+    the_result.each do |the_answer|
+      if scores.keys.include?(the_answer[:uuid])
+        scores[(the_answer[:uuid])] = (scores[(the_answer[:uuid])] + 1)
+      else
+        scores[(the_answer[:uuid])] = 1
+      end
+    end
+  end
+  scores.keys.each do |the_uuid|
+    if scores[(the_uuid)] == result_sets.size
+      result_sets[0].each do |the_record|
+        if the_record[:uuid] == the_uuid
+          result << the_record
+          break
+        end
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ serialize() + + click to toggle source + +
+ + +
+ +

### Various methods Comm objects cannot be serialized - return encoded nil

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 5097
+def serialize()
+  "marshal:BAgw"
+end
+
+ +
+ + + + +
+ + +
+ +
+ setting_keys() + + click to toggle source + +
+ + +
+ +

Settings methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 3480
+def setting_keys()
+  result = []
+  begin
+    if self.alive?()
+      if self.open?()
+        @connector[:settings].all.each do |the_entry|
+          result << the_entry[:title].to_sym
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:setting_key => setting_key}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ structure_attached?(the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2201
+def structure_attached?(the_uuid=nil)
+  # Review : rewrite - in use
+  result = false
+  record = self.structural_link(the_uuid)
+  if record
+    result = true
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ structure_detach(the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2151
+def structure_detach(the_uuid=nil)
+  if ::GxG::valid_uuid?(the_uuid)
+    record = self.structural_link(the_uuid)
+    if record
+      record[:database].connector()[(record[:table])].filter({:dbid => record[:dbid]}).update({:element => "element_boolean", :element_boolean => -1, :element_hash_uuid => "", :element_array_uuid => ""})
+      true
+    else
+      false
+    end
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ sync_export(credential=nil, uuid_list=[], options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 5029
+def sync_export(credential=nil, uuid_list=[], options={})
+  result = {:formats => {}, :records => []}
+  if uuid_list.is_any?(::Array, ::GxG::Database::PersistedArray)
+    uuid_list.each do |the_reference|
+      the_uuid = the_reference
+      if ::GxG::valid_uuid?(the_uuid)
+        the_object = self.retrieve_by_uuid(the_uuid, credential)
+        if the_object
+          object_record = the_object.export()
+          object_record.search do |item,selector,container|
+            if selector == :format || selector == :constraint
+              if item.to_s.size > 0
+                format_uuid = item
+                unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash)
+                  format_sample = self.format_load({:uuid => format_uuid.to_s.to_sym})
+                  format_sample[:content] = format_sample[:content].gxg_export()
+                  result[:formats][(format_uuid.to_s.to_sym)] = format_sample
+                end
+              end
+            end
+          end
+          result[:records] << object_record
+          #
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 5059
+def sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={})
+  result = false
+  #
+  if the_records.is_a?(::Hash)
+    if the_records[:formats].is_a?(::Hash)
+      op_frame = {:operation => :merge_format, :data => []}
+      the_records[:formats].each_pair do |the_uuid, the_format_record|
+        # gxg_import format content prior to sync update op
+        the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content])
+        op_frame[:data] << the_format_record
+      end
+      if op_frame[:data].size > 0
+        self.synchronize_records([(op_frame)],credential)
+      end
+    end
+    #
+    if the_records[:records].is_a?(::Array)
+      if the_records[:records].size > 0
+        self.synchronize_records([{:operation => :merge, :data => the_records[:records]}],credential)
+        result = true
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ synchronize_records(the_records=[], credential=nil, options={}) + + click to toggle source + +
+ + +
+ +

Synchronize records: (and supports)

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 4571
+def synchronize_records(the_records=[], credential=nil, options={})
+  # Review : rewrite for new architecture.
+  result = {:status => :completed}
+  #
+  total_records_processed = 0
+  total_formats_processed = 0
+  # List of format uuids needed from the OTHER database to complete the sync.
+  formats_needed = []
+  # List of format records we are sharing.
+  formats_provided = []
+  # records on THIS database that are higher version than submitted version.
+  updates_provided = []
+  # In case something goes horribly wrong:
+  clean_up_list = []
+  #
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(credential)
+          raise ArgumentError, "You MUST provide a valid UUID as credential."
+        end
+        #
+        if the_records.is_a?(::String)
+          if the_records.base64?
+            the_records = the_records.decode64
+          end
+          if the_records.json?
+            the_records = ::JSON.parse(the_records,{:symbolize_names => true})
+          end
+        end
+        if the_records.is_a?(::Hash)
+          the_records = [(the_records)]
+        end
+        unless the_records.is_a?(::Array)
+          raise ArgumentError, "You MUST provide: Base64, JSON, Hash, or Array of operation Hashes."
+        end
+        # Process each operation in the_records, data is inline under :data key.
+        the_records.each do |the_operation|
+          if the_operation.is_a?(::Hash)
+            if [:merge, :delete, :fetch, :merge_format, :delete_format, :formats_provided, :formats_needed].include?(the_operation[:operation])
+              #
+              case the_operation[:operation]
+              when :merge
+                unless self.db_permissions()[:write]
+                  raise Exception, "Attempted to alter a read-only database."
+                end
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if entry[:format]
+                    unless self.format_list({:uuid => (entry[:format])}).size > 0
+                      raise Exception, "Attempted to merge with unknown :format #{entry[:format].inspect}. Synchronize formats first, then records."
+                    end
+                  end
+                  # fetch existing:
+                  # if existing - compare versions - if greater provide, if lesser update, else add to db.
+                  queue = [(entry)]
+                  while queue.size > 0 do
+                    record = queue.shift
+                    if ::GxG::valid_uuid?(record[:uuid])
+                      formatting = {:format => nil, :constraint => nil}
+                      existing = self.retrieve_by_uuid(record[:uuid], credential)
+                      unless existing
+                        # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
+                        unless @connector[(record[:type].to_s.downcase.to_sym)].filter({:uuid => record[:uuid].to_s}).count > 0
+                          # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
+                          dbid = @connector[(record[:type].to_s.downcase.to_sym)].insert({:uuid => record[:uuid].to_s, :title => "Merged Persisted Object"})
+                          self.assign_element_permission(record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                          #
+                        end
+                        existing = self.retrieve_by_uuid(record[:uuid], credential)
+                      end
+                      if existing
+                        # ### Compare versions
+                        if existing.version > record[:version]
+                          action = :provide
+                        else
+                          if existing.version < record[:version]
+                            action = :update
+                          else
+                            action = :ignore
+                          end
+                        end
+                        # ### Take Action
+                        case action
+                        when :provide
+                          updates_provided << {:operation => :merge, :data => [(existing.export())]}
+                          existing.deactivate
+                          total_records_processed += 1
+                        when :update
+                          # ### Prelude
+                          if record[:format].to_s.size > 0
+                            formatting[:format] = record[:format].to_s
+                          end
+                          if record[:constraint].to_s.size > 0
+                            formatting[:constraint] = record[:constraint].to_s
+                          end
+                          clean_up_list << existing
+                          existing.get_reservation()
+                          unless existing.write_permission?() && existing.write_reserved?()
+                            raise Exception, "Failed to secure write permission and write reservation for this credential."
+                          end
+                          existing.title = (record[:title])
+                          # ### Update Elements
+                          if record[:content].is_a?(::Hash)
+                            # Review : What to do / how to handle formatted existing Hashes??
+                            record[:content].each_pair do |property, element_record|
+                              if element_record[:uuid]
+                                # ### Element is Hash or Array
+                                element = self.retrieve_by_uuid(element_record[:uuid], credential)
+                                unless element
+                                  # ### Create blank element record and retrieve
+                                  # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
+                                  unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0
+                                    # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
+                                    dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"})
+                                    self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                                    #
+                                  end
+                                  element = self.retrieve_by_uuid(element_record[:uuid], credential)
+                                end
+                                if element
+                                  # ### Ensure Element Linking
+                                  # Review : Warning - absence of a :write permission can bugger this up.
+                                  clean_up_list << element
+                                  element.get_reservation()
+                                  unless element.write_permission?() && element.write_reserved?()
+                                    raise Exception, "Failed to secure write permission and write reservation for this credential."
+                                  end
+                                  existing[(property)] = element
+                                  # element.deactivate ??
+                                end
+                                # Note: return to processing queue to set its elements
+                                queue << element_record
+                              else
+                                # ### Base Element
+                                unless existing.version(property) > element_record[:version]
+                                  the_content = nil
+                                  case element_record[:type].to_s.downcase.to_sym
+                                  when :element_boolean, :element_integer, :element_float
+                                    the_content = element_record[:content]
+                                  when :element_bigdecimal
+                                    the_content = ::BigDecimal.new(element_record[:content].to_s)
+                                  when :element_datetime
+                                    the_content = ::DateTime::parse(element_record[:content].to_s)
+                                  when :element_text
+                                    the_content = element_record[:content].to_s
+                                  when :element_binary
+                                    the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64)
+                                  end
+                                  existing[(property)] = the_content
+                                end
+                              end
+                            end
+                          else
+                            if record[:content].is_a?(::Array)
+                              record[:content].each_with_index do |element_record, indexer|
+                                if element_record[:uuid]
+                                  # ### Element is Hash or Array
+                                  element = self.retrieve_by_uuid(element_record[:uuid], credential)
+                                  unless element
+                                    # ### Create blank element record and retrieve
+                                    # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later.
+                                    unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0
+                                      # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation:
+                                      dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"})
+                                      self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                                      #
+                                    end
+                                    element = self.retrieve_by_uuid(element_record[:uuid], credential)
+                                  end
+                                  if element
+                                    # ### Ensure Element Linking
+                                    # Review : Warning - absence of a :write permission can bugger this up.
+                                    clean_up_list << element
+                                    element.get_reservation()
+                                    unless element.write_permission?() && element.write_reserved?()
+                                      raise Exception, "Failed to secure write permission and write reservation for this credential."
+                                    end
+                                    existing[(indexer)] = element
+                                    # element.deactivate ??
+                                  end
+                                  # Note: return to processing queue to set its elements
+                                  queue << element_record
+                                else
+                                  # ### Base Element
+                                  unless existing.version(indexer) > element_record[:version]
+                                    the_content = nil
+                                    case element_record[:type].to_s.downcase.to_sym
+                                    when :element_boolean, :element_integer, :element_float
+                                      the_content = element_record[:content]
+                                    when :element_bigdecimal
+                                      the_content = ::BigDecimal.new(element_record[:content].to_s)
+                                    when :element_datetime
+                                      the_content = ::DateTime::parse(element_record[:content].to_s)
+                                    when :element_text
+                                      the_content = element_record[:content].to_s
+                                    when :element_binary
+                                      the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64)
+                                    end
+                                    existing[(indexer)] = the_content
+                                  end
+                                end
+                                #
+                              end
+                            end
+                          end
+                          # ### Set Fromatting / Constraints
+                          if existing.is_a?(::GxG::Database::PersistedHash)
+                            if formatting[:format].to_s.size > 0
+                              existing.format = (formatting[:format])
+                            end
+                          end
+                          if existing.is_a?(::GxG::Database::PersistedArray)
+                            if formatting[:constraint].to_s.size > 0
+                              existing.constraint = (formatting[:constraint])
+                            end
+                          end
+                          # ### Conclude Existing
+                          existing.version = (record[:version])
+                          existing.save
+                          existing.deactivate
+                          total_records_processed += 1
+                        end
+                      end
+                    end
+                  end
+                  #
+                end
+              when :delete
+                unless self.db_permissions()[:write]
+                  raise Exception, "Attempted to alter a read-only database."
+                end
+                #
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
+                    if existing
+                      the_address = existing.db_address()
+                      existing.deactivate
+                      if self.element_destroy(credential,the_address[:table],the_address[:dbid])
+                        total_records_processed += 1
+                      else
+                        log_warn("Record #{entry[:uuid].inspect} was not deleted.")
+                      end
+                    else
+                      log_warn("Record #{entry[:uuid].inspect} was not found.")
+                    end
+                  else
+                    log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
+                  end
+                end
+                #
+              when :fetch
+                # wrap export record in a :merge operation and add to updates_provided.
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential)
+                    if existing
+                      updates_provided << {:operation => :merge, :data => [(existing.export())]}
+                      existing.deactivate
+                      total_records_processed += 1
+                    else
+                      log_warn("Record #{entry[:uuid].inspect} was not found.")
+                    end
+                  else
+                    log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}")
+                  end
+                end
+                #
+              when :merge_format
+                unless self.db_permissions()[:write]
+                  raise Exception, "Attempted to alter a read-only database."
+                end
+                # add format record if missing - check for existing, and provide if ours is newer.
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    existing = self.format_load({:uuid => entry[:uuid].to_sym})
+                    if existing.is_a?(::Hash)
+                      if existing[:version] < entry[:version]
+                        if self.format_update(entry)
+                          total_formats_processed += 1
+                        else
+                          log_warn("Could not update Format #{entry[:uuid].inspect}")
+                        end
+                      end
+                      if existing[:version] > entry[:version]
+                        unless formats_provided.include?(existing)
+                          formats_provided << existing
+                          total_formats_processed += 1
+                        end
+                      end
+                    else
+                      if self.format_create(entry)
+                        total_formats_processed += 1
+                      else
+                        log_warn("Could not create Format #{entry[:uuid].inspect}")
+                      end
+                    end
+                  else
+                    log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
+                  end
+                end
+              when :delete_format
+                unless self.db_permissions()[:write]
+                  raise Exception, "Attempted to alter a read-only database."
+                end
+                # If the format is in use, log_warn that you're ignoring the directive.
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    if @connector[:element_hash].filter({:format => entry[:uuid].to_s}).count == 0 && @connector[:element_array].filter({:constraint => entry[:uuid].to_s}).count == 0
+                      # Not in use, safe to delete
+                      if self.format_destroy({:uuid => entry[:uuid].to_sym})
+                        total_formats_processed += 1
+                      end
+                    else
+                      log_warn("Format #{entry[:uuid].inspect} in use (ignorning).")
+                    end
+                  else
+                    log_warn("Invalid format specifier on :delete directive (ignorning): #{entry[:uuid].inspect}")
+                  end
+                end
+              when :formats_provided
+                unless self.db_permissions()[:write]
+                  raise Exception, "Attempted to alter a read-only database."
+                end
+                # compare uuids against db-installed formats and add to formats_needed if missing.
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    if self.format_list({:uuid => (entry[:uuid])}).size == 0
+                      record = {:uuid => (entry[:uuid].to_sym)}
+                      unless formats_needed.include?(record)
+                        formats_needed << record
+                        total_formats_processed += 1
+                      end
+                    end
+                  else
+                    log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
+                  end
+                end
+              when :formats_needed
+                # Gather up existing format records to share.
+                manifest = []
+                if the_operation[:data].is_a?(::Array)
+                  manifest = the_operation[:data]
+                else
+                  if the_operation[:data].is_a?(::Hash)
+                    manifest = [(the_operation[:data])]
+                  else
+                    log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}")
+                  end
+                end
+                manifest.each do |entry|
+                  if ::GxG::valid_uuid?(entry[:uuid])
+                    existing = self.format_load({:uuid => entry[:uuid].to_sym})
+                    if existing.is_a?(::Hash)
+                      unless formats_provided.include?(existing)
+                        formats_provided << existing
+                        total_formats_processed += 1
+                      end
+                    end
+                  else
+                    log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}")
+                  end
+                end
+                #
+              end
+            else
+              log_warn("Invalid operation (ignoring): #{the_operation[:operation].inspect}")
+            end
+          else
+            raise Exception, "Invalid formatted operation passed (MUST be a Hash): #{the_operation.inspect}"
+          end
+        end
+        #
+      else
+        raise Exception, "Database not available."
+      end
+    else
+      raise Exception, "Attempted to access a defunct database."
+    end
+  rescue Exception => the_error
+    clean_up_list.each do |the_object|
+      if the_object.alive?
+        the_object.release_reservation()
+        the_object.deactivate()
+      end
+    end
+    result[:status] = :failed
+    #log_error({:error => the_error, :parameters => {:records => (the_records), :options => (options)}})
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result[:formats_needed] = formats_needed
+  result[:formats_provided] = formats_provided
+  result[:record_updates] = updates_provided
+  result[:records_processed] = total_records_processed
+  result[:formats_processed] = total_formats_processed
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_uri() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 427
+def to_uri()
+  ::URI.parse(@url.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+ try_persist(the_object=nil, credential=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 2659
+def try_persist(the_object=nil, credential=nil, options={})
+  result = nil
+  if the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    result = the_object
+  else
+    begin
+      unless ::GxG::valid_uuid?(credential)
+        raise ArgumentError, "You must supply a credential (36 character limit) as a String or Symbol"
+      end
+      unless options.is_a?(::Hash)
+        raise ArgumentError, "You must supply persisting options as a Hash"
+      end
+      table = ::GxG::Database::Database::element_table_for_instance(the_object)
+      if table == :unspecified
+        raise ArgumentError, "You provided an object that cannot be persisted."
+      end
+      if [:element_hash, :element_array].include?(table)
+        result = self.iterative_persist(the_object, credential)
+      else
+        result = the_object
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:object => the_object, :credential => credential, :options => options}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ update_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1814
+def update_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false})
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "file_id must be specified with a String"
+    end
+    unless permission.is_a?(::Hash)
+      raise ArgumentError, "permission must be specified with a Hash"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    #
+    record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first
+    if record
+      update = true
+      dbid = record.delete(:dbid)
+    else
+      update = false
+      dbid = nil
+      record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0}
+    end
+    permission.keys.each do |the_permission_key|
+      if permission[(the_permission_key)] == true
+        record[(the_permission_key)] = 1
+      else
+        record[(the_permission_key)] = 0
+      end
+    end
+    if update == true
+      @connector[:file_permissions].filter({:dbid => dbid}).update(record)
+    else
+      @connector[:file_permissions].insert(record)
+    end
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_create(the_user_id="", the_password="", use_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 581
+def user_create(the_user_id="", the_password="", use_credential=nil)
+  result = false
+  if @authority
+    result = @authority.user_create(the_user_id, the_password)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if the_user_id.to_s.size <= 256
+              if the_password.to_s.size > 0
+                if self.user_id_available?(the_user_id)
+                  if ::GxG::valid_uuid?(use_credential)
+                    the_uuid = use_credential.to_s
+                    if (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
+                      raise Exception, "That credential UUID is already in use"
+                    end
+                  else
+                    the_uuid = ::GxG::uuid_generate()
+                    while (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0)
+                      the_uuid = ::GxG::uuid_generate()
+                    end
+                  end
+                  salt = ::SecureRandom.base64(24)
+                  pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password, salt, 1000, 24)
+                  the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
+                  @connector[:users].insert({:uuid => (the_uuid), :user_id => (the_user_id.to_s), :password_hash => (the_pass_hash)})
+                  result = true
+                else
+                  raise Exception, "UserID not available: #{the_user_id}"
+                end
+              else
+                raise ArgumentError, "Empty password not supported"
+              end
+            else
+              raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_credential(the_user_id="", the_password="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 543
+def user_credential(the_user_id="", the_password="")
+  result = nil
+  if @authority
+    result = @authority.user_credential(the_user_id, the_password)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if the_user_id.to_s.size <= 256
+            if the_password.to_s.size > 0
+              record = @connector[:users].select(:uuid, :user_id, :password_hash).where({:user_id => (the_user_id.to_s)}).first
+              if record
+                salt = record[:password_hash].split("\t")[0].to_s
+                pbkdf2 = ::Base64.decode64(record[:password_hash].split("\t")[1].to_s)
+                tester = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password.to_s, salt, 1000, pbkdf2.length)
+                if pbkdf2 == tester
+                  result = record[:uuid].to_sym
+                else
+                  log_warn("Password attempt invalid for #{the_user_id}")
+                end
+              else
+                raise Exception, "UserID not found: #{the_user_id}"
+              end
+            else
+              raise Exception, "Empty password not supported"
+            end
+          else
+            raise Exception, "Oversized UserID attempted: #{the_user_id}"
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_destroy(credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 707
+def user_destroy(credential=nil)
+  result = false
+  if @authority
+    result = @authority.user_destroy(credential)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(credential)
+              if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
+                the_list = self.db_list()
+                the_list << (self)
+                the_list.each do |the_db|
+                  the_db.connector()[:permissions].filter({:credential => (credential.to_s)}).delete
+                  the_db.connector()[:user_roles].filter({:user_uuid => (credential.to_s)}).delete
+                end
+                @connector[:users].filter({:uuid => (credential.to_s)}).delete
+                result = true
+              end
+            else
+              raise ArgumentError, "Invalid credential argument: #{credential}"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:credential => credential}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_exist?(the_user_id) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 513
+def user_exist?(the_user_id)
+  result = false
+  if @authority
+    result = @authority.user_exist?(the_user_id)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if ::GxG::valid_uuid?(the_user_id)
+            if @connector[:users].filter({:uuid => (the_user_id.to_s)}).count > 0
+              result = true
+            end
+          else
+            if the_user_id.to_s.size <= 256
+              if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count > 0
+                result = true
+              end
+            else
+              raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
+            end
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_fetch(search_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 740
+def user_fetch(search_options={})
+  result = nil
+  if @authority
+    result = @authority.user_fetch(search_options)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          result = @connector[:users].filter(search_options).first
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:credential => (credential)}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_id_available?(the_user_id) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 489
+def user_id_available?(the_user_id)
+  result = false
+  if @authority
+    result = @authority.user_id_available?(the_user_id)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if the_user_id.to_s.size <= 256
+            if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count == 0
+              result = true
+            end
+          else
+            raise ArgumentError, "Oversized UserID attempted: #{the_user_id}"
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:user_id => the_user_id}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_manifest(include_password=false) + + click to toggle source + +
+ + +
+ +

User, Role, and Group methods

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 464
+def user_manifest(include_password=false)
+  result = []
+  if @authority
+    result = @authority.user_manifest(include_password)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if include_password == true
+            dataset = @connector[:users].select(:uuid, :user_id, :password_hash, :version).all
+          else
+            dataset = @connector[:users].select(:uuid, :user_id, :version).all
+          end
+          dataset.each do |entry|
+            result << entry
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_roles(credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 758
+def user_roles(credential=nil)
+  result = []
+  if @authority
+    result = @authority.user_roles(credential)
+  else
+    if self.alive?
+      if self.open?
+        begin
+          if ::GxG::valid_uuid?(credential)
+            memberships = @connector[:user_roles].select(:role_uuid).where({:user_uuid => (credential.to_s)})
+            pull_list = []
+            memberships.each do |record|
+              unless pull_list.find_index(record[:role_uuid].to_sym)
+                pull_list << record[:role_uuid].to_sym
+              end
+            end
+            pull_list.each do |role_uuid|
+              record = @connector[:roles].filter({:uuid => (role_uuid.to_s)})
+              if record
+                result << {:credential => (role_uuid), :title => (record[:title]), :seo => (record[:seo])}
+              end
+            end
+          else
+            raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
+          end
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:credential => (credential)}})
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_set_password(credential=nil, old_password="", new_password="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 662
+def user_set_password(credential=nil, old_password="", new_password="")
+  result = false
+  if @authority
+    result = @authority.user_set_password(credential, old_password, new_password)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(credential)
+              if new_password.to_s.size > 0
+                if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0
+                  record = @connector[:users].filter({:uuid => (credential.to_s)}).first
+                  if record
+                    if self.user_credential(record[:user_id], old_password).to_s == credential.to_s
+                      salt = ::SecureRandom.base64(24)
+                      pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(new_password, salt, 1000, 24)
+                      the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t")
+                      @connector[:users].filter({:uuid => (credential.to_s)}).update({:password_hash => (the_pass_hash), :version => (record[:version] + 0.0001)})
+                      result = true
+                    else
+                      log_warn("Invalid password change attempted for #{credential}")
+                    end
+                  else
+                    raise Exception, "Could not retrieve user record: #{credential}"
+                  end
+                else
+                  raise Exception, "No user with that credential exists"
+                end
+              else
+                raise ArgumentError, "Empty password not supported"
+              end
+            else
+              raise ArgumentError, "Invalid credential argument: #{credential}"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:credential => credential}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ user_update(credential=nil, user_id=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 628
+def user_update(credential=nil, user_id=nil)
+  result = false
+  if @authority
+    result = @authority.user_update(credential, user_id)
+  else
+    if self.alive?
+      if self.open?
+        if self.db_permissions()[:write]
+          begin
+            if ::GxG::valid_uuid?(credential)
+              if self.user_id_available?(user_id.to_s)
+                record = @connector[:users].filter({:uuid => (credential.to_s)}).first
+                if record
+                  @connector[:users].filter({:uuid => (credential.to_s)}).update({:user_id => (user_id), :version => (record[:version] + 0.0001)})
+                  result = true
+                else
+                  raise Exception, "User record not found"
+                end
+              else
+                log_warn("UserID is not available: #{user_id.to_s}")
+              end
+            else
+              raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)"
+            end
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:credential => credential, :user_id => user_id}})
+          end
+        end
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid_list(credential=nil, criteria={}) + + click to toggle source + +
+ + +
+ +

Search function method

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 3908
+def uuid_list(credential=nil, criteria={})
+  # will return an array of all structures the credential has (at least) read access to
+  result = []
+  begin
+    if self.alive?
+      if self.open?
+        unless ::GxG::valid_uuid?(credential)
+          raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)"
+        end
+        unless criteria.is_a?(::Hash)
+          raise ArgumentError, "You must supply a search criteria as a Hash"
+        end
+        hash_criteria = {}
+        array_criteria = {}
+        # Limit, Offset, and Page selection
+        if criteria[:limit].is_a?(::Integer)
+          limit = criteria[:limit]
+        else
+          limit = nil
+        end
+        if criteria[:offset].is_a?(::Integer)
+          offset = criteria[:offset]
+        else
+          offset = 0
+        end
+        if criteria[:page].is_a?(::Integer)
+          page = criteria[:page]
+          unless limit
+            limit = 100
+          end
+          offset = ((page * limit) - 1)
+        else
+          page = nil
+        end
+        # UUID, Title, and Format searches
+        if ::GxG::valid_uuid?(criteria[:uuid])
+          hash_criteria[:uuid] = criteria[:uuid].to_s
+          array_criteria[:uuid] = criteria[:uuid].to_s
+        end
+        if criteria[:title].is_a?(::String)
+          hash_criteria[:title] = criteria[:title].to_s[0..256]
+          array_criteria[:title] = criteria[:title].to_s[0..256]
+        end
+        if criteria[:format].is_any?([::String, ::Symbol])
+          if ::GxG::valid_uuid?(criteria[:format])
+            format_list = self.format_list({:uuid => criteria[:format].to_sym})
+            unless format_list.size > 0
+              raise ArgumentError, "Format not found: #{criteria[:format].inspect}"
+            end
+            hash_criteria[:format] = criteria[:format].to_s
+          end
+        end
+        if criteria[:ufs].is_any?(::String, ::Symbol)
+          if criteria[:ufs_version].is_a?(::Float)
+            format_list = self.format_list({:ufs => criteria[:ufs].to_sym, :version => criteria[:ufs_version]})
+          else
+            format_list = self.format_list({:ufs => criteria[:ufs].to_sym})
+          end
+          unless format_list.size > 0
+            if criteria[:ufs_version].is_a?(::Float)
+              raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}, Version: #{criteria[:ufs_version].inspect}"
+            else
+              raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}"
+            end
+          end
+          hash_criteria[:format] = format_list[0][:uuid].to_s
+        end
+        # Output column selection: (very very basic at first)
+        selectables = [:uuid, :title, :version]
+        selected = []
+        if criteria[:select].is_any?(::Symbol, ::Array)
+          if criteria[:select].is_a?(::Array)
+            criteria[:select].each do |the_column_symbol|
+              if selectables.include?(the_column_symbol)
+                selected << the_column_symbol
+              end
+            end
+          else
+            if selectables.include?(criteria[:select])
+              selected << criteria[:select]
+            end
+          end
+        end
+        if selected.size == 0
+          selected << :uuid
+        end
+        #
+        reduce_to_datetime = Proc.new do |the_value|
+          the_result = nil
+          db_content = nil
+          if the_value.is_a?(::String)
+            if the_value.include?("between")
+              the_times = the_value.split("between")[1].split("and")
+              the_times[0] = ::Chronic.parse(the_times[0])
+              the_times[1] = ::Chronic.parse(the_times[1])
+              if the_times[0].is_a?(::Time) && the_times[1].is_a?(::Time)
+                db_content = ((the_times[0].to_datetime)..(the_times[1].to_datetime))
+              else
+                raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
+              end
+            else
+              if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
+                db_content = ::DateTime::parse(the_value)
+              else
+                the_times = ::Chronic.parse(the_value)
+                if the_times.is_a?(::Time)
+                  db_content = the_times.to_datetime
+                else
+                  raise Exception, "Unable to understand your time specification text: #{the_value.inspect}"
+                end
+              end
+            end
+          end
+          if the_value.is_a?(::Range)
+            the_times = [(the_value.first), (the_value.last)]
+            if the_times[0].is_a?(::Time)
+              the_times[0] = the_times[0].to_datetime
+            end
+            if the_times[0].is_a?(::Date)
+              the_times[0] = ::DateTime::parse(the_times[0].to_s)
+            end
+            if the_times[1].is_a?(::Time)
+              the_times[1] = the_times[1].to_datetime
+            end
+            if the_times[1].is_a?(::Date)
+              the_times[1] = ::DateTime::parse(the_times[1].to_s)
+            end
+            the_times = ((the_times[0])..(the_times[1]))
+            if the_times.first.is_a?(::DateTime) && the_value.last.is_a?(::DateTime)
+              db_content = the_times
+            else
+              raise Exception, "Invalid Range of dates, use DateTime objects in the Range: #{the_value.inspect}"
+            end
+          end
+          if the_value.is_any?(::Time, ::Date)
+            db_content = ::DateTime::parse(the_value.to_s)
+          end
+          if the_value.is_a?(::DateTime)
+            db_content = the_value
+          end
+          if db_content
+            result = db_content
+          else
+            raise Exception, "Unable to reduce to a DateTime with this input: #{the_value.inspect}"
+          end
+          the_result
+        end
+        #
+        db_content_value = Proc.new do |the_value|
+          the_result = nil
+          the_table = ::GxG::Database::Database::element_table_for_instance(the_value)
+          if the_table == :unspecified
+            raise Exception, "Invalid value passed as persistable: #{the_value.inspect}"
+          else
+            db_content = nil
+            case the_table
+            when :element_boolean
+              if the_value.is_a?(::TrueClass)
+                db_content = 1
+              else
+                if the_value.is_a?(::FalseClass)
+                  db_content = 0
+                else
+                  db_content = -1
+                end
+              end
+            when :element_binary
+              db_content = ""
+              the_value.each do |the_byte|
+                db_content << the_byte.to_s(base=16)
+              end
+            when :element_text
+              if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime?
+                db_content = reduce_to_datetime.call(the_value)
+              else
+                db_content = the_value
+              end
+            else
+              db_content = the_value
+            end
+            if db_content
+              the_result = db_content
+            else
+              raise Exception, "Could not cast the value to something useful: #{the_value.inspect}"
+            end
+          end
+          the_result
+        end
+        #
+        find_property_records = Proc.new do |a_selector, a_container|
+          a_result = []
+          a_container.each do |item|
+            if item[(a_selector)]
+              a_result << item
+            end
+          end
+          a_result
+        end
+        find_element_records = Proc.new do |a_selector, a_container|
+          a_result = []
+          a_container.each do |item|
+            if item[(a_selector)]
+              a_result << item
+            end
+          end
+          a_result
+        end
+        found_list = []
+        # criteria[:property] :
+        # For all
+        # {:property_name => {:equals => value}}
+        # For :element_integer, :element_float, :element_bigdecimal
+        # {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}}
+        # For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it.
+        # {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => <range-value>}
+        # For Text, Binary
+        # {:property_name => {:include => value}}
+        #
+        # criteria[:element] :
+        # similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed.
+        #
+        if criteria[:property] || criteria[:element]
+          # Expansion
+          # Text Search: @connector[:text_page].where(::Sequel.like(:content,'%#{search_text}%'))
+          # Binary Search: @connector[:binary_page].where(::Sequel.like(:content,'%#{byte_sequence}%'))
+          # self.element_parent_address(the_table=:unspecified, the_dbid=0)
+          if criteria[:property].is_a?(::Hash)
+            # Only ONE property key can be compared (TODO: expand to multiple keys)
+            property_matches = []
+            last_key = nil
+            #
+            criteria[:property].keys.each do |property_key|
+              # compare each key's value to qualify the parent object:
+              # {:html => {:include => "\n"}}
+              comparison_op = criteria[:property][(property_key)].keys[0].to_sym
+              if criteria[:property][(property_key)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
+                comparison_value = reduce_to_datetime.call(criteria[:property][(property_key)][(comparison_op)])
+                table_test = ::GxG::Database::Database::element_table_for_instance(comparison_value)
+              else
+                table_test = ::GxG::Database::Database::element_table_for_instance(criteria[:property][(property_key)][(comparison_op)])
+                comparison_value = db_content_value.call(criteria[:property][(property_key)][(comparison_op)])
+              end
+              #
+              if [:element_hash, :element_array].include?(table_test)
+                # Only properties with base elements can be compared - not structures (TODO: expand this)
+                log_warn("Attempted to compare a structure as a property value (not supported, ignoring).")
+                next
+              end
+              # Review : rewrite - adapt to new arch.
+              match_uuid_list = []
+              # comparison_value
+              @connector[:hash_properties].filter({:property => property_key.to_s}).each do |entry|
+                element_value = nil
+                case entry[:element]
+                when "element_boolean"
+                  case entry[:element_boolean]
+                  when -1
+                    element_value = nil
+                  when 0
+                    element_value = false
+                  when 1
+                    element_value = nil
+                  end
+                when "element_integer"
+                  element_value = entry[:element_integer]
+                when "element_float"
+                  element_value = entry[:element_float]
+                when "element_bigdecimal"
+                  element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
+                when "element_datetime"
+                  element_value = entry[:element_datetime]
+                when "element_text"
+                  if entry[:element_text_uuid].to_s.size > 0
+                    # Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
+                    element_value = ""
+                    @connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
+                      element_value << text_page[:content]
+                    end
+                  else
+                    element_value = entry[:element_text]
+                  end
+                when "element_binary"
+                  element_value = ::GxG::ByteArray.new
+                  @connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
+                    element_value << binary_page[:content]
+                  end
+                end
+                #
+                begin
+                  case comparison_op
+                  when :equals, :at
+                    if element_value == comparison_value
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :greater_than, :after
+                    if element_value > comparison_value
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :greater_or_equal, :at_or_after
+                    if element_value >= comparison_value
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :less_than, :before
+                    if element_value < comparison_value
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :less_or_equal, :at_or_before
+                    if element_value <= comparison_value
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :between
+                    if comparison_value.include?(element_value)
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :include
+                    if element_value.to_s.include?(comparison_value.to_s)
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  when :match
+                    if element_value.to_s.match(comparison_value)
+                      match_uuid_list << entry[:parent_uuid].to_sym
+                    end
+                  end
+                rescue Exception => cast_error
+                  # Probably a type casting error (comparison of dissimilar types)
+                  next
+                end
+              end
+              #
+              match_uuid_list.each do |the_matched_uuid|
+                # Add to main found list
+                parent_record = @connector[:element_hash].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
+                if parent_record
+                  new_record = {}
+                  selected.each do |the_selected_key|
+                    if parent_record.keys.include?(the_selected_key)
+                      new_record[(the_selected_key)] = parent_record[(the_selected_key)]
+                      if ::GxG::valid_uuid?(new_record[(the_selected_key)])
+                        new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
+                      end
+                    end
+                  end
+                  unless found_list.include?(new_record)
+                    found_list << new_record
+                  end
+                end
+              end
+              #
+              if last_key != property_key
+                last_key = property_key
+              end
+              #
+            end
+            #
+          else
+            if criteria[:element].is_a?(::Array)
+              criteria[:element].each_with_index do |the_element, element_index|
+                comparison_op = criteria[:element][(element_index)].keys[0].to_sym
+                if criteria[:element][(element_index)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op)
+                  comparison_value = reduce_to_datetime.call(criteria[:element][(element_index)][(comparison_op)])
+                  element_table = ::GxG::Database::Database::element_table_for_instance(comparison_value)
+                else
+                  element_table = ::GxG::Database::Database::element_table_for_instance(criteria[:element][(element_index)][(comparison_op)])
+                  comparison_value = db_content_value.call(criteria[:element][(element_index)][(comparison_op)])
+                end
+                # Structural comparison not supported.
+                if [:element_hash, :element_array].include?(element_table)
+                  log_warn("Attempted to compare a structure as an element (not supported, ignoring).")
+                  next
+                end
+                match_uuid_list = []
+                # comparison_value
+                @connector[:array_elements].filter({:element => element_table.to_s}).each do |entry|
+                  element_value = nil
+                  case entry[:element]
+                  when "element_boolean"
+                    case entry[:element_boolean]
+                    when -1
+                      element_value = nil
+                    when 0
+                      element_value = false
+                    when 1
+                      element_value = nil
+                    end
+                  when "element_integer"
+                    element_value = entry[:element_integer]
+                  when "element_float"
+                    element_value = entry[:element_float]
+                  when "element_bigdecimal"
+                    element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s)
+                  when "element_datetime"
+                    element_value = entry[:element_datetime]
+                  when "element_text"
+                    if entry[:element_text_uuid].to_s.size > 0
+                      # Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now.
+                      element_value = ""
+                      @connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
+                        element_value << text_page[:content]
+                      end
+                    else
+                      element_value = entry[:element_text]
+                    end
+                  when "element_binary"
+                    element_value = ::GxG::ByteArray.new
+                    @connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
+                      element_value << binary_page[:content]
+                    end
+                  end
+                  #
+                  begin
+                    case comparison_op
+                    when :equals, :at
+                      if element_value == comparison_value
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :greater_than, :after
+                      if element_value > comparison_value
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :greater_or_equal, :at_or_after
+                      if element_value >= comparison_value
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :less_than, :before
+                      if element_value < comparison_value
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :less_or_equal, :at_or_before
+                      if element_value <= comparison_value
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :between
+                      if comparison_value.include?(element_value)
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :include
+                      if element_value.to_s.include?(comparison_value.to_s)
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    when :match
+                      if element_value.to_s.match(comparison_value)
+                        match_uuid_list << entry[:parent_uuid].to_sym
+                      end
+                    end
+                  rescue Exception => cast_error
+                    # Probably a type casting error (comparison of dissimilar types)
+                    next
+                  end
+                  # end - comparison loop
+                end
+                #
+                match_uuid_list.each do |the_matched_uuid|
+                  # Add to main found list
+                  parent_record = @connector[:element_array].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first
+                  if parent_record
+                    new_record = {}
+                    selected.each do |the_selected_key|
+                      if parent_record.keys.include?(the_selected_key)
+                        new_record[(the_selected_key)] = parent_record[(the_selected_key)]
+                        if ::GxG::valid_uuid?(new_record[(the_selected_key)])
+                          new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym
+                        end
+                      end
+                    end
+                    unless found_list.include?(new_record)
+                      found_list << new_record
+                    end
+                  end
+                end
+                #
+                # unless last_index == element_index
+                #   last_index = element_index
+                # end
+              end
+              #
+            else
+              # Criteria Expansion Here
+            end
+          end
+        else
+          @connector[:element_hash].filter(hash_criteria).each do |entry|
+            new_record = {}
+            selected.each do |the_key|
+              if ::GxG::valid_uuid?(entry[(the_key)])
+                new_record[(the_key)] = entry[(the_key)].to_sym
+              else
+                new_record[(the_key)] = entry[(the_key)]
+              end
+            end
+            found_list << new_record
+          end
+          #
+          unless hash_criteria[:format]
+            @connector[:element_array].filter(array_criteria).each do |entry|
+              new_record = {}
+              selected.each do |the_key|
+                if ::GxG::valid_uuid?(entry[(the_key)])
+                  new_record[(the_key)] = entry[(the_key)].to_sym
+                else
+                  new_record[(the_key)] = entry[(the_key)]
+                end
+              end
+              found_list << new_record
+            end
+          end
+        end
+        #
+        # Sorting Options Here: (Expansion)
+        if criteria[:order].is_any?(::Symbol, ::Hash)
+          ascending = true
+          sort_key = nil
+          if criteria[:order].is_a?(::Hash)
+            if criteria[:order][:ascending].is_a?(::Symbol)
+              sort_key = criteria[:order][:ascending]
+            end
+            if criteria[:order][:descending].is_a?(::Symbol)
+              sort_key = criteria[:order][:descending]
+              ascending = false
+            end
+          else
+            sort_key = criteria[:order]
+          end
+          if selected.include?(sort_key)
+            found_list = found_list.sort do |entry, another|
+              entry[(sort_key)] <=> another[(sort_key)]
+            end
+            unless ascending
+              found_list.reverse!
+            end
+          end
+        end
+        # Impose result set limit and offset
+        if limit.is_a?(::Integer)
+          if found_list.size > 0
+            if (offset + (limit - 1)) <= (found_list.size - 1)
+              found_list = found_list[((offset)..(offset + (limit - 1)))]
+            else
+              if offset <= (found_list.size - 1)
+                found_list = found_list[((offset)..(-1))]
+              end
+            end
+          end
+        end
+        #
+        found_list.size.times do
+          result << found_list.shift
+        end
+        #
+      else
+        raise Exception, "Database not available"
+      end
+    else
+      raise Exception, "Attempted to access a defunct Database"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:credential => (credential), :criteria => (criteria)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ vfs_permission(file_id=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1642
+def vfs_permission(file_id=nil, credential=nil)
+  result = []
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "dbid must be specified with a String"
+    end
+    if credential
+      unless ::GxG::valid_uuid?(credential)
+        raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)"
+      end
+    end
+    #
+    if credential
+      list = @connector[:file_permissions].filter({:file_id => (file_id.to_s), :credential => (credential.to_s)})
+    else
+      list = @connector[:file_permissions].filter({:file_id => (file_id.to_s)})
+    end
+    list.each do |record|
+      # [:execute, :rename, :move, :destroy, :create, :write, :read]
+      entry = {:credential => (record[:credential].to_sym), :permissions => {}}
+      [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission|
+        if record[(the_permission)] == 1
+          entry[:permissions][(the_permission)] = true
+        else
+          entry[:permissions][(the_permission)] = false
+        end
+      end
+      result << entry
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ vfs_permission_exist?(file_id=nil, credential=nil) + + click to toggle source + +
+ + +
+ +

Effective Permission Handling methods VFS File permission mappings

+ + + + +
+
# File lib/gxg/gxg_database.rb, line 1617
+def vfs_permission_exist?(file_id=nil, credential=nil)
+  result = false
+  begin
+    unless self.alive?
+      raise Exception, "Database inactive"
+    end
+    unless self.open?
+      raise Exception, "Database not available"
+    end
+    unless file_id.is_a?(::String)
+      raise ArgumentError, "file_id must be specified with a String"
+    end
+    unless ::GxG::valid_uuid?(credential)
+      raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)"
+    end
+    #
+    if @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).count > 0
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ vfs_permission_manifest(file_id=nil, credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database.rb, line 1683
+def vfs_permission_manifest(file_id=nil, credential=nil)
+  result = []
+  self.vfs_permission(file_id, credential).each do |entry|
+    the_record = {:credential => nil, :permissions => {}, :details => {}}
+    #
+    the_record[:credential] = entry[:credential]
+    the_record[:permissions] = entry[:permissions]
+    if self.role_exist?(entry[:credential])
+      role = self.role_fetch({:uuid => entry[:credential].to_s})
+      group = self.group_fetch({:uuid => role[:group_uuid].to_s})
+      the_record[:details][:role_title] = role[:title]
+      the_record[:details][:group] = group[:uuid].to_s.to_sym
+      the_record[:details][:group_title] = group[:title]
+    else
+      if self.user_exist?(entry[:credential])
+        user = self.user_fetch({:uuid => entry[:credential].to_s})
+        the_record[:details][:user_title] = user[:user_id]
+      else
+        log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}")
+        the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !"
+      end
+    end
+    result << the_record
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Database/ExternalAuthority.html b/doc/GxG/Database/ExternalAuthority.html new file mode 100644 index 0000000..948ec2b --- /dev/null +++ b/doc/GxG/Database/ExternalAuthority.html @@ -0,0 +1,106 @@ + + + + + + +class GxG::Database::ExternalAuthority - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Database::ExternalAuthority +

+ +
+ +

External Authority (Such as LDAP)

+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Database/LDAPAuthority.html b/doc/GxG/Database/LDAPAuthority.html new file mode 100644 index 0000000..774bd7f --- /dev/null +++ b/doc/GxG/Database/LDAPAuthority.html @@ -0,0 +1,104 @@ + + + + + + +class GxG::Database::LDAPAuthority - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Database::LDAPAuthority +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Database/PersistedArray.html b/doc/GxG/Database/PersistedArray.html new file mode 100644 index 0000000..b1afc6a --- /dev/null +++ b/doc/GxG/Database/PersistedArray.html @@ -0,0 +1,3354 @@ + + + + + + +class GxG::Database::PersistedArray - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Database::PersistedArray +

+ +
+ +

Class Place-holder Definitions

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(settings = {}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 651
+def initialize(settings = {})
+  # mount element: {:database => <database>, :uuid => <uuid>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+  # create element: {:database => <database>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+  #
+  if settings.is_a?(::Hash)
+    unless ::GxG::valid_uuid?(settings[:credential])
+      raise ArgumentError, "You must supply a valid credential UUID (37 character limit) as a String or Symbol"
+    end
+    @quick = settings[:quick]
+    @credential = settings[:credential].to_sym
+    @reservation = nil
+    @delegate = settings[:delegate]
+    if settings[:parent].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+      @parent = settings[:parent]
+    else
+      @parent = nil
+    end
+    @constraint = settings[:constraint]
+    if settings[:database].is_a?(::GxG::Database::Database)
+      if settings[:database].open?()
+        the_uuid = settings[:uuid]
+        if the_uuid.is_a?(::Symbol)
+          the_uuid = the_uuid.to_s
+        end
+        # TODO: validate uuid pattern
+        if settings[:title].is_a?(::String)
+          the_title = settings[:title]
+        else
+          the_title = ("Untitled PersistedArray " << (settings[:database].connector()[:element_array].count.to_s))
+        end
+        unless ::GxG::valid_uuid?(the_uuid)
+          # without specifying a uuid, the implied intent is to create a new PersistedArray
+          the_uuid = ::GxG::uuid_generate()
+          while ((settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0) or (settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0))
+            the_uuid = ::GxG::uuid_generate()
+          end
+          # create blank array record
+          new_dbid = settings[:database].connector()[:element_array].insert({:uuid => (the_uuid), :title => (the_title), :constraint => (@constraint.to_s)})
+          #
+          @db_address = {:database => (settings[:database]), :table => :element_array, :dbid => (new_dbid)}
+          if @parent
+            @permission = @parent.get_permissions()
+            # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => settings[:parent], :destination => self})
+            #                  settings[:database].extend_element_permissions(settings[:parent].db_address()[:table], settings[:parent].db_address()[:dbid], :element_array, new_dbid)
+          else
+            # create raw permissions for this element
+            @permission = {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}
+            # settings[:database].assign_element_permission(:element_array, new_dbid, @credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+            # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => self})
+          end
+          settings[:database].assign_element_permission(:element_array, new_dbid, @credential, @permission)
+        end
+        # mount according to uuid
+        if the_uuid
+          # mount
+          record = settings[:database].connector()[:element_array].filter({:uuid => (the_uuid)}).first
+          if record
+            @uuid = the_uuid.to_sym
+            @title = record[:title].to_s
+            @version = record[:version].to_f
+            @db_address = {:database => (settings[:database]), :table => :element_array, :dbid => (record[:dbid])}
+            @element_links = []
+            if record[:constraint].to_s.size > 0
+              @constraint = record[:constraint].to_sym
+            else
+              @constraint = nil
+            end
+            unless @permission
+              @permission = @db_address[:database].effective_element_permission(@db_address[:table], @db_address[:dbid], @credential)
+            end
+            # @read_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :read)
+            #
+            load_element_links
+            #
+          else
+            raise Exception, "Unable to mount the PersistedArray record"
+          end
+        else
+          raise Exception, "Unable to mount the PersistedArray record"
+        end
+        #
+      else
+        raise Exception, "Database not available for storage"
+      end
+      #
+    else
+      raise Exception, "Invalid Database object"
+    end
+    #
+  else
+    raise ArgumentError, "You must supply a hash of details"
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <<(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1303
+def <<(*args)
+  if args.size > 0
+    args.each do |item|
+      self[(@element_links.size)] = item
+    end
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ [](indexer=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1203
+def [](indexer=nil)
+  result = nil
+  # if exists
+  # if is not loaded, load element (if String or ByteArray)
+  if self.alive?()
+    if @db_address[:database].open?()
+      if indexer.is_a?(::Integer)
+        if @element_links[(indexer)]
+          if @element_links[(indexer)][:content].is_any?(::String, ::GxG::ByteArray)
+            unless @element_links[(indexer)][:loaded] == true
+              # ### Load Base Element contents from the database.
+              if @element_links[(indexer)][:content].is_a?(::String)
+                if @element_links[(indexer)][:record][:element_text_uuid].to_s.size > 0
+                  @db_address[:database].connector()[:text_page].filter({:parent_uuid => @element_links[(indexer)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
+                    @element_links[(indexer)][:content] << text_page[:content]
+                  end
+                else
+                  @element_links[(indexer)][:content] = @element_links[(indexer)][:record][:element_text].to_s
+                end
+              else
+                @db_address[:database].connector()[:binary_page].filter({:parent_uuid => @element_links[(indexer)][:record][:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
+                  @element_links[(indexer)][:content] << binary_page[:content]
+                end
+              end
+              @element_links[(indexer)][:loaded] = true
+            end
+          end
+          result = @element_links[(indexer)][:content]
+        end
+      else
+        raise ArgumentError, "You must specify with an Integer, not a #{indexer.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ []=(indexer=nil, value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 892
+def []=(indexer=nil, value=nil)
+  # Only works with Integer indexes.
+  unless self.alive?()
+    raise Exception, "Attempted to alter a defunct structure"
+  end
+  unless @db_address[:database].open?()
+    raise Exception, "Database not available"
+  end
+  result = nil
+  if indexer.is_a?(::Integer)
+    # Review : rewrite ????
+    if value.is_any?(::GxG::Database::Database::valid_field_classes()) || value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array)
+      unless self.write_reserved?()
+        self.get_reservation()
+      end
+      if self.write_reserved?()
+        # Further screen value provided:
+        # ### Check provided PersistedHashes and PersistedArrays
+        if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          unless value.db_address()
+            raise Exception, "The structure you are attaching is Deactivated (not supported)."
+          end
+          if value.structure_attached?()
+            # Review : experimental - auto-detach from prior structural relationship and reassign here.
+            # raise Exception, "Cross linked structures not supported"
+            value.structure_detach
+          end
+          value.search do |the_value, the_selector, the_container|
+            if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              unless the_value.db_address()
+                raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)."
+              end
+            end
+          end
+        end
+        # ### Check provided Hashes and Arrays
+        if value.is_any?(::Hash, ::Array)
+          value.search do |the_value, the_selector, the_container|
+            if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              unless the_value.db_address()
+                raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)."
+              end
+              if @db_address[:database].structure_attached?(the_value.uuid.to_s) && the_container.is_any?(::Hash, ::Array)
+                raise Exception, "The structure you are attaching has a cross-linked structure within it (cross linked structures not supported)."
+              end
+            end
+            unless the_value.is_any?(::GxG::Database::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array)
+              raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)."
+            end
+          end
+        end
+        # ### Property Exists?
+        if @element_links[(indexer)]
+          # set property to value
+          operation = :set_value
+        else
+          if @format
+            raise Exception, "Formatted - the structure cannot be altered"
+          else
+            # add property : value pair
+            operation = :add_value
+          end
+        end
+        # ### Prepare new value
+        new_value = {
+          :linkid => nil,
+          :content => nil,
+          :loaded => false,
+          :state => 0,
+          :record => {
+            :parent_uuid => @uuid.to_s,
+            :ordinal => 0,
+            :version => 0.0,
+            :element => "element_boolean",
+            :element_boolean => -1,
+            :element_integer => 0,
+            :element_float => 0.0,
+            :element_bigdecimal => ::BigDecimal.new("0.0"),
+            :element_datetime => ::DateTime.now,
+            :time_offset => 0.0,
+            :time_prior => 0.0,
+            :time_after => 0.0,
+            :length => 0,
+            :element_text => "",
+            :element_text_uuid => "",
+            :element_binary_uuid => "",
+            :element_array_uuid => "",
+            :element_hash_uuid => ""
+          }
+        }
+        #
+        if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          # ### Assimilate PersistedHash or PersistedArray
+          new_value[:content] = value
+          new_value[:loaded] = true
+          new_value[:state] = new_value[:content].hash
+          # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method.
+          new_value[:record][:version] = new_value[:content].version()
+          if value.is_a?(::GxG::Database::PersistedHash)
+            new_value[:record][:element] = "element_hash"
+            new_value[:record][:element_hash_uuid] = value.uuid.to_s
+          else
+            new_value[:record][:element] = "element_array"
+            new_value[:record][:element_array_uuid] = value.uuid.to_s
+          end
+        else
+          # ### Persist Hashes and Arrays
+          if value.is_any?(::Hash, ::Array)
+            new_value[:content] = @db_address[:database].iterative_persist(value,@credential)
+            new_value[:loaded] = true
+            new_value[:state] = new_value[:content].hash
+            # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method.
+            new_value[:record][:version] = new_value[:content].version()
+            if value.is_a?(::Hash)
+              new_value[:record][:element] = "element_hash"
+              new_value[:record][:element_hash_uuid] = new_value[:content].uuid.to_s
+            else
+              new_value[:record][:element] = "element_array"
+              new_value[:record][:element_array_uuid] = new_value[:content].uuid.to_s
+            end
+          else
+            # ### Persist Base Element Values
+            new_value[:record][:element] = ::GxG::Database::Database::element_table_for_instance(value).to_s
+            case new_value[:record][:element]
+            when "element_boolean"
+              case value.class
+              when ::NilClass
+                new_value[:record][:element_boolean] = -1
+                new_value[:content] = nil
+              when ::FalseClass
+                new_value[:record][:element_boolean] = 0
+                new_value[:content] = false
+              when ::TrueClass
+                new_value[:record][:element_boolean] = 1
+                new_value[:content] = true
+              end
+            when "element_integer"
+              new_value[:record][:element_integer] = value
+              new_value[:content] = value
+            when "element_float"
+              new_value[:record][:element_float] = value
+              new_value[:content] = value
+            when "element_bigdecimal"
+              new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s)
+              new_value[:content] = new_value[:record][:element_bigdecimal]
+            when "element_datetime"
+              new_value[:record][:element_datetime] = value
+              new_value[:content] = value
+            when "element_text"
+              if value.size > 256
+                # Note: be sure to keep text version & length sync'd with linked text element record
+                if operation == :set_value
+                  new_value[:record][:version] = @element_links[(indexer)][:record][:version]
+                  new_value[:record][:element_text_uuid] = @element_links[(indexer)][:record][:element_text_uuid]
+                end
+                new_value[:record][:length] = value.size
+              else
+                new_value[:record][:element_text] = value
+                new_value[:record][:length] = value.size
+              end
+              new_value[:content] = value
+            when "element_binary"
+              # Note: be sure to keep version & length sync'd with linked binary element record
+              if operation == :set_value
+                new_value[:record][:version] = @element_links[(indexer)][:record][:version]
+                new_value[:record][:element_binary_uuid] = @element_links[(indexer)][:record][:element_binary_uuid]
+              end
+              new_value[:record][:length] = value.size
+              new_value[:content] = value
+            else
+              raise Exception, "Unable to map an element type for value: #{value.inspect}"
+            end
+          end
+        end
+        # ### Commit Changes
+        case operation
+        when :set_value
+          # Note: Set In-memory value only, don't save unless you have to.
+          if new_value[:content].is_a?(@element_links[(indexer)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@element_links[(indexer)][:content]))
+            # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data).
+            new_value[:linkid] = @element_links[(indexer)][:linkid]
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              new_value[:record][:version] = new_value[:content].version()
+            else
+              new_value[:record][:version] = (((@element_links[(indexer)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+            end
+            new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal]
+            new_value[:loaded] = true
+            new_value[:state] = @element_links[(indexer)][:state]
+            # ### Did string grow or shrink crossing the threshold??
+            if new_value[:content].is_a?(::String)
+              if (@element_links[(indexer)][:content].size > 256 && new_value[:content].size <= 256)
+                # shrank - delete text object and pages as well as property record (slow)
+                element_destroy(indexer)
+                @element_links[(indexer)] = new_value
+                element_write(indexer)
+              else
+                if (@element_links[(indexer)][:content].size <= 256 && new_value[:content].size > 256)
+                  # grew - create text object and pages (a little less slow)
+                  @element_links[(indexer)] = new_value
+                  element_write(indexer)
+                else
+                  # N/A
+                  @element_links[(indexer)] = new_value
+                end
+              end
+            else
+              # N/A
+              @element_links[(indexer)] = new_value
+            end
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              # Note: this ensures attachment to this object
+              property_write(property_key)
+            end
+            #
+          else
+            # Other class value being substituted.
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              new_value[:record][:version] = new_value[:content].version()
+            else
+              new_value[:record][:version] = (((@element_links[(indexer)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+            end
+            new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal]
+            new_value[:loaded] = true
+            # Destroy old property record on db, and associated linked objects.
+            # Review : this is slow - look into optimizations here.
+            if @element_links[(indexer)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::GxG::ByteArray, ::String)
+              if @element_links[(indexer)][:content].is_a?(::String)
+                if (@element_links[(indexer)][:content].size > 256 && new_value[:content].size <= 256) || (@element_links[(indexer)][:content].size <= 256 && new_value[:content].size > 256)
+                  element_destroy(indexer)
+                end
+              else
+                element_destroy(indexer)
+              end
+            end
+            #
+            @element_links[(indexer)] = new_value
+            element_write(indexer)
+          end
+          #
+        when :add_value
+          # Note: set in-memory value and save.
+          if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+            new_value[:record][:version] = new_value[:content].version()
+          else
+            new_value[:record][:version] = 0.0
+          end
+          new_value[:record][:ordinal] = @element_links.size
+          new_value[:loaded] = true
+          #
+          @element_links[(indexer)] = new_value
+          # Review : is this wise to do this here??
+          element_write(indexer)
+        end
+        # ### Handle coordination between persisted objects:
+        if @element_links[(indexer)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          # ### Extend Reservation to persisted objects ( Review : does this slow it down too much?? )
+          @element_links[(indexer)][:content].release_reservation
+          @db_address[:database].reservation_add_element((@reservation || @delegate), (@element_links[(indexer)][:content].db_address()[:table]), @element_links[(indexer)][:content].db_address()[:dbid])
+          @element_links[(indexer)][:content].set_delegate((@reservation || @delegate))
+          # ### Extend Permissions to persisted object
+          @db_address[:database].extend_element_permissions(@db_address[:table],@db_address[:dbid],@element_links[(indexer)][:content].db_address()[:table],@element_links[(indexer)][:content].db_address()[:dbid])
+          @db_address[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => @element_links[(indexer)][:content]})
+          #
+          @element_links[(indexer)][:content].set_parent(self)
+        end
+        #
+        self.increment_version()
+        result = @element_links[(indexer)][:content]
+        #
+      else
+        raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)"
+      end
+    else
+      raise Exception, "The value is not persistable."
+    end
+  else
+    raise Exception, "You must provide a index in the form of an Integer."
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ alive?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 773
+def alive?()
+  if @db_address
+    if @db_address[:database].element_exists?(@db_address[:table],@db_address[:dbid])
+      true
+    else
+      false
+    end
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ clear_constraint() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 602
+def clear_constraint()
+  if self.alive?
+    if @constraint
+      @constraint = nil
+      @db_address[:database].connector()[:element_array].filter({:dbid => (@db_address[:dbid])}).update({:constraint => ""})
+    end
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ constraint() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 612
+def constraint()
+  @constraint.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_address() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 785
+def db_address()
+  @db_address
+end
+
+ +
+ + + + +
+ + +
+ +
+ deactivate() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 789
+def deactivate()
+  if self.alive?()
+    @element_links.each_index do |index|
+      element = @element_links[(index)][:content]
+      if element.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        containers = [(element)]
+        element.search({:include_inactive => false}) do |the_value, the_selector, the_container|
+          if the_value.alive?
+            the_value.deactivate
+          end
+          if the_container.alive?
+            unless containers.include?(the_container)
+                containers.unshift(the_container)
+            end
+          end
+          nil
+        end
+        containers.each do |the_container|
+          the_container.deactivate
+        end
+      end
+    end
+    if self.write_reserved?()
+      self.release_reservation()
+    end
+    @element_links = []
+    @db_address = nil
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete_at(indexer=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1244
+def delete_at(indexer=nil)
+  result = nil
+  # if exists
+  # if is not loaded, load element
+  if self.alive?()
+    if @db_address[:database].open?()
+      if indexer.is_a?(::Integer)
+        if self.write_reserved?()
+          property_key = key.to_s.to_sym
+          #
+          if @element_links[(indexer)]
+            # This will load the unloaded prior to separation.
+            result = self[(indexer)]
+            the_link = @element_links.delete_at(indexer)
+            if the_link[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              the_link[:content].save
+              the_link[:content].release_reservation()
+              @db_address[:database].reservation_remove_element((@reservation || @delegate), the_link[:content].db_address()[:table], the_link[:content].db_address()[:dbid])
+            else
+              # Eliminate orphaned text and binary objects in the database. They will exist only in memory.
+              if the_link[:content].is_any?(::String, ::GxG::ByteArray)
+                if the_link[:content].is_a?(::String)
+                  if the_link[:content].size > 256
+                    # eliminate text object and pages.
+                    the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_link[:record][:element_text_uuid]}).first
+                    if the_temp_record
+                      @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid])
+                    end
+                  end
+                end
+                if the_link[:content].is_a?(::GxG::ByteArray)
+                  # eliminate binary object and pages.
+                  the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_link[:record][:element_binary_uuid]}).first
+                  if the_temp_record
+                    @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid])
+                  end
+                end
+              end
+            end
+            @db_address[:database].connector()[:array_elements].filter({:dbid => the_link[:linkid]}).delete
+            refresh_ordinals
+            self.increment_version()
+          end
+          #
+        else
+          raise Exception, "You do not have sufficient privileges to make this change"
+        end
+      else
+        raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ destroy() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 847
+def destroy()
+  result = false
+  if self.alive?
+    begin
+      unless self.write_reserved?()
+        raise Exception, "You do not have a write-lock for this element"
+      end
+      unless @permission[:destroy]
+        raise Exception, "You do not have permission to destory this element"
+      end
+      if @db_address[:database].open?()
+        if @db_address[:database].element_in_use?(@db_address[:table], @db_address[:dbid])
+          raise Exception, "Element is still linked to a structure.  Use <PersistedHash>.delete(<key>).destroy or <PersistedArray>.delete_at(<index>).destroy"
+        else
+          if self.write_reserved?()
+            address = @db_address
+            self.deactivate
+            address[:database].element_destroy(@credential, address[:table],address[:dbid])
+            # Review : consider either emptying the db trash now, or adding a flag to manifest records to prevent future loading.
+            result = true
+          else
+            raise Exception, "You do not have sufficient privileges to make this change"
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ each(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1432
+def each(&block)
+  if block.respond_to?(:call)
+    load_element_links
+    if @element_links.size > 0
+      @element_links.each_index do |index|
+        block.call(self[(index)])
+      end
+    end
+    self
+  else
+    self.to_enum(:each)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_index(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1446
+def each_index(&block)
+  if block.respond_to?(:call)
+    load_element_links
+    if @element_links.size > 0
+      @element_links.each_index do |index|
+        block.call(index)
+      end
+    end
+    self
+  else
+    self.to_enum(:each_index)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1460
+def each_with_index(offset=0,&block)
+  if block.respond_to?(:call)
+    load_element_links
+    if @element_links.size > 0
+      @element_links.to_enum(:each).with_index(offset).each do |entry, index|
+        block.call(self[(index)], index)
+      end
+    end
+    self
+  else
+    self.to_enum(:each_with_index,offset)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_version(index=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 526
+def element_version(index=nil)
+  result = 0.0
+  if index.is_a?(::Numeric)
+    if @element_links[(index)]
+      result = @element_links[(index)][:record][:version]
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ export(options={:exclude_file_segments=>false}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1347
+def export(options={:exclude_file_segments=>false})
+  if self.alive?
+    exclude_file_segments = (options[:exclude_file_segments] || false)
+    if options[:clone] == true
+      # Review : why are cloned objects unconstrained? sync issues??
+      result = {:type => :element_array, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.clone, :content => []}
+    else
+      result = {:type => :element_array, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :constraint => @constraint.clone, :content => []}
+    end
+    export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+    children_of = Proc.new do |the_parent=nil|
+      list = []
+      export_db.each do |node|
+        if node[:parent].object_id == the_parent.object_id
+          list << node
+        end
+      end
+      list
+    end
+    # Build up export_db:
+    self.search do |the_value, the_selector, the_container|
+      if the_value.is_a?(::GxG::Database::PersistedHash)
+        if options[:clone] == true
+          the_uuid = GxG::uuid_generate.to_s.to_sym
+        else
+          the_uuid = the_value.uuid().clone
+        end
+        if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}}
+        end
+      end
+      if the_value.is_a?(::GxG::Database::PersistedArray)
+        if options[:clone] == true
+          the_uuid = GxG::uuid_generate.to_s.to_sym
+        else
+          the_uuid = the_value.uuid().clone
+        end
+        if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}}
+        end
+      end
+      if the_value.is_any?(::GxG::Database::Database::valid_field_classes())
+        data_type = GxG::Database::Database::element_table_for_instance(the_value)
+        case data_type
+        when :element_bigdecimal
+          data = the_value.to_s
+        when :element_datetime
+          data = the_value.to_s
+        when :element_binary
+          data = the_value.to_s.encode64
+        when :element_text
+          data = the_value.to_s
+        else
+          data = the_value
+        end              
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (the_container.version(the_selector) || 0.0), :content => data}}
+      end
+    end
+    # Collect children export content:
+    link_db = [(export_db[0])]
+    while link_db.size > 0 do
+      entry = link_db.shift
+      children_of.call(entry[:object]).each do |the_child|
+        entry[:record][:content][(the_child[:parent_selector])] = the_child[:record]
+        if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          link_db << the_child
+        end
+      end
+    end
+  else
+    result = nil
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ find_index(the_value) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1336
+def find_index(the_value)
+  result = nil
+  @element_links.each_with_index do |element, indexer|
+    if element[:content] == the_value
+      result = indexer
+      break
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_at_path(the_path="/") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1633
+def get_at_path(the_path="/")
+  # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric
+  # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu
+  #
+  # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/"))
+  result = nil
+  if the_path == "/"
+    result = self
+  else
+    object_stack = [(self)]
+    path_stack = the_path.split("/")
+    path_stack.to_enum.each do |path_element|
+      element = nil
+      if path_element.size > 0
+        if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          element = path_element.to_i
+        else
+          element = path_element
+          element.gsub!("%2f","/")
+          if element[0] == ":"
+            element = element[(1..-1)].to_sym
+          end
+        end
+      end
+      if element
+        result = object_stack.first[(element)]
+        if result.is_a?(NilClass)
+          break
+        else
+          object_stack.unshift(result)
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_delegate() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 496
+def get_delegate()
+  if self.alive?
+    if @delegate.is_any?(::String, ::Symbol)
+      @delegate.clone
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_permissions() + + click to toggle source + +
+ + +
+ +

Permission Management:

+ + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 321
+def get_permissions()
+  @permission.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 424
+def get_reservation()
+  result = false
+  # Review : Experimental design change - deprecate @delegate use, require entire structure (and sub-structures) to get solid @reservation (all of them) or this fails.
+  # Note : keep your structures small as this opens the door for stack-depth exceptions if it is nested too deep. (say 256 or less layers deep??)
+  if @reservation
+    result = true
+  else
+    if self.alive?
+      if @permission[:write]
+        clean_up_list = []
+        # Review : efficiency - write locks only apply to PersistedHashes and PersistedArrays so do we need a full manifest??
+        the_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write)
+        if the_reservation
+          @element_links.each_index do |ordinal|
+            if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+              clean_up_list << @element_links[(ordinal)][:content]
+              unless @element_links[(ordinal)][:content].get_reservation()
+                @db_address[:database].release_element_locks(the_reservation)
+                the_reservation = nil
+                break
+              end
+            end
+          end
+          #
+        end
+        if the_reservation
+          @reservation = the_reservation
+          result = true
+        else
+          clean_up_list.each do |the_object|
+            the_object.release_reservation
+          end
+        end
+      end
+    end
+  end
+  # xxx keep old code for now
+  # if (@reservation || @delegate)
+  #   result = true
+  # else
+  #   if self.alive?
+  #     if @permission[:write]
+  #       @reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write)
+  #       if @reservation
+  #         @element_links.each_index do |ordinal|
+  #           if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+  #             @element_links[(ordinal)][:content].set_delegate((@reservation || @delegate))
+  #           end
+  #         end
+  #         result = true
+  #       end
+  #       #
+  #     end
+  #   end
+  # end
+  # xxx end old code
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ include?(the_value) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1325
+def include?(the_value)
+  result = false
+  @element_links.each do |element|
+    if element[:content] == the_value
+      result = true
+      break
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ increment_version() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 552
+def increment_version()
+  if self.alive?
+    if @db_address[:database].open?
+      if self.write_reserved?()
+        @version = (((@version + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 746
+def inspect()
+  # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with PersistedArray.
+  # TODO: make interative instead of re-entrant.
+  result = "(Not Loaded)"
+  if @db_address
+    if @db_address[:database].element_byte_size(@db_address[:table], @db_address[:dbid]) <= 10485760
+      # smaller than 10MB
+      result = "["
+      @element_links.each_index do |element_index|
+        if @element_links[(element_index)].is_a?(::Hash)
+          result << @element_links[(element_index)][:content].inspect
+        else
+          result << "(Not Loaded)"
+        end
+        unless element_index == (@element_links.size - 1)
+          result << ", "
+        end
+      end
+      result << "]"
+    else
+      result = "(Too Damned Big)"
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ iterative(options={:include_inactive => true}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1474
+def iterative(options={:include_inactive => true}, &block)
+  result = []
+  visit = Proc.new do |the_node=nil, accumulator=[]|
+    node_stack = []
+    if the_node
+      node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)})
+      while (node_stack.size > 0) do
+        a_node = node_stack.shift
+        #
+        if a_node[:object].is_a?(::GxG::Database::PersistedHash)
+          if a_node[:object].alive?
+            a_node[:object].each_pair do |the_key, the_value|
+              node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value})
+            end
+          else
+            if options[:include_inactive]
+              accumulator << a_node
+            end
+          end
+        end
+        if a_node[:object].is_a?(::GxG::Database::PersistedArray)
+          if a_node[:object].alive?
+            a_node[:object].each_with_index do |the_value, the_index|
+              node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value})
+            end
+          else
+            if options[:include_inactive]
+              accumulator << a_node
+            end
+          end
+        end
+        #
+        if a_node.alive? || options[:include_inactive]
+          accumulator << a_node
+        end
+      end
+    end
+    accumulator
+  end
+  #
+  children_of = Proc.new do |the_db=[], the_parent=nil|
+    list = []
+    the_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  #
+  begin
+    database = visit.call(self,[])
+    link_db = children_of.call(database, self)
+    if block.respond_to?(:call)
+      while (link_db.size > 0) do
+        entry = link_db.shift
+        unless entry[:object].object_id == self.object_id
+          # calls with parameters: the_value, the_key/the_index (the_selector), the_container
+          raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent])
+          if raw_result
+            result << raw_result
+          end
+        end
+        if entry[:object].object_id != nil.object_id
+          children = children_of.call(database, entry[:object])
+          children.each do |child|
+            link_db << child
+          end
+        end
+      end
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ parent() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 629
+def parent()
+  @parent
+end
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(the_object=nil,base_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1566
+def paths_to(the_object=nil,base_path="")
+  # new idea here:
+  search_results = []
+  unless base_path[0] == "/"
+    base_path = ("/" << base_path)
+  end
+  if base_path.size > 1
+    path_stack = base_path.split("/")[1..-1].reverse
+  else
+    path_stack = []
+  end
+  origin = self.get_at_path(base_path)
+  container_stack = [{:selector => nil, :container => origin}]
+  find_container = Proc.new do |the_container|
+    result = nil
+    container_stack.each_with_index do |entry, index|
+      if entry[:container] == the_container
+        result = entry
+        break
+      end
+    end
+    result
+  end
+  last_container = origin
+  found = false
+  # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}}
+  if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    origin.process! do |the_value, selector, container|
+      if last_container.object_id != container.object_id
+        container_record = find_container.call(container)
+        if container_record
+          path_stack = container_record[:prefix].split("/").reverse
+          if path_stack.size == 0
+            path_stack << ""
+          end
+        end
+        last_container = container
+      end
+      if selector.is_a?(Symbol)
+        safe_key = (":" + selector.to_s)
+      else
+        safe_key = selector.to_s
+      end
+      safe_key.gsub!("/","%2f")
+      path_stack.unshift(safe_key)
+      # compare the_value
+      found = false
+      if (the_value == the_object)
+        found = true
+      end
+      if found
+        search_results << ("/" << path_stack.reverse.join("/"))
+      end
+      #
+      if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))})
+      end
+      path_stack.shift
+      #
+      nil
+    end
+  else
+    search_results << ("/" << path_stack.join("/"))
+  end
+  search_results
+end
+
+ +
+ + + + +
+ + +
+ +
+ pop() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1321
+def pop()
+  self.delete_at((@element_links.size - 1))
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(options={:include_inactive => true}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1553
+def process!(options={:include_inactive => true}, &block)
+  self.iterative(options, &block)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ push(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1312
+def push(*args)
+  if args.size > 0
+    args.each do |item|
+      self << item
+    end
+  end
+  self
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ release_reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 405
+def release_reservation()
+  if self.alive?()
+    if @reservation
+      @db_address[:database].release_element_locks(@reservation)
+      @reservation = nil
+    end
+    if @delegate
+      @delegate = nil
+    end
+    # release write-locks of all sub-items
+    @element_links.each_index do |ordinal|
+      if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+        @element_links[(ordinal)][:content].release_reservation
+      end
+    end
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 385
+def reservation()
+  if self.alive?
+    if @reservation.is_any?(::String, ::Symbol)
+      @reservation.clone
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ save() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 820
+def save()
+  result = false
+  if self.alive?
+    begin
+      if @db_address[:database].open?()
+        @element_links.each_index do |index|
+          # Note: only save property data and header if: a) data is still loaded and b) state has changed.
+          if @element_links[(index)][:loaded] == true
+            if @element_links[(index)][:state] != @element_links[(index)][:content].hash
+              @element_links[(index)][:record][:version] = (((@element_links[(index)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+              element_write(index)
+            end
+          end
+          #
+        end
+      else
+        raise Exception, "Database not available"
+      end
+      self.save_version
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ save_version() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 562
+def save_version()
+  @db_address[:database].connector()[:element_array].filter({:dbid => @db_address[:dbid]}).update({:version => @version})
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ set_at_path(the_path="/",the_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1673
+def set_at_path(the_path="/",the_value=nil)
+  result = nil
+  if the_path != "/"
+    container = self.get_at_path(::File::dirname(the_path))
+    if container
+      raw_selector = ::File::basename(the_path)
+      selector = nil
+      if raw_selector.size > 0
+        if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          selector = raw_selector.to_i
+        else
+          selector = raw_selector
+          selector.gsub!("%2f","/")
+          if selector[0] == ":"
+            selector = selector[(1..-1)].to_sym
+          end
+        end
+      end
+      if selector
+        container[(selector)] = the_value
+        result = container[(selector)]
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+      #
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_constraint(format_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 591
+def set_constraint(format_uuid=nil)
+  if self.alive?
+    unless @constraint
+      if ::GxG::valid_uuid?(format_uuid)
+        @constraint = format_uuid.to_sym
+        @db_address[:database].connector()[:element_array].filter({:dbid => (@db_address[:dbid])}).update({:constraint => (@constraint.to_s)})
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_delegate(the_delegate=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 504
+def set_delegate(the_delegate=nil)
+  if self.alive?
+    if the_delegate.is_any?([::String, ::Symbol])
+      @delegate = the_delegate.to_sym
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_element_version(element_index, the_version=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 537
+def set_element_version(element_index, the_version=nil)
+  result = false
+  if @element_links[(element_index)]
+    if the_version.is_a?(::Numeric)
+      @element_links[(element_index)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+      result = true
+    else
+      log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for index #{element_index.inspect} on Object #{@uuid.inspect}")
+    end
+  else
+    log_warning("Attempted to set version with an invalid index #{element_index.inspect} on Object #{@uuid.inspect}")
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_parent(object=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 633
+def set_parent(object=nil)
+  if self.alive?
+    if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+      unless @parent
+        @parent = object
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_permissions(credential=nil, the_permission=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 325
+def set_permissions(credential=nil, the_permission=nil)
+  if self.alive?
+    if credential.is_a?(::Hash) && the_permission.is_a?(::Hash)
+      if credential[:user]
+        if credential[:user] == "public" || credential[:user] == "Public"
+          credential = "00000000-0000-4000-0000-000000000000"
+        else
+          credential = (@db_address[:database].user_fetch({:user_id => credential[:user].to_s}) || {})[:uuid]
+        end
+        if credential
+          credential = credential.to_s.to_sym
+        end
+      else
+        if credential[:group]
+          credential = @db_address[:database].group_credential(credential)
+        else
+          credential = nil
+        end
+      end
+    else
+      if credential.is_a?(::Hash) && the_permission == nil
+        the_permission = credential
+        credential = @credential
+      else
+        unless credential
+          credential = @credential
+        end
+      end
+    end
+    if the_permission.is_a?(::Hash) && ::GxG::valid_uuid?(credential)
+      manifest = []
+      structures = [{:table => @db_address[:table], :dbid => @db_address[:dbid]}]
+      while structures.size > 0 do
+        entry = structures[0]
+        @db_address[:database].element_manifest(entry[:table],entry[:dbid]).each do |record|
+          if [:element_hash, :element_array].include?(record[:table])
+            unless structures.include?(record)
+              structures << record
+            end
+          else
+            # Review - only set/get/check permissions on persisted arrays/hashes
+            # unless manifest.include?(record)
+            #   manifest << record
+            # end
+          end
+        end
+        manifest << structures.shift
+      end
+      manifest.each do |entry|
+        @db_address[:database].assign_element_permission(entry[:table], entry[:dbid], credential, the_permission)
+      end
+      #
+      true
+    else
+      log_error({:error => Exception.new("You MUST provide a valid permissions Hash."), :parameters => {:credential => credential, :permission => the_permission}})
+      false
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_title(the_title=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 577
+def set_title(the_title=nil)
+  if self.alive?
+    if @db_address[:database].open?
+      if self.write_reserved?()
+        if the_title
+          @title = the_title.to_s[0..256]
+          @db_address[:database].connector()[:element_array].filter({:dbid => @db_address[:dbid]}).update({:title => @title})
+          self.increment_version
+        end
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_version(the_version=0.0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 566
+def set_version(the_version=0.0)
+  if self.write_reserved?()
+    @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+    self.save_version
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ size() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 881
+def size()
+  @element_links.size
+end
+
+ +
+ + + + +
+ + +
+ +
+ structure_attached?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 885
+def structure_attached?()
+  @db_address[:database].structure_attached?(@uuid.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+ structure_detach() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 888
+def structure_detach()
+  @db_address[:database].structure_detach(@uuid.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+ sync_export(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1426
+def sync_export(options={})
+  # Review : does this work with PersistedArray ??
+  self.save
+  @db_address[:database].sync_export(@credential, [(@uuid)], options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ title() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 573
+def title()
+  @title.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ ufs() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 616
+def ufs()
+  if @constraint
+    record = @db_address[:database].format_load({:uuid => @constraint.to_s})
+    if record
+      record[:ufs]
+    else
+      ""
+    end
+  else
+    ""
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ unload(indexer=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1174
+def unload(indexer=nil)
+  result = false
+  # if key exists
+  # if key is loaded, unload element (if String or ByteArray)
+  if self.alive?()
+    if @db_address[:database].open?()
+      if indexer.is_a?(::Integer)
+        if @element_links[(indexer)]
+          if @element_links[(indexer)][:content].is_any?(::String, ::GxG::ByteArray)
+            if @element_links[(indexer)][:loaded] == true
+              # ### Unload Base Element contents from memory, leaving reference harness intact and active.
+              @element_links[(indexer)][:content].clear
+              @element_links[(indexer)][:loaded] = false
+            end
+          end
+          result = true
+        end
+      else
+        raise ArgumentError, "You must specify with an Integer, not a #{indexer.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ unpersist() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 1704
+def unpersist()
+  result = []
+  if self.alive?
+    #
+    export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+    children_of = Proc.new do |the_parent=nil|
+      list = []
+      export_db.each do |node|
+        if node[:parent].object_id == the_parent.object_id
+          list << node
+        end
+      end
+      list
+    end
+    # Build up export_db:
+    self.search do |the_value, the_selector, the_container|
+      if the_value.is_a?(::GxG::Database::PersistedHash)
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}}
+      else
+        if the_value.is_a?(::GxG::Database::PersistedArray)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value}
+        end
+      end
+    end
+    # Collect children export content:
+    link_db =[(export_db[0])]
+    while link_db.size > 0 do
+      entry = link_db.shift
+      children_of.call(entry[:object]).each do |the_child|
+        entry[:record][(the_child[:parent_selector])] = the_child[:record]
+        if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          link_db << the_child
+        end
+      end
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 512
+def uuid()
+  @uuid.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ version(input=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 516
+def version(input=nil)
+  @version.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ version=(the_version=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 520
+def version=(the_version=nil)
+  if the_version.is_a?(::Numeric)
+    @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ wait_for_reservation(timeout=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 483
+def wait_for_reservation(timeout=nil)
+  result = true
+  the_timeout = Time.now.to_f + (timeout || 30.0).to_f
+  until self.get_reservation() == true do
+    sleep 1.0
+    if Time.now.to_f >= the_timeout
+      result = false
+      break
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_permission?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 393
+def write_permission?()
+  @permission[:write]
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_reserved?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedarray.rb, line 397
+def write_reserved?()
+  if @reservation or @delegate
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Database/PersistedHash.html b/doc/GxG/Database/PersistedHash.html new file mode 100644 index 0000000..cddbb28 --- /dev/null +++ b/doc/GxG/Database/PersistedHash.html @@ -0,0 +1,3334 @@ + + + + + + +class GxG::Database::PersistedHash - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Database::PersistedHash +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(settings = {}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 735
+      def initialize(settings = {})
+        # mount element: {:database => <database>, :uuid => <uuid>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+        # create element: {:database => <database>, :credential => <uuid>, :delegate => nil/<uuid>, :parent => nil/<an-object>}
+        #
+        if settings.is_a?(::Hash)
+          unless ::GxG::valid_uuid?(settings[:credential])
+            raise ArgumentError, "You must supply a valid credential UUID (37 character limit) as a String or Symbol"
+          end
+          @quick = settings[:quick]
+          @credential = settings[:credential].to_sym
+          @reservation = nil
+          @delegate = settings[:delegate]
+          if settings[:parent].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+            @parent = settings[:parent]
+          else
+            @parent = nil
+          end
+          if settings[:database].is_a?(::GxG::Database::Database)
+            if settings[:database].open?()
+              # note - rewrite.  if create then as subset, eventually will always mount if all checks out.
+              the_uuid = settings[:uuid]
+              if the_uuid.is_a?(::Symbol)
+                the_uuid = the_uuid.to_s
+              end
+              # TODO: validate uuid pattern
+              if settings[:title].is_a?(::String)
+                the_title = settings[:title]
+              else
+                the_title = ("Untitled PersistedHash " << (settings[:database].connector()[:element_hash].count.to_s))
+              end
+              @format = settings[:format]
+              unless the_uuid
+                # without specifying a uuid, the implied intent is to create a new PersistedHash
+                the_uuid = ::GxG::uuid_generate()
+                while ((settings[:database].connector()[:element_hash].filter({:uuid => the_uuid}).count > 0) or (settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0))
+                  the_uuid = ::GxG::uuid_generate()
+                end
+                # creat blank hash record
+                new_dbid = settings[:database].connector()[:element_hash].insert({:uuid => (the_uuid), :title => (the_title), :format => (@format.to_s)})
+                #
+                @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (new_dbid)}
+                if @parent
+                  @permission = @parent.get_permissions()
+                  # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => settings[:parent], :destination => self})
+#                  settings[:database].extend_element_permissions(settings[:parent].db_address()[:table], settings[:parent].db_address()[:dbid], :element_hash, new_dbid)
+                else
+                  # create raw permissions for this element
+                  @permission = {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}
+                  # settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true})
+                  # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => self})
+                end
+                settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, @permission)
+              end
+              # mount according to uuid
+              if the_uuid
+                # mount
+                record = settings[:database].connector()[:element_hash].filter({:uuid => (the_uuid)}).first
+                if record
+                  @uuid = the_uuid.to_sym
+                  @title = record[:title].to_s
+                  @version = record[:version].to_f
+                  @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (record[:dbid])}
+                  if record[:format].to_s.size > 0
+                    @format = record[:format].to_sym
+                  end
+                  @property_links = {}
+                  #
+                  unless @permission
+                    @permission = @db_address[:database].effective_element_permission(@db_address[:table], @db_address[:dbid], @credential)
+                  end
+                  # @read_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :read)
+                  #
+                  load_property_links
+                  #
+                else
+                  raise Exception, "Unable to mount the PersistedHash record"
+                end
+              else
+                raise Exception, "Unable to mount the PersistedHash record"
+              end
+              #
+            else
+              raise Exception, "Database not available for storage"
+            end
+            #
+          else
+            raise Exception, "Invalid Database object"
+          end
+          #
+        else
+          raise ArgumentError, "You must supply a hash of details"
+        end
+        #
+      end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1311
+def [](key=nil)
+  result = nil
+  # if key exists
+  # if key is not loaded, load element (if String or ByteArray)
+  if self.alive?()
+    if @db_address[:database].open?()
+      if key.is_a?(::Symbol)
+        if key.to_s.size > 256
+          log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"})
+          key = key.to_s[(0..255)].to_sym
+        end
+        property_key = key.to_s.to_sym
+        if @property_links[(property_key)]
+          if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray)
+            unless @property_links[(property_key)][:loaded] == true
+              # ### Load Base Element contents from the database.
+              if @property_links[(property_key)][:content].is_a?(::String)
+                if @property_links[(property_key)][:record][:element_text_uuid].to_s.size > 0
+                  @db_address[:database].connector()[:text_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page|
+                    @property_links[(property_key)][:content] << text_page[:content]
+                  end
+                else
+                  @property_links[(property_key)][:content] = @property_links[(property_key)][:record][:element_text].to_s
+                end
+              else
+                @db_address[:database].connector()[:binary_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page|
+                  @property_links[(property_key)][:content] << binary_page[:content]
+                end
+              end
+              @property_links[(property_key)][:loaded] = true
+            end
+          end
+          result = @property_links[(property_key)][:content]
+        end
+      else
+        raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ []=(key=nil, value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 980
+def []=(key=nil, value=nil)
+  # Only works with Symbols.
+  unless self.alive?()
+    raise Exception, "Attempted to alter a defunct structure"
+  end
+  unless @db_address[:database].open?()
+    raise Exception, "Database not available"
+  end
+  result = nil
+  if key.is_a?(::Symbol)
+    if key.to_s.size > 256
+      log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"})
+      key = key.to_s[(0..255)].to_sym
+    end
+    property_key = key.to_s.to_sym
+    # Review : rewrite ????
+    if value.is_any?(::GxG::Database::Database::valid_field_classes()) || value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array)
+      unless self.write_reserved?()
+        self.get_reservation()
+      end
+      if self.write_reserved?()
+        # Further screen value provided:
+        # ### Check provided PersistedHashes and PersistedArrays
+        if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          unless value.db_address()
+            raise Exception, "The structure you are attaching is Deactivated (not supported)."
+          end
+          if value.structure_attached?()
+            # Review : experimental - auto-detach from prior structural relationship and reassign here.
+            # raise Exception, "Cross linked structures not supported"
+            unless value.write_reserved?()
+              value.get_reservation()
+            end
+            if value.write_reserved?()
+              value.structure_detach
+            else
+              raise Exception, "Unable to secure write-reservation for this structural change."
+            end
+          end
+          value.search do |the_value, the_selector, the_container|
+            if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              unless the_value.db_address()
+                raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)."
+              end
+            end
+          end
+        end
+        # ### Check provided Hashes and Arrays
+        if value.is_any?(::Hash, ::Array)
+          value.search do |the_value, the_selector, the_container|
+            if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              unless the_value.db_address()
+                raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)."
+              end
+              if @db_address[:database].structure_attached?(the_value.uuid.to_s) && the_container.is_any?(::Hash, ::Array)
+                raise Exception, "The structure you are attaching has a cross-linked structure within it (cross linked structures not supported)."
+              end
+            end
+            unless the_value.is_any?(::GxG::Database::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array)
+              raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)."
+            end
+          end
+        end
+        # ### Property Exists?
+        if @property_links[(key.to_s.to_sym)]
+          # set property to value
+          operation = :set_value
+        else
+          if @format
+            raise Exception, "Formatted - the structure cannot be altered"
+          else
+            # add property : value pair
+            operation = :add_value
+          end
+        end
+        # ### Prepare new value
+        new_value = {
+          :linkid => nil,
+          :content => nil,
+          :loaded => false,
+          :state => 0,
+          :record => {
+            :parent_uuid => @uuid.to_s,
+            :property => key.to_s,
+            :ordinal => 0,
+            :version => 0.0,
+            :element => "element_boolean",
+            :element_boolean => -1,
+            :element_integer => 0,
+            :element_float => 0.0,
+            :element_bigdecimal => ::BigDecimal.new("0.0"),
+            :element_datetime => ::DateTime.now,
+            :time_offset => 0.0,
+            :time_prior => 0.0,
+            :time_after => 0.0,
+            :length => 0,
+            :element_text => "",
+            :element_text_uuid => "",
+            :element_binary_uuid => "",
+            :element_array_uuid => "",
+            :element_hash_uuid => ""
+          }
+        }
+        #
+        if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          # ### Assimilate PersistedHash or PersistedArray
+          new_value[:content] = value
+          new_value[:loaded] = true
+          new_value[:state] = new_value[:content].hash
+          # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method.
+          new_value[:record][:version] = new_value[:content].version()
+          if value.is_a?(::GxG::Database::PersistedHash)
+            new_value[:record][:element] = "element_hash"
+            new_value[:record][:element_hash_uuid] = value.uuid.to_s
+          else
+            new_value[:record][:element] = "element_array"
+            new_value[:record][:element_array_uuid] = value.uuid.to_s
+          end
+        else
+          # ### Persist Hashes and Arrays
+          if value.is_any?(::Hash, ::Array)
+            new_value[:content] = @db_address[:database].iterative_persist(value,@credential)
+            new_value[:loaded] = true
+            new_value[:state] = new_value[:content].hash
+            # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method.
+            new_value[:record][:version] = new_value[:content].version()
+            if value.is_a?(::Hash)
+              new_value[:record][:element] = "element_hash"
+              new_value[:record][:element_hash_uuid] = new_value[:content].uuid.to_s
+            else
+              new_value[:record][:element] = "element_array"
+              new_value[:record][:element_array_uuid] = new_value[:content].uuid.to_s
+            end
+          else
+            # ### Persist Base Element Values
+            new_value[:record][:element] = ::GxG::Database::Database::element_table_for_instance(value).to_s
+            case new_value[:record][:element]
+            when "element_boolean"
+              if value.class == ::NilClass
+                new_value[:record][:element_boolean] = -1
+                new_value[:content] = nil
+              end
+              if value.class == ::FalseClass
+                new_value[:record][:element_boolean] = 0
+                new_value[:content] = false
+              end
+              if value.class == ::TrueClass
+                new_value[:record][:element_boolean] = 1
+                new_value[:content] = true
+              end
+            when "element_integer"
+              new_value[:record][:element_integer] = value
+              new_value[:content] = value
+            when "element_float"
+              new_value[:record][:element_float] = value
+              new_value[:content] = value
+            when "element_bigdecimal"
+              new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s)
+              new_value[:content] = new_value[:record][:element_bigdecimal]
+            when "element_datetime"
+              new_value[:record][:element_datetime] = value
+              new_value[:content] = value
+            when "element_text"
+              if value.size > 256
+                # Note: be sure to keep text version & length sync'd with linked text element record
+                if operation == :set_value
+                  new_value[:record][:version] = @property_links[(property_key)][:record][:version]
+                  new_value[:record][:element_text_uuid] = @property_links[(property_key)][:record][:element_text_uuid]
+                end
+                new_value[:record][:length] = value.size
+              else
+                new_value[:record][:element_text] = value
+                new_value[:record][:length] = value.size
+              end
+              new_value[:content] = value
+            when "element_binary"
+              # Note: be sure to keep version & length sync'd with linked binary element record
+              if operation == :set_value
+                new_value[:record][:version] = @property_links[(property_key)][:record][:version]
+                new_value[:record][:element_binary_uuid] = @property_links[(property_key)][:record][:element_binary_uuid]
+              end
+              new_value[:record][:length] = value.size
+              new_value[:content] = value
+            else
+              raise Exception, "Unable to map an element type for value: #{value.inspect}"
+            end
+          end
+        end
+        # ### Commit Changes
+        case operation
+        when :set_value
+          # Note: Set In-memory value only, don't save unless you have to.
+          if new_value[:content].is_a?(@property_links[(property_key)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@property_links[(property_key)][:content]))
+            # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data).
+            new_value[:linkid] = @property_links[(property_key)][:linkid]
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              new_value[:record][:version] = new_value[:content].version()
+            else
+              new_value[:record][:version] = (((@property_links[(property_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+            end
+            new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal]
+            new_value[:loaded] = true
+            new_value[:state] = @property_links[(property_key)][:state]
+            # ### Did string grow or shrink crossing the threshold??
+            if new_value[:content].is_a?(::String)
+              if (@property_links[(property_key)][:content].size > 256 && new_value[:content].size <= 256)
+                # shrank - delete text object and pages as well as property record (slow)
+                property_destroy(property_key)
+                @property_links[(property_key)] = new_value
+                property_write(property_key)
+              else
+                if (@property_links[(property_key)][:content].size <= 256 && new_value[:content].size > 256)
+                  # grew - create text object and pages (a little less slow)
+                  @property_links[(property_key)] = new_value
+                  property_write(property_key)
+                else
+                  # N/A
+                  @property_links[(property_key)] = new_value
+                end
+              end
+            else
+              # N/A
+              @property_links[(property_key)] = new_value
+            end
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              # Note: this ensures attachment to this object
+              property_write(property_key)
+            end
+            #
+          else
+            # Other class value being substituted.
+            if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              new_value[:record][:version] = new_value[:content].version()
+            else
+              new_value[:record][:version] = (((@property_links[(property_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+            end
+            new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal]
+            new_value[:loaded] = true
+            # Destroy old property record on db, and associated linked objects.
+            # Review : this is slow - look into optimizations here.
+            if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::GxG::ByteArray, ::String)
+              if @property_links[(property_key)][:content].is_a?(::String)
+                if (@property_links[(property_key)][:content].size > 256 && new_value[:content].to_s.size <= 256) || (@property_links[(property_key)][:content].size <= 256 && new_value[:content].to_s.size > 256)
+                  property_destroy(property_key)
+                end
+              else
+                property_destroy(property_key)
+              end
+            end
+            #
+            @property_links[(property_key)] = new_value
+            property_write(property_key)
+          end
+          #
+        when :add_value
+          # Note: set in-memory value and save.
+          if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+            new_value[:record][:version] = new_value[:content].version()
+          else
+            new_value[:record][:version] = 0.0
+          end
+          new_value[:record][:ordinal] = @property_links.keys.size
+          new_value[:loaded] = true
+          #
+          @property_links[(property_key)] = new_value
+          # Review : is this wise to do this here??
+          property_write(property_key)
+          # self.refresh_ordinals ??
+        end
+        # ### Handle coordination between persisted objects:
+        if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          # ### Extend Reservation to persisted objects ( Review : does this slow it down too much?? )
+          @property_links[(property_key)][:content].release_reservation
+          @db_address[:database].reservation_add_element((@reservation || @delegate), (@property_links[(property_key)][:content].db_address()[:table]), @property_links[(property_key)][:content].db_address()[:dbid])
+          @property_links[(property_key)][:content].set_delegate((@reservation || @delegate))
+          # ### Extend Permissions to persisted object
+          @db_address[:database].extend_element_permissions(@db_address[:table],@db_address[:dbid],@property_links[(property_key)][:content].db_address()[:table],@property_links[(property_key)][:content].db_address()[:dbid])
+          @db_address[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => @property_links[(property_key)][:content]})
+          #
+          @property_links[(property_key)][:content].set_parent(self)
+        end
+        #
+        self.increment_version()
+        result = @property_links[(property_key)][:content]
+        #
+      else
+        raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)"
+      end
+    else
+      raise Exception, "The value is not persistable."
+    end
+  else
+    raise Exception, "You must provide a property key in the form of a Symbol."
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ alive?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 861
+def alive?()
+  if @db_address
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_address() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 869
+def db_address()
+  @db_address
+end
+
+ +
+ + + + +
+ + +
+ +
+ deactivate() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 873
+def deactivate()
+  if self.alive?()
+    @property_links.keys.each do |key|
+      element = @property_links[(key)][:content]
+      if element.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        containers = [(element)]
+        element.search({:include_inactive => false}) do |the_value, the_selector, the_container|
+          if the_value.alive? && the_value.respond_to?(:deactivate)
+            the_value.deactivate
+          end
+          if the_container.alive?
+            unless containers.include?(the_container)
+                containers.unshift(the_container)
+            end
+          end
+          nil
+        end
+        containers.each do |the_container|
+          the_container.deactivate
+        end
+      end
+    end
+    if self.write_reserved?()
+      self.release_reservation()
+    end
+    @property_links = {}
+    @db_address = nil
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete(key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1361
+def delete(key=nil)
+  result = nil
+  # if key exists
+  # if key is not loaded, load element
+  if self.alive?()
+    if @db_address[:database].open?()
+      if key.is_a?(::Symbol)
+        if key.to_s.size > 256
+          log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"})
+          key = key.to_s[(0..255)].to_sym
+        end
+        if @format
+          raise Exception, "Formatted - the structure cannot be altered"
+        else
+          if self.write_reserved?()
+            property_key = key.to_s.to_sym
+            #
+            if @property_links[(property_key)]
+              # This will load the unloaded prior to separation.
+              result = self[(property_key)]
+              the_link = @property_links.delete(property_key)
+              if the_link[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+                the_link[:content].save
+                the_link[:content].release_reservation()
+                @db_address[:database].reservation_remove_element((@reservation || @delegate), the_link[:content].db_address()[:table], the_link[:content].db_address()[:dbid])
+              else
+                # Eliminate orphaned text and binary objects in the database. They will exist only in memory.
+                if the_link[:content].is_any?(::String, ::GxG::ByteArray)
+                  if the_link[:content].is_a?(::String)
+                    if the_link[:content].size > 256
+                      # eliminate text object and pages.
+                      the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_link[:record][:element_text_uuid]}).first
+                      if the_temp_record
+                        @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid])
+                      end
+                    end
+                  end
+                  if the_link[:content].is_a?(::GxG::ByteArray)
+                    # eliminate binary object and pages.
+                    the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_link[:record][:element_binary_uuid]}).first
+                    if the_temp_record
+                      @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid])
+                    end
+                  end
+                end
+              end
+              @db_address[:database].connector()[:hash_properties].filter({:dbid => the_link[:linkid]}).delete
+              refresh_ordinals
+              self.increment_version()
+            end
+            #
+          else
+            raise Exception, "You do not have sufficient privileges to make this change"
+          end
+        end
+      else
+        raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ destroy() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 931
+def destroy()
+  result = false
+  if self.alive?
+    begin
+      unless self.write_reserved?()
+        raise Exception, "You do not have a write-lock for this element"
+      end
+      unless @permission[:destroy]
+        raise Exception, "You do not have permission to destory this element"
+      end
+      if @db_address[:database].open?()
+        if @db_address[:database].structure_attached?(@uuid)
+          raise Exception, "Element is still linked to a structure.  Use <PersistedHash>.delete(<key>).destroy or <PersistedArray>.delete_at(<index>).destroy"
+        else
+          if self.write_reserved?()
+            address = @db_address
+            self.deactivate
+            address[:database].element_destroy(@credential, address[:table],address[:dbid])
+            # Review : consider either emptying the db trash now, or adding a flag to manifest records to prevent future loading.
+            result = true
+          else
+            raise Exception, "You do not have sufficient privileges to make this change"
+          end
+        end
+      else
+        raise Exception, "Database not available"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_pair(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1577
+def each_pair(&block)
+  collection = {}
+  @property_links.keys.each do |key|
+    collection[(key)] = (self[(key)])
+  end
+  if block.respond_to?(:call)
+    collection.to_enum(:each_pair).each do |key,value|
+      block.call(key,value)
+    end
+  else
+    collection.to_enum(:each_pair)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ element_version(key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 375
+def element_version(key=nil)
+  result = 0.0
+  if key.is_a?(::Symbol)
+    if key.to_s.size > 256
+      log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"})
+      key = key.to_s[(0..255)].to_sym
+    end
+    #
+    if @property_links[(key.to_s.to_sym)]
+      result = @property_links[(key.to_s.to_sym)][:record][:version]
+    else
+      result = 0.0
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ export(options={:exclude_file_segments=>false}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1492
+def export(options={:exclude_file_segments=>false})
+  if self.alive?
+    exclude_file_segments = (options[:exclude_file_segments] || false)
+    if options[:clone] == true
+      # Review : why are cloned objects unformatted? sync issues??
+      result = {:type => :element_hash, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.clone, :content => {}}
+    else
+      result = {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}}
+    end
+    export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+    children_of = Proc.new do |the_parent=nil|
+      list = []
+      export_db.each do |node|
+        if node[:parent].object_id == the_parent.object_id
+          list << node
+        end
+      end
+      list
+    end
+    # Build up export_db:
+    self.search do |the_value, the_selector, the_container|
+      if the_value.is_a?(::GxG::Database::PersistedHash)
+        if options[:clone] == true
+          the_uuid = GxG::uuid_generate.to_s.to_sym
+        else
+          the_uuid = the_value.uuid().clone
+        end
+        if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}}
+        end
+      end
+      if the_value.is_a?(::GxG::Database::PersistedArray)
+        if options[:clone] == true
+          the_uuid = GxG::uuid_generate.to_s.to_sym
+        else
+          the_uuid = the_value.uuid().clone
+        end
+        if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}}
+        end
+      end
+      if the_value.is_any?(::GxG::Database::Database::valid_field_classes())
+        data_type = GxG::Database::Database::element_table_for_instance(the_value)
+        case data_type
+        when :element_bigdecimal
+          data = the_value.to_s
+        when :element_datetime
+          data = the_value.to_s
+        when :element_binary
+          data = the_value.to_s.encode64
+        when :element_text
+          data = the_value.to_s
+        else
+          data = the_value
+        end              
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (the_container.version(the_selector) || 0.0), :content => data}}
+      end
+    end
+    # Collect children export content:
+    link_db = [(export_db[0])]
+    while link_db.size > 0 do
+      entry = link_db.shift
+      children_of.call(entry[:object]).each do |the_child|
+        entry[:record][:content][(the_child[:parent_selector])] = the_child[:record]
+        if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          link_db << the_child
+        end
+      end
+    end
+  else
+    result = nil
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ format() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 408
+def format()
+  @format.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ format=(the_format=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 412
+def format=(the_format=nil)
+  if GxG::valid_uuid?(the_format)
+    @format = the_format.to_s.to_sym
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_at_path(the_path="/") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1750
+def get_at_path(the_path="/")
+  result = nil
+  if the_path == "/"
+    result = self
+  else
+    object_stack = [(self)]
+    path_stack = the_path.split("/")
+    path_stack.to_enum.each do |path_element|
+      element = nil
+      if path_element.size > 0
+        if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          element = path_element.to_i
+        else
+          element = path_element
+          element.gsub!("%2f","/")
+          if element[0] == ":"
+            element = element[(1..-1)].to_sym
+          end
+        end
+      end
+      if element
+        result = object_stack.first[(element)]
+        if result.is_a?(NilClass)
+          break
+        else
+          object_stack.unshift(result)
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_delegate() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 608
+def get_delegate()
+  if self.alive?
+    if @delegate.is_any?(::String, ::Symbol)
+      @delegate.clone
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_permissions() + + click to toggle source + +
+ + +
+ +

Review: move to Database class as generic toolbox method at some point:

+ + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 432
+def get_permissions()
+  @permission.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 535
+def get_reservation()
+  result = false
+  # Review : Experimental design change - deprecate @delegate use, require entire structure (and sub-structures) to get solid @reservation (all of them) or this fails.
+  # Note : keep your structures small as this opens the door for stack-depth exceptions if it is nested too deep. (say 256 or less layers deep??)
+  if @reservation
+    result = true
+  else
+    if self.alive?
+      if @permission[:write]
+        clean_up_list = []
+        # Review : efficiency - write locks only apply to PersistedHashes and PersistedArrays so do we need a full manifest??
+        the_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write)
+        if the_reservation
+          #
+          @property_links.keys.each do |the_key|
+            if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+              clean_up_list << @property_links[(the_key)][:content]
+              unless @property_links[(the_key)][:content].get_reservation()
+                @db_address[:database].release_element_locks(the_reservation)
+                the_reservation = nil
+                break
+              end
+            end
+          end
+          #
+        end
+        if the_reservation
+          @reservation = the_reservation
+          result = true
+        else
+          clean_up_list.each do |the_object|
+            the_object.release_reservation
+          end
+        end
+      end
+    end
+  end
+  # xxx
+  # if (@reservation || @delegate)
+  #   result = true
+  # else
+  #   if self.alive?
+  #     if @permission[:write]
+  #       @reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write)
+  #       if @reservation
+  #         @property_links.keys.each do |the_key|
+  #           if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+  #             @property_links[(the_key)][:content].set_delegate((@reservation || @delegate))
+  #           end
+  #         end
+  #         result = true
+  #       end
+  #       #
+  #     end
+  #   end
+  # end
+  # xxx
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ import_update(the_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1470
+def import_update(the_record=nil)
+  if self.alive?
+    if the_record.is_a?(::Hash)
+      if @uuid == the_record[:uuid].to_s.to_sym
+        if @reservation
+          had_reservation = true
+          self.release_reservation
+        else
+          had_reservation = false
+        end
+        @db_address[:database].synchronize_records([{:operation => :merge, :data => the_record}],@credential)
+        @property_links = {}
+        load_property_links
+        if had_reservation
+          self.get_reservation
+        end
+      end
+    end
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ include?(the_key) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1357
+def include?(the_key)
+  @property_links.include?(the_key)
+end
+
+ +
+ + + + +
+ + +
+ +
+ increment_version() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 696
+def increment_version()
+  if self.alive?
+    if @db_address[:database].open?
+      if self.write_reserved?()
+        @version = (((@version + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 830
+def inspect()
+  # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with PersistedHash.
+  # TODO: make interative instead of re-entrant.
+  result = "(Not Loaded)"
+  if @db_address
+    if @db_address[:database].element_byte_size(@db_address[:table], @db_address[:dbid]) <= 10485760
+      # smaller than 10MB
+      inspection_db = []
+      last_key = @property_links.keys.last
+      result = "{"
+      @property_links.keys.each do |element_key|
+        if @property_links[(element_key)][:loaded] == true
+          result << (":#{element_key.to_s} => " << @property_links[(element_key)][:content].inspect)
+        else
+          result << (":#{element_key.to_s} => (Not Loaded)")
+        end
+        unless last_key == element_key
+          result << ", "
+        end
+      end
+      result << "}"
+    else
+      result = "(Too Damned Big)"
+    end
+    #
+    #
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ iterative(options={:include_inactive => true}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1591
+def iterative(options={:include_inactive => true}, &block)
+  result = []
+  visit = Proc.new do |the_node=nil, accumulator=[]|
+    node_stack = []
+    if the_node
+      node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)})
+      while (node_stack.size > 0) do
+        a_node = node_stack.shift
+        #
+        if a_node[:object].is_a?(::GxG::Database::PersistedHash)
+          if a_node[:object].alive?
+            a_node[:object].each_pair do |the_key, the_value|
+              node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value})
+            end
+          else
+            if options[:include_inactive]
+              accumulator << a_node
+            end
+          end
+        end
+        if a_node[:object].is_a?(::GxG::Database::PersistedArray)
+          if a_node[:object].alive?
+            a_node[:object].each_with_index do |the_value, the_index|
+              node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value})
+            end
+          else
+            if options[:include_inactive]
+              accumulator << a_node
+            end
+          end
+        end
+        # Review : is this for a Field??
+        if a_node.alive? || options[:include_inactive]
+          accumulator << a_node
+        end
+      end
+    end
+    accumulator
+  end
+  #
+  children_of = Proc.new do |the_db=[], the_parent=nil|
+    list = []
+    the_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  #
+  begin
+    database = visit.call(self,[])
+    link_db = children_of.call(database, self)
+    if block.respond_to?(:call)
+      while (link_db.size > 0) do
+        entry = link_db.shift
+        unless entry[:object].object_id == self.object_id
+          # calls with parameters: the_value, the_key/the_index (the_selector), the_container
+          raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent])
+          if raw_result
+            result << raw_result
+          end
+        end
+        if entry[:object].object_id != nil.object_id
+          children = children_of.call(database, entry[:object])
+          children.each do |child|
+            link_db << child
+          end
+        end
+      end
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ keys() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 976
+def keys()
+  @property_links.keys
+end
+
+ +
+ + + + +
+ + +
+ +
+ parent() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 418
+def parent()
+    @parent
+end
+
+ +
+ + + + +
+ + +
+ +
+ parent=(object=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 422
+def parent=(object=nil)
+  if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+    # Review : parent can only be set once -- is this best??
+    unless @parent
+      @parent = object
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(the_object=nil,base_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1683
+def paths_to(the_object=nil,base_path="")
+  # new idea here:
+  search_results = []
+  unless base_path[0] == "/"
+    base_path = ("/" << base_path)
+  end
+  if base_path.size > 1
+    path_stack = base_path.split("/")[1..-1].reverse
+  else
+    path_stack = []
+  end
+  origin = self.get_at_path(base_path)
+  container_stack = [{:selector => nil, :container => origin}]
+  find_container = Proc.new do |the_container|
+    result = nil
+    container_stack.each_with_index do |entry, index|
+      if entry[:container] == the_container
+        result = entry
+        break
+      end
+    end
+    result
+  end
+  last_container = origin
+  found = false
+  # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}}
+  if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    origin.process! do |the_value, selector, container|
+      if last_container.object_id != container.object_id
+        container_record = find_container.call(container)
+        if container_record
+          path_stack = container_record[:prefix].split("/").reverse
+          if path_stack.size == 0
+            path_stack << ""
+          end
+        end
+        last_container = container
+      end
+      if selector.is_a?(Symbol)
+        safe_key = (":" + selector.to_s)
+      else
+        safe_key = selector.to_s
+      end
+      safe_key.gsub!("/","%2f")
+      path_stack.unshift(safe_key)
+      # compare the_value
+      found = false
+      if (the_value == the_object)
+        found = true
+      end
+      if found
+        search_results << ("/" << path_stack.reverse.join("/"))
+      end
+      #
+      if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))})
+      end
+      path_stack.shift
+      #
+      nil
+    end
+  else
+    search_results << ("/" << path_stack.join("/"))
+  end
+  search_results
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(options={:include_inactive => true}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1670
+def process!(options={:include_inactive => true}, &block)
+  self.iterative(options, &block)
+  self
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ release_reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 516
+def release_reservation()
+  if self.alive?()
+    if @reservation
+      @db_address[:database].release_element_locks(@reservation)
+      @reservation = nil
+    end
+    if @delegate
+      @delegate = nil
+    end
+    # release write-locks of all sub-items
+    @property_links.keys.each do |the_key|
+      if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        @property_links[(the_key)][:content].release_reservation
+      end
+    end
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ reservation() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 496
+def reservation()
+  if self.alive?
+    if @reservation.is_any?(::String, ::Symbol)
+      @reservation.clone
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ save() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 904
+def save()
+  result = false
+  if self.alive?
+    begin
+      if @db_address[:database].open?()
+        @property_links.keys.each do |the_key|
+          # Note: only save property data and header if: a) data is still loaded and b) state has changed.
+          if @property_links[(the_key)][:loaded] == true
+            if @property_links[(the_key)][:state] != @property_links[(the_key)][:content].hash
+              @property_links[(the_key)][:record][:version] = (((@property_links[(the_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0)
+              property_write(the_key)
+            end
+          end
+          #
+        end
+      else
+        raise Exception, "Database not available"
+      end
+      self.save_version
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ save_version() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 706
+def save_version()
+  @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:version => @version})
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ set_at_path(the_path="/",the_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1786
+def set_at_path(the_path="/",the_value=nil)
+  result = nil
+  if the_path != "/"
+    container = self.get_at_path(::File::dirname(the_path))
+    if container
+      raw_selector = ::File::basename(the_path)
+      selector = nil
+      if raw_selector.size > 0
+        if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          selector = raw_selector.to_i
+        else
+          selector = raw_selector
+          selector.gsub!("%2f","/")
+          if selector[0] == ":"
+            selector = selector[(1..-1)].to_sym
+          end
+        end
+      end
+      if selector
+        container[(selector)] = the_value
+        result = container[(selector)]
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+      #
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_delegate(the_delegate=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 616
+def set_delegate(the_delegate=nil)
+  if self.alive?
+    if the_delegate.is_any?([::String, ::Symbol])
+      @delegate = the_delegate.to_sym
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_element_version(element_key, the_version=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 393
+def set_element_version(element_key, the_version=nil)
+  result = false
+  if @property_links[(element_key)]
+    if the_version.is_a?(::Numeric)
+      @property_links[(element_key)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+      result = true
+    else
+      log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for #{element_key.inspect} on Object #{@uuid.inspect}")
+    end
+  else
+    log_warning("Attempted to set version with an invalid key #{element_key.inspect} on Object #{@uuid.inspect}")
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_format(format_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 624
+def set_format(format_uuid=nil)
+  if self.alive?
+    unless @format
+      if ::GxG::valid_uuid?(format_uuid)
+        @format = format_uuid.to_sym
+        @db_address[:database].connector()[:element_hash].filter({:dbid => (@db_address[:dbid])}).update({:format => (@format.to_s)})
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_parent(object=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 678
+def set_parent(object=nil)
+  if self.alive?
+    if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray])
+      unless @parent
+        @parent = object
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_permissions(credential=nil, the_permission=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 436
+def set_permissions(credential=nil, the_permission=nil)
+  if self.alive?
+    if credential.is_a?(::Hash) && the_permission.is_a?(::Hash)
+      if credential[:user]
+        if credential[:user] == "public" || credential[:user] == "Public"
+          credential = "00000000-0000-4000-0000-000000000000"
+        else
+          credential = (@db_address[:database].user_fetch({:user_id => credential[:user].to_s}) || {})[:uuid]
+        end
+        if credential
+          credential = credential.to_s.to_sym
+        end
+      else
+        if credential[:group]
+          credential = @db_address[:database].group_credential(credential)
+        else
+          credential = nil
+        end
+      end
+    else
+      if credential.is_a?(::Hash) && the_permission == nil
+        the_permission = credential
+        credential = @credential
+      else
+        unless credential
+          credential = @credential
+        end
+      end
+    end
+    if the_permission.is_a?(::Hash) && ::GxG::valid_uuid?(credential)
+      manifest = []
+      structures = [{:table => @db_address[:table], :dbid => @db_address[:dbid]}]
+      while structures.size > 0 do
+        entry = structures[0]
+        @db_address[:database].element_manifest(entry[:table],entry[:dbid]).each do |record|
+          if [:element_hash, :element_array].include?(record[:table])
+            unless structures.include?(record)
+              structures << record
+            end
+          else
+            # Review - only set/get/check permissions on persisted arrays/hashes
+            # unless manifest.include?(record)
+            #   manifest << record
+            # end
+          end
+        end
+        manifest << structures.shift
+      end
+      manifest.each do |record|
+        @db_address[:database].assign_element_permission(record[:table], record[:dbid], credential, the_permission)
+      end
+      #
+      true
+    else
+      log_error({:error => Exception.new("You MUST provide a valid permissions Hash."), :parameters => {:credential => credential, :permission => the_permission}})
+      false
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_title(the_title=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 721
+def set_title(the_title=nil)
+  if self.alive?
+    if @db_address[:database].open?
+      if self.write_reserved?()
+        if the_title
+          @title = the_title.to_s[0..255]
+          @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:title => @title})
+          self.increment_version
+        end
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_version(the_version=0.0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 710
+def set_version(the_version=0.0)
+  if self.write_reserved?()
+    @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+    self.save_version
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ size() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 965
+def size()
+  @property_links.size
+end
+
+ +
+ + + + +
+ + +
+ +
+ structure_attached?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 969
+def structure_attached?()
+  @db_address[:database].structure_attached?(@uuid.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+ structure_detach() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 972
+def structure_detach()
+  @db_address[:database].structure_detach(@uuid.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+ sync_export(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1572
+def sync_export(options={})
+  self.save
+  @db_address[:database].sync_export(@credential, [(@uuid)], options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ title() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 354
+def title()
+    @title.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ title=(the_title=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 358
+def title=(the_title=nil)
+  if the_title
+    @title = the_title.to_s[0..255]
+    self.increment_version
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ ufs() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 639
+def ufs()
+  if @format
+    record = @db_address[:database].format_load({:uuid => @format.to_s})
+    if record
+      record[:ufs]
+    else
+      ""
+    end
+  else
+    ""
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ unload(key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1277
+def unload(key=nil)
+  result = false
+  # if key exists
+  # if key is loaded, unload element (if String or ByteArray)
+  if self.alive?()
+    if @db_address[:database].open?()
+      if key.is_a?(::Symbol)
+        if key.to_s.size > 256
+          log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"})
+          key = key.to_s[(0..255)].to_sym
+        end
+        property_key = key.to_s.to_sym
+        if @property_links[(property_key)]
+          if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray)
+            if @property_links[(property_key)][:loaded] == true
+              # ### Unload Base Element contents from memory, leaving reference harness intact and active.
+              @property_links[(property_key)][:content].clear
+              @property_links[(property_key)][:loaded] = false
+            end
+          end
+          result = true
+        end
+      else
+        raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}"
+      end
+    else
+      raise Exception, "Database not available"
+    end
+  else
+    raise Exception, "Attempted to access a defunct structure"
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ unpersist() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 1428
+def unpersist()
+  result = {}
+  if self.alive?
+    #
+    export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+    children_of = Proc.new do |the_parent=nil|
+      list = []
+      export_db.each do |node|
+        if node[:parent].object_id == the_parent.object_id
+          list << node
+        end
+      end
+      list
+    end
+    # Build up export_db:
+    self.search do |the_value, the_selector, the_container|
+      if the_value.is_a?(::GxG::Database::PersistedHash)
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}}
+      else
+        if the_value.is_a?(::GxG::Database::PersistedArray)
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []}
+        else
+          export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value}
+        end
+      end
+    end
+    # Collect children export content:
+    link_db =[(export_db[0])]
+    while link_db.size > 0 do
+      entry = link_db.shift
+      children_of.call(entry[:object]).each do |the_child|
+        entry[:record][(the_child[:parent_selector])] = the_child[:record]
+        if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+          link_db << the_child
+        end
+      end
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ +

Review : favor the following more Ruby-like accessors over the set_xxx style methods, eliminate older duplicate methods.

+ + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 344
+def uuid()
+    @uuid.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid=(the_uuid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 348
+def uuid=(the_uuid=nil)
+  if GxG::valid_uuid?(the_uuid)
+    @uuid = the_uuid.to_s.to_sym
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ version() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 365
+def version()
+  @version.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ version=(the_version=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 369
+def version=(the_version=nil)
+  if the_version.is_a?(::Numeric)
+    @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ wait_for_reservation(timeout=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 595
+def wait_for_reservation(timeout=nil)
+  result = true
+  the_timeout = Time.now.to_f + (timeout || 30.0).to_f
+  until self.get_reservation() == true do
+    sleep 1.0
+    if Time.now.to_f >= the_timeout
+      result = false
+      break
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_permission?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 504
+def write_permission?()
+  @permission[:write]
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_reserved?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_database_persistedhash.rb, line 508
+def write_reserved?()
+  if @reservation or @delegate
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Engine.html b/doc/GxG/Engine.html new file mode 100644 index 0000000..367a5fc --- /dev/null +++ b/doc/GxG/Engine.html @@ -0,0 +1,3067 @@ + + + + + + +module GxG::Engine - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Engine +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ [](the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 49
+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
+
+ +
+ + + + +
+ + +
+ +
+ abort(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 252
+def self.abort(*args)
+  ::Process::abort(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ available_event_descriptors() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 758
+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
+
+ +
+ + + + +
+ + +
+ +
+ available_file_descriptors() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 729
+def self.available_file_descriptors()
+  (::GxG::Engine::maximum_file_descriptors() - ::GxG::Engine::file_descriptors().size)
+end
+
+ +
+ + + + +
+ + +
+ +
+ daemon(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 383
+def self.daemon(*args)
+  ::Process::daemon(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ descriptor_heap_used() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 687
+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
+
+ +
+ + + + +
+ + +
+ +
+ descriptor_limits() + + click to toggle source + +
+ + +
+ +

Descriptors in general

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 683
+def self.descriptor_limits()
+  {:events => @@details[:events][:maximum_event_descriptors], :files => @@details[:files][:maximum_file_descriptors]}.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ detach(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 280
+def self.detach(*args)
+  ::Process::detach(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ detail_mapping() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 45
+def self.detail_mapping()
+  @@details[:key_map]
+end
+
+ +
+ + + + +
+ + +
+ +
+ determine_engine_load() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 155
+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
+
+ +
+ + + + +
+ + +
+ +
+ determine_event_allocations() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 794
+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
+
+ +
+ + + + +
+ + +
+ +
+ determine_loads() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 190
+def self.determine_loads()
+  ::GxG::Engine::determine_memory_load()
+  ::GxG::Engine::determine_engine_load()
+end
+
+ +
+ + + + +
+ + +
+ +
+ determine_memory_load() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 122
+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
+
+ +
+ + + + +
+ + +
+ +
+ egid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 365
+def self.egid(*args)
+  ::Process::egid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ engine_data_keys(other_platform=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 520
+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
+
+ +
+ + + + +
+ + +
+ +
+ engine_gear() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 195
+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
+
+ +
+ + + + +
+ + +
+ +
+ engine_load() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 116
+def self.engine_load()
+  @@thread_safety.synchronize {
+    @@details[:engine_load][:percent]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ engine_load_high?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 98
+def self.engine_load_high?()
+  @@thread_safety.synchronize {
+    @@details[:engine_load][:high]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ engine_load_low?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 110
+def self.engine_load_low?()
+  @@thread_safety.synchronize {
+    @@details[:engine_load][:low]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ engine_load_medium?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 104
+def self.engine_load_medium?()
+  @@thread_safety.synchronize {
+    @@details[:engine_load][:medium]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ euid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 361
+def self.euid(*args)
+  ::Process::euid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ event_allocation_envelope() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 820
+def self.event_allocation_envelope()
+  result = (20..100)
+  @@thread_safety.synchronize {
+    result = @@details[:events][:event_descriptors_envelope]
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ event_descriptors_threshold() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 738
+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
+
+ +
+ + + + +
+ + +
+ +
+ event_minimum_threshold() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 745
+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
+
+ +
+ + + + +
+ + +
+ +
+ events_in_use() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 752
+def self.events_in_use()
+  @@thread_safety.synchronize {
+    @@event_descriptors
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ exec(*args) + + click to toggle source + +
+ + +
+ +

overrides

+ +

[:exec, :fork, :spawn, :exit!, :exit, :abort, :kill, :wait, :wait2, :waitpid, :waitpid2, :waitall, :detach, :pid, :ppid, .

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 232
+def self.exec(*args)
+  ::Process::exec(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ exit(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 248
+def self.exit(*args)
+  ::Process::exit(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ exit!(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 244
+def self.exit!(*args)
+  ::Process::exit!(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ file_descriptors() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 711
+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
+
+ +
+ + + + +
+ + +
+ +
+ fork(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 236
+def self.fork(*args)
+  ::Process::fork(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ gear() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 223
+def self.gear()
+  "#{::GxG::Engine::engine_gear().to_s}_#{::GxG::Engine::memory_gear().to_s}".to_sym
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_engine_data(keys=[]) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 553
+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
+
+ +
+ + + + +
+ + +
+ +
+ getpgid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 302
+def self.getpgid(*args)
+  ::Process::getpgid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ getpgrp(*args) + + click to toggle source + +
+ + +
+ +

:getpgrp, :setpgrp, :getpgid, :setpgid, :setsid, :getpriority, :setpriority, :getrlimit, :setrlimit, :uid, :gid, :euid, :egid,

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 294
+def self.getpgrp(*args)
+  ::Process::getpgrp(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ getpriority(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 314
+def self.getpriority(*args)
+  ::Process::getpriority(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ getrlimit(key) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 322
+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
+
+ +
+ + + + +
+ + +
+ +
+ gid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 357
+def self.gid(*args)
+  ::Process::gid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ groups(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 375
+def self.groups(*args)
+  ::Process::groups(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ initgroups(*args) + + click to toggle source + +
+ + +
+ +

:initgroups, :groups, :maxgroups, :daemon, :times]

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 371
+def self.initgroups(*args)
+  ::Process::initgroups(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ kill(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 256
+def self.kill(*args)
+  ::Process::kill(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ maxgroups(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 379
+def self.maxgroups(*args)
+  ::Process::maxgroups(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ maximum_event_descriptors() + + click to toggle source + +
+ + +
+ +

Event Descriptors

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 734
+def self.maximum_event_descriptors()
+  ::GxG::Engine::descriptor_limits()[:events]
+end
+
+ +
+ + + + +
+ + +
+ +
+ maximum_file_descriptors() + + click to toggle source + +
+ + +
+ +

File Descriptors

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 707
+def self.maximum_file_descriptors()
+    ::GxG::Engine::descriptor_limits()[:files]
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_available() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 481
+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
+
+ +
+ + + + +
+ + +
+ +
+ memory_gear() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 209
+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
+
+ +
+ + + + +
+ + +
+ +
+ memory_load() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 92
+def self.memory_load()
+  @@thread_safety.synchronize {
+    @@details[:memory_load][:percent]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_load_high?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 74
+def self.memory_load_high?()
+  @@thread_safety.synchronize {
+    @@details[:memory_load][:high]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_load_low?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 86
+def self.memory_load_low?()
+  @@thread_safety.synchronize {
+    @@details[:memory_load][:low]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_load_medium?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 80
+def self.memory_load_medium?()
+  @@thread_safety.synchronize {
+    @@details[:memory_load][:medium]
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_used() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 415
+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
+
+ +
+ + + + +
+ + +
+ +
+ object_memory_used() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 399
+def self.object_memory_used()
+  #
+  slots = 0
+  other = 0
+  ::ObjectSpace.each_object 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
+
+ +
+ + + + +
+ + +
+ +
+ pid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 284
+def self.pid(*args)
+  ::Process::pid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ ppid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 288
+def self.ppid(*args)
+  ::Process::ppid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ profile() + + click to toggle source + +
+ + +
+ +

extensions

+ + + + +
+
# File lib/gxg/gxg_engine.rb, line 393
+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
+
+ +
+ + + + +
+ + +
+ +
+ release_event_descriptor() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 783
+def self.release_event_descriptor()
+  result = false
+  @@thread_safety.synchronize {
+    if @@event_descriptors > 0
+      @@event_descriptors -= 1
+    end
+  }
+  result = true
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ reserve_event_descriptor() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 770
+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
+
+ +
+ + + + +
+ + +
+ +
+ set_engine_data(keys=[]) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 672
+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
+
+ +
+ + + + +
+ + +
+ +
+ setpgid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 306
+def self.setpgid(*args)
+  ::Process::setpgid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ setpgrp(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 298
+def self.setpgrp(*args)
+  ::Process::setpgrp(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ setpriority(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 318
+def self.setpriority(*args)
+  ::Process::setpriority(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ setrlimit(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 349
+def self.setrlimit(*args)
+  ::Process::setrlimit(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ setsid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 310
+def self.setsid(*args)
+  ::Process::setsid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ spawn(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 240
+def self.spawn(*args)
+  ::Process::spawn(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ times(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 387
+def self.times(*args)
+  ::Process::times(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ uid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 353
+def self.uid(*args)
+  ::Process::uid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ wait(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 260
+def self.wait(*args)
+  ::Process::wait(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ wait2(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 264
+def self.wait2(*args)
+  ::Process::wait2(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ waitall(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 276
+def self.waitall(*args)
+  ::Process::waitall(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ waitpid(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 268
+def self.waitpid(*args)
+  ::Process::waitpid(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ waitpid2(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_engine.rb, line 272
+def self.waitpid2(*args)
+  ::Process::waitpid2(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Entity.html b/doc/GxG/Entity.html new file mode 100644 index 0000000..c0e1c7d --- /dev/null +++ b/doc/GxG/Entity.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Entity - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Entity +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Entity/LocalSystem.html b/doc/GxG/Entity/LocalSystem.html new file mode 100644 index 0000000..dcb8f63 --- /dev/null +++ b/doc/GxG/Entity/LocalSystem.html @@ -0,0 +1,2779 @@ + + + + + + +class GxG::Entity::LocalSystem - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Entity::LocalSystem +

+ +
+ +

SysInfo provide default filesystem object

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(bootstrap_data={}) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_entities.rb, line 15
+def initialize(bootstrap_data={})
+  # Rather than only using RUBY_PLATFORM, I want to define the *specific* environment on that platform to help further characterize what is at hand.
+  super()
+  # @thread_safety = ::Mutex.new
+  @thread_safety = ::Mutex.new
+  @details = {}
+  #
+  @details[:architecture] = bootstrap_data.delete(:architecture)
+  #
+  # ### Process bootstrap_data regarding platform and environment
+  @details[:platform] = bootstrap_data.delete(:platform)
+  @details[:platform_version] = GxG::Version.new(bootstrap_data.delete(:platform_version))
+  @details[:platform_configuration] = {:process => {}, :flags => {}}
+  @details[:environment] = bootstrap_data.delete(:environment)
+  @details[:environment_variant] = bootstrap_data.delete(:environment_variant)
+  if bootstrap_data[:environment_version]
+    @details[:environment_version] = GxG::Version.new(bootstrap_data.delete(:environment_version))
+  else
+    @details[:environment_version] = nil
+  end
+  @details[:environment_configuration] = {}
+  #
+  # ### Process CPU info
+  @details[:processors] = bootstrap_data.delete(:processors)
+  @details[:processors].process! do |item, selector, container|
+    if selector.is_any?(::String, ::Symbol)
+      # hash/struct processing
+      if selector.is_a?(Symbol)
+        key = selector
+      else
+        key = selector.to_s.to_sym
+      end
+      # massage data by key
+      # [:processor, :vendor_id, :cpu_family, :model, :model_name, :stepping, :cpu_mhz, :cache_size, :physical_id, :siblings, :core_id, :cpu_cores, :apicid, :initial_apicid, ::fpu, :fpu_exception, :cpuid_level, :wp, :flags, :bogomips, :clflush_size, :cache_alignment, :address_sizes, :power_management]
+      entry = nil
+      case key
+      when :processor, :cpu_family, :model, :stepping, :physical_id, :siblings, :core_id, :cpu_cores, :apicid, :initial_apicid, :cpuid_level, :clflush_size, :cache_alignment
+        entry = item.to_s.numeric_values()
+        if entry.is_a?(Hash)
+          item = entry[:integer]
+        end
+      when :cpu_mhz, :bogomips
+        entry = item.to_s.numeric_values()
+        if entry.is_a?(Hash)
+          item = entry[:float]
+        end
+      when :cache_size
+        entry = item.to_s.numeric_values([:byte])
+        if entry.is_a?(Hash)
+          item = entry[:byte]
+        end
+      when :address_sizes
+        entry = item.to_s.numeric_values([:bit],",")
+        if entry.is_a?(Array)
+          item = {:physical_bits => (entry[0][:bit] || entry[0][:integer]), :virtual_bits => (entry[1][:bit] || entry[1][:integer])}
+        end
+      when :flags
+        item = item.to_s.split(" ").to_enum.map do |flag|
+          flag.to_sym
+        end
+      end
+      if item
+        if container.is_a?(::Hash)
+          container.delete(selector)
+          container[(key)] = item
+        end
+      end
+    end
+    nil
+  end
+  # TODO: Build up a list of flags that the cpu supports and are *actually* in use (i.e. PAE, ...)
+  @details[:platform_flags] = {}
+  # Gather platform kernel configuration data for processing.
+  case @details[:platform]
+  when :linux
+    release = (`uname -r`.split("\n")[0])
+    likely_paths = ["/proc/config.gz",("/lib/modules/#{release}/build/.config"),("/usr/src/linux-#{release}/.config"),("/usr/src/linux-#{release}/configs/#{release}-#{@details[:architecture]}.config"),("/boot/config-#{release}")]
+    raw_data = ""
+    likely_paths.to_enum.each do |the_path|
+      if ::File.exist?(the_path)
+        # Note: at this point in everything it is ok for file io that blocks.
+        if the_path == "/proc/config.gz"
+          ::Zlib::GzipReader.open(the_path) { |gz|  raw_data = gz.read }
+        else
+          ::File.open(the_path, "r") { |i|  raw_data = i.read }
+        end
+        break
+      end
+    end
+    # process raw_data (each line)
+    raw_flags = {}
+    raw_data.to_enum(:each_line).each do |line_text|
+      flag = nil
+      setting = nil
+      if line_text.size > 0
+        if line_text[0] == "#"
+          if line_text.include?("is not set")
+            # cull unset thing and make its value nil.
+            flag = line_text[1..-1].scan(/[A-Z_]*/)[0]
+            if flag.size > 0
+              # exclude redundant 'CONFIG' portion
+              flag = flag.split("_")[1..-1].join("_")
+            else
+              flag = nil
+            end
+          end
+        else
+          flag, setting = line_text.split("=")
+          # exclude redundant 'CONFIG' portion
+          flag = flag.split("_")[1..-1].join("_")
+          if (setting == "y" || setting == "y\n")
+            setting = true
+          end
+          if (setting == "n" || setting == "n\n")
+            setting = false
+          end
+        end
+      end
+      if (flag)
+        raw_flags[(flag)] = setting
+      end
+    end
+    raw_data = nil
+    # Note: I am *loathe* to use strings instead of symbols in hashes generally, but I don't like what ruby does
+    # with symbols and memory for temporary data sets (looking at you matz and my slapping hand twitches).
+    universal_synonyms = {"X86_PAE" => "physical_address_extension"}
+    universal_synonyms["X86_32_SMP"] = "symetric_multiprocessing"
+    universal_synonyms["X86_64_SMP"] = "symetric_multiprocessing"
+    universal_synonyms["X86_HT"] = "hyperthreading"
+    universal_synonyms["SWAP"] = "virtual_memory"
+    #
+    raw_flags.each_pair do |selector,value|
+      #
+      if universal_synonyms[(selector)]
+        # include the flag ONLY if it is actually enabled (in use) on this build of the kernel
+        if value
+          @details[:platform_configuration][:flags][(universal_synonyms[(selector)].to_sym)] = value
+        end
+      end
+    end
+    # end linux area
+  end
+  #
+  # ### Memory crap:
+  #
+  # Set computational and storage/memory limits by platform and environment:
+  # See (with respect to Ruby) : http://www.rubynotes.net/uk/operating-systems/windows/item/82-physical-memory-limits.html
+  # Note: RVALUE size (consumption size) is determined in a completely separate manner than the limit size (according to cpu virtual addressing bits used).
+  # default for 32 bit OS used:
+  system_memory = self.memory()
+  per_process_max = 2147483648
+  case @details[:platform]
+  when :linux
+    # for :linux : See http://linuxfollies.blogspot.com/2010/10/linux-memory-limits-rlimits.html
+    # Note: Process::getrlimit("AS") does not reflect the reality of total ram + total swap space as a limit.
+    if @details[:processors][0][:address_sizes].is_a?(::Hash)
+      if @details[:processors][0][:address_sizes][:virtual_bits] == 32
+        if @details[:platform_configuration][:flags][:physical_address_extension]
+          # ??? what does that do to the memory limit under 32 bits
+          # 3.12GB rumored: 3350074490.88 (rounded-down)
+          per_process_max = 3350074490
+        else
+          per_process_max = 3221225472
+        end
+      end
+    end
+    if @details[:processors][0][:address_sizes].is_a?(::Hash)
+      if @details[:processors][0][:address_sizes][:virtual_bits] == 64
+        # odd thing: they built 64bit computers that only have slots enough for 2GB max ram (imagine!)
+        # So... to deal with this dilemma, I'm going to set the per_process_max to system_memory[:actual][:total] (:actual=RAM) unless it is really a full 8TB+.
+        if system_memory[:actual][:total] >= 8796093022208
+          per_process_max = 8796093022208
+        else
+          per_process_max = system_memory[:actual][:total]
+        end
+      end
+    else
+      # virtual bits reading NOT supported:
+      if ([ -1 ].pack('l!').length * 8) == 32
+        if @details[:platform_configuration][:flags][:physical_address_extension]
+          # ??? what does that do to the memory limit under 32 bits
+          # 3.12GB rumored: 3350074490.88 (rounded-down)
+          per_process_max = 3350074490
+        else
+          per_process_max = 3221225472
+        end
+      end
+      if ([ -1 ].pack('l!').length * 8) == 64
+        if system_memory[:actual][:total] >= 8796093022208
+          per_process_max = 8796093022208
+        else
+          per_process_max = system_memory[:actual][:total]
+        end
+      end
+    end
+  when :windows
+    # for :windows : See http://msdn.microsoft.com/en-us/library/windows/desktop/aa366778%28v=vs.85%29.aspx
+    # And "4GB-tuning' at http://msdn.microsoft.com/en-us/library/windows/desktop/bb613473%28v=vs.85%29.aspx
+    # And "BCDEdit /set' at http://msdn.microsoft.com/en-us/library/ff542202.aspx
+    # TODO: :windows : find a way to test if IMAGE_FILE_LARGE_ADDRESS_AWARE is set, whether os runs in PAE mode, and
+    # what BCDEdit /set Increaseuserva (gigabytes-as-megabytes) was set to. (kernel_mode address space = 4GB-(2048MB-to-3072MB value of it))
+    # on infamous /3GB switch in boot.ini : http://www.ditii.com/2007/04/14/memory-management-demystifying-3gb/
+    if @details[:processors][0][:address_sizes][:virtual_bits] == 32
+      # :windows 32-bit : 2GB per process memory limit (non-contiguous-only)
+      per_process_max = 2147483648
+    end
+    if @details[:processors][0][:address_sizes][:virtual_bits] == 64
+      # :if IMAGE_FILE_LARGE_ADDRESS_AWARE is set on process image, then 8TB max per 64-bit process, except on Intel Itanium-based systems (7TB)
+      # otherwise (if unset, and since we have no F'n way to tell within ruby):
+      per_process_max = 2147483648
+    end
+  end
+  #
+  if system_memory[:total][:total] >= per_process_max
+    @details[:memory_limits]={:process => ((per_process_max)..(system_memory[:total][:total]))}
+  else
+    @details[:memory_limits]={:process => ((system_memory[:total][:total])..(system_memory[:total][:total]))}
+  end
+  # stock setrlimit will ignore the 'hard-wired' limit reset to actual available memory (both kinds), but the overridden GxG::Engine::getrlimit(:AS) will respect it.
+  # GxG::Engine::memory_available() is built around these limits to ensure you do not encounter an issue without some warning (no Exabyte delusions).
+  # ::Process::setrlimit(:AS,@details[:memory_limits][:process].min,@details[:memory_limits][:process].max)
+  #
+  # ### Initial Buffer Sizes
+  # TODO: find a clever way to determine the platform's maximum buffer size (after linux 2.6.14 (I think) buffers are now 16x4KiB).
+  @details[:memory_limits][:buffers] = {:default => 4096, :terminal => 1024,:socket => {:ipc => {}}, :ipv4 => {:tcp => {}, :udp => {}}}
+  # Attribution: http://www.psc.edu/networking/projects/tcptune/#detailed and
+  # http://www.psc.edu/networking/projects/tcptune/#tutorial
+  #
+  case @details[:platform]
+  when :linux
+    # TODO: provide for better version comparisons: create >, <. etc methods that accept a string to interperet or a value instance.
+    # See: http://www.pixelbeat.org/programming/stdio_buffering/
+    platform_version = @details[:platform_version].value
+    if platform_version[:major] > 2
+      @details[:memory_limits][:buffers][:default] = 65536
+    end
+    if platform_version[:major] == 2
+      if platform_version[:minor] >= 6
+        if platform_version[:teeny] >= 14
+          @details[:memory_limits][:buffers][:default] = 65536
+        end
+      end
+    end
+  end
+  #
+  # ### The Ruby runtime itself
+  # Now defined in GxG::Engine as a chance to correct some 'free-form-jazz-ensemble-a-la-SpinalTap' stuff from the guys at Ruby Core! :)
+  #
+  # ### Network stuff
+  # node (or host) name:
+  @details[:nodename] = ::Socket.gethostname
+  @details[:network] = {}
+  begin
+    address = ::TCPSocket.getaddress(@details[:nodename])
+  rescue Exception
+    # raise Exception, "You need to edit your host file, and add an alias for this host."
+    # exit 1
+    address = nil
+  end
+  # netstat --interfaces --all --verbose --extend
+  #
+  @details[:network][:hostname] = @details[:nodename]
+  # TODO: @gather localhost info: convert ip address info to std. ip_addr objects
+  @details[:network][:address] = address
+  @details[:network][:hosts] = []
+  #        raw_list = Sys::Host.info
+  #        raw_list.to_enum.each do |entry|
+  #          new_entry = {}
+  #          entry.each_pair do |key,item|
+  #            # TODO: @gather hosts info: convert ip address info to std. ip_addr objects
+  #            new_entry[(key.to_sym)] = item
+  #          end
+  #          @details[:network][:hosts] << new_entry
+  #          #
+  #        end
+  #
+  # TODO: inventory each network interface and host address of the system
+  # then, attach active, kernel supported, protocols
+  # then, use self["a/setting/path"] to set the data for each setting of each protocol of each nic that is supported and active.
+  protocols = {
+    :ipv4=>{:settings=>{:tcp => {}, :udp => {}}, :aliases=>["IP"], :proto=>0},
+    :icmp=>{:settings=>{}, :aliases=>["ICMP"], :proto=>1},
+    :igmp=>{:settings=>{}, :aliases=>["IGMP"], :proto=>2},
+    :ggp=>{:settings=>{}, :aliases=>["GGP"], :proto=>3},
+    :ipencap=>{:settings=>{}, :aliases=>["IP-ENCAP"], :proto=>4},
+    :st=>{:settings=>{}, :aliases=>["ST"], :proto=>5},
+    :tcp=>{:settings=>{}, :aliases=>["TCP"], :proto=>6},
+    :egp=>{:settings=>{}, :aliases=>["EGP"], :proto=>8},
+    :igp=>{:settings=>{}, :aliases=>["IGP"], :proto=>9},
+    :pup=>{:settings=>{}, :aliases=>["PUP"], :proto=>12},
+    :udp=>{:settings=>{}, :aliases=>["UDP"], :proto=>17},
+    :hmp=>{:settings=>{}, :aliases=>["HMP"], :proto=>20},
+    :"xns-idp"=>{:settings=>{}, :aliases=>["XNS-IDP"], :proto=>22},
+    :rdp=>{:settings=>{}, :aliases=>["RDP"], :proto=>27},
+    :"iso-tp4"=>{:settings=>{}, :aliases=>["ISO-TP4"], :proto=>29},
+    :dccp=>{:settings=>{}, :aliases=>["DCCP"], :proto=>33},
+    :xtp=>{:settings=>{}, :aliases=>["XTP"], :proto=>36},
+    :ddp=>{:settings=>{}, :aliases=>["DDP"], :proto=>37},
+    :"idpr-cmtp"=>{:settings=>{}, :aliases=>["IDPR-CMTP"], :proto=>38},
+    :ipv6=>{:settings=>{:tcp=>{}, :udp=>{}}, :aliases=>["IPv6"], :proto=>41},
+    :"ipv6-route"=>{:settings=>{}, :aliases=>["IPv6-Route"], :proto=>43},
+    :"ipv6-frag"=>{:settings=>{}, :aliases=>["IPv6-Frag"], :proto=>44},
+    :idrp=>{:settings=>{}, :aliases=>["IDRP"], :proto=>45},
+    :rsvp=>{:settings=>{}, :aliases=>["RSVP"], :proto=>46},
+    :gre=>{:settings=>{}, :aliases=>["GRE"], :proto=>47},
+    :esp=>{:settings=>{}, :aliases=>["IPSEC-ESP"], :proto=>50},
+    :ah=>{:settings=>{}, :aliases=>["IPSEC-AH"], :proto=>51},
+    :skip=>{:settings=>{}, :aliases=>["SKIP"], :proto=>57},
+    :"ipv6-icmp"=>{:settings=>{}, :aliases=>["IPv6-ICMP"], :proto=>58},
+    :"ipv6-nonxt"=>{:settings=>{}, :aliases=>["IPv6-NoNxt"], :proto=>59},
+    :"ipv6-opts"=>{:settings=>{}, :aliases=>["IPv6-Opts"], :proto=>60},
+    :rspf=>{:settings=>{}, :aliases=>["RSPF", "CPHB"], :proto=>73},
+    :vmtp=>{:settings=>{}, :aliases=>["VMTP"], :proto=>81},
+    :eigrp=>{:settings=>{}, :aliases=>["EIGRP"], :proto=>88},
+    :ospf=>{:settings=>{}, :aliases=>["OSPFIGP"], :proto=>89},
+    :"ax.25"=>{:settings=>{}, :aliases=>["AX.25"], :proto=>93},
+    :ipip=>{:settings=>{}, :aliases=>["IPIP"], :proto=>94},
+    :etherip=>{:settings=>{}, :aliases=>["ETHERIP"], :proto=>97},
+    :encap=>{:settings=>{}, :aliases=>["ENCAP"], :proto=>98},
+    :pim=>{:settings=>{}, :aliases=>["PIM"], :proto=>103},
+    :ipcomp=>{:settings=>{}, :aliases=>["IPCOMP"], :proto=>108},
+    :vrrp=>{:settings=>{}, :aliases=>["VRRP"], :proto=>112},
+    :l2tp=>{:settings=>{}, :aliases=>["L2TP"], :proto=>115},
+    :isis=>{:settings=>{}, :aliases=>["ISIS"], :proto=>124},
+    :sctp=>{:settings=>{}, :aliases=>["SCTP"], :proto=>132},
+    :fc=>{:settings=>{}, :aliases=>["FC"], :proto=>133},
+    :"mobility-header"=>{:settings=>{}, :aliases=>["Mobility-Header"], :proto=>135},
+    :udplite=>{:settings=>{}, :aliases=>["UDPLite"], :proto=>136},
+    :"mpls-in-ip"=>{:settings=>{}, :aliases=>["MPLS-in-IP"], :proto=>137},
+    :manet=>{:settings=>{}, :aliases=>[], :proto=>138},
+    :hip=>{:settings=>{}, :aliases=>["HIP"], :proto=>139},
+    :shim6=>{:settings=>{}, :aliases=>["Shim6"], :proto=>140},
+    :wesp=>{:settings=>{}, :aliases=>["WESP"], :proto=>141},
+    :rohc=>{:settings=>{}, :aliases=>["ROHC"], :proto=>142}
+    }
+  #
+  @details[:network][:protocols] = protocols
+  #        if @details[:platform] == :windows
+  #          # TODO: find suitable alternative to Net::Proto.getprotoent (breaks under rbx)
+  #        else
+  #          raw_list = Net::Proto.getprotoent
+  #          raw_list.to_enum.each do |entry|
+  #            new_entry = {:settings => {}}
+  #            proto_name = :unknown
+  #            entry.each_pair do |key,item|
+  #              if key.to_s.to_sym == :name
+  #                if item.to_s.to_sym == :ip
+  #                  proto_name = :ipv4
+  #                else
+  #                  proto_name = item.to_s.to_sym
+  #                end
+  #              else
+  #                new_entry[(key)] = item
+  #              end
+  #            end
+  #            if [:ipv4,:ipv6].include?(proto_name)
+  #              new_entry[:settings][:tcp] = {}
+  #              new_entry[:settings][:udp] = {}
+  #            end
+  #            @details[:network][:protocols][(proto_name)] = new_entry
+  #            #
+  #          end
+  #        end
+  # Read in Sysctl-style keys and set @details data.
+  get_system_data(detail_mapping())
+  #
+  #
+  # File descriptor reservation and release support, reserving $stdin, $stdout, and $stderr, etc.
+  # TODO: get a *complete* list of getconf variables *and their meanings* and incorporate them into sysctl-style path system.
+  # See: http://www.lainoox.com/system-wide-config-getconf/
+  # self["configuration.process.maximum_file_descriptors"]
+  # LATER: add lsof to requirements list within installer.
+  case @details[:platform]
+  when :linux, :aix, :solaris, :bsd, :darwin
+    # TODO: research -F option of lsof
+    # See: http://geek00l.blogspot.com/2006/03/openbsd-fstat-vs-lsof.html
+  end
+  #
+  case @details[:platform]
+  when :bsd
+    # See: fstat vs lsof : http://geek00l.blogspot.com/2006/03/openbsd-fstat-vs-lsof.html
+    #
+    # if EventMachine::kqueue?
+      # ?
+      # @details[:maximum_descriptors][:events] = 1000000
+    # end
+  when :linux
+    # if EventMachine::epoll?
+      # ?
+      # @details[:maximum_descriptors][:events] = 1000000
+    # end
+  end
+  # ###
+  @valid_modes = {}
+  # Internal Services to Port Usage mapping
+  @details[:service_ports] = {
+    :tcpmux=>{:aliases=>[], :clients=>[], :description=>"TCP port service multiplexer", :usage=>{:tcp=>[1]}},
+    :echo=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[7], :udp=>[7], :ddp=>[4]}},
+    :discard=>{:aliases=>["sink", "null"], :clients=>[], :description=>"", :usage=>{:tcp=>[9], :udp=>[9]}},
+    :systat=>{:aliases=>["users"], :clients=>[], :description=>"", :usage=>{:tcp=>[11]}},
+    :daytime=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[13], :udp=>[13]}},
+    :netstat=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[15]}},
+    :qotd=>{:aliases=>["quote"], :clients=>[], :description=>"", :usage=>{:tcp=>[17]}},
+    :msp=>{:aliases=>[], :clients=>[], :description=>"message send protocol", :usage=>{:tcp=>[18], :udp=>[18]}},
+    :chargen=>{:aliases=>["ttytst", "source"], :clients=>[], :description=>"", :usage=>{:tcp=>[19], :udp=>[19]}},
+    :"ftp-data"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[20]}},
+    :ftp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[21]}},
+    :fsp=>{:aliases=>["fspd"], :clients=>[], :description=>"", :usage=>{:udp=>[21]}},
+    :ssh=>{:aliases=>[], :clients=>[], :description=>"SSH Remote Login Protocol", :usage=>{:tcp=>[22], :udp=>[22]}},
+    :telnet=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[23]}},
+    :smtp=>{:aliases=>["mail"], :clients=>[], :description=>"", :usage=>{:tcp=>[25]}},
+    :time=>{:aliases=>["timserver"], :clients=>[], :description=>"", :usage=>{:tcp=>[37], :udp=>[37]}},
+    :rlp=>{:aliases=>["resource"], :clients=>[], :description=>"resource location", :usage=>{:udp=>[39]}},
+    :nameserver=>{:aliases=>["name"], :clients=>[], :description=>"IEN 116", :usage=>{:tcp=>[42]}},
+    :whois=>{:aliases=>["nicname"], :clients=>[], :description=>"", :usage=>{:tcp=>[43]}},
+    :tacacs=>{:aliases=>[], :clients=>[], :description=>"Login Host Protocol (TACACS)", :usage=>{:tcp=>[49], :udp=>[49]}},
+    :"re-mail-ck"=>{:aliases=>[], :clients=>[], :description=>"Remote Mail Checking Protocol", :usage=>{:tcp=>[50], :udp=>[50]}},
+    :domain=>{:aliases=>[], :clients=>[], :description=>"name-domain server", :usage=>{:tcp=>[53], :udp=>[53]}},
+    :mtp=>{:aliases=>[], :clients=>[], :description=>"deprecated", :usage=>{:tcp=>[57]}},
+    :"tacacs-ds"=>{:aliases=>[], :clients=>[], :description=>"TACACS-Database Service", :usage=>{:tcp=>[65], :udp=>[65]}},
+    :bootps=>{:aliases=>[], :clients=>[], :description=>"BOOTP server", :usage=>{:tcp=>[67], :udp=>[67]}},
+    :bootpc=>{:aliases=>[], :clients=>[], :description=>"BOOTP client", :usage=>{:tcp=>[68], :udp=>[68]}},
+    :tftp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:udp=>[69]}},
+    :gopher=>{:aliases=>[], :clients=>[], :description=>"Internet Gopher", :usage=>{:tcp=>[70], :udp=>[70]}},
+    :rje=>{:aliases=>["netrjs"], :clients=>[], :description=>"", :usage=>{:tcp=>[77]}},
+    :finger=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[79]}},
+    :www=>{:aliases=>["http"], :clients=>[], :description=>"WorldWideWeb HTTP", :usage=>{:tcp=>[80], :udp=>[80]}},
+    :link=>{:aliases=>["ttylink"], :clients=>[], :description=>"", :usage=>{:tcp=>[87]}},
+    :kerberos=>{:aliases=>["kerberos5", "krb5", "kerberos-sec"], :clients=>[], :description=>"Kerberos v5", :usage=>{:tcp=>[88], :udp=>[88]}},
+    :supdup=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[95]}},
+    :hostnames=>{:aliases=>["hostname"], :clients=>[], :description=>"usually from sri-nic", :usage=>{:tcp=>[101]}},
+    :"iso-tsap"=>{:aliases=>["tsap"], :clients=>[], :description=>"part of ISODE", :usage=>{:tcp=>[102]}},
+    :"acr-nema"=>{:aliases=>["dicom"], :clients=>[], :description=>"Digital Imag. & Comm. 300", :usage=>{:tcp=>[104], :udp=>[104]}},
+    :"csnet-ns"=>{:aliases=>["cso-ns"], :clients=>[], :description=>"also used by CSO name server", :usage=>{:tcp=>[105], :udp=>[105]}},
+    :rtelnet=>{:aliases=>[], :clients=>[], :description=>"Remote Telnet", :usage=>{:tcp=>[107], :udp=>[107]}},
+    :pop2=>{:aliases=>["postoffice", "pop-2"], :clients=>[], :description=>"POP version 2", :usage=>{:tcp=>[109], :udp=>[109]}},
+    :pop3=>{:aliases=>["pop-3"], :clients=>[], :description=>"POP version 3", :usage=>{:tcp=>[110], :udp=>[110]}},
+    :sunrpc=>{:aliases=>["portmapper"], :clients=>[], :description=>"RPC 4.0 portmapper", :usage=>{:tcp=>[111], :udp=>[111]}},
+    :auth=>{:aliases=>["authentication", "tap", "ident"], :clients=>[], :description=>"", :usage=>{:tcp=>[113]}},
+    :sftp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[115]}},
+    :"uucp-path"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[117]}},
+    :nntp=>{:aliases=>["readnews", "untp"], :clients=>[], :description=>"USENET News Transfer Protocol", :usage=>{:tcp=>[119]}},
+    :ntp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[123], :udp=>[123]}},
+    :pwdgen=>{:aliases=>[], :clients=>[], :description=>"PWDGEN service", :usage=>{:tcp=>[129], :udp=>[129]}},
+    :"loc-srv"=>{:aliases=>["epmap"], :clients=>[], :description=>"Location Service", :usage=>{:tcp=>[135], :udp=>[135]}},
+    :"netbios-ns"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS Name Service", :usage=>{:tcp=>[137], :udp=>[137]}},
+    :"netbios-dgm"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS Datagram Service", :usage=>{:tcp=>[138], :udp=>[138]}},
+    :"netbios-ssn"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS session service", :usage=>{:tcp=>[139], :udp=>[139]}},
+    :imap2=>{:aliases=>["imap"], :clients=>[], :description=>"Interim Mail Access P 2 and 4", :usage=>{:tcp=>[143], :udp=>[143]}},
+    :snmp=>{:aliases=>[], :clients=>[], :description=>"Simple Net Mgmt Protocol", :usage=>{:tcp=>[161], :udp=>[161]}},
+    :"snmp-trap"=>{:aliases=>["snmptrap"], :clients=>[], :description=>"Traps for SNMP", :usage=>{:tcp=>[162], :udp=>[162]}},
+    :"cmip-man"=>{:aliases=>[], :clients=>[], :description=>"ISO mgmt over IP (CMOT)", :usage=>{:tcp=>[163], :udp=>[163]}},
+    :"cmip-agent"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[164], :udp=>[164]}},
+    :mailq=>{:aliases=>[], :clients=>[], :description=>"Mailer transport queue for Zmailer", :usage=>{:tcp=>[174], :udp=>[174]}},
+    :xdmcp=>{:aliases=>[], :clients=>[], :description=>"X Display Mgr. Control Proto", :usage=>{:tcp=>[177], :udp=>[177]}},
+    :nextstep=>{:aliases=>["NeXTStep", "NextStep"], :clients=>[], :description=>"NeXTStep window", :usage=>{:tcp=>[178], :udp=>[178]}},
+    :bgp=>{:aliases=>[], :clients=>[], :description=>"Border Gateway Protocol", :usage=>{:tcp=>[179], :udp=>[179]}},
+    :prospero=>{:aliases=>[], :clients=>[], :description=>"Cliff Neuman's Prospero", :usage=>{:tcp=>[191], :udp=>[191]}},
+    :irc=>{:aliases=>[], :clients=>[], :description=>"Internet Relay Chat", :usage=>{:tcp=>[194], :udp=>[194]}},
+    :smux=>{:aliases=>[], :clients=>[], :description=>"SNMP Unix Multiplexer", :usage=>{:tcp=>[199], :udp=>[199]}},
+    :"at-rtmp"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk routing", :usage=>{:tcp=>[201], :udp=>[201]}},
+    :"at-nbp"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk name binding", :usage=>{:tcp=>[202], :udp=>[202]}},
+    :"at-echo"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk echo", :usage=>{:tcp=>[204], :udp=>[204]}},
+    :"at-zis"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk zone information", :usage=>{:tcp=>[206], :udp=>[206]}},
+    :qmtp=>{:aliases=>[], :clients=>[], :description=>"Quick Mail Transfer Protocol", :usage=>{:tcp=>[209], :udp=>[209]}},
+    :z3950=>{:aliases=>["wais"], :clients=>[], :description=>"NISO Z39.50 database", :usage=>{:tcp=>[210], :udp=>[210]}},
+    :ipx=>{:aliases=>[], :clients=>[], :description=>"IPX", :usage=>{:tcp=>[213], :udp=>[213]}},
+    :imap3=>{:aliases=>[], :clients=>[], :description=>"Interactive Mail Access", :usage=>{:tcp=>[220], :udp=>[220]}},
+    :pawserv=>{:aliases=>[], :clients=>[], :description=>"Perf Analysis Workbench", :usage=>{:tcp=>[345], :udp=>[345]}},
+    :zserv=>{:aliases=>[], :clients=>[], :description=>"Zebra server", :usage=>{:tcp=>[346], :udp=>[346]}},
+    :fatserv=>{:aliases=>[], :clients=>[], :description=>"Fatmen Server", :usage=>{:tcp=>[347], :udp=>[347]}},
+    :rpc2portmap=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[369], :udp=>[369]}},
+    :codaauth2=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[370], :udp=>[370]}},
+    :clearcase=>{:aliases=>["Clearcase"], :clients=>[], :description=>"", :usage=>{:tcp=>[371], :udp=>[371]}},
+    :ulistserv=>{:aliases=>[], :clients=>[], :description=>"UNIX Listserv", :usage=>{:tcp=>[372], :udp=>[372]}},
+    :ldap=>{:aliases=>[], :clients=>[], :description=>"Lightweight Directory Access Protocol", :usage=>{:tcp=>[389], :udp=>[389]}},
+    :imsp=>{:aliases=>[], :clients=>[], :description=>"Interactive Mail Support Protocol", :usage=>{:tcp=>[406], :udp=>[406]}},
+    :svrloc=>{:aliases=>[], :clients=>[], :description=>"Server Location", :usage=>{:tcp=>[427], :udp=>[427]}},
+    :https=>{:aliases=>[], :clients=>[], :description=>"http protocol over TLS/SSL", :usage=>{:tcp=>[443], :udp=>[443]}},
+    :snpp=>{:aliases=>[], :clients=>[], :description=>"Simple Network Paging Protocol", :usage=>{:tcp=>[444], :udp=>[444]}},
+    :"microsoft-ds"=>{:aliases=>[], :clients=>[], :description=>"Microsoft Naked CIFS", :usage=>{:tcp=>[445], :udp=>[445]}},
+    :kpasswd=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[464], :udp=>[464]}},
+    :saft=>{:aliases=>[], :clients=>[], :description=>"Simple Asynchronous File Transfer", :usage=>{:tcp=>[487], :udp=>[487]}},
+    :isakmp=>{:aliases=>[], :clients=>[], :description=>"IPsec - Internet Security Association", :usage=>{:tcp=>[500], :udp=>[500]}},
+    :rtsp=>{:aliases=>[], :clients=>[], :description=>"Real Time Stream Control Protocol", :usage=>{:tcp=>[554], :udp=>[554]}},
+    :nqs=>{:aliases=>[], :clients=>[], :description=>"Network Queuing system", :usage=>{:tcp=>[607], :udp=>[607]}},
+    :"npmp-local"=>{:aliases=>["dqs313_qmaster"], :clients=>[], :description=>"npmp-local / DQS", :usage=>{:tcp=>[610], :udp=>[610]}},
+    :"npmp-gui"=>{:aliases=>["dqs313_execd"], :clients=>[], :description=>"npmp-gui / DQS", :usage=>{:tcp=>[611], :udp=>[611]}},
+    :"hmmp-ind"=>{:aliases=>["dqs313_intercell"], :clients=>[], :description=>"HMMP Indication / DQS", :usage=>{:tcp=>[612], :udp=>[612]}},
+    :qmqp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[628], :udp=>[628]}},
+    :ipp=>{:aliases=>[], :clients=>[], :description=>"Internet Printing Protocol", :usage=>{:tcp=>[631], :udp=>[631]}},
+    :exec=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[512]}},
+    :biff=>{:aliases=>["comsat"], :clients=>[], :description=>"", :usage=>{:udp=>[512]}},
+    :login=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[513]}},
+    :who=>{:aliases=>["whod"], :clients=>[], :description=>"", :usage=>{:udp=>[513]}},
+    :shell=>{:aliases=>["cmd"], :clients=>[], :description=>"no passwords used", :usage=>{:tcp=>[514]}},
+    :syslog=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:udp=>[514]}},
+    :printer=>{:aliases=>["spooler"], :clients=>[], :description=>"line printer spooler", :usage=>{:tcp=>[515]}},
+    :talk=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:udp=>[517]}},
+    :ntalk=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:udp=>[518]}},
+    :route=>{:aliases=>["router", "routed"], :clients=>[], :description=>"RIP", :usage=>{:udp=>[520]}},
+    :timed=>{:aliases=>["timeserver"], :clients=>[], :description=>"", :usage=>{:udp=>[525]}},
+    :tempo=>{:aliases=>["newdate"], :clients=>[], :description=>"", :usage=>{:tcp=>[526]}},
+    :courier=>{:aliases=>["rpc"], :clients=>[], :description=>"", :usage=>{:tcp=>[530]}},
+    :conference=>{:aliases=>["chat"], :clients=>[], :description=>"", :usage=>{:tcp=>[531]}},
+    :netnews=>{:aliases=>["readnews"], :clients=>[], :description=>"", :usage=>{:tcp=>[532]}},
+    :netwall=>{:aliases=>[], :clients=>[], :description=>"for emergency broadcasts", :usage=>{:udp=>[533]}},
+    :gdomap=>{:aliases=>[], :clients=>[], :description=>"GNUstep distributed objects", :usage=>{:tcp=>[538], :udp=>[538]}},
+    :uucp=>{:aliases=>["uucpd"], :clients=>[], :description=>"uucp daemon", :usage=>{:tcp=>[540]}},
+    :klogin=>{:aliases=>[], :clients=>[], :description=>"Kerberized `rlogin' (v5)", :usage=>{:tcp=>[543]}},
+    :kshell=>{:aliases=>["krcmd"], :clients=>[], :description=>"Kerberized `rsh' (v5)", :usage=>{:tcp=>[544]}},
+    :"dhcpv6-client"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[546], :udp=>[546]}},
+    :"dhcpv6-server"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[547], :udp=>[547]}},
+    :afpovertcp=>{:aliases=>[], :clients=>[], :description=>"AFP over TCP", :usage=>{:tcp=>[548], :udp=>[548]}},
+    :idfp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[549], :udp=>[549]}},
+    :remotefs=>{:aliases=>["rfs_server", "rfs"], :clients=>[], :description=>"Brunhoff remote filesystem", :usage=>{:tcp=>[556]}},
+    :nntps=>{:aliases=>["snntp"], :clients=>[], :description=>"NNTP over SSL", :usage=>{:tcp=>[563], :udp=>[563]}},
+    :submission=>{:aliases=>[], :clients=>[], :description=>"Submission [RFC4409]", :usage=>{:tcp=>[587], :udp=>[587]}},
+    :ldaps=>{:aliases=>[], :clients=>[], :description=>"LDAP over SSL", :usage=>{:tcp=>[636], :udp=>[636]}},
+    :tinc=>{:aliases=>[], :clients=>[], :description=>"tinc control port", :usage=>{:tcp=>[655], :udp=>[655]}},
+    :silc=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[706], :udp=>[706]}},
+    :"kerberos-adm"=>{:aliases=>[], :clients=>[], :description=>"Kerberos `kadmin' (v5)", :usage=>{:tcp=>[749]}},
+    :webster=>{:aliases=>[], :clients=>[], :description=>"Network dictionary", :usage=>{:tcp=>[765], :udp=>[765]}},
+    :rsync=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[873], :udp=>[873]}},
+    :"ftps-data"=>{:aliases=>[], :clients=>[], :description=>"FTP over SSL (data)", :usage=>{:tcp=>[989]}},
+    :ftps=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[990]}},
+    :telnets=>{:aliases=>[], :clients=>[], :description=>"Telnet over SSL", :usage=>{:tcp=>[992], :udp=>[992]}},
+    :imaps=>{:aliases=>[], :clients=>[], :description=>"IMAP over SSL", :usage=>{:tcp=>[993], :udp=>[993]}},
+    :ircs=>{:aliases=>[], :clients=>[], :description=>"IRC over SSL", :usage=>{:tcp=>[994], :udp=>[994]}},
+    :pop3s=>{:aliases=>[], :clients=>[], :description=>"POP-3 over SSL", :usage=>{:tcp=>[995], :udp=>[995]}},
+    :socks=>{:aliases=>[], :clients=>[], :description=>"socks proxy server", :usage=>{:tcp=>[1080], :udp=>[1080]}},
+    :proofd=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1093], :udp=>[1093]}},
+    :rootd=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1094], :udp=>[1094]}},
+    :openvpn=>{:aliases=>["vpn"], :clients=>[], :description=>"", :usage=>{:tcp=>[1194], :udp=>[1194]}, :usage_preference => :udp},
+    :rmiregistry=>{:aliases=>[], :clients=>[], :description=>"Java RMI Registry", :usage=>{:tcp=>[1099], :udp=>[1099]}},
+    :kazaa=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1214], :udp=>[1214]}},
+    :nessus=>{:aliases=>[], :clients=>[], :description=>"Nessus vulnerability", :usage=>{:tcp=>[1241], :udp=>[1241]}},
+    :lotusnote=>{:aliases=>[], :clients=>[], :description=>"Lotus Note", :usage=>{:tcp=>[1352], :udp=>[1352]}},
+    :"ms-sql-s"=>{:aliases=>[], :clients=>[], :description=>"Microsoft SQL Server", :usage=>{:tcp=>[1433], :udp=>[1433]}},
+    :"ms-sql-m"=>{:aliases=>[], :clients=>[], :description=>"Microsoft SQL Monitor", :usage=>{:tcp=>[1434], :udp=>[1434]}},
+    :ingreslock=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1524], :udp=>[1524]}},
+    :"prospero-np"=>{:aliases=>[], :clients=>[], :description=>"Prospero non-privileged", :usage=>{:tcp=>[1525], :udp=>[1525]}},
+    :datametrics=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1645], :udp=>[1645]}},
+    :"sa-msg-port"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1646], :udp=>[1646]}},
+    :kermit=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1649], :udp=>[1649]}},
+    :l2f=>{:aliases=>["l2tp"], :clients=>[], :description=>"", :usage=>{:tcp=>[1701], :udp=>[1701]}},
+    :radius=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[1812], :udp=>[1812]}},
+    :"radius-acct"=>{:aliases=>[], :clients=>[], :description=>"Radius Accounting", :usage=>{:tcp=>[1813], :udp=>[1813]}},
+    :msnp=>{:aliases=>[], :clients=>[], :description=>"MSN Messenger", :usage=>{:tcp=>[1863], :udp=>[1863]}},
+    :"unix-status"=>{:aliases=>[], :clients=>[], :description=>"remstats unix-status server", :usage=>{:tcp=>[1957]}},
+    :"log-server"=>{:aliases=>[], :clients=>[], :description=>"remstats log server", :usage=>{:tcp=>[1958]}},
+    :remoteping=>{:aliases=>[], :clients=>[], :description=>"remstats remoteping server", :usage=>{:tcp=>[1959]}},
+    :"cisco-sccp"=>{:aliases=>[], :clients=>[], :description=>"Cisco SCCP", :usage=>{:tcp=>[2000], :udp=>[2000]}},
+    :search=>{:aliases=>["ndtp"], :clients=>[], :description=>"", :usage=>{:tcp=>[2010]}},
+    :pipe_server=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[2010]}},
+    :nfs=>{:aliases=>[], :clients=>[], :description=>"Network File System", :usage=>{:tcp=>[2049], :udp=>[2049]}},
+    :gnunet=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[2086], :udp=>[2086]}},
+    :"rtcm-sc104"=>{:aliases=>[], :clients=>[], :description=>"RTCM SC-104 IANA 1/29/99", :usage=>{:tcp=>[2101], :udp=>[2101]}},
+    :gsigatekeeper=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[2119], :udp=>[2119]}},
+    :gris=>{:aliases=>[], :clients=>[], :description=>"Grid Resource Information Server", :usage=>{:tcp=>[2135], :udp=>[2135]}},
+    :cvspserver=>{:aliases=>[], :clients=>[], :description=>"CVS client/server operations", :usage=>{:tcp=>[2401], :udp=>[2401]}},
+    :venus=>{:aliases=>[], :clients=>[], :description=>"codacon port", :usage=>{:tcp=>[2430], :udp=>[2430]}},
+    :"venus-se"=>{:aliases=>[], :clients=>[], :description=>"tcp side effects", :usage=>{:tcp=>[2431], :udp=>[2431]}},
+    :codasrv=>{:aliases=>[], :clients=>[], :description=>"not used", :usage=>{:tcp=>[2432], :udp=>[2432]}},
+    :"codasrv-se"=>{:aliases=>[], :clients=>[], :description=>"tcp side effects", :usage=>{:tcp=>[2433], :udp=>[2433]}},
+    :mon=>{:aliases=>[], :clients=>[], :description=>"MON traps", :usage=>{:tcp=>[2583], :udp=>[2583]}},
+    :dict=>{:aliases=>[], :clients=>[], :description=>"Dictionary server", :usage=>{:tcp=>[2628], :udp=>[2628]}},
+    :gsiftp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[2811], :udp=>[2811]}},
+    :gpsd=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[2947], :udp=>[2947]}},
+    :gds_db=>{:aliases=>[], :clients=>[], :description=>"InterBase server", :usage=>{:tcp=>[3050], :udp=>[3050]}},
+    :icpv2=>{:aliases=>["icp"], :clients=>[], :description=>"Internet Cache Protocol", :usage=>{:tcp=>[3130], :udp=>[3130]}},
+    :mysql=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[3306], :udp=>[3306]}},
+    :nut=>{:aliases=>[], :clients=>[], :description=>"Network UPS Tools", :usage=>{:tcp=>[3493], :udp=>[3493]}},
+    :distcc=>{:aliases=>[], :clients=>[], :description=>"distributed compiler", :usage=>{:tcp=>[3632], :udp=>[3632]}},
+    :daap=>{:aliases=>[], :clients=>[], :description=>"Digital Audio Access Protocol", :usage=>{:tcp=>[3689], :udp=>[3689]}},
+    :svn=>{:aliases=>["subversion"], :clients=>[], :description=>"Subversion protocol", :usage=>{:tcp=>[3690], :udp=>[3690]}},
+    :suucp=>{:aliases=>[], :clients=>[], :description=>"UUCP over SSL", :usage=>{:tcp=>[4031], :udp=>[4031]}},
+    :sysrqd=>{:aliases=>[], :clients=>[], :description=>"sysrq daemon", :usage=>{:tcp=>[4094], :udp=>[4094]}},
+    :sieve=>{:aliases=>[], :clients=>[], :description=>"ManageSieve Protocol", :usage=>{:tcp=>[4190]}},
+    :epmd=>{:aliases=>[], :clients=>[], :description=>"Erlang Port Mapper Daemon", :usage=>{:tcp=>[4369], :udp=>[4369]}},
+    :remctl=>{:aliases=>[], :clients=>[], :description=>"Remote Authenticated Command Service", :usage=>{:tcp=>[4373], :udp=>[4373]}},
+    :iax=>{:aliases=>[], :clients=>[], :description=>"Inter-Asterisk eXchange", :usage=>{:tcp=>[4569], :udp=>[4569]}},
+    :mtn=>{:aliases=>[], :clients=>[], :description=>"monotone Netsync Protocol", :usage=>{:tcp=>[4691], :udp=>[4691]}},
+    :"radmin-port"=>{:aliases=>[], :clients=>[], :description=>"RAdmin Port", :usage=>{:tcp=>[4899], :udp=>[4899]}},
+    :rfe=>{:aliases=>[], :clients=>[], :description=>"Radio Free Ethernet", :usage=>{:udp=>[5002], :tcp=>[5002]}},
+    :mmcc=>{:aliases=>[], :clients=>[], :description=>"multimedia conference control tool (Yahoo IM)", :usage=>{:tcp=>[5050], :udp=>[5050]}},
+    :sip=>{:aliases=>[], :clients=>[], :description=>"Session Initiation Protocol", :usage=>{:tcp=>[5060], :udp=>[5060]}},
+    :"sip-tls"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[5061], :udp=>[5061]}},
+    :aol=>{:aliases=>[], :clients=>[], :description=>"AIM", :usage=>{:tcp=>[5190], :udp=>[5190]}},
+    :"xmpp-client"=>{:aliases=>[], :clients=>[], :description=>"Jabber Client Connection", :usage=>{:tcp=>[5222], :udp=>[5222]}},
+    :"xmpp-server"=>{:aliases=>[], :clients=>[], :description=>"Jabber Server Connection", :usage=>{:tcp=>[5269], :udp=>[5269]}},
+    :cfengine=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[5308], :udp=>[5308]}},
+    :mdns=>{:aliases=>[], :clients=>[], :description=>"Multicast DNS", :usage=>{:tcp=>[5353], :udp=>[5353]}},
+    :postgresql=>{:aliases=>[], :clients=>[], :description=>"PostgreSQL Database", :usage=>{:tcp=>[5432], :udp=>[5432]}},
+    :freeciv=>{:aliases=>["rptp"], :clients=>[], :description=>"Freeciv gameplay", :usage=>{:tcp=>[5556], :udp=>[5556]}},
+    :amqp=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[5672], :udp=>[5672], :sctp=>[5672]}},
+    :ggz=>{:aliases=>[], :clients=>[], :description=>"GGZ Gaming Zone", :usage=>{:tcp=>[5688], :udp=>[5688]}},
+    :x11=>{:aliases=>["x11-0"], :clients=>[], :description=>"X Window System", :usage=>{:tcp=>[6000], :udp=>[6000]}},
+    :"x11-1"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6001], :udp=>[6001]}},
+    :"x11-2"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6002], :udp=>[6002]}},
+    :"x11-3"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6003], :udp=>[6003]}},
+    :"x11-4"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6004], :udp=>[6004]}},
+    :"x11-5"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6005], :udp=>[6005]}},
+    :"x11-6"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6006], :udp=>[6006]}},
+    :"x11-7"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[6007], :udp=>[6007]}},
+    :"gnutella-svc"=>{:aliases=>[], :clients=>[], :description=>"gnutella", :usage=>{:tcp=>[6346], :udp=>[6346]}},
+    :"gnutella-rtr"=>{:aliases=>[], :clients=>[], :description=>"gnutella", :usage=>{:tcp=>[6347], :udp=>[6347]}},
+    :sge_qmaster=>{:aliases=>[], :clients=>[], :description=>"Grid Engine Qmaster Service", :usage=>{:tcp=>[6444], :udp=>[6444]}},
+    :sge_execd=>{:aliases=>[], :clients=>[], :description=>"Grid Engine Execution Service", :usage=>{:tcp=>[6445], :udp=>[6445]}},
+    :"mysql-proxy"=>{:aliases=>[], :clients=>[], :description=>"MySQL Proxy", :usage=>{:tcp=>[6446], :udp=>[6446]}},
+    :"afs3-fileserver" => {:aliases => ["bbs"], :clients => [], :description => "file server itself", :usage => {:tcp => [7000], :udp => [7000]}},
+    :"afs3-callback"=>{:aliases=>[], :clients=>[], :description=>"callbacks to cache managers", :usage=>{:tcp=>[7001], :udp=>[7001]}},
+    :"afs3-prserver"=>{:aliases=>[], :clients=>[], :description=>"users & groups database", :usage=>{:tcp=>[7002], :udp=>[7002]}},
+    :"afs3-vlserver"=>{:aliases=>[], :clients=>[], :description=>"volume location database", :usage=>{:tcp=>[7003], :udp=>[7003]}},
+    :"afs3-kaserver"=>{:aliases=>[], :clients=>[], :description=>"AFS/Kerberos authentication", :usage=>{:tcp=>[7004], :udp=>[7004]}},
+    :"afs3-volser"=>{:aliases=>[], :clients=>[], :description=>"volume managment server", :usage=>{:tcp=>[7005], :udp=>[7005]}},
+    :"afs3-errors"=>{:aliases=>[], :clients=>[], :description=>"error interpretation service", :usage=>{:tcp=>[7006], :udp=>[7006]}},
+    :"afs3-bos"=>{:aliases=>[], :clients=>[], :description=>"basic overseer process", :usage=>{:tcp=>[7007], :udp=>[7007]}},
+    :"afs3-update"=>{:aliases=>[], :clients=>[], :description=>"server-to-server updater", :usage=>{:tcp=>[7008], :udp=>[7008]}},
+    :"afs3-rmtsys"=>{:aliases=>[], :clients=>[], :description=>"remote cache manager service", :usage=>{:tcp=>[7009], :udp=>[7009]}},
+    :"font-service"=>{:aliases=>[], :clients=>[], :description=>"X Font Service", :usage=>{:tcp=>[7100], :udp=>[7100]}},
+    :"http-alt"=>{:aliases=>[], :clients=>[], :description=>"WWW caching service", :usage=>{:tcp=>[8080], :udp=>[8080]}},
+    :"bacula-dir"=>{:aliases=>[], :clients=>[], :description=>"Bacula Director", :usage=>{:tcp=>[9101], :udp=>[9101]}},
+    :"bacula-fd"=>{:aliases=>[], :clients=>[], :description=>"Bacula File Daemon", :usage=>{:tcp=>[9102], :udp=>[9102]}},
+    :"bacula-sd"=>{:aliases=>[], :clients=>[], :description=>"Bacula Storage Daemon", :usage=>{:tcp=>[9103], :udp=>[9103]}},
+    :xmms2=>{:aliases=>[], :clients=>[], :description=>"Cross-platform Music Multiplexing System", :usage=>{:tcp=>[9667], :udp=>[9667]}},
+    :nbd=>{:aliases=>[], :clients=>[], :description=>"Linux Network Block Device", :usage=>{:tcp=>[10809]}},
+    :"zabbix-agent"=>{:aliases=>[], :clients=>[], :description=>"Zabbix Agent", :usage=>{:tcp=>[10050], :udp=>[10050]}},
+    :"zabbix-trapper"=>{:aliases=>[], :clients=>[], :description=>"Zabbix Trapper", :usage=>{:tcp=>[10051], :udp=>[10051]}},
+    :amanda=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :usage=>{:tcp=>[10080], :udp=>[10080]}},
+    :hkp=>{:aliases=>[], :clients=>[], :description=>"OpenPGP HTTP Keyserver", :usage=>{:tcp=>[11371], :udp=>[11371]}},
+    :bprd=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :usage=>{:tcp=>[13720], :udp=>[13720]}},
+    :bpdbm=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :usage=>{:tcp=>[13721], :udp=>[13721]}},
+    :"bpjava-msvc"=>{:aliases=>[], :clients=>[], :description=>"BP Java MSVC Protocol", :usage=>{:tcp=>[13722], :udp=>[13722]}},
+    :vnetd=>{:aliases=>[], :clients=>[], :description=>"Veritas Network Utility", :usage=>{:tcp=>[13724], :udp=>[13724]}},
+    :bpcd=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :usage=>{:tcp=>[13782], :udp=>[13782]}},
+    :vopied=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :usage=>{:tcp=>[13783], :udp=>[13783]}},
+    :dcap=>{:aliases=>[], :clients=>[], :description=>"dCache Access Protocol", :usage=>{:tcp=>[22125]}},
+    :gsidcap=>{:aliases=>[], :clients=>[], :description=>"GSI dCache Access Protocol", :usage=>{:tcp=>[22128]}},
+    :wnn6=>{:aliases=>[], :clients=>[], :description=>"wnn6", :usage=>{:tcp=>[22273], :udp=>[22273]}},
+    :rtmp=>{:aliases=>[], :clients=>[], :description=>"Routing Table Maintenance Protocol", :usage=>{:ddp=>[1]}},
+    :nbp=>{:aliases=>[], :clients=>[], :description=>"Name Binding Protocol", :usage=>{:ddp=>[2]}},
+    :zip=>{:aliases=>[], :clients=>[], :description=>"Zone Information Protocol", :usage=>{:ddp=>[6]}},
+    :kerberos4=>{:aliases=>["kerberos-iv", "kdc"], :clients=>[], :description=>"Kerberos (server)", :usage=>{:udp=>[750], :tcp=>[750]}},
+    :kerberos_master=>{:aliases=>[], :clients=>[], :description=>"Kerberos authentication", :usage=>{:udp=>[751], :tcp=>[751]}},
+    :passwd_server=>{:aliases=>[], :clients=>[], :description=>"Kerberos passwd server", :usage=>{:udp=>[752]}},
+    :krb_prop=>{:aliases=>["krb5_prop", "hprop"], :clients=>[], :description=>"Kerberos slave propagation", :usage=>{:tcp=>[754]}},
+    :krbupdate=>{:aliases=>["kreg"], :clients=>[], :description=>"Kerberos registration", :usage=>{:tcp=>[760]}},
+    :swat=>{:aliases=>[], :clients=>[], :description=>"swat", :usage=>{:tcp=>[901]}},
+    :kpop=>{:aliases=>[], :clients=>[], :description=>"Pop with Kerberos", :usage=>{:tcp=>[1109]}},
+    :knetd=>{:aliases=>[], :clients=>[], :description=>"Kerberos de-multiplexor", :usage=>{:tcp=>[2053]}},
+    :"zephyr-srv"=>{:aliases=>[], :clients=>[], :description=>"Zephyr server", :usage=>{:udp=>[2102]}},
+    :"zephyr-clt"=>{:aliases=>[], :clients=>[], :description=>"Zephyr serv-hm connection", :usage=>{:udp=>[2103]}},
+    :"zephyr-hm"=>{:aliases=>[], :clients=>[], :description=>"Zephyr hostmanager", :usage=>{:udp=>[2104]}},
+    :eklogin=>{:aliases=>[], :clients=>[], :description=>"Kerberos encrypted rlogin", :usage=>{:tcp=>[2105]}},
+    :kx=>{:aliases=>[], :clients=>[], :description=>"X over Kerberos", :usage=>{:tcp=>[2111]}},
+    :iprop=>{:aliases=>[], :clients=>[], :description=>"incremental propagation", :usage=>{:tcp=>[2121]}},
+    :supfilesrv=>{:aliases=>[], :clients=>[], :description=>"SUP server", :usage=>{:tcp=>[871]}},
+    :supfiledbg=>{:aliases=>[], :clients=>[], :description=>"SUP debugging", :usage=>{:tcp=>[1127]}},
+    :linuxconf=>{:aliases=>[], :clients=>[], :description=>"LinuxConf", :usage=>{:tcp=>[98]}},
+    :poppassd=>{:aliases=>[], :clients=>[], :description=>"Eudora", :usage=>{:tcp=>[106], :udp=>[106]}},
+    :ssmtp=>{:aliases=>["smtps"], :clients=>[], :description=>"SMTP over SSL", :usage=>{:tcp=>[465]}},
+    :moira_db=>{:aliases=>[], :clients=>[], :description=>"Moira database", :usage=>{:tcp=>[775]}},
+    :moira_update=>{:aliases=>[], :clients=>[], :description=>"Moira update protocol", :usage=>{:tcp=>[777]}},
+    :moira_ureg=>{:aliases=>[], :clients=>[], :description=>"Moira user registration", :usage=>{:udp=>[779]}},
+    :spamd=>{:aliases=>[], :clients=>[], :description=>"spamassassin daemon", :usage=>{:tcp=>[783]}},
+    :omirr=>{:aliases=>["omirrd"], :clients=>[], :description=>"online mirror", :usage=>{:tcp=>[808], :udp=>[808]}},
+    :customs=>{:aliases=>[], :clients=>[], :description=>"pmake customs server", :usage=>{:tcp=>[1001], :udp=>[1001]}},
+    :skkserv=>{:aliases=>[], :clients=>[], :description=>"skk jisho server port", :usage=>{:tcp=>[1178]}},
+    :predict=>{:aliases=>[], :clients=>[], :description=>"predict -- satellite tracking", :usage=>{:udp=>[1210]}},
+    :rmtcfg=>{:aliases=>[], :clients=>[], :description=>"Gracilis Packeten remote config server", :usage=>{:tcp=>[1236]}},
+    :wipld=>{:aliases=>[], :clients=>[], :description=>"Wipl network monitor", :usage=>{:tcp=>[1300]}},
+    :xtel=>{:aliases=>[], :clients=>[], :description=>"french minitel", :usage=>{:tcp=>[1313]}},
+    :xtelw=>{:aliases=>[], :clients=>[], :description=>"french minitel", :usage=>{:tcp=>[1314]}},
+    :support=>{:aliases=>[], :clients=>[], :description=>"GNATS", :usage=>{:tcp=>[1529]}},
+    :cfinger=>{:aliases=>[], :clients=>[], :description=>"GNU Finger", :usage=>{:tcp=>[2003]}},
+    :frox=>{:aliases=>[], :clients=>[], :description=>"frox: caching ftp proxy", :usage=>{:tcp=>[2121]}},
+    :ninstall=>{:aliases=>[], :clients=>[], :description=>"ninstall service", :usage=>{:tcp=>[2150], :udp=>[2150]}},
+    :zebrasrv=>{:aliases=>[], :clients=>[], :description=>"zebra service", :usage=>{:tcp=>[2600]}},
+    :zebra=>{:aliases=>[], :clients=>[], :description=>"zebra vty", :usage=>{:tcp=>[2601]}},
+    :ripd=>{:aliases=>[], :clients=>[], :description=>"ripd vty (zebra)", :usage=>{:tcp=>[2602]}},
+    :ripngd=>{:aliases=>[], :clients=>[], :description=>"ripngd vty (zebra)", :usage=>{:tcp=>[2603]}},
+    :ospfd=>{:aliases=>[], :clients=>[], :description=>"ospfd vty (zebra)", :usage=>{:tcp=>[2604]}},
+    :bgpd=>{:aliases=>[], :clients=>[], :description=>"bgpd vty (zebra)", :usage=>{:tcp=>[2605]}},
+    :ospf6d=>{:aliases=>[], :clients=>[], :description=>"ospf6d vty (zebra)", :usage=>{:tcp=>[2606]}},
+    :ospfapi=>{:aliases=>[], :clients=>[], :description=>"OSPF-API", :usage=>{:tcp=>[2607]}},
+    :isisd=>{:aliases=>[], :clients=>[], :description=>"ISISd vty (zebra)", :usage=>{:tcp=>[2608]}},
+    :afbackup=>{:aliases=>[], :clients=>[], :description=>"Afbackup system", :usage=>{:tcp=>[2988], :udp=>[2988]}},
+    :afmbackup=>{:aliases=>[], :clients=>[], :description=>"Afmbackup system", :usage=>{:tcp=>[2989], :udp=>[2989]}},
+    :xtell=>{:aliases=>[], :clients=>[], :description=>"xtell server", :usage=>{:tcp=>[4224]}},
+    :fax=>{:aliases=>[], :clients=>[], :description=>"FAX transmission service (old)", :usage=>{:tcp=>[4557]}},
+    :hylafax=>{:aliases=>[], :clients=>[], :description=>"HylaFAX client-server protocol (new)", :usage=>{:tcp=>[4559]}},
+    :distmp3=>{:aliases=>[], :clients=>[], :description=>"distmp3host daemon", :usage=>{:tcp=>[4600]}},
+    :munin=>{:aliases=>["lrrd"], :clients=>[], :description=>"Munin", :usage=>{:tcp=>[4949]}},
+    :"enbd-cstatd"=>{:aliases=>[], :clients=>[], :description=>"ENBD client statd", :usage=>{:tcp=>[5051]}},
+    :"enbd-sstatd"=>{:aliases=>[], :clients=>[], :description=>"ENBD server statd", :usage=>{:tcp=>[5052]}},
+    :pcrd=>{:aliases=>[], :clients=>[], :description=>"PCR-1000 Daemon", :usage=>{:tcp=>[5151]}},
+    :noclog=>{:aliases=>[], :clients=>[], :description=>"noclogd with TCP (nocol)", :usage=>{:tcp=>[5354], :udp=>[5354]}},
+    :hostmon=>{:aliases=>[], :clients=>[], :description=>"hostmon uses TCP (nocol)", :usage=>{:tcp=>[5355], :udp=>[5355]}},
+    :rplay=>{:aliases=>[], :clients=>[], :description=>"RPlay audio service", :usage=>{:udp=>[5555]}},
+    :nrpe=>{:aliases=>[], :clients=>[], :description=>"Nagios Remote Plugin Executor", :usage=>{:tcp=>[5666]}},
+    :nsca=>{:aliases=>[], :clients=>[], :description=>"Nagios Agent - NSCA", :usage=>{:tcp=>[5667]}},
+    :mrtd=>{:aliases=>[], :clients=>[], :description=>"MRT Routing Daemon", :usage=>{:tcp=>[5674]}},
+    :bgpsim=>{:aliases=>[], :clients=>[], :description=>"MRT Routing Simulator", :usage=>{:tcp=>[5675]}},
+    :canna=>{:aliases=>[], :clients=>[], :description=>"cannaserver", :usage=>{:tcp=>[5680]}},
+    :"sane-port"=>{:aliases=>[], :clients=>[], :description=>"SANE network scanner daemon", :usage=>{:tcp=>[6566]}},
+    :ircd=>{:aliases=>[], :clients=>[], :description=>"Internet Relay Chat", :usage=>{:tcp=>[6667]}},
+    :"zope-ftp"=>{:aliases=>[], :clients=>[], :description=>"zope management by ftp", :usage=>{:tcp=>[8021]}},
+    :tproxy=>{:aliases=>[], :clients=>[], :description=>"Transparent Proxy", :usage=>{:tcp=>[8081]}},
+    :omniorb=>{:aliases=>[], :clients=>[], :description=>"OmniORB", :usage=>{:tcp=>[8088], :udp=>[8088]}},
+    :xinetd=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[9098]}},
+    :mandelspawn=>{:aliases=>[], :clients=>[], :description=>"network mandelbrot", :usage=>{:udp=>[9359]}},
+    :git=>{:aliases=>[], :clients=>[], :description=>"Git Version Control System", :usage=>{:tcp=>[9418]}},
+    :zope=>{:aliases=>[], :clients=>[], :description=>"zope server", :usage=>{:tcp=>[9673]}},
+    :webmin=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:tcp=>[10000]}},
+    :kamanda=>{:aliases=>[], :clients=>[], :description=>"amanda backup services (Kerberos)", :usage=>{:tcp=>[10081], :udp=>[10081]}},
+    :amandaidx=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :usage=>{:tcp=>[10082]}},
+    :amidxtape=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :usage=>{:tcp=>[10083]}},
+    :smsqp=>{:aliases=>[], :clients=>[], :description=>"Alamin SMS gateway", :usage=>{:tcp=>[11201], :udp=>[11201]}},
+    :xpilot=>{:aliases=>[], :clients=>[], :description=>"XPilot Contact Port", :usage=>{:tcp=>[15345], :udp=>[15345]}},
+    :"sgi-cmsd"=>{:aliases=>[], :clients=>[], :description=>"Cluster membership services daemon", :usage=>{:udp=>[17001]}},
+    :"sgi-crsd"=>{:aliases=>[], :clients=>[], :description=>"", :usage=>{:udp=>[17002]}},
+    :"sgi-gcd"=>{:aliases=>[], :clients=>[], :description=>"SGI Group membership daemon", :usage=>{:udp=>[17003]}},
+    :"sgi-cad"=>{:aliases=>[], :clients=>[], :description=>"Cluster Admin daemon", :usage=>{:tcp=>[17004]}},
+    :isdnlog=>{:aliases=>[], :clients=>[], :description=>"isdn logging system", :usage=>{:tcp=>[20011], :udp=>[20011]}},
+    :vboxd=>{:aliases=>[], :clients=>[], :description=>"voice box system", :usage=>{:tcp=>[20012], :udp=>[20012]}},
+    :binkp=>{:aliases=>[], :clients=>[], :description=>"binkp fidonet protocol", :usage=>{:tcp=>[24554]}},
+    :asp=>{:aliases=>[], :clients=>[], :description=>"Address Search Protocol", :usage=>{:tcp=>[27374], :udp=>[27374]}},
+    :csync2=>{:aliases=>[], :clients=>[], :description=>"cluster synchronization tool", :usage=>{:tcp=>[30865]}},
+    :dircproxy=>{:aliases=>[], :clients=>[], :description=>"Detachable IRC Proxy", :usage=>{:tcp=>[57000]}},
+    :tfido=>{:aliases=>[], :clients=>[], :description=>"fidonet EMSI over telnet", :usage=>{:tcp=>[60177]}},
+    :fido=>{:aliases=>[], :clients=>[], :description=>"fidonet EMSI over TCP", :usage=>{:tcp=>[60179]}}
+  }
+  #
+  #
+  # ### end of SYSTEM detail initialization
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 973
+def [](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 = 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
+
+ +
+ + + + +
+ + +
+ +
+ architecture() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 998
+def architecture()
+  @thread_safety.synchronize {
+    {:architecture => @details[:architecture], :processors => @details[:processors]}.clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ cpuload() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1042
+def cpuload
+  # this or load_average ??
+  average = Sys::CPU.load_avg
+  { :minute1 => average[0], :minute5 => average[1], :minute15 => average[2] }
+end
+
+ +
+ + + + +
+ + +
+ +
+ current_user() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1324
+def current_user()
+  Sys::Admin.get_user(Sys::Admin.get_login)
+end
+
+ +
+ + + + +
+ + +
+ +
+ engine() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1016
+def engine()
+  # ??? just info ??? GxG::Engine::profile()
+  ::GxG::Engine
+end
+
+ +
+ + + + +
+ + +
+ +
+ environment() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1010
+def environment()
+  @thread_safety.synchronize {
+    {:environment => @details[:environment], :version => @details[:environment_version], :variant => @details[:environment_variant]}.clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ import_services_file() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1963
+def import_services_file()
+  #
+  current_system_list = self.services_file()
+  if current_system_list.size >0
+    current_system_list.to_enum(:each).each do |the_entry|
+      self.service_ports_register(the_entry.keys()[0],the_entry[(the_entry.keys()[0])])
+    end
+    true
+  else
+    false
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ load_average() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1027
+def load_average()
+  # this or cpuload ??
+  this_platform = @details[:platform]
+  case this_platform
+  when :linux
+    result = (`uptime`.split(":")[-1].chomp.split(","))
+    result.to_enum(:each_index).each do |indexer|
+      result[(indexer)] = result[(indexer)].to_f
+    end
+  else
+    result = []
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ maximum_buffer_size() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1065
+def maximum_buffer_size()
+  self.memory_limits()[:buffers]
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1069
+def memory()
+  result = {}
+  this_platform = nil
+  @thread_safety.synchronize { this_platform = @details[:platform] }
+  case this_platform
+    # TODO: GxG::SYSTEM.memory : research free mem discovery for Winderz and BSD
+  when :windows
+    # No known way to get this info. MEM is dosbox only style memory counting. :P
+    # Might have to bind to a C or C++ lib for this kind of data (PITA)
+    # FORNOW: just pretend upper theoretical limit on a process is all you have (sucks)
+    mem_limit = ::GxG::SYSTEM.memory_limits()[:process].max
+    result[:actual] = {:total => mem_limit, :used => 0, :free => mem_limit, :shared => 0, :buffers => 0, :cached => 0}
+    result[:buffers] = {:used => 0, :free => 0}
+    result[:virtual] = {:total => 0, :used => 0, :free => 0}
+  when :bsd, :darwin, :macos
+    # Inspired by: http://www.cyberciti.biz/faq/freebsd-command-to-get-ram-information/ by Ralf S. Engelschall
+    # ported to Ruby:
+    env = self.environment()
+    case env[:environment]
+    when :freebsd, :dragonfly, :pcbsd
+      # TODO: GxG::SYSTEM.memory() : test under freebsd, dragonfly and pcbsd
+      page_size = (`sysctl hw.pagesize`.to_s.split(":")[1].numeric_values()[:integer] || 0)
+      mem_phys = (`sysctl hw.physmem`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_all = (`sysctl vm.stats.vm.v_page_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_wire = (`sysctl vm.stats.vm.v_wire_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_active = (`sysctl vm.stats.vm.v_active_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_inactive = (`sysctl vm.stats.vm.v_inactive_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_cache = (`sysctl vm.stats.vm.v_cache_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_free = (`sysctl vm.stats.vm.v_free_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size
+      mem_vm_total = (`sysctl vm.swap_total`.to_s.split(":")[1].numeric_values()[:integer] || 0)
+      mem_vm_used = (`pstat -s`.to_s.split(" ")[2].numeric_values()[:integer] || 0)
+    when :netbsd
+      # TODO: GxG::SYSTEM.memory() : test under netbsd
+      page_size = 0
+      mem_phys = 0
+      mem_all = 0
+      mem_wire = 0
+      mem_active = 0
+      mem_inactive = 0
+      mem_cache = 0
+      mem_free = 0
+      mem_vm_total = 0
+      mem_vm_used = 0
+      #
+      raw_data = `vmstat -s`
+      raw_data.to_enum(:each_line).each do |the_line|
+        if the_line.include?("bytes per page")
+          page_size = (the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0)
+          break
+        end
+      end
+      if page_size > 0
+        raw_data.to_enum(:each_line).each do |the_line|
+          if the_line.include?("pages managed")
+            mem_phys = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+            mem_all = mem_phys
+          end
+          if the_line.include?("pages wired")
+            mem_wire = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+          if the_line.include?("pages active")
+            mem_active = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+          if the_line.include?("pages inactive")
+            mem_inactive = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+          if the_line.include?("pages free")
+            mem_free = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+          if the_line.include?("swap pages in use")
+            mem_vm_used = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+          if the_line.split(" ")[(1..-1)].join(" ") == "swap pages"
+            mem_vm_total = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size)
+          end
+        end
+      end
+    when :darwin, :macos
+      # TODO: GxG::SYSTEM.memory() : test under darwin
+      raw_data = `top -l 1`
+      page_size = (`sysctl hw.pagesize`.to_s.split(":")[1].numeric_values()[:integer] || 0)
+      # gives a byte-count, not a page count : hw.physmem
+      mem_phys = (`sysctl hw.physmem`.to_s.split(":")[1].numeric_values()[:integer] || 0)
+      mem_all = mem_phys
+      # virtual memory usage:
+      raw_data = (`sysctl vm.swapusage`.to_s.split(":")[1].split(" "))
+      mem_vm_total = ((raw_data[5].to_s.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+      mem_vm_used = ((raw_data[2].to_s.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+      #SharedLibs: num =  146, resident = 22.9M code, 3.40M data, 8.36M LinkEdit
+      #MemRegions: num =  7213, resident =  344M + 4.92M private, 22.5M shared
+      #PhysMem:  59.1M wired,  285M active,  138M inactive,  483M used, 28.3M free
+      raw_data.to_enum(:each_line).each do |the_line|
+        if the_line.include?("PhysMem:")
+          data = the_line.split(":")[1].to_s.split(",")
+          data.to_enum(:each).each do |the_param|
+            numeric_string = the_param.split(" ")[0]
+            if numeric_string.include?(".")
+              numeric_string.gsub!("M","")
+            else
+              numeric_string.gsub!("M",".0")
+            end
+            case the_param.split(" ")[1]
+            when /wired/
+              mem_wire = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+            when /active/
+              mem_active = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+            when /inactive/
+              mem_inactive = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+            when /free/
+              mem_free = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i
+            end
+          end
+          break
+        end
+      end
+    else
+      # default BSD behavior ???  Seems like everyone does their own special thing.
+    end
+    #
+    chip_size = 1
+    chip_guess = ((mem_phys / 8) - 1)
+    while (chip_guess != 0)
+      chip_guess >>= 1
+      chip_size <<= 1
+    end
+    mem_hw = (((mem_phys / chip_size).to_i + 1) * chip_size)
+    #
+    mem_gap_vm = (mem_all - (mem_wire + mem_active + mem_inactive + mem_cache + mem_free))
+    mem_gap_sys = (mem_phys - mem_all)
+    mem_gap_hw = (mem_hw - mem_phys)
+    #
+    mem_available = (mem_inactive + mem_cache + mem_free)
+    mem_used = (mem_hw - mem_available)
+    # format result:
+    result[:actual] = {:total => mem_hw, :used => mem_used, :free => mem_available, :shared => 0, :buffers => 0, :cached => 0}
+    result[:buffers] = {:used => 0, :free => 0}
+    result[:virtual] = {:total => mem_vm_total, :used => mem_vm_used, :free => (mem_vm_total - mem_vm_used)}
+    #
+  when :linux, :solaris
+    # total       used       free     shared    buffers     cached
+    raw_text = `free -b`
+    raw_text.to_enum(:each_line).each do |text_line|
+      if text_line.include?("Mem:")
+        stats = text_line.split(" ")
+        result[:actual] = {:total => stats[1].to_i, :used => stats[2].to_i, :free => stats[3].to_i, :shared => stats[4].to_i, :buffers => stats[5].to_i, :cached => stats[6].to_i}
+      end
+      if text_line.include?("-/+ buffers/cache:")
+        stats = text_line.split(" ")
+        result[:buffers] = {:used => stats[2].to_i, :free => stats[3].to_i}
+      end
+      if text_line.include?("Swap:")
+        stats = text_line.split(" ")
+        result[:virtual] = {:total => stats[1].to_i, :used => stats[2].to_i, :free => stats[3].to_i}
+      end
+    end
+  end
+  result.process! do |entry,selector,container|
+    if entry.is_a?(::String)
+      item = entry.numeric_values()
+      if item.is_a?(::Hash)
+        if item[:integer]
+          container[(selector)] = item[:integer].to_i
+        end
+        if item[:float]
+          container[(selector)] = item[:float].to_f
+        end
+      end
+      if item.is_a?(::Array)
+        item.each do |the_data|
+          if the_data.is_a?(::Hash)
+            if the_data[:integer]
+              container[(selector)] = the_data[:integer].to_i
+            end
+            if the_data[:float]
+              container[(selector)] = the_data[:float].to_f
+            end
+          end
+          if the_data.is_a?(::Array)
+            data = the_data[0]
+            if data.is_a?(::Hash)
+              if data[:integer]
+                container[(selector)] = data[:integer].to_i
+              end
+              if data[:float]
+                container[(selector)] = data[:float].to_f
+              end
+              break
+            end
+          end
+        end
+      end
+    end
+    nil
+  end
+  result[:total] = {:total => (result[:actual][:total].to_i + result[:virtual][:total].to_i), :used => (result[:actual][:used].to_i + result[:virtual][:used].to_i), :free => (result[:actual][:free].to_i + result[:virtual][:free].to_i)}
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ memory_limits() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1056
+def memory_limits()
+  # See: http://linux-mm.org/LinuxMM
+  # http://linux.die.net/man/2/mlock
+  # https://answers.launchpad.net/graphite/+question/191807
+  #
+  @thread_safety.synchronize {
+    @details[:memory_limits].clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ network() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1021
+def network()
+  @thread_safety.synchronize {
+    @details[:network].clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ network_next_port(the_address="0.0.0.0", honor_conventions=true) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1591
+def network_next_port(the_address="0.0.0.0", honor_conventions=true)
+  # finds next available port on given interface, by default respecting common port usage conventions.
+  if the_address.is_a?(::Addrinfo)
+    the_address = the_address.ip_address()
+  end
+  if the_address == "*" || the_address == nil
+    the_address = "0.0.0.0"
+  end
+  unless the_address.is_a?(::String)
+    raise ArgumentError, "address needs to be specified as a String, you provided #{the_address.class}"
+  end
+  #
+  if honor_conventions
+    commonly_available = (49152..65535)
+  else
+    commonly_available = (1..65535)
+  end
+  #
+  found = nil
+  currently_used_ports = []
+  current = self.network_status()
+  current[:internet].to_enum(:each).each do |entry|
+    if entry[:local_address].ip_address() == the_address
+      currently_used_ports << entry[:local_address].ip_port()
+    end
+    if entry[:foreign_address].ip_address() == the_address
+      currently_used_ports << entry[:foreign_address].ip_port()
+    end
+  end
+  #
+  commonly_available.to_enum(:each).each do |the_port|
+    unless currently_used_ports.include?(the_port)
+      found = the_port
+      break
+    end
+  end
+  #
+  found
+end
+
+ +
+ + + + +
+ + +
+ +
+ network_port_used?(the_port=0, the_address="0.0.0.0") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1546
+def network_port_used?(the_port=0, the_address="0.0.0.0")
+  if the_port.is_a?(::Addrinfo)
+    the_address = the_port.ip_address()
+    the_port = the_port.ip_port()
+  end
+  if the_address == "*" || the_address == nil
+    the_address = "0.0.0.0"
+  end
+  unless the_address.is_a?(::String)
+    raise ArgumentError, "address needs to be specified as a String, you provided #{the_address.class}"
+  end
+  if the_port == "*" || the_port == nil
+    the_port = 0
+  else
+    if the_port.is_any?(::String, ::Numeric)
+      the_port = the_port.to_i
+    else
+      raise ArgumentError, "port needs to be specified by an Integer or a String of an Integer or asterisk character, you provided #{the_port.class}"
+    end
+  end
+  current = {:internet => [], :local => []}
+  if the_port == 0
+    found = true
+  else
+    found = false
+    current = self.network_status()
+  end
+  #
+  unless found
+    current[:internet].to_enum(:each).each do |entry|
+      if ((entry[:local_address].ip_address() == the_address && entry[:local_address].ip_port() == the_port) || (entry[:foreign_address].ip_address() == the_address && entry[:foreign_address].ip_port() == the_port))
+        found = true
+        break
+      end
+    end
+  end
+  unless found
+    current[:local].to_enum(:each).each do |entry|
+      # pending further Research and hefty work-arounds within self.network_status().
+    end
+  end
+  #
+  found
+end
+
+ +
+ + + + +
+ + +
+ +
+ network_status() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1328
+def network_status()
+  # LATER: find out how to get the PID for AF_UNIX output of bsd-netstat -f unix
+  # goal: lsof given a path, find the PID associated
+  # lsof -Rt -t path (have to have read permissions)
+  # OK, I give up (F-U bsd guys for not having a PID field in netstat unix output :P )
+  # no unix socket info until I can figure a hooley-hoop set to jump through on this -- F'n Dorks!
+  data = []
+  local_data = []
+  case ::GxG::SYSTEM.platform()[:platform]
+  when :bsd
+    predata = []
+    raw_data = `netstat -an`
+    raw_data.to_enum(:each_line).each do |the_line|
+      ["tcp4 ", "tcp6 ", "udp4 ", "udp6 "].to_enum(:each).each do |entry|
+        if the_line.include?(entry)
+          predata << the_line.split(" ")
+          break
+        end
+      end
+      #
+    end
+    predata.to_enum(:each).each do |the_entry|
+      new_entry = {}
+      addressing = nil
+      case the_entry[0].downcase
+      when "tcp4", "tcp"
+        addressing = {:family => ::Socket::AF_INET, :protocol => :tcp}
+      when "tcp6"
+        addressing = {:family => ::Socket::AF_INET6, :protocol => :tcp6}
+      when "udp4", "udp"
+        addressing = {:family => ::Socket::AF_INET, :protocol => :udp}
+      when "udp6"
+        addressing = {:family => ::Socket::AF_INET6, :protocol => :udp6}
+      end
+      if addressing
+        new_entry[:protocol] = addressing[:protocol]
+        if addressing[:family] == ::Socket::AF_INET6
+          chunks = the_entry[3].split(".")
+          unless chunks[1] == "*"
+            chunks[1] = chunks[1].to_i
+          end
+        else
+          if the_entry[3].include?("*")
+            chunks = the_entry[3].split(".")
+            if chunks[0] == "*"
+              chunks = ["0.0.0.0",(the_entry[3].split(".").last)]
+            else
+              chunks = [(the_entry[3].split(".")[0..3].join(".")),(the_entry[3].split(".")[4])]
+            end
+            unless chunks[1] == "*"
+              chunks[1] = chunks[1].to_i
+            end
+          else
+            chunks = [(the_entry[3].split(".")[0..3].join(".")),(the_entry[3].split(".")[4].to_i)]
+          end
+        end
+        # bug fix:
+        if chunks[1] == "*"
+          chunks[1] = 0
+        end
+        # ##
+        if addressing[:protocol] == :udp || addressing[:protocol] == :udp6
+          new_entry[:local_address] = Addrinfo.udp(chunks[0],chunks[1])
+        else
+          new_entry[:local_address] = Addrinfo.tcp(chunks[0],chunks[1])
+        end
+        #
+        if addressing[:family] == ::Socket::AF_INET6
+          chunks = the_entry[4].split(".")
+          unless chunks[1] == "*"
+            chunks[1] = chunks[1].to_i
+          end
+        else
+          if the_entry[4].include?("*")
+            chunks = the_entry[4].split(".")
+            if chunks[0] == "*"
+              chunks = ["0.0.0.0",(the_entry[4].split(".").last)]
+            else
+              chunks = [(the_entry[4].split(".")[0..3].join(".")),(the_entry[4].split(".")[4])]
+            end
+            unless chunks[1] == "*"
+              chunks[1] = chunks[1].to_i
+            end
+          else
+            chunks = [(the_entry[4].split(".")[0..3].join(".")),(the_entry[4].split(".")[4].to_i)]
+          end
+        end
+        # bug fix:
+        if chunks[1] == "*"
+          chunks[1] = 0
+        end
+        # ##
+        if addressing[:protocol] == :udp || addressing[:protocol] == :udp6
+          new_entry[:foreign_address] = Addrinfo.udp(chunks[0],chunks[1])
+        else
+          new_entry[:foreign_address] = Addrinfo.tcp(chunks[0],chunks[1])
+        end
+        #
+        if the_entry[5].to_s.size > 0
+          new_entry[:status] = the_entry[5].to_sym
+        else
+          new_entry[:status] = nil
+        end
+        #
+        data << new_entry
+      end
+      #
+    end
+  when :linux
+    predata = []
+    raw_data = `netstat -an`
+    raw_data.to_enum(:each_line).each do |the_line|
+      ["tcp ", "tcp6 ", "udp ", "udp6 "].to_enum(:each).each do |entry|
+        if the_line.include?(entry)
+          predata << the_line.split(" ")
+          break
+        end
+      end
+      #
+    end
+    predata.to_enum(:each).each do |the_entry|
+      the_error = nil
+      new_entry = {}
+      addressing = nil
+      case the_entry[0].downcase
+      when "tcp"
+        addressing = {:family => ::Socket::AF_INET, :protocol => :tcp}
+      when "tcp6"
+        addressing = {:family => ::Socket::AF_INET6, :protocol => :tcp6}
+      when "udp"
+        addressing = {:family => ::Socket::AF_INET, :protocol => :udp}
+      when "udp6"
+        addressing = {:family => ::Socket::AF_INET6, :protocol => :udp6}
+      end
+      if addressing
+        new_entry[:protocol] = addressing[:protocol]
+        if addressing[:family] == ::Socket::AF_INET6
+          chunks = the_entry[3].split(":")
+          unless chunks[-1] == "*"
+            chunks[-1] = chunks[-1].to_i
+          end
+          chunks = [(chunks[0..-2]).join(":"),(chunks.last)]
+        else
+          # Odd bug work-around
+          addr = the_entry[3].split(":")[0]
+          port = the_entry[3].split(":")[1]
+          unless port == "*"
+            port = port.to_i
+          end
+          chunks = [(addr.split(":")[0]),(port)]
+        end
+        # bug fix:
+        if chunks[1] == "*"
+          chunks[1] = 0
+        end
+        #
+        begin
+          if addressing[:protocol] == :udp || addressing[:protocol] == :udp6
+            new_entry[:local_address] = Addrinfo.udp(chunks[0],chunks[1])
+          else
+            new_entry[:local_address] = Addrinfo.tcp(chunks[0],chunks[1])
+          end
+        rescue Exception => the_error
+        end
+        # ##
+        if addressing[:family] == ::Socket::AF_INET6
+          # IPV6
+          chunks = the_entry[4].split(":")
+          unless chunks[-1] == "*"
+            chunks[-1] = chunks[-1].to_i
+          end
+          chunks = [(chunks[0..-2]).join(":"),(chunks.last)]
+        else
+          # IPV4
+          # Odd bug work-around
+          addr = the_entry[4].split(":")[0]
+          port = the_entry[4].split(":")[1]
+          unless port == "*"
+            port = port.to_i
+          end
+          chunks = [(addr.split(":")[0]),(port)]
+          # puts "Got: #{chunks.inspect}"
+        end
+        #              if chunks[1].is_a?(::String)
+        #                if chunks[1].numeric_values(:integer)
+        #                  chunks[1] = chunks[1].numeric_values(:integer)[:integer]
+        #                end
+        #              end
+        # bug fix:
+        if chunks[1] == "*"
+          chunks[1] = 0
+        end
+        #
+        begin
+          if addressing[:protocol] == :udp || addressing[:protocol] == :udp6
+            new_entry[:foreign_address] = Addrinfo.udp(chunks[0],chunks[1])
+          else
+            new_entry[:foreign_address] = Addrinfo.tcp(chunks[0],chunks[1])
+          end
+        rescue Exception => the_error
+        end
+        # ##
+        if the_entry[5].to_s.size > 0
+          new_entry[:status] = the_entry[5].to_sym
+        else
+          new_entry[:status] = nil
+        end
+        #
+        unless the_error
+          data << new_entry
+        end
+        #
+      end
+    end
+  end
+  {:internet => data, :local => local_data}
+end
+
+ +
+ + + + +
+ + +
+ +
+ platform() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1004
+def platform()
+  @thread_safety.synchronize {
+    {:platform => @details[:platform], :version => @details[:platform_version], :configuration => @details[:platform_configuration]}.clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ processes(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1267
+def processes(params={})
+  results = []
+  columns = [:cmdline, :cwd, :environ, :exe, :fd, :root, :pid, :comm, :state, :ppid, :pgrp, :session, :tty_nr, :tpgid,
+    :flags, :minflt, :cminflt, :majflt, :cmajflt, :utime, :stime, :cutime, :cstime, :priority, :nice, :itrealvalue, :starttime,
+    :vsize, :rss, :rlim, :startcode, :endcode, :startstack, :kstkesp, :kstkeip, :signal, :blocked, :sigignore, :sigcatch, :wchan,
+    :nswap, :cnswap, :exit_signal, :processor, :rt_priority, :policy, :name, :uid, :euid, :gid, :egid, :pctcpu, :pctmem]
+  include_columns = []
+  if params[:columns].respond_to?(:each)
+    params[:columns].to_enum.each do |entry|
+      if columns.include?(entry.to_s.to_sym)
+        include_columns << entry.to_s.to_sym
+      end
+    end
+  else
+    include_columns = columns
+  end
+  params.delete(:columns)
+  #
+  search_for = {}
+  if params.respond_to?(:each_pair)
+    params.each_pair do |key,entry|
+      if columns.include?(key.to_s.to_sym)
+        search_for[(key.to_s.to_sym)] = entry
+      end
+    end
+  end
+  #
+  if (params[:pid])
+    raw_data = (Sys::ProcTable.ps(params[:pid]) || [])
+  else
+    raw_data = (Sys::ProcTable.ps() || [])
+  end
+  raw_data.to_enum.each do |entry|
+    new_entry = {}
+    process = true
+    if search_for.keys.size > 0
+      search_for.each_pair do |search_key,search_value|
+        unless (entry[(search_key)] == search_value)
+          process = false
+        end
+      end
+    end
+    if process
+      entry.each_pair do |key,item|
+        if include_columns.include?(key.to_s.to_sym)
+          new_entry[(key.to_sym)] = item
+        end
+      end
+    end
+    if new_entry.keys.size > 0
+      results << new_entry
+    end
+  end
+  #
+  results
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports() + + click to toggle source + +
+ + +
+ +

Services general port usage reference data

+ + + + +
+
# File lib/gxg/gxg_entities.rb, line 1631
+def service_ports()
+  @details[:service_ports]
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_register(service, entry={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1752
+def service_ports_register(service, entry={})
+  unless service.to_s.size > 0
+    raise ArgumentError, "you must specify a service symbol (i.e. :www ...), you provided #{service.inspect}"
+  end
+  unless service.is_a?(::Symbol)
+    service = service.to_s.to_sym
+  end
+  unless entry.is_a?(::Hash)
+    entry = {}
+  end
+  unless entry[:aliases].is_a?(::Array)
+    entry[:aliases] = []
+  end
+  unless entry[:clients].is_a?(::Array)
+    entry[:clients] = []
+  end
+  unless entry[:description].is_a?(::String)
+    entry[:description] = ""
+  end
+  unless entry[:usage].is_a?(::Hash)
+    entry[:usage] = {}
+  end
+  result = false
+  #
+  if @details[:service_ports][(service)]
+    entry[:aliases].to_enum(:each).each do |the_alias|
+      unless @details[:service_ports][(service)][:aliases].include?(the_alias)
+        @details[:service_ports][(service)][:aliases] << the_alias.to_s
+      end
+    end
+    entry[:clients].to_enum(:each).each do |the_client|
+      unless @details[:service_ports][(service)][:clients].include?(the_client.to_s)
+        @details[:service_ports][(service)][:clients] << the_client.to_s
+      end
+    end
+    entry[:usage].keys.to_enum(:each).each do |the_protocol|
+      if @details[:service_ports][(service)][:usage].keys.include?(the_protocol)
+        entry[:usage][(the_protocol)].to_enum(:each).each do |the_entry_port|
+          included = false
+          @details[:service_ports][(service)][:usage][(the_protocol)].each do |the_result_port|
+            if the_entry_port.is_a?(::Range)
+              if the_result_port.is_a?(::Range)
+                if (the_result_port.include?(the_entry_port.first) && the_result_port.include?(the_entry_port.last))
+                  included = true
+                  break
+                end
+              else
+                if the_entry_port.include?(the_result_port)
+                  included = true
+                  break
+                end
+              end
+            else
+              if the_result_port.is_a?(::Range)
+                if the_result_port.include?(the_entry_port)
+                  included = true
+                  break
+                end
+              else
+                if the_result_port == the_entry_port
+                  included = true
+                  break
+                end
+              end
+            end
+          end
+          unless included
+            @details[:service_ports][(service)][:usage][(the_protocol)] << the_entry_port
+          end
+        end
+      else
+        @details[:service_ports][(service)][:usage][(the_protocol)] = entry[:usage][(the_protocol)]
+      end
+      #
+    end
+    #
+  else
+    @details[:service_ports][(service.to_sym)] = entry
+    result = true
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_register_alias(service,alias_name) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1635
+def service_ports_register_alias(service,alias_name)
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if alias_name.to_s.size > 0
+      unless @details[:service_ports][(service.to_sym)][:aliases].include?(alias_name.to_s)
+        @details[:service_ports][(service.to_sym)][:aliases] << alias_name.to_s
+        result = true
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_register_client(service,client_scheme) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1662
+def service_ports_register_client(service,client_scheme)
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if client_scheme.to_s.size > 0
+      unless @details[:service_ports][(service.to_sym)][:clients].include?(client_scheme)
+        @details[:service_ports][(service.to_sym)][:clients] << client_scheme.to_s
+        result = true
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_register_usage(service,the_protocol,the_entry_port) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1707
+def service_ports_register_usage(service,the_protocol,the_entry_port)
+  # the_entry_port can be an Integer or a Range
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if @details[:service_ports][(service)][:usage].keys.include?(the_protocol)
+      included = false
+      @details[:service_ports][(service)][:usage][(the_protocol)].to_enum(:each).each do |the_result_port|
+        if the_entry_port.is_a?(::Range)
+          if the_result_port.is_a?(::Range)
+            if (the_result_port.include?(the_entry_port.first) && the_result_port.include?(the_entry_port.last))
+              included = true
+              break
+            end
+          else
+            if the_entry_port.include?(the_result_port)
+              included = true
+              break
+            end
+          end
+        else
+          if the_result_port.is_a?(::Range)
+            if the_result_port.include?(the_entry_port)
+              included = true
+              break
+            end
+          else
+            if the_result_port == the_entry_port
+              included = true
+              break
+            end
+          end
+        end
+      end
+      unless included
+        @details[:service_ports][(service)][:usage][(the_protocol)] << the_entry_port
+      end
+    else
+      @details[:service_ports][(service)][:usage][(the_protocol)] = the_entry_port
+    end
+    result = true
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_remove_alias(service,alias_name) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1648
+def service_ports_remove_alias(service,alias_name)
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if @details[:service_ports][(service.to_sym)][:aliases].include?(alias_name.to_s)
+      position = @details[:service_ports][(service.to_sym)][:aliases].index(alias_name.to_s)
+      if position
+        @details[:service_ports][(service.to_sym)][:aliases].delete_at(position)
+        result = true
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ service_ports_remove_client(service,client_scheme) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1675
+def service_ports_remove_client(service,client_scheme)
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if @details[:service_ports][(service.to_sym)][:clients].include?(client_scheme.to_s)
+      position = @details[:service_ports][(service.to_sym)][:clients].index(client_scheme.to_s)
+      if position
+        @details[:service_ports][(service.to_sym)][:clients].delete_at(position)
+        result = true
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ service_ports_usage_preference(service,preference=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1689
+def service_ports_usage_preference(service,preference=nil)
+  result = false
+  if @details[:service_ports][(service.to_sym)]
+    if preference
+      if [:tcp, :udp, :icmp, :snmp].include?(preference)
+        if @details[:service_ports][(service.to_sym)][:usage][(preference)]
+          @details[:service_ports][(service.to_sym)][:usage_preference] = preference
+          result = true
+        end
+      end
+    else
+      @details[:service_ports][(service.to_sym)].delete(:usage_preference)
+      result = true
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ services_file() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1888
+def services_file()
+  # "www" => {:aliases => ["http"], :clients => ["web"], :description => "", :usage => {:tcp => [(0..23),80], :udp => [80]}}
+  #
+  result = []
+  raw_data = []
+  case ::GxG::SYSTEM.platform()[:platform]
+  when :windows
+    # not possible like on linux/bsd ??
+  when :linux, :bsd
+    the_file = "/etc/services"
+    begin
+      file_handle = GxG::IO::File.new(the_file, ::IO::RDONLY)
+      raw_data = file_handle.readlines()
+      file_handle.close
+    rescue Exception
+      # give up on this
+    end
+    if raw_data.size > 0
+      #
+      raw_data.to_enum(:each).each do |the_line|
+        if the_line.split("#")[0]
+          service = ""
+          entry = {:aliases => [], :clients => [], :description => "", :usage => {}}
+          #
+          raw_entry = the_line.split("#")[0].split("\t")
+          if raw_entry[0].to_s.size > 0
+            service = raw_entry[0].to_sym
+          else
+            service = :unknown
+          end
+          if raw_entry[1].to_s.split("/").size == 2
+            if raw_entry[1].split("/")[0].to_s.size > 0
+              entry[:usage][(raw_entry[1].split("/")[1].gsub("\n","").to_sym)] = [ raw_entry[1].split("/")[0].to_i ]
+            end
+          else
+            if raw_entry[2].to_s.split("/").size == 2
+              entry[:usage][(raw_entry[2].split("/")[1].gsub("\n","").to_sym)] = [ raw_entry[2].split("/")[0].to_i ]
+            end
+          end
+          if raw_entry[3].to_s.size > 0
+            raw_entry[3].split(" ").to_enum(:each).each do |the_alias|
+              entry[:aliases] << the_alias
+            end
+          end
+          if (raw_entry.size - 1) > 3
+            (4..(raw_entry.size - 1)).each do |the_index|
+              if raw_entry[(the_index)].to_s.size > 0
+                raw_entry[(the_index)].split(" ").to_enum(:each).each do |the_alias|
+                  if the_alias.size > 0
+                    unless entry[:aliases].include?(the_alias)
+                      entry[:aliases] << the_alias
+                    end
+                  end
+                end
+              end
+            end
+          end
+          if the_line.split("#")[1]
+            entry[:description] = the_line.split("#")[1].split(" ").join(" ")
+          end
+          #
+          if entry[:usage].keys.size > 0
+            new_entry = {}
+            new_entry[(service.to_sym)] = entry
+            result << new_entry
+          end
+          #
+        end
+      end
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_modes() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_entities.rb, line 1048
+def valid_modes()
+  result = {}
+  result.merge(GxG::IO::IO::valid_modes())
+  @thread_safety.synchronize {
+    result.merge(@valid_modes)
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Entity/RemoteSystem.html b/doc/GxG/Entity/RemoteSystem.html new file mode 100644 index 0000000..81a49d1 --- /dev/null +++ b/doc/GxG/Entity/RemoteSystem.html @@ -0,0 +1,104 @@ + + + + + + +class GxG::Entity::RemoteSystem - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Entity::RemoteSystem +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Enumerator.html b/doc/GxG/Enumerator.html new file mode 100644 index 0000000..bedf7a1 --- /dev/null +++ b/doc/GxG/Enumerator.html @@ -0,0 +1,517 @@ + + + + + + +class GxG::Enumerator - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Enumerator +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(object=nil,method=:each,*args, &block) + + click to toggle source + +
+ + +
+ +

this is a bit inefficient, but it gets the job done. It is essentially an each wrapper to do a cooperative pauses for non-blocking goodness. :) TODO: inspiration: what about just leveraging 'super' after a pause operation on all overrides. (thx)

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 282
+def initialize(object=nil,method=:each,*args, &block)
+  # Without the call to super() it works, but will be uninitialized
+  # super(object,method,*args)
+  @collection = object.stock_to_enum(method,*args, &block)
+  # @collection = ::Enumerator.new(object,method,*args,&block)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ each(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 290
+def each(&block)
+  # reference = {:object => self, :method => __callee__, :fiber => Fiber.current, :thread => Thread.current}
+  if block.respond_to?(:call)
+    if ::GxG::EventManager::manager_running?
+      @collection.each do |*args|
+        block.call(*args)
+        pause
+      end
+      self
+    else
+      @collection.each(&block)
+      self
+    end
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_with_index(offset=0,&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: with_index +
+ +
+ + +
+ +
+ next() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 308
+def next()
+  pause
+  @collection.next()
+end
+
+ +
+ + + + +
+ + +
+ +
+ next_values() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 313
+def next_values()
+  pause
+  @collection.next_values()
+end
+
+ +
+ + + + +
+ + +
+ +
+ peek() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 318
+def peek()
+  pause
+  @collection.peek()
+end
+
+ +
+ + + + +
+ + +
+ +
+ peek_values() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 323
+def peek_values()
+  pause
+  @collection.peek_values()
+end
+
+ +
+ + + + +
+ + +
+ +
+ rewind() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 328
+def rewind()
+  if @collection.respond_to?(:rewind)
+    @collection.rewind
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 335
+def with_index(offset=0,&block)
+  enumerator = GxG::Enumerator.new(@collection.with_index(offset))
+  if block.respond_to?(:call)
+    enumerator.each do |entry,index|
+      block.call(entry,index)
+    end
+  else
+    enumerator
+  end
+end
+
+ +
+ + +
+ Also aliased as: each_with_index +
+ + + +
+ + +
+ +
+ with_object(an_object=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 347
+def with_object(an_object=nil,&block)
+  enumerator = GxG::Enumerator.new(@collection.with_object(an_object))
+  if block.respond_to?(:call)
+    enumerator.each do |entry,the_object|
+      block.call(entry,the_object)
+    end
+    an_object
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/EventManager.html b/doc/GxG/EventManager.html new file mode 100644 index 0000000..5315703 --- /dev/null +++ b/doc/GxG/EventManager.html @@ -0,0 +1,152 @@ + + + + + + +module GxG::EventManager - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::EventManager +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ manager_running?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 510
+def self.manager_running?()
+  result = false
+  if $Dispatcher
+    result = $Dispatcher.running?
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Events.html b/doc/GxG/Events.html new file mode 100644 index 0000000..ec0edea --- /dev/null +++ b/doc/GxG/Events.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Events - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Events +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Events/EventDispatcher.html b/doc/GxG/Events/EventDispatcher.html new file mode 100644 index 0000000..3220655 --- /dev/null +++ b/doc/GxG/Events/EventDispatcher.html @@ -0,0 +1,1110 @@ + + + + + + +class GxG::Events::EventDispatcher - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Events::EventDispatcher +

+ +
+ +

Provide a Cooperative Processing facility for sessions

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(interval=0.333, thread_reservation=100) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_events.rb, line 114
+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
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ adjust_event_queue(queue=:root,settings={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 259
+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
+
+ +
+ + + + +
+ + +
+ +
+ at(expression="",&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 364
+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
+    @scheduler.at(the_time.iso8601,&block)
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ cancel_timer(timer_reference=nil) + + click to toggle source + +
+ + +
+ +

Adding Timers See: stackoverflow.com/questions/235504/validating-crontab-entries-with-php Cron REGEX: /^((?:[1-9]?d|*)s*(?:(?:[1-9]?d)|(?:,?d)+)?s*){5}$/ Rufus Duration REGEX: /^(-?)([d.smhdwy]+)$/ at, in, every, cron

+ + + + +
+
# File lib/gxg/gxg_events.rb, line 354
+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
+
+ +
+ + + + +
+ + +
+ +
+ create_event_queue(settings={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 281
+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
+
+ +
+ + + + +
+ + +
+ +
+ cron(expression="",&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 496
+def cron(expression="",&block)
+  if /^((?:[1-9]?\d|\*)\s*(?:(?:[\/-][1-9]?\d)|(?:,[1-9]?\d)+)?\s*){5}$/.match(expression)
+    @scheduler.cron(expression,&block)
+  else
+    raise ArgumentError, "Invalid CRON expression: #{expression.inspect}"
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete_event_queue(queue=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 304
+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
+
+ +
+ + + + +
+ + +
+ +
+ every(expression="", &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 441
+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
+  @scheduler.every(the_interval,&block)
+end
+
+ +
+ + + + +
+ + +
+ +
+ in(expression="", &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 386
+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
+  @scheduler.in(interval,&block)
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect_queue(queue=:root) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 255
+def inspect_queue(queue=:root)
+  @event_queues[(queue)]
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect_timers() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 252
+def inspect_timers()
+  @scheduler.jobs()
+end
+
+ +
+ + + + +
+ + +
+ +
+ pause_event_queue(queue=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 318
+def pause_event_queue(queue=nil)
+  unless queue.to_s.to_sym == :root
+    self.adjust_event_queue(queue,{:active => true})
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ post_event(queue_name=:root,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 341
+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
+
+ +
+ + + + +
+ + +
+ +
+ post_to_event_queue(queue=:root,the_event=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 330
+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
+
+ +
+ + + + +
+ + +
+ +
+ running?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 134
+def running?()
+  @running
+end
+
+ +
+ + + + +
+ + +
+ +
+ shutdown() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 159
+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
+
+ +
+ + + + +
+ + +
+ +
+ startup() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 138
+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
+
+ +
+ + + + +
+ + +
+ +
+ tick() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 172
+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
+
+ +
+ + + + +
+ + +
+ +
+ unpause_event_queue(queue=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 324
+def unpause_event_queue(queue=nil)
+  unless queue.to_s.to_sym == :root
+    self.adjust_event_queue(queue,{:active => false})
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Events/LoggerDB.html b/doc/GxG/Events/LoggerDB.html new file mode 100644 index 0000000..a5ed2fc --- /dev/null +++ b/doc/GxG/Events/LoggerDB.html @@ -0,0 +1,104 @@ + + + + + + +class GxG::Events::LoggerDB - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Events::LoggerDB +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Events/Message.html b/doc/GxG/Events/Message.html new file mode 100644 index 0000000..ffece0f --- /dev/null +++ b/doc/GxG/Events/Message.html @@ -0,0 +1,824 @@ + + + + + + +class GxG::Events::Message - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Events::Message +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 7
+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), :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
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](the_key) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 81
+def [](the_key)
+  @data[(the_key)]
+end
+
+ +
+ + + + +
+ + +
+ +
+ []=(the_key, the_value) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 84
+def []=(the_key, the_value)
+  @data[(the_key)] = the_value
+end
+
+ +
+ + + + +
+ + +
+ +
+ body() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 36
+def body()
+  @data[:body]
+end
+
+ +
+ + + + +
+ + +
+ +
+ compose_reply(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 40
+def compose_reply(&block)
+  if block.respond_to?(:call)
+    reply_message = new_message({:sender => self[:to], :to => self[:sender], :context => self[:context], :subject => self[:subject], :body => self[:body]})
+    block.call(reply_message)
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ fail(input=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 55
+def fail(input=nil)
+  if @data[:on_fail].respond_to?(:call)
+    @data[:on_fail].call(input)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_at_path(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 99
+def get_at_path(*args)
+  @data.get_at_path(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ id() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 24
+def id()
+  @data[:id]
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 20
+def inspect()
+  @data.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ keys() + + click to toggle source + +
+ + +
+ +

Hash-like support methods:

+ + + + +
+
# File lib/gxg/gxg_events.rb, line 78
+def keys()
+  @data.keys()
+end
+
+ +
+ + + + +
+ + +
+ +
+ on(event_type=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 61
+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
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 96
+def paths_to(*args)
+  @data.paths_to(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 87
+def process(&block)
+  @data.process(&block)
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 90
+def process!(&block)
+  @data.process!(&block)
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ sender() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 28
+def sender()
+  @data[:sender]
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_at_path(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 102
+def set_at_path(*args)
+  @data.set_at_path(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+ subject() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 32
+def subject()
+  @data[:subject]
+end
+
+ +
+ + + + +
+ + +
+ +
+ succeed(input=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_events.rb, line 49
+def succeed(input=nil)
+  if @data[:on_success].respond_to?(:call)
+    @data[:on_success].call(input)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO.html b/doc/GxG/IO.html new file mode 100644 index 0000000..2270c94 --- /dev/null +++ b/doc/GxG/IO.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::IO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::IO +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/IO/Duplex.html b/doc/GxG/IO/Duplex.html new file mode 100644 index 0000000..bc4435c --- /dev/null +++ b/doc/GxG/IO/Duplex.html @@ -0,0 +1,171 @@ + + + + + + +class GxG::IO::Duplex - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::Duplex +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + GxG::IO::IO::new +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2541
+def initialize(*args)
+  params = self.process_parameters(*args)
+  if params[:mode].index(:read)
+    params[:mode].delete(:read)
+  end
+  if params[:mode].index(:write)
+    params[:mode].delete(:write)
+  end
+  unless params[:mode].index(:readwrite)
+    params[:mode] << :readwrite
+  end
+  super(params)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO/File.html b/doc/GxG/IO/File.html new file mode 100644 index 0000000..158b98e --- /dev/null +++ b/doc/GxG/IO/File.html @@ -0,0 +1,347 @@ + + + + + + +class GxG::IO::File - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::File +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ +

As of Ruby 1.9.2: if a file is not set to :binary mode, it defaults to :text mode. See: www.tutorialspoint.com/ruby/ruby_input_output.htm <file>.stat table: (mode example : 0100664)

+ +
0700       rwx mask for owner
+0400       r for owner
+0200       w for owner
+0100       x for owner
+0070       rwx mask for group
+0040       r for group
+0020       w for group
+0010       x for group
+0007       rwx mask for other
+0004       r for other
+0002       w for other
+0001       x for other
+4000       Set user ID on execution
+2000       Set group ID on execution
+1000       Save swapped text, even after use
+ + +
+ Calls superclass method + GxG::IO::IO::new +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2577
+def initialize(*args)
+  if args[0].is_a?(String)
+    the_path = args[0]
+    the_mode = (args[1] || "r")
+    the_permissions = args[2]
+    the_options = args[3]
+  else
+    raise ArgumentError, "filename or path must be passed as a String"
+  end
+  #
+  if the_options
+    params = self.process_parameters(0,the_mode,the_options)
+  else
+    params = self.process_parameters(0,the_mode)
+  end
+  #
+  mode_flags = {:binary => ::File::BINARY, :text => ::File::TEXT, :read => ::File::RDONLY, :write => ::File::WRONLY, :readwrite => ::File::RDWR, :create => ::File::CREAT, :overwrite => ::File::TRUNC, :append => ::File::APPEND}
+  modes = GxG::IO::IO::valid_mode_set({:type => :file, :read => true, :write => true, :text => true, :binary => true})
+  the_mode_used = nil
+  mode_string = ""
+  mode_numeric = 0
+  #
+  if params[:mode]
+    if params[:mode].index(:binary)
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "binary_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite])
+        else
+          mode_string = "binary_read"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read])
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "binary_write"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write])
+        end
+      end
+    else
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "text_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite])
+        else
+          mode_string = "text_read"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read])
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "text_write"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write])
+        end
+      end
+    end
+    if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) )
+      mode_string << "_overwrite"
+      mode_numeric = (mode_numeric | mode_flags[:overwrite])
+    else
+      if params[:mode].index(:append)
+        mode_string << "_append"
+        mode_numeric = (mode_numeric | mode_flags[:append])
+      end
+    end
+    # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary]
+    modes.to_enum.each do |mode_entry|
+      if (mode_entry[:synonyms].include?(mode_string.to_sym) && mode_entry[:synonyms].include?(mode_numeric))
+        the_mode_used = mode_entry
+        break
+      end
+      #
+    end
+    if (the_mode_used)
+      options = {}
+      the_mode = nil
+      if params[:external_encoding]
+        options[:external_encoding] = params.delete(:external_encoding)
+      end
+      if params[:internal_encoding]
+        #If the value is nil no conversion occurs.
+        if params[:internal_encoding] == params[:external_encoding]
+          options[:internal_encoding] = nil
+        else
+          options[:internal_encoding] = params.delete(:internal_encoding)
+        end
+      end
+      if params[:autoclose]
+        options[:autoclose] = params.delete(:autoclose)
+      end
+      if ::File::exist?(the_path)
+        the_mode = the_mode_used[:if_exist].to_i
+      else
+        if the_mode_used[:if_create]
+          the_mode = the_mode_used[:if_create].to_i
+        else
+          raise ArgumentError, "you cannot operate upon a non-existent file in read-only mode"
+        end
+      end
+      if the_mode
+        file_descriptor = GxG::IO::IO::sysopen(the_path,the_mode)
+      else
+        file_descriptor = nil
+      end
+      #
+      if file_descriptor
+        #
+        if ::File::directory?(the_path)
+          raise ArgumentError, "you cannot operate upon a directory as if it were a file"
+        else
+          if the_permissions
+            super(file_descriptor, the_mode, the_permissions, options)
+          else
+            super(file_descriptor, the_mode, nil, options)
+          end
+        end
+        #
+      else
+        raise Exception, "unable to create a valid file descriptor with: #{args.inspect}"
+      end
+      #
+    else
+      modes_list = []
+      modes.to_enum.each do |item|
+        modes_list << item[:synonyms]
+      end
+      raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}"
+    end
+  end
+  #
+  @path = the_path
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ to_path() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 2709
+def to_path()
+  @path.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO/IO.html b/doc/GxG/IO/IO.html new file mode 100644 index 0000000..5a6857b --- /dev/null +++ b/doc/GxG/IO/IO.html @@ -0,0 +1,1228 @@ + + + + + + +class GxG::IO::IO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::IO +

+ +
+ +
local input / output objects: Stream, Pipe, File, Device
+replacement File/Pipe/Socket IO object and methods built upon EM/EM-Synchrony (uses std. IO obj. for low-level work but attempts actual non-blocking)
+built-in 'non-blocking' does not appear to actually work (looking at you matz)
+.fd: numeric file descriptor or IO object
+mode: file mode. a string or an integer
+opt: hash for specifying mode by name.
+   :mode
+ +

Same as mode parameter

+ +

:external_encoding

+ +

External encoding for the IO. “-” is a synonym for the default external encoding.

+ +

:internal_encoding

+ +

Internal encoding for the IO. “-” is a synonym for the default internal encoding.

+ +

If the value is nil no conversion occurs.

+ +

:encoding

+ +

Specifies external and internal encodings as “extern:intern”.

+ +

:textmode

+ +

If the value is truth value, same as “t” in argument mode.

+ +

:binmode

+ +

If the value is truth value, same as “b” in argument mode.

+ +

:autoclose

+ +

If the value is false, the fd will be kept open after this IO instance gets finalized.

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 1396
+def initialize(*args)
+  # Sockets : http://www.tutorialspoint.com/ruby/ruby_socket_programming.htm
+  # io.c : http://rxr.whitequark.org/mri/source/io.c?v=1.9.3#8028
+  # File Permission Modes : http://www.tutorialspoint.com/ruby/ruby_input_output.htm
+  params = self.process_parameters(*args)
+  mode_string = ""
+  mode_numeric = 0
+  if params[:file_descriptor]
+    mode_flags = {:binary => ::File::BINARY, :text => ::File::TEXT, :read => ::File::RDONLY, :write => ::File::WRONLY, :readwrite => ::File::RDWR, :create => ::File::CREAT, :overwrite => ::File::TRUNC, :append => ::File::APPEND}
+    modes = GxG::IO::IO::valid_mode_set({:type => :file, :read => true, :write => true, :text => true, :binary => true})
+  else
+    mode_flags = {:binary => ::IO::BINARY, :text => ::IO::TEXT, :read => ::IO::RDONLY, :write => ::IO::WRONLY, :readwrite => ::IO::RDWR, :create => ::IO::CREAT, :overwrite => ::IO::TRUNC, :append => ::IO::APPEND}
+    modes = GxG::IO::IO::valid_mode_set({:type => :io, :read => true, :write => true, :text => true, :binary => true})
+  end
+  the_mode_used = nil
+  options = {}
+  # :mode
+  #Same as mode parameter
+  if params[:mode]
+    if params[:mode].index(:binary)
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "binary_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite])
+        else
+          mode_string = "binary_read"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read])
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "binary_write"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write])
+        end
+      end
+    else
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "text_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite])
+        else
+          mode_string = "text_read"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read])
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "text_write"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write])
+        end
+      end
+    end
+    if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) )
+      mode_string << "_overwrite"
+      mode_numeric = (mode_numeric | mode_flags[:overwrite])
+    else
+      if params[:mode].index(:append)
+        mode_string << "_append"
+        mode_numeric = (mode_numeric | mode_flags[:append])
+      else
+        if params[:mode].index(:create)
+          mode_string << "_create"
+          mode_numeric = (mode_numeric | mode_flags[:create])
+        end
+      end
+    end
+    # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary]
+    modes.to_enum.each do |mode_entry|
+      mode_entry[:synonyms].to_enum.each do |the_synonym|
+        if the_synonym.is_a?(Symbol)
+          if the_synonym == mode_string.to_sym
+            the_mode_used = mode_entry
+            break
+          end
+        else
+          if the_synonym.is_a?(Numeric)
+            if (the_synonym & mode_numeric == mode_numeric)
+              the_mode_used = mode_entry
+              break
+            end
+          end
+        end
+      end
+      if the_mode_used
+        break
+      end
+      #
+    end
+    if (the_mode_used)
+      if params[:file_descriptor]
+        # At this point, only fs objects that already exist will be referenced here.
+        if (mode_numeric & mode_flags[:create]) == mode_flags[:create]
+          if the_mode_used[:if_create]
+            options[:mode] = the_mode_used[:if_create].to_i
+          else
+            raise ArgumentError, "you cannot open read-only on a non-existent file system object"
+          end
+        else
+          options[:mode] = the_mode_used[:if_exist].to_i
+        end
+      else
+        # IO-ish object.
+        # When the mode of original IO is read only, the mode cannot be changed to be writable. Similarly,
+        # the mode cannot be changed from write only to readable.  When such a change is attempted
+        # the error is raised in different locations according to the platform.
+        if params[:object].is_any?(::IO, GxG::IO::IO)
+          if (params[:object].stat.readable? && (params[:mode].include?(:write) || params[:mode].include?(:readwrite)) && (params[:object].stat.writable? == false))
+            raise ArgumentError, "you cannot make a read-only IO writable"
+          end
+          if (params[:object].stat.writable? && (params[:mode].include?(:read) || params[:mode].include?(:readwrite)) && (params[:object].stat.readable? == false))
+            raise ArgumentError, "you cannot make a write-only IO readable"
+          end
+          if (params[:object].stat.binmode? && params[:mode].include?(:text))
+            raise ArgumentError, "you cannot make a binary IO text-only"
+          end
+        else
+          unless params[:object].is_any?( ::StringIO, GxG::IO::StringIO)
+            raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum"
+          end
+        end
+        #              if params[:mode].include?(:binary)
+        #                unless params[:object].binmode?
+        #                  params[:object].binmode
+        #                end
+        #              end
+        if (mode_numeric & mode_flags[:create]) == mode_flags[:create]
+          if the_mode_used[:if_create]
+            options[:mode] = the_mode_used[:if_create].to_i
+          else
+            raise ArgumentError, "you cannot open read-only on a non-existent file system object"
+          end
+        else
+          options[:mode] = the_mode_used[:if_exist].to_i
+        end
+      end
+      #
+    else
+      modes_list = []
+      modes.to_enum.each do |item|
+        modes_list << item[:synonyms]
+      end
+      raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}"
+    end
+  end
+  #
+  @conversion_options = {:external => {}, :internal => {}}
+  #
+  if params[:mode].include?(:binary)
+    options[:external_encoding] = ::Encoding::ASCII_8BIT
+  else
+    if params[:external_encoding].is_a?(::Encoding)
+      options[:external_encoding] = params.delete(:external_encoding)
+      #:2nd internal_encoding parameter and 3rd parameter optional hash ignored
+      # if you want the supplied string's encoding - best to simply pass it on the .new method call params.
+    else
+      options[:external_encoding] = ::Encoding.default_external
+    end
+  end
+  #
+  # if external_encoding is BINARY/ASCII_8BIT - just nullify internal_encoding, and skip external/internal conversion option mapping.
+  if params[:mode].include?(:binary)
+    @internal_encoding = nil
+  else
+    if (params[:internal_encoding].is_a?(::Encoding) && params[:internal_encoding] != options[:external_encoding])
+      @internal_encoding = params.delete(:internal_encoding)
+    else
+      @internal_encoding = ::Encoding.default_internal
+    end
+    #
+    if @internal_encoding
+      if @internal_encoding != options[:external_encoding]
+        if params[:external_conversion]
+          # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding
+          @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding,params[:external_conversion])
+        else
+          @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding)
+        end
+        #
+        if @internal_encoding == ::Encoding::ASCII_8BIT
+          #
+          if params[:internal_conversion]
+            nl_op = self.newline_option_used(params[:internal_conversion])
+            conv_options = {}
+            if nl_op
+              conv_options[(nl_op)] = true
+            end
+            @conversion_options[:internal] = conv_options
+          end
+        else
+          if params[:internal_conversion]
+            @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding],params[:internal_conversion])
+          else
+            @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding])
+          end
+        end
+        #
+      end
+    end
+    #
+  end
+  #
+  @external_field_separator = nil
+  if params[:external_field_separator]
+    @external_field_separator = params[:external_field_separator]
+  end
+  @external_record_separator = nil
+  if params[:external_record_separator]
+    @external_record_separator = params[:external_record_separator]
+  end
+  @internal_field_separator = nil
+  if params[:internal_field_separator]
+    @internal_field_separator = params[:internal_field_separator]
+  end
+  @internal_record_separator = nil
+  if params[:internal_record_separator]
+    @internal_record_separator = params[:internal_record_separator]
+  end
+  #:autoclose
+  # If the value is false, the fd will be kept open after this IO instance gets finalized.
+  if params[:autoclose]
+    options[:autoclose] = params.delete(:autoclose)
+  end
+  #
+  mode_numeric = options.delete(:mode)
+  if params[:file_descriptor]
+    super(params[:file_descriptor],mode_numeric,options)
+  else
+    super(params[:object],mode_numeric,options)
+  end
+  #
+  unless params[:object].is_any?(::StringIO, GxG::IO::StringIO)
+    self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK)
+  end
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ pipe(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 1335
+def self.pipe(*args)
+  # Goal: make IO.pipe available on ALL platforms a la false-pipe for code portability (via VFS??).  Duplex contains an @input and an @output IO channel
+  # pipes are not 'select' able on Winderz, so a faux-pipe must be shimmed in.
+  if [:kazoo_os].include?(GxG::SYSTEM.platform[:platform])
+    # TODO: GxG::IO::IO::pipe : provide a ring buffer and return read and write endpoint ios for it where the platform does not support it
+    # See: https://en.wikipedia.org/wiki/Circular_buffer
+    # See also: http://comments.gmane.org/gmane.comp.lang.ruby.io-splice.general/11
+    #
+  else
+    super(*args)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Protected Class Methods

+
+ + +
+ +
+ interpret_mode(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1271
+def self.interpret_mode(params={})
+  # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom]
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_mode_set(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1275
+def self.valid_mode_set(params={})
+  # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom]
+  valid_modes = []
+  valid_flags = []
+  if params[:binary]
+    if params[:read]
+      if params[:write]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_readwrite]
+      else
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read]
+      end
+    else
+      if params[:write]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_write]
+      end
+    end
+  else
+    if params[:read]
+      if params[:write]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_readwrite]
+      else
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read]
+      end
+    else
+      if params[:write]
+        valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_write]
+      end
+    end
+  end
+  @@valid_modes[(params[:type] || :io)].keys.to_enum.each do |mode_key|
+    @@valid_modes[(params[:type] || :io)][(mode_key)][:synonyms].to_enum.each do |synonym|
+      if synonym.is_a?(Numeric)
+        valid_flags.to_enum.each do |flag|
+          if synonym & flag == flag
+            unless valid_modes.include?(@@valid_modes[(params[:type] || :io)][(mode_key)])
+              valid_modes << @@valid_modes[(params[:type] || :io)][(mode_key)]
+            end
+          end
+        end
+      end
+    end
+  end
+  #
+  valid_modes
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_modes() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1268
+def self.valid_modes()
+  @@valid_modes
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <<(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1940
+def <<(*args)
+  self.write(*args)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_encoding=(*args) + + click to toggle source + +
+ + +
+ +

Public Instance Methods:

+ + + + +
+
# File lib/gxg/gxg_io.rb, line 1348
+def external_encoding=(*args)
+  #
+  unless self.binmode?()
+    if args[0].is_any?(::Encoding, ::NilClass)
+      #
+      if args[0] == ::Encoding::ASCII_8BIT
+        self.binmode()
+      else
+        #              old_data = self.string().dup
+        #
+        if args[0].is_a?(::Encoding)
+          self.set_encoding(args[0])
+          if @internal_encoding
+            if @internal_encoding != self.external_encoding
+              # preserve newline settings
+              nl_op = self.newline_option_used(:external)
+              options = {}
+              if nl_op
+                options[(nl_op)] = true
+              end
+              @conversion_options[:external] = ::String::transcoding_options(self.external_encoding,@internal_encoding,options)
+            end
+          end
+        else
+          self.set_encoding(::Encoding.default_external())
+        end
+        # convert existing data???
+        #              if self.external_encoding() != old_data.encoding()
+        #                # preserve newline settings
+        #                nl_op = self.newline_option_used(:external)
+        #                options = {}
+        #                if nl_op
+        #                  options[(nl_op)] = true
+        #                end
+        #                old_data.transcode!(self.external_encoding(),options)
+        #                #
+        #                self.string.replace(old_data)
+        #              end
+        #
+      end
+      #
+    else
+      raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}"
+    end
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ fcntl(*args) + + click to toggle source + +
+ + +
+ +

portable, cross-platform facade for Fcntl (even under Winderz)

+ + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 1631
+def fcntl(*args)
+  # Thanks to Jon Cooper for the find at http://www.mail-archive.com/beanstalk-talk@googlegroups.com/msg01092.html
+  #The FD_CLOEXEC flag is part of the POSIX API, which Windows doesn't support,
+  #but it does  provide analogous functionality.
+  #
+  #See http://www.perlmonks.org/index.pl?node_id=574349
+  #
+  #You can implement a patch with https://github.com/jarib/childprocess/ which
+  #appears to provide a consistent facade.
+  #
+  #See
+  #http://rubydoc.info/github/jarib/childprocess/master/ChildProcess#close_on_exec-class_method
+  #
+  #Cheers,
+  #Jon
+  #
+  # Attribution: https://github.com/jarib for Childprocess work.
+  #
+  # For Win32 See: http://rxr.whitequark.org/mri/source/win32/win32.c?v=1.9.3#fcntl
+  # ... and older stuff at : http://rxr.whitequark.org/mri/source/wince/fcntl.h?v=1.8.7
+  # Ruby General Fcntl Stuff: http://ruby-doc.org/stdlib-1.9.3/libdoc/fcntl/rdoc/Fcntl.html
+  #
+  # ### fcntl commands:
+  # F_DUPFD       Integer      Positive Integer; -1 on error       Find the lowest numbered available file descriptor greater than or equal to arg and make it a copy of the receiver’s file descriptor. If arg is omitted, it is assumed to be equal to the receiver’s file descriptor.
+  # F_GETFD       N/A  File descriptor flags   Retrieves the associated file descriptor flags. Currently, these are either 0 or FD_CLOEXEC. These flags may be set with F_SETFD.
+  # F_GETFL       N/A  Integer         Returns the file status flags, i.e. a bitwise OR of O_APPEND, O_ASYNC, O_DIRECT, etc. O_ACCMODE is a bitmask for extracting the access mode from these flags.
+  # F_GETLK       struct flock *       N/A  The argument describes a lock the caller wishes to place on the file. If this is possible, the l_type field of the struct is set to Fcntl::F_UNLCK; otherwise the struct is updated with details of the current lock holder.
+  # F_SETFD       FD_CLOEXEC or 0      0; -1 on error      Sets the file descriptor flags to arg.When arg is FD_CLOEXEC, this is equivalent to #close_on_exec=true.
+  # F_SETFL       Integer      0; -1 on error      Set the file status flags to arg
+  # F_SETLK       struct flock *       0; -1 on error.      When the struct’s l_type field has the value F_RDLCK or F_WRLCK, acquires the lock; when it has the value F_UNLCK, releases the lock.
+  # F_SETLKW      struct flock *      0; -1 on error.     Behaves like F_SETLK, except when a conflicting lock is held this call blocks until the lock is released or a signal is caught.
+  #
+  fcntl_modes = {}
+  case GxG::SYSTEM.platform()[:platform]
+  when :windows
+    #          unless nil
+    #            fcntl_modes[:io] = {}
+    #          end
+    # I *think* as of 1.9.2+ winderz now has fcntl support in ruby code, see: win32.c
+    # TODO: test fcntl under Winderz
+    super(*args)
+  else
+    super(*args)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ read(length=0,outbuffer="") + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 1677
+def read(length=0,outbuffer="")
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    if outbuffer.is_a?(::String)
+      outbuffer = self.transcode_to_internal(outbuffer)
+      if self.internal_encoding
+        if outbuffer.encoding != self.internal_encoding()
+          outbuffer.force_encoding(self.internal_encoding())
+        end
+      end
+    end
+    #
+    # Attribution: MRI 1.9.3 docs.
+    # TODO: research *proper* non-blocking reads and writes
+    #read_nonblock just calls the read(2) system call. It causes all errors the
+    #read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The caller
+    #should care such errors.
+    #
+    #If the exception is Errno::EWOULDBLOCK or Errno::AGAIN, it is extended by
+    #IO::WaitReadable. So IO::WaitReadable can be used to rescue the exceptions for
+    #retrying read_nonblock.
+    #
+    #read_nonblock causes EOFError on EOF.
+    #
+    #If the read byte buffer is not empty, read_nonblock reads from the buffer like
+    #readpartial. In this case, the read(2) system call is not called.
+    #
+    #When read_nonblock raises an exception kind of IO::WaitReadable, read_nonblock
+    #should not be called until io is readable for avoiding busy loop. This can be
+    #done as follows.
+    #
+    #  # emulates blocking read (readpartial).
+    #  begin
+    #    result = io.read_nonblock(maxlen)
+    #  rescue IO::WaitReadable
+    #    IO.select([io])
+    #    retry
+    #  end
+    #
+    #Although IO#read_nonblock doesn't raise IO::WaitWritable.
+    #OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. If IO and SSL
+    #should be used polymorphically, IO::WaitWritable should be rescued too. See
+    #the document of OpenSSL::Buffering#read_nonblock for sample code.
+    #
+    #Note that this method is identical to readpartial except the non-blocking flag
+    #is set.
+    # millisecond: 1/1000th of a second (reminder)
+    read_bytes = 0
+    # unless IO is some other type, use default buffer size
+    if (self.tty? || self.stat.pipe?)
+      buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i
+    else
+      if self.stat.socket?
+        buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:read][:initial].to_i
+      else
+        # assume file
+        buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i
+      end
+    end
+    #
+    until (read_bytes == length)
+      begin
+        if ((length - read_bytes) < buffer_size)
+          outbuffer << self.transcode_to_internal(read_nonblock((length - read_bytes)))
+          read_bytes += (length - read_bytes)
+        else
+          outbuffer << self.transcode_to_internal(read_nonblock(buffer_size))
+          read_bytes += buffer_size
+        end
+        #
+      rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable
+        pause
+        begin
+          # about 10 ms
+          selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          until selected
+            pause
+            selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          end
+        rescue Exception
+          pause
+          retry
+        end
+        retry
+      end
+      #
+      pause
+    end
+    #
+    outbuffer
+  else
+    result = self.transcode_to_internal(super(length))
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ readpartial(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1772
+def readpartial(*args)
+  # args[0] maxlen (Fixnum)
+  # args[1] (optional) buffer_object
+  # Note: use the same approach to pause/retry as IO.read_nonblock.
+  #
+  # Reads at most maxlen bytes from the I/O stream. It blocks only if ios has no data immediately available. It doesn’t block if some data available.
+  # If the optional outbuf argument is present, it must reference a String, which will receive the data. It raises EOFError on end of file.
+  # readpartial is designed for streams such as pipe, socket, tty, etc. It blocks only when no data immediately available.
+  # This means that it blocks only when following all conditions hold:
+  #   the byte buffer in the IO object is empty.
+  #   the content of the stream is empty.
+  #   the stream is not reached to EOF.
+  # When readpartial blocks, it waits data or EOF on the stream. If some data is reached, readpartial returns with the data. If EOF is reached,
+  # readpartial raises EOFError.
+  # When readpartial doesn’t blocks, it returns or raises immediately. If the byte buffer is not empty, it returns the data in the buffer.
+  # Otherwise if the stream has some content, it returns the data in the stream. Otherwise if the stream is reached to EOF, it raises EOFError.
+  #
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      result = ""
+      result.force_encoding(self.internal_encoding)
+      data = self.read(*args)
+      if data
+        # non-nil
+        if data.is_a?(::String)
+          # 'blocking' or not?
+          if (self.eof?() || (data && data.to_s.size > 0))
+            # no pseudo-blocking
+            result << data
+            data = nil
+          else
+            # pseudo-blocking
+            until (self.eof?() || (data && data.to_s.size > 0))
+              data = self.read(*args)
+              # string, buffer, or nil
+              if data
+                #
+                if data.is_a?(::String)
+                  if (data && data.to_s.size > 0)
+                    result << data
+                    data = nil
+                    break
+                  else
+                    if self.eof?()
+                      raise EOFError, "end of stream reached"
+                    end
+                  end
+                else
+                  # buffer
+                  result = data
+                  data = nil
+                  break
+                end
+              else
+                if self.eof?()
+                  raise EOFError, "end of stream reached"
+                end
+              end
+              #
+              pause
+              #
+            end
+          end
+        else
+          # buffer
+          result = data
+        end
+      else
+        if self.eof?()
+          raise EOFError, "end of stream reached"
+        end
+      end
+      #
+      if result
+        if result.is_a?(::String)
+          if result.bytesize > 0
+            result
+          else
+            nil
+          end
+        else
+          # buffer
+          result
+        end
+      else
+        nil
+      end
+      #
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ write(data="") + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 1868
+def write(data="")
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    # on Winderz, just to a buffer sized spoon full until done ??
+    # written_bytes = self.write_nonblock(data)
+    # TODO: research *proper* non-blocking reads and writes
+    data = self.transcode_to_external(data)
+    length = data.size
+    written_bytes = 0
+    # unless IO is some other type, use default buffer size
+    if (self.tty? || self.stat.pipe?)
+      buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i
+    else
+      if self.stat.socket?
+        buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:write][:initial].to_i
+      else
+        # assume file
+        buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i
+      end
+    end
+    until (written_bytes == length)
+      # on Winderz, use self.write(data) ??
+      chunk = ""
+      chunk.force_encoding(data.encoding)
+      if ((written_bytes) + (buffer_size - 1)) > length
+        chunk << data.slice((written_bytes),(length - 1))
+      else
+        chunk << data.slice((written_bytes),((written_bytes) + (buffer_size - 1)))
+      end
+      #
+      begin
+        # On some platforms such as Windows, write_nonblock is not supported according
+        # to the kind of the IO object. In such cases, write_nonblock raises
+        # Errno::EBADF.
+        # # write_nonblock writes only 65536 bytes and return 65536.
+        # (The pipe size is 65536 bytes on this environment.)
+        #  s = "a" * 100000
+        #  p w.write_nonblock(s)     #=> 65536
+        #
+        #  # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN).
+        #  p w.write_nonblock("b")   # Resource temporarily unavailable (Errno::EAGAIN)
+        #
+        #If the write buffer is not empty, it is flushed at first.
+        #
+        #When write_nonblock raises an exception kind of IO::WaitWritable,
+        #write_nonblock should not be called until io is writable for avoiding busy
+        #loop. This can be done as follows.
+        #
+        # bytes_written = super(data)
+        written_bytes += write_nonblock(chunk).to_i
+      rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN
+        pause
+        begin
+          selected = GxG::IO::IO::select(nil,[self],nil,0.010)
+          until selected
+            pause
+            selected = GxG::IO::IO::select(nil,[self],nil,0.010)
+          end
+        rescue Exception
+          pause
+          retry
+        end
+        retry
+      end
+      #
+      pause
+    end
+  else
+    written_bytes = super(self.transcode_to_external(data))
+  end
+  written_bytes
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO/Input.html b/doc/GxG/IO/Input.html new file mode 100644 index 0000000..238fceb --- /dev/null +++ b/doc/GxG/IO/Input.html @@ -0,0 +1,168 @@ + + + + + + +class GxG::IO::Input - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::Input +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ +

ensure mode is read-only, else raise Exception

+ + +
+ Calls superclass method + GxG::IO::IO::new +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2509
+def initialize(*args)
+  params = self.process_parameters(*args)
+  if params[:mode].index(:write)
+    params[:mode].delete(:write)
+  end
+  if params[:mode].index(:readwrite)
+    params[:mode].delete(:readwrite)
+  end
+  super(params)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO/Output.html b/doc/GxG/IO/Output.html new file mode 100644 index 0000000..ca9b4a9 --- /dev/null +++ b/doc/GxG/IO/Output.html @@ -0,0 +1,171 @@ + + + + + + +class GxG::IO::Output - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::Output +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ +

ensure mode is write-only, else raise Exception

+ + +
+ Calls superclass method + GxG::IO::IO::new +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2524
+def initialize(*args)
+  params = self.process_parameters(*args)
+  if params[:mode].index(:read)
+    params[:mode].delete(:read)
+  end
+  if params[:mode].index(:readwrite)
+    params[:mode].delete(:readwrite)
+  end
+  unless params[:mode].index(:write)
+    params[:mode] << :write
+  end
+  super(params)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/IO/StringIO.html b/doc/GxG/IO/StringIO.html new file mode 100644 index 0000000..3358802 --- /dev/null +++ b/doc/GxG/IO/StringIO.html @@ -0,0 +1,1284 @@ + + + + + + +class GxG::IO::StringIO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::IO::StringIO +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2063
+def initialize(*args)
+  # TODO: override methods to operate in cooperative processing event system
+  # TODO: provide a REAL external:internal encoding translation aspect (stock version is poorly designed/wrong, it would act *exactly* like a real IO)
+  # TODO: optimize this class for memory and call efficiency (see Question at super() call)
+  # LATER: GxG::StringIO : create/learn/whatever an optimized any-encoding-to-any-encoding transcoding/normalization matrix : possibly extend Encoding Class? This should include a conversion newline/field_separator/record_separator for each pair of Encodings.
+  # Question: is there some possible benefit to using a ByteArray instead of a stock StringIO ?
+  params = self.process_parameters(*args)
+  unless params[:mode].size > 0
+    params[:mode] << :read
+    params[:mode] << :write
+  end
+  if params[:external_encoding] == ::Encoding::ASCII_8BIT
+    unless params[:mode].include?(:binary)
+      params[:mode] << :binary
+    end
+  end
+  #
+  mode_string = ""
+  mode_numeric = 0
+  @fcntl_mode = ::Fcntl::O_NONBLOCK
+  mode_flags = {:binary => ::IO::BINARY, :text => ::IO::TEXT, :read => ::IO::RDONLY, :write => ::IO::WRONLY, :readwrite => ::IO::RDWR, :create => ::IO::CREAT, :overwrite => ::IO::TRUNC, :append => ::IO::APPEND}
+  modes = GxG::IO::IO::valid_mode_set({:type => :io, :read => true, :write => true, :text => true, :binary => true})
+  the_mode_used = nil
+  options = {}
+  # :mode
+  #Same as mode parameter
+  if params[:mode]
+    if params[:mode].index(:binary)
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "binary_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR)
+        else
+          mode_string = "binary_read"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY)
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "binary_write"
+          mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY)
+        end
+      end
+    else
+      if params[:mode].index(:read)
+        if params[:mode].index(:write)
+          mode_string = "text_readwrite"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR)
+        else
+          mode_string = "text_read"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY)
+        end
+      else
+        if params[:mode].index(:write)
+          mode_string = "text_write"
+          mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY)
+        end
+      end
+    end
+    if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) )
+      mode_string << "_overwrite"
+      mode_numeric = (mode_numeric | mode_flags[:overwrite])
+      @fcntl_mode = (@fcntl_mode | ::Fcntl::O_TRUNC)
+    else
+      if params[:mode].index(:append)
+        mode_string << "_append"
+        mode_numeric = (mode_numeric | mode_flags[:append])
+        @fcntl_mode = (@fcntl_mode | ::Fcntl::O_APPEND)
+      else
+        if params[:mode].index(:create)
+          mode_string << "_create"
+          mode_numeric = (mode_numeric | mode_flags[:create])
+          @fcntl_mode = (@fcntl_mode | ::Fcntl::O_CREAT)
+        end
+      end
+    end
+    # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary]
+    modes.to_enum.each do |mode_entry|
+      mode_entry[:synonyms].to_enum.each do |the_synonym|
+        if the_synonym.is_a?(Symbol)
+          if the_synonym == mode_string.to_sym
+            the_mode_used = mode_entry
+            break
+          end
+        else
+          if the_synonym.is_a?(Numeric)
+            if (the_synonym & mode_numeric == mode_numeric)
+              the_mode_used = mode_entry
+              break
+            end
+          end
+        end
+      end
+      if the_mode_used
+        break
+      end
+      #
+    end
+    if (the_mode_used)
+      options[:mode] = the_mode_used[:if_exist].to_i
+    else
+      modes_list = []
+      modes.to_enum.each do |item|
+        modes_list << item[:synonyms]
+      end
+      raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}"
+    end
+  end
+  #
+  mode_numeric = options.delete(:mode)
+  #
+  @conversion_options = {:external => {}, :internal => {}}
+  # init-text clobber work-around : defer :write of init-text until the end.
+  # init_text = params[:object].dup
+  # params[:object].clear
+  # FORNOW: just use default behavior
+  super(params[:object],mode_numeric)
+  #
+  if params[:mode].include?(:binary)
+    self.set_encoding(::Encoding::ASCII_8BIT)
+  else
+    if params[:external_encoding].is_a?(::Encoding)
+      self.set_encoding(params.delete(:external_encoding))
+      #:2nd internal_encoding parameter and 3rd parameter optional hash ignored
+      # if you want the supplied string's encoding - best to simply pass it on the .new method call params.
+    else
+      self.set_encoding(::Encoding.default_external)
+    end
+  end
+  #
+  # if external_encoding is BINARY/ASCII_8BIT - just nullify internal_encoding, and skip external/internal conversion option mapping.
+  if params[:mode].include?(:binary)
+    @internal_encoding = nil
+  else
+    if params[:internal_encoding].is_a?(::Encoding)
+      @internal_encoding = params.delete(:internal_encoding)
+    else
+      @internal_encoding = ::Encoding.default_internal
+    end
+    #
+    if @internal_encoding
+      if @internal_encoding != self.external_encoding
+        if params[:external_conversion]
+          # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding
+          @conversion_options[:external] = ::String::transcode_options(self.external_encoding,@internal_encoding,params[:external_conversion])
+        else
+          @conversion_options[:external] = ::String::transcode_options(self.external_encoding,@internal_encoding)
+        end
+        #
+        if @internal_encoding == ::Encoding::ASCII_8BIT
+          #
+          if params[:internal_conversion]
+            nl_op = self.newline_option_used(params[:internal_conversion])
+            conv_options = {}
+            if nl_op
+              conv_options[(nl_op)] = true
+            end
+            @conversion_options[:internal] = conv_options
+          end
+        else
+          if params[:internal_conversion]
+            @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,self.external_encoding,params[:internal_conversion])
+          else
+            @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,self.external_encoding)
+          end
+        end
+        #
+      end
+    end
+    #
+  end
+  #
+  @external_field_separator = nil
+  if params[:external_field_separator]
+    self.external_field_separator = params[:external_field_separator]
+  end
+  @external_record_separator = nil
+  if params[:external_record_separator]
+    self.external_record_separator = params[:external_record_separator]
+  end
+  @internal_field_separator = nil
+  if params[:internal_field_separator]
+    self.external_field_separator = params[:internal_field_separator]
+  end
+  @internal_record_separator = nil
+  if params[:internal_record_separator]
+    self.external_field_separator = params[:internal_record_separator]
+  end
+  #
+  # init-text clobber workaround : deferred :write
+  #self.write(init_text)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ open(the_string="",mode=::IO::RDWR,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 1962
+def self.open(the_string="",mode=::IO::RDWR,&block)
+  result = nil
+  the_io = GxG::IO::StringIO.new(the_string,mode)
+  #
+  if block.respond_to?(:call)
+    result = block.call(the_io)
+    the_io.close
+  else
+    result = the_io
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <<(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 2392
+def <<(*args)
+  self.write(*args)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ binmode() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + StringIO#binmode +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2276
+def binmode()
+  if self.binmode?
+    self
+  else
+    @internal_encoding = nil
+    @conversion_options[:internal] = {}
+    @conversion_options[:external] = {}
+    self.set_encoding(::Encoding::ASCII_8BIT)
+    super()
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ binmode?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 2268
+def binmode?()
+  if self.external_encoding == ::Encoding::ASCII_8BIT
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ close() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2493
+def close()
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ close_read() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2497
+def close_read()
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ close_write() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2501
+def close_write()
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_encoding=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 2288
+def external_encoding=(*args)
+  #
+  unless self.binmode?
+    if args[0].is_any?(::Encoding, ::NilClass)
+      #
+      if args[0] == ::Encoding::ASCII_8BIT
+        self.binmode()
+      else
+        old_data = self.string().dup
+        #
+        if args[0].is_a?(::Encoding)
+          self.set_encoding(args[0])
+          if @internal_encoding
+            if @internal_encoding != self.external_encoding
+              # preserve newline settings
+              nl_op = self.newline_option_used(:external)
+              options = {}
+              if nl_op
+                options[(nl_op)] = true
+              end
+              @conversion_options[:external] = ::String::transcoding_options(self.external_encoding,@internal_encoding,options)
+            end
+          end
+        else
+          self.set_encoding(::Encoding.default_external())
+        end
+        #
+        if self.external_encoding() != old_data.encoding()
+          # preserve newline settings
+          nl_op = self.newline_option_used(:external)
+          options = {}
+          if nl_op
+            options[(nl_op)] = true
+          end
+          old_data.transcode!(self.external_encoding(),options)
+          #
+          self.string.replace(old_data)
+        end
+        #
+      end
+      #
+    else
+      raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}"
+    end
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ flags() + + click to toggle source + +
+ + +
+ +

Public instance methods

+ + + + +
+
# File lib/gxg/gxg_io.rb, line 1977
+def flags()
+  result = []
+  current = self.fcntl(::Fcntl::F_GETFL)
+  flags = {}
+  if self.binmode?()
+    result << :binary
+    if self.external_encoding == ::Encoding::ASCII_8BIT
+      result << :setenc_by_bom
+    end
+  else
+    result << :text
+    result << :wsplit
+  end
+  if self.tty?
+    result << :tty
+  end
+  #
+  #flag[:read] = 0x00000001
+  #flag[:write] = 0x00000002
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  #flag[:binmode] = 0x00000004
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  flags[:read] = ::Fcntl::O_RDONLY
+  flags[:write] = ::Fcntl::O_WRONLY
+  flags[:readwrite] =::Fcntl::O_RDWR
+  #flags[:binary] = 0x00000004
+  flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
+  flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata.
+  flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
+  #flags[:tty] = 0x00000010
+  flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY
+  flags[:duplex] = 0x00000020
+  flags[:append] = ::Fcntl::O_APPEND
+  flags[:create] = ::Fcntl::O_CREAT
+  flags[:exclusive] = ::Fcntl::O_EXCL
+  # flags[:wsplit] = 0x00000200
+  flags[:wsplit_initialized] = 0x00000400
+  flags[:trunc] = ::Fcntl::O_TRUNC
+  #flags[:text] = 0x00001000
+  #flags[:setenc_by_bom] = 0x00100000
+  flags[:seek_set] = ::File::SEEK_SET
+  flags[:seek_current] = ::File::SEEK_CUR
+  flags[:seek_end] = ::File::SEEK_END
+  # ### file region locking:
+  flags[:shared_lock] = ::File::LOCK_SH # for reading
+  flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking)
+  flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
+  flags[:unlock] = ::File::LOCK_UN
+  #
+  flags[:nonblocking] = ::Fcntl::O_NONBLOCK
+  flags[:ndelay] = ::Fcntl::O_NDELAY
+  # unrelated to in-memory IO-like objects
+  #        flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks.
+  #        flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file.
+  #        flags[:match_noescape] = ::File::FNM_NOESCAPE #
+  #        flags[:match_pathname] = ::File::FNM_PATHNAME #
+  #        flags[:match_dotmatch] = ::File::FNM_DOTMATCH #
+  #        flags[:match_casefold] = ::File::FNM_CASEFOLD #
+  #        flags[:match_systemcase] = ::File::FNM_SYSCASE #
+  #
+  flags.keys.to_enum.each do |the_flag_key|
+    if (current & flags[(the_flag_key)]) == flags[(the_flag_key)]
+      case the_flag_key
+      when :readwrite
+        if result.index(:write)
+          result << :read
+        else
+          if result.index(:read)
+            result << :write
+          end
+        end
+      when :wsplit_initialized
+        if result.index(:wsplit)
+          result[(result.index(:wsplit))] = :wsplit_initialized
+        else
+          result << :wsplit_initialized
+        end
+      else
+        result << the_flag_key
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ +

:reopen, :string, :string, :lineno, :lineno, :close_read, :close_write, :closed?, :closed_read?, :closed_write?, :eof, :eof?, :fcntl, :flush, :fsync, :pos, :pos, :rewind, :seek, :sync, :sync, :tell, :each, :each_line, :lines, :each_codepoint, :codepoints, :getc, :ungetc, :ungetbyte, :readchar, :getbyte, :readbyte, :gets, :readline, :readlines, :read, :sysread, :readpartial, :read_nonblock, :write, :, :print, :printf, :putc, :puts, :syswrite, :write_nonblock, :isatty, :tty?, :pid, :fileno, :size, :length, :truncate, :external_encoding, :internal_encoding, :set_encoding

+ + + + +
+
# File lib/gxg/gxg_io.rb, line 2264
+def inspect()
+  ("<#" << "#{self.class}:#{super().split(" ")[0].split(":").last}")
+end
+
+ +
+ + + + +
+ + +
+ +
+ read(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2336
+def read(*args)
+  # read length bytes from StringIO and optionally route to a buffer object
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if args[0].is_a?(Numeric)
+      # length
+      data = super(args[0].to_i)
+    else
+      # LATER: provide for StringIO buffer size manipulation??? what is default read length??
+      data = super()
+    end
+    if data.is_a?(::String)
+      data = self.transcode_to_internal(data)
+      # buffer
+      if args[1].is_any?(::String, ::IO, ::StringIO, ::GxG::ByteArray)
+        # check which classes this can use and in what ways.
+        data.to_enum(:chars).each do |the_character|
+          args[1] << the_character
+        end
+        # return buffer object
+        args[1]
+      else
+        # return string
+        data
+      end
+    else
+      # return nil
+      nil
+    end
+    #
+  end
+end
+
+ +
+ + +
+ Also aliased as: read_nonblock, sysread +
+ + + +
+ + +
+ +
+ read_nonblock(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: read +
+ +
+ + +
+ +
+ readpartial(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 2397
+def readpartial(*args)
+  # args[0] maxlen (Fixnum)
+  # args[1] (optional) buffer_object
+  # Note: use the same approach to pause/retry as IO.read_nonblock.
+  #
+  # Reads at most maxlen bytes from the I/O stream. It blocks only if ios has no data immediately available. It doesn’t block if some data available.
+  # If the optional outbuf argument is present, it must reference a String, which will receive the data. It raises EOFError on end of file.
+  # readpartial is designed for streams such as pipe, socket, tty, etc. It blocks only when no data immediately available.
+  # This means that it blocks only when following all conditions hold:
+  #   the byte buffer in the IO object is empty.
+  #   the content of the stream is empty.
+  #   the stream is not reached to EOF.
+  # When readpartial blocks, it waits data or EOF on the stream. If some data is reached, readpartial returns with the data. If EOF is reached,
+  # readpartial raises EOFError.
+  # When readpartial doesn’t blocks, it returns or raises immediately. If the byte buffer is not empty, it returns the data in the buffer.
+  # Otherwise if the stream has some content, it returns the data in the stream. Otherwise if the stream is reached to EOF, it raises EOFError.
+  #
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      result = ""
+      result.force_encoding(self.internal_encoding)
+      data = self.read(*args)
+      if data
+        # non-nil
+        if data.is_a?(::String)
+          # 'blocking' or not?
+          if (self.eof?() || (data && data.to_s.size > 0))
+            # no pseudo-blocking
+            result << data
+            data = nil
+          else
+            # pseudo-blocking
+            until (self.eof?() || (data && data.to_s.size > 0))
+              data = self.read(*args)
+              # string, buffer, or nil
+              if data
+                #
+                if data.is_a?(::String)
+                  if (data && data.to_s.size > 0)
+                    result << data
+                    data = nil
+                    break
+                  else
+                    if self.eof?()
+                      raise EOFError, "end of stream reached"
+                    end
+                  end
+                else
+                  # buffer
+                  result = data
+                  data = nil
+                  break
+                end
+              else
+                if self.eof?()
+                  raise EOFError, "end of stream reached"
+                end
+              end
+              #
+              pause
+              #
+            end
+          end
+        else
+          # buffer
+          result = data
+        end
+      else
+        if self.eof?()
+          raise EOFError, "end of stream reached"
+        end
+      end
+      #
+      if result
+        if result.is_a?(::String)
+          if result.bytesize > 0
+            result
+          else
+            nil
+          end
+        else
+          # buffer
+          result
+        end
+      else
+        nil
+      end
+      #
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ sysread(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: read +
+ +
+ + +
+ +
+ syswrite(data="") + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: write +
+ +
+ + +
+ +
+ write(data="") + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_io.rb, line 2372
+def write(data="")
+  # writes from (self.pos) forward : this will clobber any init string that was passed.
+  written = 0
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    if data.is_a?(::String)
+      data = self.transcode_to_external(data.dup)
+    else
+      data = self.transcode_to_external(data)
+    end
+    if data.size > 0
+      written = super(data)
+    end
+  end
+  written
+end
+
+ +
+ + +
+ Also aliased as: write_nonblock, syswrite +
+ + + +
+ + +
+ +
+ write_nonblock(data="") + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: write +
+ +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking.html b/doc/GxG/Networking.html new file mode 100644 index 0000000..9990b5a --- /dev/null +++ b/doc/GxG/Networking.html @@ -0,0 +1,386 @@ + + + + + + +module GxG::Networking - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Networking +

+ +
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
REGISTERED + +
+ + +
+
+ + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ get_handler_class(the_url=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_tools.rb, line 11
+def self.get_handler_class(the_url=nil)
+  result = nil
+  begin
+    if the_url.is_a?(::URI::Generic)
+      @@protocol_thread_safety.synchronize {
+        ::GxG::Networking::REGISTERED[:protocols].keys.each do |the_scheme|
+          if the_url.scheme.to_s == the_scheme.to_s
+            result = ::GxG::Networking::REGISTERED[:protocols][(the_scheme)]
+            break
+          end
+        end
+      }
+    else
+      raise ArgumentError, "You MUST provide a valid URI"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ register_handler_class(the_scheme=nil, the_class=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_tools.rb, line 32
+def self.register_handler_class(the_scheme=nil, the_class=nil)
+  result = false
+  begin
+    unless the_class.is_a?(::Class)
+      raise ArgumentError, "You MUST provide a valid class."
+    end
+    unless the_scheme.is_any?(::String, ::Symbol)
+      raise ArgumentError, "You MUST provide a valid URI scheme (as a String or Symbol)."
+    end
+    @@protocol_thread_safety.synchronize {
+      ::GxG::Networking::REGISTERED[:protocols][(the_scheme.to_sym)] = the_class
+    }
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:scheme => the_scheme, :class => the_class}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ registered_protocols() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_tools.rb, line 67
+def self.registered_protocols()
+  @@protocol_thread_safety.synchronize {
+    ::GxG::Networking::REGISTERED[:protocols].clone
+  }
+end
+
+ +
+ + + + +
+ + +
+ +
+ unregister_handler_class(the_scheme=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_tools.rb, line 51
+def self.unregister_handler_class(the_scheme=nil)
+  result = false
+  begin
+    unless the_scheme.is_any?(::String, ::Symbol)
+      raise ArgumentError, "You MUST provide a valid URI scheme (as a String or Symbol)."
+    end
+      @@protocol_thread_safety.synchronize {
+        ::GxG::Networking::REGISTERED[:protocols].delete(the_scheme.to_sym)
+      }
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:scheme => the_scheme}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ wget(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_tools.rb, line 73
+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 options[:pem_file]
+        client = ::GxG::Networking::HttpsClientWithPem.new(options[:pem_file])
+      else
+        if the_url.scheme.to_s.downcase == "https" || options[:use_ssl]
+          client = ::GxG::Networking::HttpsClient.new
+        else
+          client = ::GxG::Networking::HttpClient.new
+        end
+      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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/FtpClient.html b/doc/GxG/Networking/FtpClient.html new file mode 100644 index 0000000..7ac56f7 --- /dev/null +++ b/doc/GxG/Networking/FtpClient.html @@ -0,0 +1,1120 @@ + + + + + + +class GxG::Networking::FtpClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::FtpClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4822
+def initialize()
+  @client = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ change_directory(dir_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5157
+def change_directory(dir_name=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        unless dir_name
+          raise ArgumentError, "You MUST provide a directory path to change to."
+        end
+        @client.chdir(dir_name)
+        if @client.pwd() != dir_name
+          raise Exception, "Failed to change directory."
+        else
+          result = true
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4912
+def closed?()
+  if @client
+    @client.closed?
+  else
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ entries(dir_name=nil, filter=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5103
+def entries(dir_name=nil, filter=nil)
+  result = []
+  unless dir_name
+    dir_name = @client.pwd()
+  end
+  begin
+    if @client
+      present_dir = @client.pwd()
+      @client.chdir(dir_name)
+      if filter
+        list = @client.list(filter)
+      else
+        list = @client.list()
+      end
+      list.each do |entry|
+        # TODO: process entry text
+        data = entry.split(" ")
+        record = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil}
+        #
+        record[:permissions] = ::File.unix_permissions_to_gxg(data[0].to_s)
+        if record[:permissions][:other][:write]
+          record[:flags] << :write
+        end
+        #              record[:owner] = data[2]
+        #              record[:group] = data[3]
+        record[:size] = data[4].to_i
+        record[:modified] = ::DateTime::parse(data[5..7].join(" "))
+        record[:title] = data[8]
+        result << record
+      end
+      @client.chdir(present_dir)
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:dir_name => dir_name, :filter => filter}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_file(file_name=nil, save_path=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5025
+def get_file(file_name=nil, save_path=nil)
+  result = nil
+  begin
+    if @client
+      if save_path
+        result = @client.getbinaryfile(file_name, save_path, 1024)
+      else
+        result = ::GxG::ByteArray.new
+        @client.getbinaryfile(file_name, save_path) do |data_chunk|
+          result << data_chunk
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_name => file_name, :save_path => save_path}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ help() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4952
+def help()
+  result = nil
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed connection."
+      else
+        result = @client.help()
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4827
+def login(the_url=nil, options={})
+  result = false
+  login_options = {}
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI"
+    end
+    # The available options are:
+    #
+    # port::      Port number (default value is 21)
+    if options[:port].is_a?(::Numeric)
+      login_options[:port] = options[:port].to_i
+    end
+    # ssl::       If options[:ssl] is true, then an attempt will be made
+    #             to use SSL (now TLS) to connect to the server.  For this
+    #             to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
+    #             extensions need to be installed.  If options[:ssl] is a
+    #             hash, it's passed to OpenSSL::SSL::SSLContext#set_params
+    #             as parameters.
+    if options[:use_ssl] == true
+      login_options[:ssl] = true
+    end
+    # private_data_connection::  If true, TLS is used for data connections.
+    #                            Default: +true+ when options[:ssl] is true.
+    # username::  Username for login.  If options[:username] is the string
+    #             "anonymous" and the options[:password] is +nil+,
+    #             "anonymous@" is used as a password.
+    login_options[:username] = the_url.user
+    # password::  Password for login.
+    login_options[:password] = the_url.password
+    # account::   Account information for ACCT.
+    if options[:account_info]
+      login_options[:account] = options[:account_info]
+    end
+    # passive::   When +true+, the connection is in passive mode. Default:
+    #             +true+.
+    if options[:passive] == false
+      login_options[:passive] = false
+    end
+    # open_timeout::  Number of seconds to wait for the connection to open.
+    #                 See Net::FTP#open_timeout for details.  Default: +nil+.
+    if options[:open_timeout].is_a?(::Numeric)
+      login_options[:open_timeout] = options[:open_timeout]
+    end
+    # read_timeout::  Number of seconds to wait for one block to be read.
+    #                 See Net::FTP#read_timeout for details.  Default: +60+.
+    if options[:read_timeout].is_a?(::Numeric)
+      login_options[:read_timeout] = options[:read_timeout]
+    end
+    # ssl_handshake_timeout::  Number of seconds to wait for the TLS
+    #                          handshake.
+    #                          See Net::FTP#ssl_handshake_timeout for
+    #                          details.  Default: +nil+.
+    if options[:ssl_handshake_timeout].is_a?(::Numeric)
+      login_options[:ssl_handshake_timeout] = options[:ssl_handshake_timeout]
+    end
+    #
+    @client = Net::FTP.new(the_url.hostname, login_options)
+    if @client.is_a?(Net::FTP)
+      @client.login
+      if @client.closed?
+        raise Exception, "Failed to log into FTP session"
+      else
+        result = true
+      end
+    else
+      raise Exception, "Failed to create FTP session"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4901
+def logout()
+  if @client
+    @client.close
+    if @client.closed?
+      true
+    else
+      false
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ make_directory(dir_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5181
+def make_directory(dir_name=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        unless dir_name
+          raise ArgumentError, "You MUST provide a directory path to create."
+        end
+        result = @client.mkdir(dir_name)
+        unless result
+          raise Exception, "Failed to change directory."
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ present_directory() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5141
+def present_directory()
+  result = nil
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        result = @client.pwd()
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ put_file(local_path=nil, remote_file=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5044
+def put_file(local_path=nil, remote_file=nil)
+  result = false
+  begin
+    if @client
+      result = @client.putbinaryfile(local_path, remote_file, 1024)
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:local_path => local_path, :remote_file => remote_file}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_directory(dir_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5203
+def remove_directory(dir_name=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        unless dir_name
+          raise ArgumentError, "You MUST provide a directory name to remove."
+        end
+        result = @client.rmdir(dir_name)
+        unless result
+          raise Exception, "Failed to remove directory."
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:dir_name => dir_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_file(file_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5081
+def remove_file(file_name=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        unless file_name
+          raise ArgumentError, "You MUST provide a file name to remove."
+        end
+        result = @client.delete(file_name)
+        unless result
+          raise Exception, "Failed to remove file."
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_name => file_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rename_file(file_name=nil, new_file_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5056
+def rename_file(file_name=nil, new_file_name=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed session"
+      else
+        unless file_name
+          raise ArgumentError, "You MUST provide a current file name to rename."
+        end
+        unless new_file_name
+          raise ArgumentError, "You MUST provide a new name to rename the file."
+        end
+        result = @client.rename(file_name, new_file_name)
+        unless result
+          raise Exception, "Failed to rename file."
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:file_name => file_name, :new_file_name => new_file_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ reopen() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5006
+def reopen()
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        @client.connect()
+        if @client.closed?
+          raise Exception, "Failed to re-open connection."
+        end
+      else
+        result = true
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_account_info(acct_info=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4968
+def send_account_info(acct_info=nil)
+  result = false
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed connection."
+      else
+        unless acct_info.is_a?(::String)
+          raise ArgumentError, "You MUST provide account information (as a String)."
+        end
+        result = @client.acct(acct_info)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:account_info => acct_info}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_command(command=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4987
+def send_command(command=nil)
+  result = nil
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed connection."
+      else
+        unless command.is_a?(::String)
+          raise ArgumentError, "You must provide a String to issue FTP commands."
+        end
+        result = @client.sendcmd(command)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:command => command}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ status() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4920
+def status()
+  result = nil
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed connection."
+      else
+        result = @client.status()
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ system_info() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4936
+def system_info()
+  result = nil
+  begin
+    if @client
+      if @client.closed?
+        raise Exception, "Attempted to access a closed connection."
+      else
+        result = @client.system()
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/HttpClient.html b/doc/GxG/Networking/HttpClient.html new file mode 100644 index 0000000..59f1cb3 --- /dev/null +++ b/doc/GxG/Networking/HttpClient.html @@ -0,0 +1,848 @@ + + + + + + +class GxG::Networking::HttpClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::HttpClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5228
+def initialize()
+  @cookie = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ clear_cache() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5233
+def clear_cache()
+  @cookie = nil
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ cookies() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5238
+def cookies()
+  @cookie.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5635
+def delete(the_url=nil, options={})
+  result = false
+  begin
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    if the_url.scheme.downcase == "https"
+      options[:use_ssl] = true
+    end
+    if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
+      client_parameters = [(the_url.hostname), (the_url.port)]
+      if options[:proxy].is_a?(::URI::Generic)
+        client_parameters << options[:proxy].host
+        client_parameters << options[:proxy].port
+        if options[:proxy].user && options[:proxy].password
+          client_parameters << options[:proxy].user
+          client_parameters << options[:proxy].password
+        else
+          client_parameters << nil
+          client_parameters << nil
+        end
+      else
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+      end
+      if options.keys.size > 0
+        client_parameters << options
+      end
+      server = Net::HTTP.start(*client_parameters)
+      if options[:pem_file].is_a?(::String)
+        pem_file = ::File.read(options[:pem_file])
+        server.use_ssl = true
+        server.cert = OpenSSL::X509::Certificate.new(pem_file)
+        server.key = OpenSSL::PKey::RSA.new(pem_file, options[:pem_password])
+        server.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      end
+      if @cookie
+        request = Net::HTTP::Delete.new(the_url.request_uri, {'Cookie' => @cookie})
+      else
+        request = Net::HTTP::Delete.new(the_url.request_uri)
+      end
+      if (the_url.user && the_url.password)
+        # basic auth
+        request.basic_auth(the_url.user, the_url.password)
+      end
+      response = server.request(request)
+      response_code = response.code.to_i
+      if options[:follow_redirects]
+        if response_code >= 300 && response_code < 400
+          # Process Redirect
+          redirect_count = 1
+          location = response['location']
+          while location do
+            #
+            if @cookie
+              redirect_request = Net::HTTP::Delete.new(location, {'Cookie' => @cookie})
+            else
+              redirect_request = Net::HTTP::Delete.new(location)
+            end
+            redirect_response = server.request(redirect_request)
+            if redirect_response.code.to_i == 200
+              response = redirect_response
+              response_code = 200
+              break
+            else
+              if redirect_response['location']
+                redirect_count += 1
+                if redirect_count > 9
+                  raise Exception, "Circular redirect chain suspected (10 or more redirects)"
+                end
+                response_code = redirect_response.code.to_i
+                location = redirect_response['location']
+              else
+                break
+              end
+            end
+            #
+          end
+        end
+      end
+      if response.code.to_i == 200
+        result = true
+      else
+        raise Exception, "Error occured: Code #{response.code}"
+      end
+    else
+      raise Exception, "You MUST provide a valid HTTP URI"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get(the_url=nil, options={:follow_redirects => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5242
+def get(the_url=nil, options={:follow_redirects => true})
+  result = nil
+  begin
+    if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
+      the_url = the_url.dup
+      if options[:query].is_a?(::Hash)
+        the_url.query = ::URI.encode_www_form(options[:query])
+      end
+      unless options[:follow_redirects] == false
+        options.merge({:follow_redirects => true})
+      end
+      #
+      if the_url.scheme.downcase == "https"
+        options[:use_ssl] = true
+      end
+      # sets following values by its accessor.
+      # The keys are ca_file, ca_path, cert, cert_store, ciphers,
+      # close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout,
+      # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
+      # If you set :use_ssl as true, you can use https and default value of
+      # verify_mode is set as OpenSSL::SSL::VERIFY_PEER.
+      client_parameters = [(the_url.hostname), (the_url.port)]
+      if options[:proxy].is_a?(::URI::Generic)
+        client_parameters << options[:proxy].host
+        client_parameters << options[:proxy].port
+        if options[:proxy].user && options[:proxy].password
+          client_parameters << options[:proxy].user
+          client_parameters << options[:proxy].password
+        else
+          client_parameters << nil
+          client_parameters << nil
+        end
+      else
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+      end
+      if options.keys.size > 0
+        client_parameters << options
+      end
+      client = Net::HTTP.start(*client_parameters)
+      if options[:pem_file].is_a?(::String)
+        pem_file = ::File.read(options[:pem_file])
+        client.use_ssl = true
+        client.cert = OpenSSL::X509::Certificate.new(pem_file)
+        client.key = OpenSSL::PKey::RSA.new(pem_file, options[:pem_password])
+        client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      end
+      if @cookie
+        if options[:header].is_a?(::Hash)
+          request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie}.merge(options[:header]))
+        else
+          request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie})
+        end
+      else
+        if options[:header].is_a?(::Hash)
+          request = Net::HTTP::Get.new(the_url.request_uri, options[:header])
+        else
+          request = Net::HTTP::Get.new(the_url.request_uri)
+        end
+      end
+      if (the_url.user && the_url.password)
+        # basic auth
+        request.basic_auth(the_url.user, the_url.password)
+      end
+      response = client.request(request)
+      @cookie = response.response['set-cookie']
+      response_code = response.code.to_i
+      if options[:follow_redirects]
+        if response_code >= 300 && response_code < 400
+          # Process Redirect
+          redirect_count = 1
+          location = response['location']
+          while location do
+            #
+            if @cookie
+              redirect_request = Net::HTTP::Get.new(location, {'Cookie' => @cookie})
+            else
+              redirect_request = Net::HTTP::Get.new(location)
+            end
+            redirect_response = client.request(redirect_request)
+            if redirect_response.code.to_i == 200
+              response = redirect_response
+              response_code = 200
+              @cookie = response.response['set-cookie']
+              break
+            else
+              if redirect_response['location']
+                @cookie = redirect_response.response['set-cookie']
+                redirect_count += 1
+                if redirect_count > 9
+                  raise Exception, "Circular redirect chain suspected (10 or more redirects)"
+                end
+                response_code = redirect_response.code.to_i
+                location = redirect_response['location']
+              else
+                break
+              end
+            end
+            #
+          end
+        end
+      end
+      if options[:save_as].is_a?(::String)
+        ::File.open(options[:save_as].to_s, 'w', 0644) do |the_io|
+          response.read_body do |data_chunk|
+            the_io.write(data_chunk)
+          end
+          # Return open file handle r/w to saved file.
+          result = ::File.open(the_io.path, "w+")
+        end
+      end
+      if options[:stream_to].is_a?(::GxG::Database::PersistedArray)
+        the_accumulator = options[:stream_to]
+        buffer = ::GxG::ByteArray.new
+        response.read_body do |data_chunk|
+          chunk_ranges = ::GxG.apportioned_ranges(data_chunk.bytesize,65536)
+          chunk_ranges.each do |the_range|
+            if (buffer.size + data_chunk[(the_range)].bytesize) <= 65536
+              buffer << data_chunk[(the_range)]
+              if buffer.size == 65536
+                the_accumulator << buffer.clone
+                the_accumulator[(the_accumulator.size - 1)].unload
+                buffer.clear
+              end
+            else
+              the_size = buffer.size
+              buffer << data_chunk[(the_range)][(0..((65536 - the_size) - 1))]
+              if buffer.size == 65536
+                the_accumulator << buffer.clone
+                the_accumulator[(the_accumulator.size - 1)].unload
+                buffer.clear
+              end
+              buffer << data_chunk[(the_range)][((65536 - the_size)..-1)]
+            end
+          end
+          #
+        end
+        if buffer.size > 0
+          the_accumulator << buffer.clone
+          the_accumulator[(the_accumulator.size - 1)].unload
+        end
+      end
+      if response_code == 200 && options[:save_as] == nil && options[:stream_to] == nil
+        # Process response
+        if options[:raw_response]
+          result = response
+        else
+          if options[:to_ruby] == true
+            response_hash = {}
+            response_hash = response.to_hash.process do |entry, the_key|
+              if the_key.is_a?(::Symbol)
+                entry
+              else
+                returns({:new_selector => (the_key.to_s.to_sym), :result => (entry)})
+              end
+            end
+            response_hash[:header] = response.header
+            #
+            body_text = (response.body.transcode({:replace => "."},::Encoding::UTF_8))
+            if body_text.xml?()
+              response_hash[:body] = body_text.from_xml()
+            else
+              if body_text.html?()
+                response_hash[:body] = body_text.from_html()
+              else
+                raise Exception, "Failed to retrieve either XML or HTML as data."
+              end
+            end
+            #
+            result = response_hash
+          else
+            # Default output:
+            result = response.body
+          end
+        end
+      else
+        unless options[:save_as]
+          raise Exception, "Error occured: Code #{response.code}"
+        end
+      end
+    else
+      raise Exception, "You MUST provide a valid HTTP URI"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ post(the_url=nil, form_data=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5534
+def post(the_url=nil, form_data=nil, options={})
+  result = false
+  begin
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    if the_url.scheme.downcase == "https"
+      options[:use_ssl] = true
+    end
+    if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
+      unless form_data.is_a?(::Hash)
+        raise Exception, "You MUST provide valid form data (in the form of a Hash)"
+      end
+      client_parameters = [(the_url.hostname), (the_url.port)]
+      if options[:proxy].is_a?(::URI::Generic)
+        client_parameters << options[:proxy].host
+        client_parameters << options[:proxy].port
+        if options[:proxy].user && options[:proxy].password
+          client_parameters << options[:proxy].user
+          client_parameters << options[:proxy].password
+        else
+          client_parameters << nil
+          client_parameters << nil
+        end
+      else
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+      end
+      if options.keys.size > 0
+        client_parameters << options
+      end
+      server = Net::HTTP.start(*client_parameters)
+      if options[:pem_file].is_a?(::String)
+        pem_file = ::File.read(options[:pem_file])
+        server.use_ssl = true
+        server.cert = OpenSSL::X509::Certificate.new(pem_file)
+        server.key = OpenSSL::PKey::RSA.new(pem_file, options[:pem_password])
+        server.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      end
+      if @cookie
+        request = Net::HTTP::Post.new(the_url.request_uri, {'Cookie' => @cookie})
+      else
+        request = Net::HTTP::Post.new(the_url.request_uri)
+      end
+      if (the_url.user && the_url.password)
+        # basic auth
+        request.basic_auth(the_url.user, the_url.password)
+      end
+      request.set_form_data(form_data)
+      response = server.request(request)
+      response_code = response.code.to_i
+      if options[:follow_redirects]
+        if response_code >= 300 && response_code < 400
+          # Process Redirect
+          redirect_count = 1
+          location = response['location']
+          while location do
+            #
+            if @cookie
+              redirect_request = Net::HTTP::Post.new(location, {'Cookie' => @cookie})
+            else
+              redirect_request = Net::HTTP::Post.new(location)
+            end
+            redirect_request.set_form_data(form_data)
+            redirect_response = server.request(redirect_request)
+            if redirect_response.code.to_i == 200
+              response = redirect_response
+              response_code = 200
+              break
+            else
+              if redirect_response['location']
+                redirect_count += 1
+                if redirect_count > 9
+                  raise Exception, "Circular redirect chain suspected (10 or more redirects)"
+                end
+                response_code = redirect_response.code.to_i
+                location = redirect_response['location']
+              else
+                break
+              end
+            end
+            #
+          end
+        end
+      end
+      if response.code.to_i == 200
+        result = true
+      else
+        raise Exception, "Error occured: Code #{response.code}"
+      end
+    else
+      raise Exception, "You MUST provide a valid HTTP URI"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :form_data => form_data, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ put(the_url=nil, data=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5433
+def put(the_url=nil, data=nil, options={})
+  #
+  result = false
+  begin
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    if the_url.scheme.downcase == "https"
+      options[:use_ssl] = true
+    end
+    if the_url.is_any?(::URI::HTTP, ::URI::HTTPS)
+      unless data.is_a?(::String)
+        raise Exception, "You MUST provide valid PUT data (in the form of a String: 'parameter = value')"
+      end
+      client_parameters = [(the_url.hostname), (the_url.port)]
+      if options[:proxy].is_a?(::URI::Generic)
+        client_parameters << options[:proxy].host
+        client_parameters << options[:proxy].port
+        if options[:proxy].user && options[:proxy].password
+          client_parameters << options[:proxy].user
+          client_parameters << options[:proxy].password
+        else
+          client_parameters << nil
+          client_parameters << nil
+        end
+      else
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+        client_parameters << nil
+      end
+      if options.keys.size > 0
+        client_parameters << options
+      end
+      server = Net::HTTP.start(*client_parameters)
+      if options[:pem_file].is_a?(::String)
+        pem_file = ::File.read(options[:pem_file])
+        server.use_ssl = true
+        server.cert = OpenSSL::X509::Certificate.new(pem_file)
+        server.key = OpenSSL::PKey::RSA.new(pem_file, options[:pem_password])
+        server.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      end
+      #
+      if @cookie
+        request = Net::HTTP::Put.new(the_url.request_uri,data, {'Cookie' => @cookie})
+      else
+        request = Net::HTTP::Put.new(the_url.request_uri,data)
+      end
+      if (the_url.user && the_url.password)
+        # basic auth
+        request.basic_auth(the_url.user, the_url.password)
+      end
+      response = server.request(request)
+      response_code = response.code.to_i
+      if options[:follow_redirects]
+        if response_code >= 300 && response_code < 400
+          # Process Redirect
+          redirect_count = 1
+          location = response['location']
+          while location do
+            #
+            if @cookie
+              redirect_request = Net::HTTP::Put.new(location, {'Cookie' => @cookie})
+            else
+              redirect_request = Net::HTTP::Put.new(location)
+            end
+            redirect_response = server.request(redirect_request)
+            if redirect_response.code.to_i == 200
+              response = redirect_response
+              response_code = 200
+              break
+            else
+              if redirect_response['location']
+                redirect_count += 1
+                if redirect_count > 9
+                  raise Exception, "Circular redirect chain suspected (10 or more redirects)"
+                end
+                response_code = redirect_response.code.to_i
+                location = redirect_response['location']
+              else
+                break
+              end
+            end
+            #
+          end
+        end
+      end
+      if response.code.to_i == 200
+        result = true
+      else
+        raise Exception, "Error occured: Code #{response.code}"
+      end
+    else
+      raise Exception, "You MUST provide a valid HTTP URI"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :form_data => data, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/HttpsClient.html b/doc/GxG/Networking/HttpsClient.html new file mode 100644 index 0000000..a785f6e --- /dev/null +++ b/doc/GxG/Networking/HttpsClient.html @@ -0,0 +1,374 @@ + + + + + + +class GxG::Networking::HttpsClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::HttpsClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5733
+def initialize()
+  @client = ::GxG::Networking::HttpClient.new
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ clear_cache() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5742
+def clear_cache()
+  @client.clear_cache()
+end
+
+ +
+ + + + +
+ + +
+ +
+ cookies() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5738
+def cookies()
+  @client.cookies()
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5755
+def delete(the_url=nil, options={})
+  @client.post(the_url,options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ get(the_url=nil, options={:follow_redirects => true, :use_ssl => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5746
+def get(the_url=nil, options={:follow_redirects => true, :use_ssl => true})
+  @client.get(the_url,options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ post(the_url=nil, form_data=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5752
+def post(the_url=nil, form_data=nil, options={})
+  @client.post(the_url,form_data,options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ put(the_url=nil, data=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5749
+def put(the_url=nil, data=nil, options={})
+  @client.put(the_url,data,options)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/HttpsClientWithPem.html b/doc/GxG/Networking/HttpsClientWithPem.html new file mode 100644 index 0000000..c068f12 --- /dev/null +++ b/doc/GxG/Networking/HttpsClientWithPem.html @@ -0,0 +1,378 @@ + + + + + + +class GxG::Networking::HttpsClientWithPem - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::HttpsClientWithPem +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(pem_file_path=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5761
+def initialize(pem_file_path=nil)
+  unless pem_file_path.is_a?(::String)
+    raise ArgumentError, "You MUST provide a path to your PEM file (as a String)."
+  end
+  @pem_path = pem_file_path
+  @client = ::GxG::Networking::HttpClient.new()
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ clear_cache() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5774
+def clear_cache()
+  @client.clear_cache()
+end
+
+ +
+ + + + +
+ + +
+ +
+ cookies() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5770
+def cookies()
+  @client.cookies()
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete(the_url=nil, options={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5787
+def delete(the_url=nil, options={:follow_symlinks => true})
+  @client.delete(the_url,options.merge({:use_ssl => true, :pem_file => @pem_path}))
+end
+
+ +
+ + + + +
+ + +
+ +
+ get(the_url=nil, options={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5778
+def get(the_url=nil, options={:follow_symlinks => true})
+  @client.get(the_url,options.merge({:use_ssl => true, :pem_file => @pem_path}))
+end
+
+ +
+ + + + +
+ + +
+ +
+ post(the_url=nil, form_data=nil, options={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5784
+def post(the_url=nil, form_data=nil, options={:follow_symlinks => true})
+  @client.post(the_url,form_data,options.merge({:use_ssl => true, :pem_file => @pem_path}))
+end
+
+ +
+ + + + +
+ + +
+ +
+ put(the_url=nil, data=nil, options={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 5781
+def put(the_url=nil, data=nil, options={:follow_symlinks => true})
+  @client.put(the_url,data,options.merge({:use_ssl => true, :pem_file => @pem_path}))
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ImapClient.html b/doc/GxG/Networking/ImapClient.html new file mode 100644 index 0000000..25cc677 --- /dev/null +++ b/doc/GxG/Networking/ImapClient.html @@ -0,0 +1,1965 @@ + + + + + + +class GxG::Networking::ImapClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ImapClient +

+ +
+ +

To Generate OAuth2 Tokens, See: github.com/google/gmail-oauth2-tools IMAP Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(host=nil, port=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 532
+def initialize(host=nil, port=nil, options={})
+  if port.is_a?(::Hash)
+    options = port
+    port = nil
+  end
+  @user = nil
+  @password = nil
+  @client = nil
+  @host = host
+  unless @host
+    raise ArgumentError, "You MUST provide a HOST String."
+  end
+  if options[:use_ssl] == true
+    @port = (port || 993)
+  else
+    @port = (port || 143)
+  end
+  unless @port
+    raise ArgumentError, "You MUST provide a valid PORT Integer."
+  end
+  #
+  @capability = []
+  #
+  @mounted = nil
+  @mount_mode = :read
+  #
+  if options[:use_ssl] == true
+    if options[:ignore_ssl_errors] == true
+      @client = Net::IMAP.new(@host, @port, true, nil, false)
+    else
+      @client = Net::IMAP.new(@host, @port, true)
+    end
+  else
+    @client = Net::IMAP.new(@host, @port)
+  end
+  @capability = @client.capability
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ add_message_flags(uid=nil, flags=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1041
+def add_message_flags(uid=nil, flags=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID name to set FLAGS."
+      end
+      unless flags.is_a?(::Array)
+        raise ArgumentError, "You MUST specify what FLAGS to set."
+      end
+      self.set_message_attributes(uid, "+FLAGS", flags)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ capability() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 576
+def capability()
+  @capability.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ connector() + + click to toggle source + +
+ + +
+ +

???

+ + + + +
+
# File lib/gxg/net_clients.rb, line 528
+def connector()
+  @client
+end
+
+ +
+ + + + +
+ + +
+ +
+ copy_to_mailbox(uid=nil, mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1096
+def copy_to_mailbox(uid=nil, mailbox=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID to copy the message."
+      end
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX to copy the message to."
+      end
+      self.uid_copy(uid, mailbox)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_mailbox(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1253
+def create_mailbox(mailbox=nil)
+  result = []
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name to create it."
+      end
+      result = @client.create(mailbox)
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ fetch_manifest(uid_set=[]) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1134
+def fetch_manifest(uid_set=[])
+  result = []
+  if @client
+    begin
+      unless self.mounted?
+        raise Exception, "You MUST mount a Mailbox prior to calling this operation."
+      end
+      if uid_set.is_a?(::Integer)
+        uid_set = [(uid_set)]
+      end
+      unless uid_set.is_a?(::Array)
+        raise ArgumentError, "You MUST provide an Array of Message UID Integers."
+      end
+      #
+      parse_header = Proc.new do |the_raw_message|
+        header = {}
+        parser = ::Mail.new(the_raw_message)
+        parser.header_fields.each do |field|
+          the_key = field.name.to_s.to_sym
+          the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
+          the_data.gsub!("         ","")
+          the_data.gsub!("        ","")
+          the_data.gsub!("   ","")
+          the_data.gsub!("\r\n","")
+          if header[(the_key)]
+            unless header[(the_key)].is_a?(::Array)
+              header[(the_key)] = [(header[(the_key)])]
+            end
+            header[(the_key)] << the_data
+          else
+            header[(the_key)] = the_data
+          end
+        end
+        header
+      end
+      #
+      uid_set.each do |uid|
+        record = {:uid => uid, :date => nil, :from => "", :to => "", :subject => ""}
+        raw_response = @client.uid_fetch(uid,"BODY[HEADER]")
+        if raw_response.is_a?(::Array)
+          if raw_response.size > 0
+            header = parse_header.call(raw_response[0].attr()["BODY[HEADER]"])
+            record[:date] = ::DateTime.parse(header[:Date].to_s)
+            record[:from] = header[:From]
+            record[:to] = header[:To]
+            record[:subject] = header[:Subject]
+            result << record
+          end
+        end
+        #
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid_set}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ fetch_message(uid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 918
+def fetch_message(uid=nil)
+  result = nil
+  if @client
+    begin
+      unless self.mounted?
+        raise Exception, "You MUST mount a Mailbox prior to calling this operation."
+      end
+      unless uid.is_a?(::Integer)
+        raise ArgumentError, "You MUST provide an Integer to specify the Message UID Number."
+      end
+      parse_message = Proc.new do |the_raw_message|
+        header = {}
+        body = []
+        attachments = []
+        parser = ::Mail.new(the_raw_message)
+        parser.header_fields.each do |field|
+          the_key = field.name.to_s.to_sym
+          the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
+          the_data.gsub!("         ","")
+          the_data.gsub!("        ","")
+          the_data.gsub!("   ","")
+          the_data.gsub!("\r\n","")
+          if header[(the_key)]
+            unless header[(the_key)].is_a?(::Array)
+              header[(the_key)] = [(header[(the_key)])]
+            end
+            header[(the_key)] << the_data
+          else
+            header[(the_key)] = the_data
+          end
+        end
+        if parser.multipart?
+          link_db = []
+          body << {:preamble => parser.body.preamble}
+          parser.parts.each do |part|
+            if part.multipart?
+              link_db << part
+            else
+              if part.attachment?
+                attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
+              else
+                body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+              end
+            end
+          end
+          while link_db.size > 0 do
+            part = link_db.shift
+            if part.multipart?
+              part.parts.each do |the_sub_part|
+                link_db << the_sub_part
+              end
+            else
+              if part.attachment?
+                attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
+              else
+                body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+              end
+            end
+          end
+          body << {:epilogue => parser.body.epilogue}
+        else
+          body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+          mime_type = "text/plain"
+          parameters = {"charset" => "UTF-8"}
+          body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)
+          if body_text.xml?
+            mime_type = "text/xml"
+          end
+          if body_text.html?
+            mime_type = "text/html"
+          end
+          body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text}
+          body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)}
+        end
+        gxg_message = new_message
+        gxg_message[:sender] = parser.from
+        gxg_message[:id] = parser.message_id
+        gxg_message[:to] = parser.to
+        gxg_message[:cc] = parser.cc
+        gxg_message[:bcc] = parser.bcc
+        gxg_message[:subject] = parser.subject
+        gxg_message[:date] = parser.date
+        gxg_message[:header] = header
+        gxg_message[:body] = body
+        gxg_message[:attachments] = attachments
+        gxg_message
+      end
+      #
+      raw_response = @client.uid_fetch(uid,"BODY[]")
+      if raw_response.is_a?(::Array)
+        if raw_response.size > 0
+          result = parse_message.call(raw_response[0].attr()["BODY[]"])
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:unique_id => uid}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_quota(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1353
+def get_quota(mailbox=nil)
+  result = nil
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name."
+      end
+      raw_response = @client.getquotaroot(mailbox)[1]
+      if raw_response.is_a?(::Net::IMAP::MailboxQuota)
+        result = {:usage => raw_response.usage.to_i, :quota => raw_response.quota.to_i}
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 572
+def inspect()
+  "<ImapClient: User: #{@user.inspect} Host: #{@host.inspect} Connected: #{! @client.disconnected?}>"
+end
+
+ +
+ + + + +
+ + +
+ +
+ list(reference=nil, mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 748
+def list(reference=nil, mailbox=nil)
+  result = []
+  if @client
+    begin
+      unless reference
+        raise ArgumentError, "You MUST sepcify a REFERENCE to list."
+      end
+      unless mailbox
+        raise ArgumentError, "You MUST sepcify a MAILBOX to list."
+      end
+      raw_list = @client.list(reference, mailbox)
+      if raw_list
+        raw_list.each do |entry|
+          if entry.is_a?(::Net::IMAP::MailboxList)
+            result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
+          end
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ list_subscriptions(reference=nil, mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1334
+def list_subscriptions(reference=nil, mailbox=nil)
+  result = []
+  if @client
+    begin
+      raw_list = @client.lsub(reference, mailbox)
+      if raw_list
+        raw_list.each do |entry|
+          if entry.is_a?(::Net::IMAP::MailboxList)
+            result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
+          end
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(user_id=nil, password=nil, options={:method => :auth}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 580
+def login(user_id=nil, password=nil, options={:method => :auth})
+  result = false
+  begin
+    if @client
+      unless @client.disconnected?
+        @user = user_id
+        unless @user
+          raise ArgumentError, "You MUST provide a USER id, like this: <user_id>,<password>,(<options>)"
+        end
+        @password = password
+        unless @password
+          raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: <user_id>,<password/oauth-token>,(<options>)"
+        end
+        case options[:method]
+        when :auth
+          @client.login(@user, @password)
+        when :login
+          @client.authenticate("LOGIN", @user, @password)
+        when :cram_md5
+          @client.authenticate("CRAM-MD5", @user, @password)
+        when :oauth2
+          @client.authenticate("XOAUTH2", @user, @password)
+        else
+          raise ArgumentError, "Authentication method #{options[:method].inspect} not supported. Choose one of these: [:auth, :login, :cram_md5, :oauth2]"
+        end
+        #
+        result = true
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:user => user_id, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 615
+def logout()
+  result = false
+  if @client
+    begin
+      @client.logout
+      @client.disconnect
+      result = @client.disconnected?
+      @client = nil
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mount(mailbox=nil,mode=:read_write) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 648
+def mount(mailbox=nil,mode=:read_write)
+  result = false
+  if @client
+    begin
+      unless [:read, :read_write].include?(mode)
+        raise ArgumentError, "Mode #{mode.inspect} is invalid. Choose one of these: [:read, :read_write]."
+      end
+      if self.mounted?
+        if @mounted == mailbox
+          if self.mount_mode() == :read && mode == :read_write
+            response = @client.select(mailbox)
+            if response.is_a?(::Net::IMAP::TaggedResponse)
+              if response.name == "OK"
+                @mounted = mailbox
+                @mount_mode = :read_write
+                result = true
+              else
+                raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write."
+              end
+            else
+              raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write."
+            end
+          else
+            result = true
+          end
+        else
+          raise Exception, "You must unmount mailbox #{@mounted.inspect} first, then mount #{mailbox.inspect}."
+        end
+      else
+        # mount
+        if mode == :read_write
+          response = @client.select(mailbox)
+          if response.is_a?(::Net::IMAP::TaggedResponse)
+            if response.name == "OK"
+              @mounted = mailbox
+              @mount_mode = :read_write
+              result = true
+            else
+              raise Exception, "Failed to mount #{mailbox.inspect} as :read_write."
+            end
+          else
+            raise Exception, "Failed to mount #{mailbox.inspect} as :read_write."
+          end
+        else
+          # mount :read only
+          response = @client.examine(mailbox)
+          if response.is_a?(::Net::IMAP::TaggedResponse)
+            if response.name == "OK"
+              @mounted = mailbox
+              @mount_mode = :read
+              result = true
+            else
+              raise Exception, "Failed to mount #{mailbox.inspect} as :read."
+            end
+          else
+            raise Exception, "Failed to mount #{mailbox.inspect} as :read."
+          end
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox, :mode => mode}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mount_mode() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 640
+def mount_mode()
+  if self.mounted?
+    @mount_mode
+  else
+    :unmounted
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ mounted?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 630
+def mounted?
+  result = false
+  if @client
+    if @mounted
+      result = true
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ move_to_mailbox(uid=nil, mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1115
+def move_to_mailbox(uid=nil, mailbox=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID to move the message."
+      end
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX to move the message to."
+      end
+      self.uid_move(uid, mailbox)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_mailbox(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1268
+def remove_mailbox(mailbox=nil)
+  result = []
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name to delete it."
+      end
+      result = @client.delete(mailbox)
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_message_flags(uid=nil, flags=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1060
+def remove_message_flags(uid=nil, flags=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID name to clear FLAGS."
+      end
+      unless flags.is_a?(::Array)
+        raise ArgumentError, "You MUST specify what FLAGS to clear."
+      end
+      self.set_message_attributes(uid, "-FLAGS", flags)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_messages(uid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1079
+def remove_messages(uid=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID to remove."
+      end
+      self.add_message_flags(uid, [:Deleted])
+      @client.expunge
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rename_mailbox(mailbox=nil, new_name=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1283
+def rename_mailbox(mailbox=nil, new_name=nil)
+  result = false
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name to rename it."
+      end
+      unless new_name
+        raise ArgumentError, "You MUST specify a NEW_NAME name to rename the MAILBOX."
+      end
+      @client.rename(mailbox, new_name)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox, :new_name => new_name}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_message_attributes(uid=nil, attributes=nil, flags=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1019
+def set_message_attributes(uid=nil, attributes=nil, flags=nil)
+  result = false
+  if @client
+    begin
+      unless uid.is_any?(::Integer, ::Array)
+        raise ArgumentError, "You MUST specify a UID to set an ATTRIBUTE."
+      end
+      unless attributes.is_a?(::String)
+        raise ArgumentError, "You MUST specify what ATTRIBUTE to set."
+      end
+      unless flags.is_a?(::Array)
+        raise ArgumentError, "You MUST specify what FLAGS to set/unset."
+      end
+      @client.uid_store(uid, attributes, flags)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:uid_set => uid, :attributes => attributes, :flags => flags}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ sort(sort_keys=["DATE"], search_keys=["ALL"], charset="UTF-8") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1235
+def sort(sort_keys=["DATE"], search_keys=["ALL"], charset="UTF-8")
+  result = []
+  if @client
+    begin
+      unless self.mounted?
+        raise Exception, "You MUST mount a Mailbox prior to calling this operation."
+      end
+      unless @mount_mode == :read_write
+        raise Exception, "You need to re-mount #{@mounted.inspect} as :read_write to peform this operation."
+      end
+      result = @client.uid_sort(sort_keys, search_keys, charset)
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:sort_keys => sort_keys, :search_keys => search_keys, :charset => charset}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ status(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 724
+def status(mailbox=nil)
+  result = {:connected => false, :unseen => 0, :recent => 0, :messages => 0}
+  if @client
+    begin
+      result[:connected] = (! @client.disconnected?)
+      the_mailbox = (mailbox || @mounted)
+      unless the_mailbox
+        raise ArgumentError, "You must either MOUNT an inbox, or specify one to get status."
+      end
+      if result[:connected] == true
+        raw_response = @client.status(the_mailbox, ["UNSEEN","MESSAGES", "RECENT"])
+        if raw_response.is_a?(::Hash)
+          result[:unseen] = raw_response["UNSEEN"]
+          result[:recent] = raw_response["RECENT"]
+          result[:messages] = raw_response["MESSAGES"]
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ subscribe(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1302
+def subscribe(mailbox=nil)
+  result = false
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name to subscribe to."
+      end
+      @client.subscribe(mailbox)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ theaded_list(algorithm=nil,search_keys=nil, charset="UTF-8") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1210
+def theaded_list(algorithm=nil,search_keys=nil, charset="UTF-8")
+  result = []
+  if @client
+    begin
+      unless self.mounted?
+        raise Exception, "You MUST mount a Mailbox prior to calling this operation."
+      end
+      unless ["orderedsubject","references"].include?(algorithm.to_s.downcase)
+        raise ArgumentError, "The algorithm you specified is invalid. Choose one of these: ['ORDEREDSUBJECT', 'REFERENCES']"
+      end
+      algorithm = algorithm.to_s.upcase
+      unless search_keys.is_a?(::Array)
+        raise ArgumentError, "You MUST provide search keys in the form of an Array of Strings."
+      end
+      unless charset
+        raise ArgumentError, "You MUST provide a CHARSET specifier. Try one of these: ['US-ASCII', 'UTF-8']"
+      end
+      result = @client.uid_thread(algorithm, search_keys, charset)
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:algorithm => algorithm, :search_keys => search_keys, :charset => charset}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ uid_list(criteria=["NEW"]) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1192
+def uid_list(criteria=["NEW"])
+  result = []
+  if @client
+    begin
+      unless self.mounted?
+        raise Exception, "You MUST mount a Mailbox prior to calling this operation."
+      end
+      unless criteria.is_a?(::Array)
+        raise ArgumentError, "You MUST provide a search criteria in the form of an Array of Strings."
+      end
+      result = @client.uid_search(criteria)
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:criteria => criteria}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ unmount() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 714
+def unmount()
+  result = false
+  if self.mounted?()
+    @mounted = nil
+    @mount_mode = :read
+    result = true
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ unsubscribe(mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1318
+def unsubscribe(mailbox=nil)
+  result = false
+  if @client
+    begin
+      unless mailbox
+        raise ArgumentError, "You MUST specify a MAILBOX name to unsubscribe from."
+      end
+      @client.unsubscribe(mailbox)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ upload_message(mailbox=nil, gxg_message=nil, flags=[:Seen], timestamp=::Time.now) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 798
+def upload_message(mailbox=nil, gxg_message=nil, flags=[:Seen], timestamp=::Time.now)
+  result = false
+  if @client
+    begin
+      if mailbox.is_a?(::GxG::Events::Message)
+        if gxg_message.is_any?(::Symbol,::Array)
+          flags = gxg_message
+        end
+        gxg_message = mailbox
+        mailbox = nil
+      end
+      unless mailbox
+        mailbox = @mounted
+        unless @mount_mode == :read_write
+          raise Exception, "You should re-mount #{@mounted.inspect} as :read_write, or select another MAILBOX."
+        end
+      end
+      unless mailbox
+        raise ArgumentError, "You MUST sepcify a MAILBOX to append the message to."
+      end
+      if flags.is_a?(::Symbol)
+        flags = [(flags)]
+      end
+      if flags.is_a?(::Array)
+        unless flags.include?(:Seen)
+          flags << :Seen
+        end
+      end
+      unless gxg_message.is_a?(::GxG::Events::Message)
+        raise ArgumentError, "You MUST provide a ::GxG::Events::Message object as the message. See: <SmtpClient>.message_template()"
+      end
+      # Build suitable mail message
+      message = Mail.new
+      the_fields = []
+      gxg_message[:header].each_pair do |the_key, the_value|
+        if the_value.is_a?(::Array)
+          the_value.each do |the_field_value|
+            the_fields << "#{the_key.to_s}: #{the_field_value.to_s}"
+          end
+        else
+          the_fields << "#{the_key.to_s}: #{the_value.to_s}"
+        end
+      end
+      message.header = the_fields.join("\r\n")
+      message.from = gxg_message[:sender]
+      message.reply_to = gxg_message[:sender]
+      if gxg_message[:id].to_s.include?("@")
+        message.message_id = gxg_message[:id].to_s
+      else
+        message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
+      end
+      message.to = gxg_message[:to]
+      if gxg_message[:cc]
+        message.cc = gxg_message[:cc]
+      end
+      if gxg_message[:bcc]
+        message.bcc = gxg_message[:bcc]
+      end
+      message.subject = gxg_message[:subject]
+      message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s)
+      if gxg_message[:attachments].size > 0
+        multipart = true
+      else
+        part_count = 0
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            part_count += 1
+          end
+        end
+        if part_count > 1
+          multipart = true
+        else
+          multipart = false
+        end
+      end
+      if multipart
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            type_info = "Content-Type: #{part[:content_type][:type]}"
+            if part[:content_type][:parameters].keys.size > 0
+              part[:content_type][:parameters].each_pair do |the_key, the_value|
+                type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'"
+              end
+            end
+            new_part = Mail::Part.new(type_info)
+            new_part.body = part[:content]
+            message.add_part(new_part)
+          end
+        end
+        # Add file attachments:
+        gxg_message[:attachments].each do |attachment|
+          if attachment[:content].size == 0
+            file_size = ::File.size(attachment[:path])
+            file_handle = ::File.open(attachment[:path],"rb")
+            ::GxG::apportioned_ranges(file_size,65536).each do |the_range|
+              file_handle.seek(the_range.first)
+              attachment[:content] << file_handle.read(the_range.size)
+            end
+            file_handle.close
+          end
+          message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s})
+        end
+      else
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            message.body = part[:content]
+            break
+          end
+        end
+      end
+      # Send it
+      @client.append(mailbox, message.to_s, flags, timestamp)
+      result = true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:mailbox => mailbox, :message => gxg_message, :flags => flags}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ xlist(reference=nil, mailbox=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 773
+def xlist(reference=nil, mailbox=nil)
+  result = []
+  if @client
+    begin
+      unless reference
+        raise ArgumentError, "You MUST sepcify a REFERENCE to list."
+      end
+      unless mailbox
+        raise ArgumentError, "You MUST sepcify a MAILBOX to list."
+      end
+      raw_list = @client.xlist(reference, mailbox)
+      if raw_list
+        raw_list.each do |entry|
+          if entry.is_a?(::Net::IMAP::MailboxList)
+            result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim}
+          end
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/Pop3Client.html b/doc/GxG/Networking/Pop3Client.html new file mode 100644 index 0000000..dd2b7af --- /dev/null +++ b/doc/GxG/Networking/Pop3Client.html @@ -0,0 +1,448 @@ + + + + + + +class GxG::Networking::Pop3Client - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::Pop3Client +

+ +
+ +

POP3 Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(host=nil,port=nil,options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1626
+def initialize(host=nil,port=nil,options={})
+  if port.is_a?(::Hash)
+    options = port
+    port = nil
+  end
+  @user = nil
+  @password = nil
+  @client = nil
+  @host = host
+  unless @host
+    raise ArgumentError, "You MUST provide a HOST, like this: <host>,(<port>),(<options>)"
+  end
+  if options[:use_ssl] == true
+    @port = (port || 995)
+  else
+    @port = (port || 110)
+  end
+  @client = ::Net::POP3.new(@host, @port, (options[:use_apop] || false))
+  if options[:use_ssl] == true
+    if options[:ignore_ssl_errors] == true
+      @client.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
+    else
+      @client.enable_ssl(OpenSSL::SSL::VERIFY_PEER)
+    end
+  end
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ login(user_id=nil,password=nil,options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1655
+def login(user_id=nil,password=nil,options={})
+  if @client
+    @user = user_id
+    @password = password
+    unless @password
+      raise ArgumentError, "You MUST provide a PASSWORD, like this: <user_id>,<password>,(<options>)"
+    end
+    @client.start(@user, @password)
+    true
+  else
+    false
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1670
+def logout()
+  if @client
+    @client.finish
+    @client = nil
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ message_count() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1678
+def message_count()
+  # Return the number of emails on the server.
+  if @client
+    @client.n_mails()
+  else
+    0
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ retrieve_messages(options={:read_only=>false},&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1687
+def retrieve_messages(options={:read_only=>false},&block)
+  result = []
+  begin
+    if @client
+      if @client.started?
+        if @client.active?
+          parse_message = Proc.new do |the_raw_message|
+            header = {}
+            body = []
+            attachments = []
+            parser = ::Mail.new(the_raw_message.pop)
+            parser.header_fields.each do |field|
+              the_key = field.name.to_s.to_sym
+              the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8)
+              the_data.gsub!("         ","")
+              the_data.gsub!("        ","")
+              the_data.gsub!("   ","")
+              the_data.gsub!("\r\n","")
+              if header[(the_key)]
+                unless header[(the_key)].is_a?(::Array)
+                  header[(the_key)] = [(header[(the_key)])]
+                end
+                header[(the_key)] << the_data
+              else
+                header[(the_key)] = the_data
+              end
+            end
+            if parser.multipart?
+              link_db = []
+              body << {:preamble => parser.body.preamble}
+              parser.parts.each do |part|
+                if part.multipart?
+                  link_db << part
+                else
+                  if part.attachment?
+                    attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
+                  else
+                    body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+                  end
+                end
+              end
+              while link_db.size > 0 do
+                part = link_db.shift
+                if part.multipart?
+                  part.parts.each do |the_sub_part|
+                    link_db << the_sub_part
+                  end
+                else
+                  if part.attachment?
+                    attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)}
+                  else
+                    body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+                  end
+                end
+              end
+              body << {:epilogue => parser.body.epilogue}
+            else
+              body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)}
+              mime_type = "text/plain"
+              parameters = {"charset" => "UTF-8"}
+              body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)
+              if body_text.xml?
+                mime_type = "text/xml"
+              end
+              if body_text.html?
+                mime_type = "text/html"
+              end
+              body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text}
+              body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)}
+            end
+            gxg_message = new_message
+            gxg_message[:sender] = parser.from
+            gxg_message[:id] = parser.message_id
+            gxg_message[:to] = parser.to
+            gxg_message[:cc] = parser.cc
+            gxg_message[:bcc] = parser.bcc
+            gxg_message[:subject] = parser.subject
+            gxg_message[:date] = parser.date
+            gxg_message[:header] = header
+            gxg_message[:body] = body
+            gxg_message[:attachments] = attachments
+            gxg_message
+          end
+          @client.each_mail do |raw_message|
+            if block.respond_to?(:call)
+              block.call(parse_message.call(raw_message))
+            else
+              result << parse_message.call(raw_message)
+            end
+            unless options[:read_only] == true
+              raw_message.delete
+            end
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/RestClient.html b/doc/GxG/Networking/RestClient.html new file mode 100644 index 0000000..bc20a9a --- /dev/null +++ b/doc/GxG/Networking/RestClient.html @@ -0,0 +1,638 @@ + + + + + + +class GxG::Networking::RestClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::RestClient +

+ +
+ +

REST Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ clear_system_proxy() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1807
+def self.clear_system_proxy()
+  result = false
+  begin
+    ::RestClient.proxy = nil
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_system_proxy(the_url=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1793
+def self.set_system_proxy(the_url=nil)
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid proxy URI."
+    end
+    ::RestClient.proxy = the_url.to_s
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ clear_cache() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1828
+def clear_cache()
+  @cookies = nil
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ delete(the_url=nil, options={}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2019
+def delete(the_url=nil, options={}, &block)
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI."
+    end
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    header_options = (options[:header_options] || {}).merge({:accept => :json})
+    if @cookies
+      unless header_options[:cookies]
+        header_options[:cookies] = @cookies
+      end
+    end
+    if options[:pem_file]
+      pem_data = ::File.read(options[:pem_file])
+      header_options = header_options.merge({:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER})
+    end
+    if options[:proxy].is_a?(::URI::Generic)
+      header_options[:proxy] = options[:proxy]
+    end
+    response = ::RestClient.delete(the_url.to_s, header_options, &block)
+    if options[:raw_response] == true
+      result = response
+    else
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get(the_url=nil, options={}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1833
+def get(the_url=nil, options={}, &block)
+  result = nil
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI."
+    end
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    header_options = (options[:header_options] || {}).merge({:accept => :json})
+    if @cookies
+      unless header_options[:cookies]
+        header_options[:cookies] = @cookies
+      end
+    end
+    if options[:pem_file]
+      pem_data = ::File.read(options[:pem_file])
+      header_options = header_options.merge({:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER})
+    end
+    if options[:proxy].is_a?(::URI::Generic)
+      header_options[:proxy] = options[:proxy]
+    end
+    if options[:save_as].is_a?(::String)
+      client = ::GxG::Networking::HttpClient.new
+      result = client.get(the_url, {:save_as => options[:save_as], :proxy => options[:proxy]})
+    else
+      if options[:parameters].is_a?(::Hash)
+        response = ::RestClient.get(the_url.to_s, header_options.merge({:params => options[:parameters]}), &block)
+      else
+        response = ::RestClient.get(the_url.to_s, header_options, &block)
+      end
+      #
+      unless @cookies
+        if response.cookies.is_a?(::Hash)
+          if response.cookies.keys.size > 0
+            @cookies = response.cookies
+          end
+        end
+      end
+      if options[:raw_response] == true
+        result = response
+      else
+        the_data = response.body.to_s
+        if the_data.base64?
+          the_data = the_data.decode64()
+        end
+        if the_data.json?
+          result = the_data.from_json()
+          # post-process value strings for DateTime, etc patterns and parse to ruby instances.
+          # post-process - symbolize keys
+        else
+          if the_data.xml?
+            if options[:xml_simple] == true
+              result = the_data.from_xml_simple()
+            else
+              result = the_data.from_xml()
+            end
+          else
+            if the_data.html?
+              result = the_data.from_html()
+            else
+              result = the_data
+            end
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ intitialize() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1818
+def intitialize()
+  # REST Servers to test with:
+  # http://services.groupkt.com/country/get/all
+  # http://services.groupkt.com/state/get/{countryCode}/all
+  # https://httpbin.org/get
+  #
+  @cookies = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ post(the_url=nil, form_data=nil, options={}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1968
+def post(the_url=nil, form_data=nil, options={}, &block)
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI."
+    end
+    unless form_data.is_a?(::Hash)
+      raise Exception, "You MUST provide valid form data (in the form of a Hash)."
+    end
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    header_options = (options[:header_options] || {}).merge({:accept => :json})
+    if @cookies
+      unless header_options[:cookies]
+        header_options[:cookies] = @cookies
+      end
+    end
+    if options[:pem_file]
+      pem_data = ::File.read(options[:pem_file])
+      header_options = header_options.merge({:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER})
+    end
+    if options[:proxy].is_a?(::URI::Generic)
+      header_options[:proxy] = options[:proxy]
+    end
+    if options[:upload_file].is_a?(::String)
+      form_data[:file] = ::File.new(options[:upload_file], "rb")
+      form_data[:multipart] = true
+    else
+      form_data.each_pair do |the_key, the_value|
+        if the_value.is_a?(::String)
+          if the_value.valid_path?
+            if ::File.exist?(the_value)
+              form_data[(the_key)] = ::File.new(the_value, "rb")
+            end
+          end
+        end
+      end
+    end
+    response = ::RestClient.post(the_url.to_s, form_data, header_options, &block)
+    if options[:raw_response] == true
+      result = response
+    else
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ put(the_url=nil, data=nil, options={}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1906
+def put(the_url=nil, data=nil, options={}, &block)
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI."
+    end
+    unless data.is_a?(::Hash)
+      raise Exception, "You MUST provide data to PUT to the server (in the form of a Hash)."
+    end
+    unless options.is_a?(::Hash)
+      options = {}
+    end
+    header_options = (options[:header_options] || {}).merge({:accept => :json})
+    if @cookies
+      unless header_options[:cookies]
+        header_options[:cookies] = @cookies
+      end
+    end
+    if options[:pem_file]
+      pem_data = ::File.read(options[:pem_file])
+      header_options = header_options.merge({:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER})
+    end
+    if options[:proxy].is_a?(::URI::Generic)
+      header_options[:proxy] = options[:proxy]
+    end
+    if options[:to_json]
+      data = data.to_json()
+      header_options[:content_type] = :json
+    else
+      if options[:upload_file].is_a?(::String)
+        if options[:upload_file].valid_path?
+          unless ::File.exist?(options[:upload_file])
+            raise ArgumentError, "File does not exist: #{options[:upload_file].inspect}"
+          end
+          data[(::Celluloid::UUID::random_generate.to_s.to_sym)] = ::File.new(options[:upload_file], "rb")
+        else
+          raise ArgumentError, "You MUST provide a valid path String if you use the :upload_file option."
+        end
+      else
+        data.each_pair do |the_key, the_value|
+          if the_value.is_a?(::String)
+            if the_value.valid_path?
+              if ::File.exist?(the_value)
+                data[(the_key)] = ::File.new(the_value, "rb")
+              end
+            end
+          end
+        end
+      end
+    end
+    response = ::RestClient.put(the_url.to_s, data, header_options, &block)
+    if options[:raw_response] == true
+      result = response
+    else
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/SftpClient.html b/doc/GxG/Networking/SftpClient.html new file mode 100644 index 0000000..e493d1e --- /dev/null +++ b/doc/GxG/Networking/SftpClient.html @@ -0,0 +1,1145 @@ + + + + + + +class GxG::Networking::SftpClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::SftpClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4329
+def initialize()
+  @client = nil
+  @user = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4457
+def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
+  # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
+  # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
+  #
+  # :requests - the number of pending SFTP requests to allow at any given time. When downloading an
+  #  entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
+  #  Reducing it will reduce throughput.
+  #
+  # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
+  # improve throughput. It defaults to 32,000 bytes.
+  #
+  result = false
+  begin
+    if @client
+      unless the_remote_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      if progress_block.respond_to?(:call)
+        channel = @client.download(the_remote_source, the_destination, options, &progress_block)
+      else
+        channel = @client.download(the_remote_source, the_destination, options)
+      end
+      channel.wait
+      # True result means the request was sent, not that it completed.
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4378
+def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
+  # options:
+  #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
+  # below.
+  #
+  #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
+  # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
+  # throughput. Reducing it will reduce throughput.
+  #
+  #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
+  # improve throughput. It defaults to 32,000 bytes.
+  #
+  #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
+  # to "<memory>".
+  result = false
+  begin
+    if @client
+      unless the_local_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      unless options[:name]
+        options[:name] = File.basename(the_local_source)
+      end
+      if progress_block.respond_to?(:call)
+        channel = @client.upload(the_local_source, the_destination, options, &progress_block)
+      else
+        channel = @client.upload(the_local_source, the_destination, options)
+      end
+      channel.wait
+      # True result means the request was sent, not that it completed.
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ change_ownership(the_path=nil, new_owners=nil, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4679
+def change_ownership(the_path=nil, new_owners=nil, &block)
+  result = false
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
+    end
+    unless new_owners.is_a?(::Hash)
+      raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
+    end
+    unless new_owners[:user].is_a?(::String)
+      raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String."
+    end
+    response = @client.setstat!(the_path, new_owners)
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:path => the_path, :new_owners => new_owners}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ change_permissions(the_path=nil, new_permissions=nil, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4702
+def change_permissions(the_path=nil, new_permissions=nil, &block)
+  result = false
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a remote path. (/path/to/something)."
+    end
+    unless new_permissions
+      raise ArgumentError, "You MUST supply a new permission value. (GxG permission or Integer/Octal)."
+    end
+    if new_permissions.is_a?(::Hash)
+      new_permissions = ::File.gxg_permissions_to_mode(new_permissions)
+    end
+    response = @client.setstat!(the_path, {:permissions => new_permissions})
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:path => the_path, :new_permissions => new_permissions}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4362
+def closed?()
+  if @client
+    @client.closed?
+  else
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4492
+def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
+  # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
+  # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html.
+  #
+  # :requests - the number of pending SFTP requests to allow at any given time. When downloading an
+  #  entire directory tree recursively, this will default to 16. Setting this higher might improve throughput.
+  #  Reducing it will reduce throughput.
+  #
+  # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
+  # improve throughput. It defaults to 32,000 bytes.
+  #
+  result = false
+  begin
+    if @client
+      unless the_remote_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path (or file name) as a String."
+      end
+      if progress_block.respond_to?(:call)
+        result = @client.download!(the_remote_source, the_destination, options, &progress_block)
+      else
+        result = @client.download!(the_remote_source, the_destination, options)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ entries(the_path=nil, pattern=nil, flags=0, &block) + + click to toggle source + +
+ + +
+ +

Remote File System Operations and Objects

+ + + + +
+
# File lib/gxg/net_clients.rb, line 4524
+def entries(the_path=nil, pattern=nil, flags=0, &block)
+  # List entries in a given remote path.
+  # If block is provided, resultant list is iterated over the block - (each_entry).
+  result = []
+  begin
+    if @client
+      # Validate the_path: /.*(?:\\|\/)(.+)$/ from: https://www.experts-exchange.com/questions/22088316/Extract-Filename-from-PATH-using-REGEX.html
+      unless (/.*(?:\\|\/)(.+)$/.match(the_path.to_s) || the_path.to_s.split("/").size > 0 || the_path == "/")
+        raise ArgumentError, "You MUST provide a valid remote file system path."
+      end
+      if pattern.is_a?(::String)
+        # use glob
+        unless flags.is_a?(::Numeric)
+          flags = 0
+        end
+        raw_list = @client.dir.glob(the_path, pattern, flags)
+      else
+        # use entries
+        raw_list = @client.dir.entries(the_path)
+      end
+      list = []
+      raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
+      blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+      #
+      raw_list.each do |details|
+        #
+        unless details.name == "." || details.name == ".."
+          #
+          record = {:title => (details.name), :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil}
+          permission_record = blank_permissions.clone
+          #
+          record = record.merge({:size => (details.attributes.size), :accessed => (Time.at(details.attributes.atime)).to_datetime, :modified => (Time.at(details.attributes.mtime)).to_datetime, :state_modified => (Time.at(details.attributes.mtime)).to_datetime, :user_id => (details.attributes.uid), :group_id => (details.attributes.gid)})
+          if details.directory?()
+            record[:type] = :directory
+            record[:owner_type] = :directory
+          end
+          if details.file?()
+            record[:type] = :file
+            record[:owner_type] = :file
+          end
+          if details.symlink?()
+            record[:type] = :symlink
+            record[:owner_type] = :symlink
+          end
+          # puts "Got: #{details.attributes.permissions.inspect}"
+          record[:permissions] = ::File.mode_permission_to_gxg(details.attributes.permissions.to_i)
+          current_groups = @client.session.exec!("groups").gsub("\n","").split(" ")
+          if current_groups.size > 0
+            effective_permissions = raw_permission.clone
+            if @user == details.attributes.owner
+              include_owner = true
+            else
+              include_owner = false
+            end
+            if current_groups.include?(details.attributes.group)
+              include_group = true
+            else
+              include_group = false
+            end
+            #
+            permissions_stack = []
+            if include_owner
+              permissions_stack << record[:permissions][:owner]
+            end
+            if include_group
+              permissions_stack << record[:permissions][:group]
+            end
+            permissions_stack << record[:permissions][:other]
+            permissions_stack.each do |entry|
+              entry.keys do |the_key|
+                if entry[(the_key)] == true
+                  effective_permissions[(the_key)] = true
+                end
+              end
+            end
+            record[:permissions][:effective] = effective_permissions
+            if record[:permissions][:effective][:read]
+              record[:flags] << :read
+            end
+            if record[:permissions][:effective][:write]
+              record[:flags] << :write
+            end
+          end
+          list << record
+        end
+        #
+      end
+      #
+      if block.respond_to?(:call)
+        list.each do |entry|
+          block.call(entry)
+        end
+      end
+      result = list
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_path}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4335
+def login(the_url=nil, options={})
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI"
+    end
+    @user = the_url.user
+    if the_url.password
+      options[:password] = the_url.password
+    end
+    if the_url.port
+      options[:port] = the_url.port
+    end
+    @client = ::Net::SFTP.start(the_url.hostname, the_url.user, options)
+    @client.connect!
+    if @client.closed?
+      @client = nil
+      raise Exception, "Failed to open a connection."
+    else
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4370
+def logout()
+  if @client
+    @client.close_channel
+    @client = nil
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ make_directory(the_path=nil, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4785
+def make_directory(the_path=nil, &block)
+  result = false
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a new directory path. (/path/to/new_dir)."
+    end
+    response = @client.mkdir!(the_path)
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ open_remote_file(the_path=nil, permissions={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4625
+def open_remote_file(the_path=nil, permissions={})
+  result = nil
+  begin
+    if @client
+      filename = ::File.basename(the_path)
+      entries = this().entries(::File.dirname(the_path))
+      open_only = false
+      flags = []
+      entries.each do |item|
+        if item[:title] == filename
+          open_only = true
+          flags = item[:flags]
+          break
+        end
+      end
+      if open_only
+        if flags.include?(:read)
+          if flags.include?(:write)
+            open_mode = "w+"
+          else
+            open_mode = "r"
+          end
+        else
+          if flags.include?(:write) 
+            open_mode = "w"
+          else                
+            open_mode = "r"
+          end
+        end
+        #open
+        result = @client.file.open(the_path.to_s, open_mode)
+      else
+        #create
+        if permissions.is_a?(::Hash)
+          if permissions[:owner].is_a?(::Hash) && permissions[:group].is_a?(::Hash) && permissions[:other].is_a?(::Hash)
+            permissions = ::File.gxg_permissions_to_mode(permissions)
+          end
+        end
+        if permissions.is_a?(::Numeric)
+          unless permissions > 0
+            permissions = 0644.to_i
+          end
+        else
+          permissions = 0644.to_i
+        end
+        result = @client.file.open(the_path.to_s, "w+", permissions)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_path, :permissions => permissions}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_directory(the_path=nil, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4802
+def remove_directory(the_path=nil, &block)
+  result = false
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a directory path to remove. (/path/to/dir)."
+    end
+    response = @client.rmdir!(the_path)
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_remote_file(the_path=nil, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4725
+def remove_remote_file(the_path=nil, &block)
+  result = false
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a remote file path. (/path/to/file)."
+    end
+    response = @client.remove!(the_path)
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:path => the_path}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rename(name=nil, new_name=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4765
+def rename(name=nil, new_name=nil,&block)
+  result = false
+  begin
+    unless name.is_a?(::String)
+      raise ArgumentError, "You MUST supply a remote file path. (/path/to/old_name)."
+    end
+    unless new_name.is_a?(::String)
+      raise ArgumentError, "You MUST supply a new file name path. (/path/to/new_name)."
+    end
+    response = @client.rename!(name,new_name,0x0004)
+    if block.respond_to?(:call)
+      block.call(response)
+    end
+    result = response.ok?
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4419
+def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
+  # options:
+  #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring"
+  # below.
+  #
+  #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory
+  # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve
+  # throughput. Reducing it will reduce throughput.
+  #
+  #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might
+  # improve throughput. It defaults to 32,000 bytes.
+  #
+  #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults
+  # to "<memory>".
+  result = false
+  begin
+    if @client
+      unless the_local_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      unless options[:name]
+        options[:name] = File.basename(the_local_source)
+      end
+      if progress_block.respond_to?(:call)
+        result = @client.upload!(the_local_source, the_destination, options, &progress_block)
+      else
+        result = @client.upload!(the_local_source, the_destination, options)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/SmtpClient.html b/doc/GxG/Networking/SmtpClient.html new file mode 100644 index 0000000..d3049b6 --- /dev/null +++ b/doc/GxG/Networking/SmtpClient.html @@ -0,0 +1,625 @@ + + + + + + +class GxG::Networking::SmtpClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::SmtpClient +

+ +
+ +

SMTP Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(host=nil,port=nil,options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1375
+def initialize(host=nil,port=nil,options={})
+  if port.is_a?(::Hash)
+    options = port
+    port = nil
+  end
+  @user = nil
+  @password = nil
+  @client = nil
+  @host = host
+  unless @host
+    raise ArgumentError, "You MUST provide a HOST, like this: <host>,(<port>),(<options>)"
+  end
+  if options[:use_ssl] == true
+    @port = (port || 465)
+    if options[:ssl_method]
+      # Valid: [:TLSv1, :TLSv1_server, :TLSv1_client, :TLSv1_1, :TLSv1_1_server, :TLSv1_1_client, :TLSv1_2, :TLSv1_2_server, :TLSv1_2_client, :SSLv23, :SSLv23_server, :SSLv23_client]
+      unless OpenSSL::SSL::SSLContext::METHODS.include?(options[:ssl_method])
+        raise ArgumentError, "Encryption method #{options[:ssl_method].inspect} is not valid. Choose one of these: #{OpenSSL::SSL::SSLContext::METHODS.inspect}"
+      end
+      openssl_context = OpenSSL::SSL::SSLContext.new(options[:ssl_method])
+    else
+      openssl_context = ::Net::SMTP.default_ssl_context
+    end
+    if options[:ignore_ssl_errors] == true
+      openssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+    else
+      openssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+    end
+  else
+    @port = (port || 25)
+    openssl_context = nil
+  end
+  @client = ::Net::SMTP.new(@host, @port)
+  if openssl_context
+    @client.enable_ssl(openssl_context)
+  end
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ attachment_template(the_path="", content_parameters={"name" => ""}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1472
+def attachment_template(the_path="", content_parameters={"name" => ""})
+  result = nil
+  begin
+    unless the_path.to_s.valid_path?
+      raise ArgumentError, "You MUST provide a valid file system path to the file."
+    end
+    unless content_parameters.is_a?(::Hash)
+      raise ArgumentError, "You MUST provide content_parameters as a Hash."
+    end
+    unless content_parameters["name"].to_s.size > 0
+      content_parameters["name"] = ::File.basename(the_path)
+    end
+    discovered_mime = ::MimeMagic.by_magic(::File.open(the_path,"rb"))
+    if discovered_mime
+      discovered_mime = discovered_mime.type
+    else
+      discovered_mime = "application/octet-stream"
+    end
+    result = {:content_type => {:type => discovered_mime, :parameters => content_parameters}, :filename => (::File.basename(the_path)), :path => the_path, :content => ::GxG::ByteArray.new}
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:path => the_path, :content_parameters => content_parameters}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ host() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1415
+def host()
+  @host.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(user_id=nil,password=nil,authentication_method=:plain) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1423
+def login(user_id=nil,password=nil,authentication_method=:plain)
+  result = false
+  if @client
+    begin
+      @user = user_id
+      unless @user
+        raise ArgumentError, "You MUST provide a USER id, like this: <user_id>,<password/oauth-token>,(<options>)"
+      end
+      @password = password
+      unless @password
+        raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: <user_id>,<password/oauth-token>,(<options>)"
+      end
+      unless @client.started?
+        unless [:plain, :login, :cram_md5, :oauth2].include?(authentication_method.to_s.to_sym)
+          raise ArgumentError, "Authentication method #{authentication_method.inspect} is invalid. Choose one of these: [:plain, :login, :cram_md5, :oauth2]"
+        end
+        # Internal cosmetics for interface consistency.
+        if authentication_method == :oauth2
+          authentication_method = :xoauth2
+        end
+        @client.start(@host, @user, @password, authentication_method)
+      end
+      result = true
+    rescue Exception => the_error
+      # Internal cosmetics for interface consistency.
+      if authentication_method == :xoauth2
+        authentication_method = :oauth2
+      end
+      log_error({:error => the_error, :parameters => {:user => user_id, :authentication => authentication_method}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1457
+def logout()
+  if @client
+    begin
+      @client.finish
+      @client = nil
+      true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+      false
+    end
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ message_template(mime_type="text/plain", content_parameters={"charset"=>"UTF-8"}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1497
+def message_template(mime_type="text/plain", content_parameters={"charset"=>"UTF-8"})
+  result = nil
+  begin
+    unless ['text/plain', 'text/html', 'text/xhtml', 'text/xml'].include?(mime_type.to_s)
+      raise ArgumentError, "Content Type specified was invalid. Choose one of these: ['text/plain', 'text/html', 'text/xhtml', 'text/xml']"
+    end
+    unless content_parameters.is_a?(::Hash)
+      raise ArgumentError, "You MUST provide content_parameters as a Hash."
+    end
+    gxg_message = new_message
+    gxg_message[:sender] = ""
+    gxg_message[:id] = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
+    gxg_message[:to] = ""
+    gxg_message[:cc] = nil
+    gxg_message[:bcc] = nil
+    gxg_message[:subject] = ""
+    gxg_message[:date] = ::DateTime.now
+    gxg_message[:header] = {}
+    gxg_message[:body] = [{:prologue => ""},{:content_type => {:type => mime_type.to_s, :parameters => content_parameters}, :content => ""},{:epilogue => ""}]
+    gxg_message[:attachments] = []
+    result = gxg_message
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:content_type => mime_type, :content_parameters => content_parameters}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ port() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1419
+def port()
+  @port.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_message(gxg_message=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 1524
+def send_message(gxg_message=nil)
+  result = false
+  if @client
+    begin
+      unless @client.started?
+        raise Exception, "SMTP session not started yet."
+      end
+      unless gxg_message.is_a?(::GxG::Events::Message)
+        raise ArgumentError, "You MUST provide a valid message object of class ::GxG::Events::Message. Suggested: <SmtpClient>.message_template(<Mime-Type-String>)"
+      end
+      # Build suitable mail message
+      message = Mail.new
+      the_fields = []
+      gxg_message[:header].each_pair do |the_key, the_value|
+        if the_value.is_a?(::Array)
+          the_value.each do |the_field_value|
+            the_fields << "#{the_key.to_s}: #{the_field_value.to_s}"
+          end
+        else
+          the_fields << "#{the_key.to_s}: #{the_value.to_s}"
+        end
+      end
+      message.header = the_fields.join("\r\n")
+      message.from = gxg_message[:sender]
+      message.reply_to = gxg_message[:sender]
+      if gxg_message[:id].to_s.include?("@")
+        message.message_id = gxg_message[:id].to_s
+      else
+        message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>"
+      end
+      message.to = gxg_message[:to]
+      if gxg_message[:cc]
+        message.cc = gxg_message[:cc]
+      end
+      if gxg_message[:bcc]
+        message.bcc = gxg_message[:bcc]
+      end
+      message.subject = gxg_message[:subject]
+      message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s)
+      if gxg_message[:attachments].size > 0
+        multipart = true
+      else
+        part_count = 0
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            part_count += 1
+          end
+        end
+        if part_count > 1
+          multipart = true
+        else
+          multipart = false
+        end
+      end
+      if multipart
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            type_info = "Content-Type: #{part[:content_type][:type]}"
+            if part[:content_type][:parameters].keys.size > 0
+              part[:content_type][:parameters].each_pair do |the_key, the_value|
+                type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'"
+              end
+            end
+            new_part = Mail::Part.new(type_info)
+            new_part.body = part[:content]
+            message.add_part(new_part)
+          end
+        end
+        # Add file attachments:
+        gxg_message[:attachments].each do |attachment|
+          if attachment[:content].size == 0
+            file_size = ::File.size(attachment[:path])
+            file_handle = ::File.open(attachment[:path],"rb")
+            ::GxG::apportioned_ranges(file_size,65536).each do |the_range|
+              file_handle.seek(the_range.first)
+              attachment[:content] << file_handle.read(the_range.size)
+            end
+            file_handle.close
+          end
+          message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s})
+        end
+      else
+        gxg_message[:body].each do |part|
+          unless part[:prologue] || part[:epilogue]
+            message.body = part[:content]
+            break
+          end
+        end
+      end
+      # Send it
+      @client.send_message(message.to_s, gxg_message[:sender], message.destinations) 
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:message => gxg_message}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/SoapClient.html b/doc/GxG/Networking/SoapClient.html new file mode 100644 index 0000000..d56f993 --- /dev/null +++ b/doc/GxG/Networking/SoapClient.html @@ -0,0 +1,415 @@ + + + + + + +class GxG::Networking::SoapClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::SoapClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ +

Standard:

+ + + + +
+
# File lib/gxg/net_clients.rb, line 2236
+def initialize()
+  @client = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ callmethod(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2265
+def callmethod(*args)
+  result = nil
+  begin
+    if args.size == 1
+      the_method = args[0].to_s
+      arguments = {}
+    else
+      if args.size > 1
+        the_method = args[0].to_s
+        unless args[1].is_any?(::Hash, ::Array)
+          raise ArgumentError, "You MUST provide a Hash or Array to specify parameters for this method."
+        end
+        arguments = args[1]
+      else
+        raise ArgumentError, "You MUST provide a valid SOAP method."
+      end
+    end
+    if @client
+      response_tag = (the_method.gsub(/^.+:/, "") + "Response")
+      response = @client.invoke(the_method) do |message|
+        if arguments.is_a?(::Hash)
+          arguments.each_pair do |the_key, the_value|
+            message.add(the_key.to_s, the_value)
+          end
+        end
+        if arguments.is_a?(::Array)
+          arguments.each do |the_value|
+            message.add("literal", the_value)
+          end
+        end
+      end
+      data = ::Oga::parse_xml(response.to_xml).css(response_tag)
+      if data
+        found_numbers = data.text.numeric_values
+        found_value = []
+        if found_numbers.is_a?(::Array)
+          found_numbers.each do |the_subarray|
+            if the_subarray.is_a?(::Array)
+              if the_subarray[0].is_a?(::Hash)
+                the_key = the_subarray[0].keys[0]
+                found_value << the_subarray[0][(the_key)]
+              end
+            end
+          end
+        end
+        result = {}
+        result[(response_tag.to_sym)] = data.text
+        #              if found_value.size > 0
+        #                if found_value.size == 1
+        #                  result[(response_tag.to_sym)] = found_value[0]
+        #                else
+        #                  result[(response_tag.to_sym)] = found_value
+        #                end
+        #              else
+        #                result[(response_tag.to_sym)] = data.text
+        #              end
+      else
+        raise Exception, "Failed to interpret response data. Data: #{data.inspect}"
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2257
+def closed?()
+  if @client
+    false
+  else
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ interface() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2331
+def interface()
+  if @client
+    @client.interface
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2241
+def login(the_url=nil, options={})
+  result = false
+  begin
+    @client = ::GxG::Networking::SoapDriver.new(the_url)
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2252
+def logout()
+  @client = nil
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/SoapDriver.html b/doc/GxG/Networking/SoapDriver.html new file mode 100644 index 0000000..a929733 --- /dev/null +++ b/doc/GxG/Networking/SoapDriver.html @@ -0,0 +1,460 @@ + + + + + + +class GxG::Networking::SoapDriver - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::SoapDriver +

+ +
+ +

SOAP Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(the_url=nil) + + click to toggle source + +
+ + +
+ +

Example sites to test with: www.restfulwebservices.net/rest/WeatherForecastService.svc?wsdl www.restfulwebservices.net/wcf/WeatherForecastService.svc?wsdl graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl www.webservicex.com/CurrencyConvertor.asmx?wsdl

+ + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/net_clients.rb, line 2062
+def initialize(the_url=nil)
+  unless the_url.is_a?(::URI::Generic)
+    raise ArgumentError, "You MUST provide a valid URI."
+  end
+  super()
+  @user = nil
+  @password = nil
+  @interface = []
+  @tns_url = nil
+  ::Handsoap::http_driver = :net_http
+  ::Handsoap::xml_query_driver = :rexml
+  if the_url.query.to_s.downcase.include?("wsdl") || (the_url.path.to_s.downcase.include?("wsdl"))
+    point = (the_url.scheme.to_s + "://")
+    if the_url.user
+      point << the_url.user
+      @user = the_url.user
+    end
+    if the_url.password
+      point << (":" << the_url.password)
+      @password = the_url.password
+    end
+    if the_url.hostname
+      if the_url.user
+        point << ("@" << the_url.hostname)
+      else
+        point << the_url.hostname
+      end
+    end
+    if the_url.path
+      point << the_url.path
+    end
+    # Process WSDL, determine actual version number from that.
+    http_tool = ::GxG::Networking::HttpClient.new
+    response = http_tool.get(the_url, {:raw_response => true})
+    if response
+      wsdl_data = response.body.transcode({:replace => "."},::Encoding::UTF_8)
+      if wsdl_data.include?("http://schemas.xmlsoap.org/soap/envelope/")
+        wsdl_version = 1
+      else
+        wsdl_version = 2
+      end
+      ::GxG::Networking::SoapDriver::endpoint({:uri => (point), :version => (wsdl_version)})
+      # Build Interface Data:
+      document = ::Oga.parse_xml(wsdl_data)
+      nodes = document.css("definitions")
+      nodes.each do |node|
+        next unless node.is_a?(Oga::XML::Element)
+        node.attributes.each do |attribute|
+          if ["tns", "targetNamespace"].include?(attribute.name)
+            @tns_url = attribute.value
+            break
+          end
+        end
+        if @tns_url
+          break
+        end
+      end
+      unless @tns_url
+        raise Exception, "Failed to load namespace (required)."
+      end
+      nodes = document.css("portType")
+      base_names = []
+      nodes.each do |node|
+        next unless node.is_a?(::Oga::XML::Element)
+        node.children.each do |the_operation|
+          next unless the_operation.is_a?(::Oga::XML::Element)
+          basename = nil
+          the_operation.attributes.each do |attribute|
+            if attribute.name == "name"
+              basename = attribute.value
+              break
+            end
+          end
+          if basename
+            record = {:base => (basename), :message => (("tns:" << basename).to_sym), :input => nil, :output => nil}
+            the_operation.children.each do |the_io|
+              next unless the_io.is_a?(::Oga::XML::Element)
+              if ["input", "output"].include?(the_io.name)
+                the_io.attributes.each do |the_message|
+                  if the_message.name == "message"
+                    record[(the_io.name.to_sym)] = the_message.value
+                  end
+                end
+              end
+            end
+            if record[:input] || record[:output]
+              base_names << record
+            end
+          end
+        end
+      end
+      messages = document.css("message")
+      base_names.each do |the_method|
+        record = {:method => (the_method[:message]), :input => nil, :output => nil}
+        messages.each do |the_message|
+          next unless the_message.is_a?(::Oga::XML::Element)
+          the_message.attributes.each do |the_attribute|
+            direction_key = nil
+            if the_method[:input].gsub(/^.+:/, "") == the_attribute.value
+              direction_key = :input
+            end
+            if the_method[:output].gsub(/^.+:/, "") == the_attribute.value
+              direction_key = :output
+            end
+            if direction_key
+              parameters = {}
+              the_message.children.each do |the_part|
+                next unless the_part.is_a?(::Oga::XML::Element)
+                if the_part.name == "part"
+                  the_name = nil
+                  the_type = nil
+                  the_part.attributes.each do |the_parameter|
+                    if the_parameter.name == "name"
+                      the_name = the_parameter.value
+                    end
+                    if the_parameter.name == "type"
+                      the_type = the_parameter.value
+                    end
+                    if the_parameter.name == "element"
+                      the_type = the_parameter.value
+                    end
+                    if (the_name && the_type)
+                      parameters[(the_name.to_sym)] = the_type
+                      the_name = nil
+                      the_type = nil
+                    end
+                  end
+                end
+              end
+              if parameters.keys.size > 0
+                record[(direction_key)] = parameters
+              end
+            end
+          end
+        end
+        if record[:input] || record[:output]
+          @interface << record
+        end
+      end
+      #
+    else
+      raise Exception, "Failed to retrieve WSDL data."
+    end
+  else
+    raise ArgumentError, "No WSDL endpoint detected (required)."
+  end
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ interface() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2228
+def interface()
+  @interface
+end
+
+ +
+ + + + +
+ + +
+ +
+ on_before_dispatch(the_document=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2218
+def on_before_dispatch(the_document=nil)
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ on_create_document(the_document=nil) + + click to toggle source + +
+ + +
+ +

Handsoap support hooks:

+ + + + +
+
# File lib/gxg/net_clients.rb, line 2212
+def on_create_document(the_document=nil)
+  if @tns_url
+    the_document.alias('tns', @tns_url)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ on_response_document(the_document=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2222
+def on_response_document(the_document=nil)
+  if @tns_url
+    the_document.add_namespace('ns', @tns_url)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/SshClient.html b/doc/GxG/Networking/SshClient.html new file mode 100644 index 0000000..2673910 --- /dev/null +++ b/doc/GxG/Networking/SshClient.html @@ -0,0 +1,628 @@ + + + + + + +class GxG::Networking::SshClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::SshClient +

+ +
+ +

SSH Client

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4138
+def initialize()
+  @client = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4271
+def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
+  result = false
+  begin
+    if @client
+      unless the_remote_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      if progress_block.respond_to?(:call)
+        channel = @client.scp.download(the_remote_source, the_destination, options, &progress_block)
+      else
+        channel = @client.scp.download(the_remote_source, the_destination, options)
+      end
+      channel.wait
+      # True result means the request was sent, not that it completed.
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4214
+def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
+  result = false
+  begin
+    if @client
+      unless the_local_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      if progress_block.respond_to?(:call)
+        channel = @client.scp.upload(the_local_source, the_destination, options, &progress_block)
+      else
+        channel = @client.scp.upload(the_local_source, the_destination, options)
+      end
+      channel.wait
+      # True result means the request was sent, not that it completed.
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ channel_operations(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4176
+def channel_operations(&block)
+  result = nil
+  begin
+    if @client
+      unless block.respond_to?(:call)
+        raise ArgumentError, "You MUST provide a valid Block/Proc to execute."
+      end
+      result = @client.open_channel(&block)
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:block => block}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4206
+def closed?()
+  if @client
+    false
+  else
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4296
+def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block)
+  # options:
+  #:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote
+  # on the remote server.
+  #
+  #:preserve - the atime and mtime of the file should be preserved.
+  #
+  #:verbose - the process should result in verbose output on the server end (useful for debugging).
+  #
+  result = false
+  begin
+    if @client
+      unless the_remote_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path (or file name) as a String."
+      end
+      if progress_block.respond_to?(:call)
+        result = @client.scp.download!(the_remote_source, the_destination, options, &progress_block)
+      else
+        result = @client.scp.download!(the_remote_source, the_destination, options)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4143
+def login(the_url=nil, options={})
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI"
+    end
+    if the_url.password
+      options[:password] = the_url.password
+    end
+    @client = ::Net::SSH.start(the_url.hostname, the_url.user, options)
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4192
+def logout()
+  result = false
+  begin
+    if @client
+      @client.exec!("exit")
+      @client = nil
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_command(the_command=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4160
+def send_command(the_command=nil)
+  result = nil
+  begin
+    if @client
+      unless the_command.is_a?(::String)
+        raise ArgumentError, "You MUST provide a command as a String."
+      end
+      result = @client.exec!(the_command)
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:command => the_command}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4239
+def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block)
+  # options:
+  #:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote
+  # on the remote server.
+  #
+  #:preserve - the atime and mtime of the file should be preserved.
+  #
+  #:verbose - the process should result in verbose output on the server end (useful for debugging).
+  #
+  #:chunk_size - the size of each "chunk" that should be sent. Defaults to 2048. Changing this value may
+  # improve throughput at the expense of decreasing interactivity.
+  result = false
+  begin
+    if @client
+      unless the_local_source.is_a?(::String)
+        raise ArgumentError, "You MUST provide a local path as a String."
+      end
+      unless the_destination.is_a?(::String)
+        raise ArgumentError, "You MUST provide a remote path as a String."
+      end
+      if progress_block.respond_to?(:call)
+        result = @client.scp.upload!(the_local_source, the_destination, options, &progress_block)
+      else
+        result = @client.scp.upload!(the_local_source, the_destination, options)
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/XmlrpcClient.html b/doc/GxG/Networking/XmlrpcClient.html new file mode 100644 index 0000000..e5a34e0 --- /dev/null +++ b/doc/GxG/Networking/XmlrpcClient.html @@ -0,0 +1,443 @@ + + + + + + +class GxG::Networking::XmlrpcClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::XmlrpcClient +

+ +
+ +

XMLRPC Classes:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2341
+def initialize()
+  @client = nil
+  @interface = nil
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ callmethod(the_method = nil, arguments = nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2404
+def callmethod(the_method = nil, arguments = nil)
+  # The +method+ parameter is converted into a String and should
+  # be a valid XML-RPC method-name.
+  #
+  # Each parameter of +args+ must be of one of the following types,
+  # where Hash, Struct and Array can contain any of these listed _types_:
+  #
+  # * Fixnum, Bignum
+  # * TrueClass, FalseClass, +true+, +false+
+  # * String, Symbol
+  # * Float
+  # * Hash, Struct
+  # * Array
+  # * Date, Time, XMLRPC::DateTime
+  # * XMLRPC::Base64
+  # * A Ruby object which class includes XMLRPC::Marshallable
+  #   (only if Config::ENABLE_MARSHALLABLE is +true+).
+  #   That object is converted into a hash, with one additional key/value
+  #   pair <code>___class___</code> which contains the class name
+  #   for restoring that object later.
+  result = nil
+  begin
+    unless the_method.is_a?(::String)
+      raise ArgumentError, "You MUST provide a valid method String."
+    end
+    if @client
+      calldata = []
+      calldata << the_method
+      #
+      # TODO: scour arguments structure for type conversions to be done.
+      if arguments.is_a?(::Array)
+        arguments.each do |entry|
+          calldata << entry
+        end
+      else
+        if arguments
+          calldata << arguments
+        end
+      end
+      result = @client.call(*calldata)
+      # TODO: scour result structure for type conversions to gxg standard.
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2396
+def closed?()
+  if @client
+    false
+  else
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ interface() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2452
+def interface()
+  begin
+    if @client
+      unless @interface
+        @interface = @client.call("interface")
+      end
+      unless @interface
+        @interface = @client.call("Interface")
+      end
+    end
+  rescue Exception => the_error
+    # log_error({:error => the_error, :parameters => {}})
+  end
+  @interface
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2347
+def login(the_url=nil, options={})
+  result = false
+  begin
+    unless the_url.is_a?(::URI::Generic)
+      raise ArgumentError, "You MUST provide a valid URI."
+    end
+    if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https'
+      client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 443)]
+    else
+      client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 80)]
+    end
+    if options[:proxy].is_a?(::URI::Generic)
+      client_parameters << options[:proxy].host
+      client_parameters << options[:proxy].port
+    else
+      client_parameters << nil
+      client_parameters << nil
+    end
+    if the_url.user && the_url.password
+      client_parameters << the_url.user
+      client_parameters << the_url.password
+    else
+      client_parameters << nil
+      client_parameters << nil
+    end
+    if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https'
+      client_parameters << true
+    else
+      client_parameters << nil
+    end
+    if options[:timeout].is_a?(::Numeric)
+      client_parameters << options[:timeout].to_i
+    else
+      client_parameters << 30
+    end
+    @client = ::XMLRPC::Client.new(*client_parameters)
+    this().interface()
+    result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2391
+def logout()
+  @client = nil
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/XmppAgent.html b/doc/GxG/Networking/XmppAgent.html new file mode 100644 index 0000000..78dc5e7 --- /dev/null +++ b/doc/GxG/Networking/XmppAgent.html @@ -0,0 +1,2656 @@ + + + + + + +class GxG::Networking::XmppAgent - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::XmppAgent +

+ +
+ +

XMPP Client

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(options={}) + + click to toggle source + +
+ + +
+ +

Initialization

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3564
+def initialize(options={})
+  @client = nil
+  @pubsub = nil
+  @roster = nil
+  @browser = nil
+  @transfer_manager = nil
+  @xfr_callbacks = nil
+  @conversations = []
+  @conversation_thread_safety = ::Mutex.new
+  @invitations = []
+  @invitation_thread_safety = ::Mutex.new
+  @download_directory = ::File.expand_path(".")
+  @file_transfers = []
+  @file_transfers_thread_safety = ::Mutex.new
+  @notification_object = options[:notify]
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ online_availability_values() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3146
+def self.online_availability_values()
+  {:online => :chat, :busy => :dnd, :away => :away, :extended_away => :xa, :offline => :unavailable, :error => :error}
+end
+
+ +
+ + + + +
+ + +
+ +
+ register_with_server(the_url=nil, fields={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3233
+def self.register_with_server(the_url=nil, fields={})
+    # Review : convert this code to Blather compatible
+    # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas.
+    # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas.
+    result = false
+    begin
+        if the_url.is_a?(::URI::Generic)
+        if the_url.password() == nil
+            raise ArgumentError, "You MUST provide a vailid password."
+        else
+            fields['password'] = the_url.password.to_s
+        end
+        the_jid = ""
+        if the_url.user()
+            the_jid << the_url.user.to_s
+            fields['username'] = the_url.user.to_s
+        else
+            raise ArgumentError, "You MUST provide a vailid user name."
+        end
+        if the_url.hostname()
+            the_jid << ("@" + the_url.hostname.to_s)
+        else
+            raise ArgumentError, "You MUST provide a vailid host or IP Address."
+        end
+        if the_url.path()
+            the_jid << ("/" + File.basename(the_url.path.to_s))
+        end
+        # xxx
+        # client = ::Jabber::Client.new(the_jid)
+        # client.connect(the_url.hostname.to_s, (the_url.port || 5222))
+        # valid_fields = ::GxG::Networking::XmppClient::registration_fields().keys
+        # the_fields = {}
+        # fields.each_pair do |key, entry|
+        #     if valid_fields.include?(key.to_sym)
+        #     the_fields[(key.to_s)] = entry
+        #     end
+        # end
+        # if the_fields.keys.size > 0
+        #     client.register(the_url.password.to_s, the_fields)
+        # else
+        #     client.register(the_url.password.to_s)
+        # end
+        # if client.status == 2
+        #     client.close
+        # end
+        result = true
+        else
+        raise ArgumentError, "You MUST provide a vailid URL."
+        end
+    rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}})
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ registration_fields() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3150
+def self.registration_fields()
+  # XEP-0077 Support
+  {:username => 'Account name associated with the user', :nick => 'Familiar name of the user', :password => 'Password or secret for the user', :name => 'Full name of the user', :first => 'First name or given name of the user', :last => 'Last name, surname, or family name of the user', :email => 'Email address of the user', :address => 'Street portion of a physical or mailing address', :city => 'Locality portion of a physical or mailing address', :state => 'Region portion of a physical or mailing address', :zip => 'Postal code portion of a physical or mailing address', :phone => 'Telephone number of the user', :url => 'URL to web page describing the user', :date => 'Some date (e.g., birth date, hire date, sign-up date)'}.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ registration_info(the_url=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3155
+def self.registration_info(the_url=nil)
+  result = nil
+  begin
+    if the_url.is_a?(::URI::Generic)
+        the_jid = "anonymous"
+        if the_url.hostname()
+            the_jid << ("@" + the_url.hostname.to_s)
+        else
+            raise ArgumentError, "You MUST provide a vailid host or IP Address in a valid URI."
+        end
+        # Review : convert this code to Blather compatible
+        # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas.
+        # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas.
+        result = []
+        #   client = ::Jabber::Client.new(the_jid)
+        #   client.connect(the_url.hostname.to_s, (the_url.port || 5222))
+        #   info = client.register_info()
+        #   if client.status == 2
+        #     client.close
+        #   end
+        #   if info.is_a?(::Array)
+        #     fields = ::GxG::Networking::XmppClient::registration_fields()
+        #     result = {:instructions => info[0], :registration_fields => {}}
+        #     info[1].each do |the_raw_key|
+        #       result[:registration_fields][(the_raw_key.to_sym)] = fields[(the_raw_key.to_sym)]
+        #     end
+        #   end
+    else
+      raise ArgumentError, "You MUST provide a vailid URL."
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:url => the_url}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_registration(the_url=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3191
+def self.remove_registration(the_url=nil)
+    # Review : convert this code to Blather compatible
+    # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas.
+    # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas.
+    result = false
+    begin
+        if the_url.is_a?(::URI::Generic)
+        if the_url.password() == nil
+            raise ArgumentError, "You MUST provide a vailid password."
+        end
+        the_jid = ""
+        if the_url.user()
+            the_jid << the_url.user.to_s
+        else
+            raise ArgumentError, "You MUST provide a vailid user name."
+        end
+        if the_url.hostname()
+            the_jid << ("@" + the_url.hostname.to_s)
+        else
+            raise ArgumentError, "You MUST provide a vailid host or IP Address."
+        end
+        if the_url.path()
+            the_jid << ("/" + File.basename(the_url.path.to_s))
+        end
+        # Here ???
+        # Connect
+        client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222))
+        client.run
+        # xxx
+        # client.remove_registration
+        # xxx
+        client.close
+        result = true
+        else
+        raise ArgumentError, "You MUST provide a vailid URL."
+        end
+    rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}})
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <<(stanza) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3296
+def <<(stanza)
+    if @client
+        @client.write stanza
+    end
+    self
+end
+
+ +
+ + + + +
+ + +
+ +
+ add_buddy(the_jid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3798
+def add_buddy(the_jid=nil)
+  result = false
+  begin
+    if @client
+      if the_jid.is_a?(::String)
+        unless the_jid.valid_jid?
+          raise ArgumentError, "You MUST provide a valid JID string."
+        end
+        @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "subscribe"))
+        result = true
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:the_jid => the_jid}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ after(handler = nil, *guards, &block) + + click to toggle source + +
+ + +
+ +

### Setup an after filter

+ +

@param [Symbol] handler (optional) the stanza handler the filter should run after @param [guards] guards (optional) a set of guards to check the stanza against @yield [Blather::Stanza] stanza

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3325
+def after(handler = nil, *guards, &block)
+    if @client
+        @client.register_filter :after, handler, *guards, &block
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ before(handler = nil, *guards, &block) + + click to toggle source + +
+ + +
+ +

### Setup a before filter

+ +

@param [Symbol] handler (optional) the stanza handler the filter should run before @param [guards] guards (optional) a set of guards to check the stanza against @yield [Blather::Stanza] stanza

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3313
+def before(handler = nil, *guards, &block)
+    if @client
+        @client.register_filter :before, handler, *guards, &block
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ buddies() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3777
+def buddies()
+    result = []
+    begin
+        if @client
+            @client.roster.items.each_pair do |key_object, data|
+                new_record = {:jid => "", :title => "Untitled", :groups => [], :status => :offline}
+                new_record[:jid] = key_object.to_s
+                new_record[:title] = data.name.to_s
+                new_record[:groups] = data.groups
+                if data.status()
+                    new_record[:status] = data.status().state()
+                end
+                result << new_record
+            end
+        end
+    rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {}})
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ client() + + click to toggle source + +
+ + +
+ +

Review : debug only method

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3582
+def client()
+    @client
+end
+
+ +
+ + + + +
+ + +
+ +
+ conversations(select_jid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3902
+def conversations(select_jid=nil)
+  result = []
+  begin
+    if @client
+      @conversation_thread_safety.synchronize {
+        @conversations.each do |entry|
+            if select_jid
+                if select_jid.to_s == entry.jid.to_s
+                    unless entry.closed?
+                        result << entry
+                        break
+                    end
+                end
+            else
+                unless entry.closed?
+                    result << entry
+                end
+            end
+        end
+      }
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ create_conversation(the_jid=nil, password=nil, configuration={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3929
+def create_conversation(the_jid=nil, password=nil, configuration={})
+  result = false
+  begin
+    if @client
+      unless the_jid.is_a?(::String)
+        raise ArgumentError, "You MUST provide a valid JID String."
+      end
+      unless the_jid.valid_jid?()
+        raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed."
+      end
+      if (self.buddies.collect {|entry| entry[:jid]}).include?(the_jid)
+        # private channel
+        the_channel = self.conversations(the_jid)[0]
+        unless the_channel
+            the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :chat})
+            @conversation_thread_safety.synchronize { @conversations << the_channel }
+        end
+      else
+        # groupchat channel
+        the_channel = self.conversations(the_jid)[0]
+        unless the_channel
+            the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat})
+            @conversation_thread_safety.synchronize { @conversations << the_channel }
+        end
+        if the_channel
+            the_channel.join(the_jid)
+            if configuration.is_any?(::Hash, ::GxG::Database::PersistedHash)
+              if configuration.keys.size > 0
+                the_channel.change_configuration(configuration)
+              end
+            end
+        end
+      end
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:jid => the_jid}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ disconnected(&block) + + click to toggle source + +
+ + +
+ +

### Wrapper for “handle :disconnected”

+ +

This is run after the connection has been shut down.

+ +

@example Reconnect after a disconnection

+ +
disconnected { client.run }
+
+ + + + +
+
# File lib/gxg/net_clients.rb, line 3353
+def disconnected(&block)
+    self.handle :disconnected, &block
+end
+
+ +
+ + + + +
+ + +
+ +
+ discover(what, who, where, &callback) + + click to toggle source + +
+ + +
+ +

### Request items or info from an entity

+ +
discover (items|info), [jid], [node] do |response|
+end
+
+ + + + +
+
# File lib/gxg/net_clients.rb, line 3479
+def discover(what, who, where, &callback)
+    if @client
+        stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
+        stanza.to = who
+        stanza.node = where
+  
+        @client.register_tmp_handler stanza.id, &callback
+        @client.write stanza
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ download_directory() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3994
+def download_directory()
+  @file_transfers_thread_safety.synchronize { @download_directory }
+end
+
+ +
+ + + + +
+ + +
+ +
+ download_directory=(the_path=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3998
+def download_directory=(the_path=nil)
+  unless the_path.is_a?(::String)
+    raise ArgumentError, "You MUST supply a String as valid file path."
+  end
+  the_path = ::File.expand_path(the_path)
+  unless the_path.valid_path?()
+    raise ArgumentError, "You MUST supply a valid file path."
+  end
+  unless ::File.exist?(the_path)
+    raise ArgumentError, "You MUST supply a valid file path - File does not exist."
+  end
+  @file_transfers_thread_safety.synchronize { @download_directory = the_path }
+end
+
+ +
+ + + + +
+ + +
+ +
+ file_transfers() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4057
+def file_transfers()
+  result = []
+  if @client
+    begin
+      @file_transfers.each do |entry|
+        if (entry.pending? || entry.in_progress? || entry.percent_completed < 100)
+          result << entry
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ find_file_transfer(reference=nil, use_session = false) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4104
+def find_file_transfer(reference=nil, use_session = false)
+  result = nil
+  begin
+    if @client
+      manifest = []
+      @file_transfers_thread_safety.synchronize {
+        @file_transfers.each do |entry|
+          manifest << entry
+        end
+      }
+      manifest.each do |entry|
+        if use_session == true
+          if (reference == entry.session_id())
+            result = entry
+            break
+          end
+        else
+          if (reference == entry.reference())
+            result = entry
+            break
+          end
+        end
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_status() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3367
+def get_status()
+    if @client
+        @client.status
+    else
+        :unavailable
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ gxg_message_to_xmpp(the_message=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3535
+def gxg_message_to_xmpp(the_message=nil)
+  # need: :to, :body, :state, :subject, :context, :type
+  # ::Celluloid::UUID::random_generate().to_s
+  unless the_message.is_a?(::GxG::Events::Message)
+    raise ArgumentError, "You MUST provide a valid message object: GxG::Events::Message or new_message Hash."
+  end
+  xmpp_message = Blather::Stanza::Message.new(the_message[:to], the_message[:body])
+  # chat states: active composing gone inactive paused
+  # xmpp_message.set_chat_state(the_message[:state].to_s.to_sym)
+  xmpp_message.id = (the_message[:id].to_s)
+  xmpp_message.from = (the_message[:sender].to_s)
+  xmpp_message.to = (the_message[:to].to_s)
+  if the_message[:context].to_s.size > 0
+    xmpp_message.thread = (the_message[:context].to_s)
+  end
+  xmpp_message.subject = (the_message[:subject].to_s)
+  #
+  # The following <type> Symbols are allowed:
+  # * :chat
+  # * :error
+  # * :groupchat
+  # * :headline
+  # * :normal
+  xmpp_message.type = (the_message[:type] || :chat)
+  xmpp_message.body = the_message[:body]
+  xmpp_message
+end
+
+ +
+ + + + +
+ + +
+ +
+ halt() + + click to toggle source + +
+ + +
+ +

### Halt the handler chain

+ +

Use this to stop the propogation of the stanza though the handler chain.

+ +

@example Ignore all IQ stanzas

+ +
before(:iq) { halt }
+
+ + + + +
+
# File lib/gxg/net_clients.rb, line 3456
+def halt
+    # Review : how to actually use this? I see no way to call 'throw' yet. (research)
+    # throw :halt
+end
+
+ +
+ + + + +
+ + +
+ +
+ handle(handler, *guards, &block) + + click to toggle source + +
+ + +
+ +

### Set handler for a stanza type

+ +

@param [Symbol] handler the stanza type it should handle @param [guards] guards (optional) a set of guards to check the stanza against @yield [Blather::Stanza] stanza

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3336
+def handle(handler, *guards, &block)
+    if @client
+        @client.register_handler handler, *guards, &block
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ invitations() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3834
+def invitations()
+  result = []
+  begin
+    if @client
+      @invitation_thread_safety.synchronize {
+        @invitations.each do |entry|
+          if entry.pending?
+            result << entry
+          end
+        end
+      }
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid() + + click to toggle source + +
+ + +
+ +

### The JID according to the server

+ +

@return [Blather::JID]

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3417
+def jid
+    if @client
+        @client.jid
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid_domain() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3433
+def jid_domain()
+    jid = self.jid
+    if jid
+        jid.domain()
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid_node() + + click to toggle source + +
+ + +
+ +

other supporting JID methods

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3425
+def jid_node()
+    jid = self.jid
+    if jid
+        jid.node()
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid_resource() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3441
+def jid_resource()
+    jid = self.jid
+    if jid
+        jid.resource()
+    else
+        nil
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ join(room, service, nickname = nil) + + click to toggle source + +
+ + +
+ +

### Helper method to join a MUC room

+ +

@overload join(room_jid, nickname)

+ +
 @param [Blather::JID, #to_s] room the JID of the room to join
+ @param [#to_s] nickname the nickname to join the room as
+@overload join(room_jid, nickname)
+ @param [#to_s] room the name of the room to join
+ @param [Blather::JID, #to_s] service the service domain the room is hosted at
+ @param [#to_s] nickname the nickname to join the room as
+ + + + +
+
# File lib/gxg/net_clients.rb, line 3397
+def join(room, service, nickname = nil)
+    join = Blather::Stanza::Presence::MUC.new
+    if nickname
+        join.to = "#{room}@#{service}/#{nickname}"
+    else
+        join.to = "#{room}/#{service}"
+    end
+    self.write join
+end
+
+ +
+ + + + +
+ + +
+ +
+ join_conversation(the_jid=nil, password=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3970
+def join_conversation(the_jid=nil, password=nil, options={})
+  result = false
+  begin
+    if @client
+      unless the_jid.is_a?(::String)
+        raise ArgumentError, "You MUST provide a valid JID String."
+      end
+      unless the_jid.valid_jid?()
+        raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed."
+      end
+      the_channel = self.conversations(the_jid)[0]
+      unless the_channel
+          the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat})
+          @conversation_thread_safety.synchronize { @conversations << the_channel }
+          the_channel.join(the_jid)
+      end
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:jid => the_jid}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ login(the_url=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3596
+def login(the_url=nil, options={})
+    result = false
+    begin
+        if the_url.is_a?(::URI::Generic)
+            if the_url.password() == nil && the_url.user != "anonymous"
+            raise ArgumentError, "You MUST provide a vailid password."
+            end
+            the_jid = ""
+            if the_url.user()
+            the_jid << the_url.user.to_s
+            else
+            raise ArgumentError, "You MUST provide a vailid user name."
+            end
+            if the_url.hostname()
+            the_jid << ("@" + the_url.hostname.to_s)
+            else
+            raise ArgumentError, "You MUST provide a vailid host or IP Address."
+            end
+            if the_url.path()
+            the_jid << ("/" + File.basename(the_url.path.to_s))
+            end
+            # Connect
+            @client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222))
+            # @client.run
+            # Initialize Supports
+            @pubsub = ::Blather::DSL::PubSub.new(@client, self.jid_domain)
+            # Auto-reconnect if disconnected
+            self.handle :disconnected do
+              @client.run
+            end
+            # ### Setup Message Reception: (skipping compose etc messages for now.)
+            # Handlers Map : https://github.com/adhearsion/blather#handlers-hierarchy
+            # Private Message (type :normal)
+            self.handle :message, :normal?, :body do |message|
+              the_message = self.xmpp_message_to_gxg(message)
+              the_channel = self.conversations(the_message[:sender].to_s)[0]
+              unless the_channel
+                  the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat})
+                  @conversation_thread_safety.synchronize { @conversations << the_channel }
+              end
+              the_channel.add_message(the_message)
+              self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s})
+              true
+            end
+            # Private Message (type :chat)
+            self.handle :message, :chat?, :body do |message|
+                the_message = self.xmpp_message_to_gxg(message)
+                the_channel = self.conversations(the_message[:sender].to_s)[0]
+                unless the_channel
+                    the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat})
+                    @conversation_thread_safety.synchronize { @conversations << the_channel }
+                end
+                the_channel.add_message(the_message)
+                self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s})
+                true
+            end
+            # Group Message
+            self.handle :message, :groupchat?, :body do |message|
+                the_message = self.xmpp_message_to_gxg(message)
+                the_channel = self.conversations(the_message[:sender].to_s)[0]
+                unless the_channel
+                    the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :groupchat})
+                    @conversation_thread_safety.synchronize { @conversations << the_channel }
+                end
+                the_channel.add_message(the_message)
+                self.notify({:event => :new_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s})
+                true
+            end
+            # Announcement Message
+            self.handle :message, :headline?, :body do |message|
+              the_message = self.xmpp_message_to_gxg(message)
+              the_channel = self.conversations(the_message[:sender].to_s)[0]
+              unless the_channel
+                  the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat})
+                  @conversation_thread_safety.synchronize { @conversations << the_channel }
+              end
+              the_channel.add_message(the_message)
+              self.notify({:event => :new_announcement, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s})
+              true
+            end
+            # Error Message
+            self.handle :message, :error?, :body do |message|
+              the_message = self.xmpp_message_to_gxg(message)
+              the_channel = self.conversations(the_message[:sender].to_s)[0]
+              unless the_channel
+                  the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat})
+                  @conversation_thread_safety.synchronize { @conversations << the_channel }
+              end
+              the_channel.add_message(the_message)
+              self.notify({:event => :new_error, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s})
+              true
+            end
+            # Presence
+            self.handle :iq do |update|
+                if update.respond_to?(:type)
+                    if update.is_a?(::Blather::Stanza::Iq::Ping)
+                        # Auto-reply to Pings:
+                        reply = update.reply
+                        reply.type = :result
+                        # Review : how to add a data payload to the reply?
+                        self.write_to_stream(reply)
+                    else
+                        if update.is_a?(::Blather::Stanza::Iq)
+                            # reply = update.reply
+                            # reply.type = :result
+                            # # Review : how to add a data payload to the reply?
+                            # self.write_to_stream(reply)
+                            # puts "Got: #{update.class.inspect}\n-----------------------------------------\n#{update.inspect}\n Sent: #{reply.inspect}\n-----------------------------------------\n"
+                        end
+                    end
+                end
+                # Note : returning 'false' signals that subsequent process of this stanza is desired.
+                false
+            end
+            # Invitation
+            self.handle :subscription do |invitation|
+                gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation)
+                @invitation_thread_safety.synchronize { @invitations << gxg_invitation }
+                self.notify({:event => :new_invitation, :invitation => gxg_invitation.uuid, :sender => gxg_invitation.from.to_s})
+                true
+            end
+            # Presence
+            self.handle :presence do |presence_update|
+                the_sender = "#{presence_update.from.node.to_s}@#{presence_update.from.domain.to_s}"
+                self.my_roster.each do |roster_item|
+                    if roster_item.jid.to_s == the_sender
+                        roster_item.status = presence_update
+                        break
+                    end
+                end
+                false
+            end
+            # File Transfer
+            self.handle :file_transfer do |invitation|
+                # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation)
+                # @invitation_thread_safety.synchronize { @invitations << gxg_invitation }
+                puts "Got (file transfer) : #{invitation.inspect}"
+                false
+            end
+            # Multi-User Chat (MUC) Invitation
+            self.handle :muc_join do |invitation|
+                # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation)
+                # @invitation_thread_safety.synchronize { @invitations << gxg_invitation }
+                puts "Got (MUC join) : #{invitation.inspect}"
+                true
+            end
+            self.handle :muc_user do |invitation|
+                # try also: :muc_user, :invite?
+                # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation)
+                # @invitation_thread_safety.synchronize { @invitations << gxg_invitation }
+                puts "Got (MUC user) : #{invitation.inspect}"
+                true
+            end
+            # Connect
+            @client.run
+            result = true
+            timeout = Time.now.to_f + 30.0
+            until @client.connected? do
+                if Time.now.to_f >= timeout
+                    result = false
+                    break
+                end
+            end
+        end
+    rescue Exception => the_error
+        puts the_error.to_s
+    end
+    #
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ logout() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3767
+def logout
+    if @client
+        @client.clear_handlers :disconnected
+        @client.close
+        @client = nil
+    end
+    #
+    true
+end
+
+ +
+ + + + +
+ + +
+ +
+ my_roster() + + click to toggle source + +
+ + +
+ +

### Direct access to the roster

+ +

@return [Blather::Roster]

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3377
+def my_roster
+    if @client
+        @client.roster
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ notify(notification_type=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3586
+def notify(notification_type=nil)
+  if notification_type
+    if @notification_object
+      if @notification_object.respond_to?(:notify)
+        @notification_object.notify(notification_type)
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ pass() + + click to toggle source + +
+ + +
+ +

### Pass responsibility to the next handler

+ +

Use this to jump out of the current handler and let the next registered handler take care of the stanza

+ +

@example Pass a message to the next handler

+ +

This is contrive and should be handled with guards, but pass a message to the next handler based on the content

+ +
message { |s| puts "message caught" }
+message { |s| pass if s.body =~ /pass along/ }
+
+ + + + +
+
# File lib/gxg/net_clients.rb, line 3472
+def pass
+    # Review : how to actually use this? I see no way to call 'throw' yet. (research)
+    # throw :pass
+end
+
+ +
+ + + + +
+ + +
+ +
+ publish_and_subscribe() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3303
+def publish_and_subscribe
+    @pubsub
+end
+
+ +
+ + + + +
+ + +
+ +
+ refresh_conversations() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3877
+def refresh_conversations()
+  result = false
+  begin
+    if @client
+      deadlist = []
+      @conversation_thread_safety.synchronize {
+        @conversations.each_index do |index|
+          if @conversations[(index)].closed?
+            deadlist << index
+          end
+        end
+        if deadlist.size > 0
+          deadlist.reverse.each do |the_index|
+            @conversations.delete_at(the_index)
+          end
+        end
+        result = true
+      }
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ refresh_file_transfers() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4073
+def refresh_file_transfers()
+  result = false
+  begin
+    if @client
+      manifest = []
+      deadlist = []
+      @file_transfers_thread_safety.synchronize {
+        @file_transfers.each do |entry|
+          manifest << entry
+        end
+      }
+      manifest.each_with_index do |entry, index|
+        unless (entry.pending?() || entry.in_progress?())
+          deadlist << index
+        end
+      end
+      @file_transfers_thread_safety.synchronize {
+        if deadlist.size > 0
+          deadlist.reverse.each do |the_index|
+            @file_transfers.delete_at(the_index)
+          end
+        end
+      }
+      result = true
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ refresh_invitations() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3852
+def refresh_invitations()
+  result = false
+  begin
+    if @client
+      deadlist = []
+      @invitation_thread_safety.synchronize {
+        @invitations.each_index do |index|
+          unless @invitations[(index)].pending?
+            deadlist << index
+          end
+        end
+        if deadlist.size > 0
+          deadlist.reverse.each do |the_index|
+            @invitations.delete_at(the_index)
+          end
+        end
+        result = true
+      }
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_buddy(the_jid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3816
+def remove_buddy(the_jid=nil)
+  result = false
+  begin
+    if @client
+      if the_jid.is_a?(::String)
+        unless the_jid.valid_jid?
+          raise ArgumentError, "You MUST provide a valid JID string."
+        end
+        @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "unsubscribe"))
+        result = true
+      end
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:the_jid => the_jid}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ say(to, msg, using = :chat) + + click to toggle source + +
+ + +
+ +

### Helper method to make sending basic messages easier

+ +

@param [Blather::JID, to_s] to the JID of the message recipient @param [#to_s] msg the message to send @param [#to_sym] the stanza method to use

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3411
+def say(to, msg, using = :chat)
+    self.write Blather::Stanza::Message.new(to, msg, using)
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_caps() + + click to toggle source + +
+ + +
+ +

Send capabilities to the server

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3502
+def send_caps
+    if @client
+        @client.register_handler :disco_info, :type => :get, :node => @client.caps.node do |s|
+          r = @client.caps.dup
+          r.to = s.from
+          r.id = s.id
+          @client.write r
+        end
+        @client.write client.caps.c
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_file(the_jid=nil,the_path=nil, description=nil, buffer_size=4096, proxy=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 4012
+def send_file(the_jid=nil,the_path=nil, description=nil, buffer_size=4096, proxy=nil)
+  if @client
+    begin
+      unless the_jid.is_a?(::String)
+        raise ArgumentError, "You MUST supply a valid JID String to specify the recipient of the file."
+      end
+      unless the_path.is_a?(::String)
+        raise ArgumentError, "You MUST supply a String as valid file path."
+      end
+      the_path = ::File.expand_path(the_path)
+      unless the_path.valid_path?()
+        raise ArgumentError, "You MUST supply a valid file path."
+      end
+      unless ::File.exist?(the_path)
+        raise ArgumentError, "You MUST supply a valid file path - File does not exist."
+      end
+      unless description.is_a?(::String)
+        description = "File Transfer: #{::File.basename(the_path)}"
+      end
+      unless buffer_size.is_a?(::Integer)
+        raise ArgumentError, "You MUST provide an Integer to set buffer size."
+      end
+      valid_buffer_size_range = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid")
+      unless valid_buffer_size_range.is_a?(::Range)
+        valid_buffer_size_range = (4096..65536)
+      end
+      unless valid_buffer_size_range.include?(buffer_size)
+        raise ArgumentError, "You MUST provide an Integer between #{valid_buffer_size_range.first} and #{valid_buffer_size_range.last}."
+      end
+      # Review : rebuild/rewrite this section and XmppAgentFileTransfer
+        #   the_transfer = ::GxG::Networking::XmppAgentFileTransfer.new(@transfer_manager, :upload, {:jid => the_jid, :path => the_path, :description => description}, this())
+        #   @file_transfers_thread_safety.synchronize {
+        #     @file_transfers << the_transfer
+        #   }
+        #   the_transfer.buffer_size = buffer_size
+        #   the_transfer.upload_file(proxy)
+        #   the_transfer.reference
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:jid => the_jid, :path => the_path, :description => description, :buffer_size => buffer_size}})
+    end
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_caps(node, identities, features) + + click to toggle source + +
+ + +
+ +

### Set the capabilities of the client

+ +

@param [String] node the URI @param [Array<Hash>] identities an array of identities @param [Array<Hash>] features an array of features

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3494
+def set_caps(node, identities, features)
+    if @client
+        @client.caps.node = node
+        @client.caps.identities = identities
+        @client.caps.features = features
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_status(state = nil, msg = nil) + + click to toggle source + +
+ + +
+ +

### Set current status

+ +

@param [Blather::Stanza::Presence::State::VALID_STATES] state the current state @param [#to_s] msg the status message to use

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3361
+def set_status(state = nil, msg = nil)
+    if @client
+        @client.status = state, msg
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ when_ready(&block) + + click to toggle source + +
+ + +
+ +

### Wrapper for “handle :ready” (just a bit of syntactic sugar)

+ +

This is run after the connection has been completely setup

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3344
+def when_ready(&block)
+    self.handle :ready, &block
+end
+
+ +
+ + + + +
+ + +
+ +
+ write(stanza) + + click to toggle source + +
+ + +
+ +

Support Methods

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3289
+def write(stanza)
+    if @client
+        @client.write stanza
+    end
+    true
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_to_stream(stanza) + + click to toggle source + +
+ + +
+ +

### Write data to the stream

+ +

@param [#to_xml, to_s] stanza the data to send down the wire.

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3385
+def write_to_stream(stanza)
+    self.write stanza
+end
+
+ +
+ + + + +
+ + +
+ +
+ xmpp_message_to_gxg(the_message=nil) + + click to toggle source + +
+ + +
+ +

Conversion Tools

+ + + + +
+
# File lib/gxg/net_clients.rb, line 3514
+def xmpp_message_to_gxg(the_message=nil)
+    # need: :to, :body, :state, :subject, :context, :type
+    # ::Celluloid::UUID::random_generate().to_s
+    unless the_message.is_a?(Blather::Stanza::Message)
+      raise ArgumentError, "You MUST provide a valid message object: Blather::Stanza::Message."
+    end
+    gxg_message = ::GxG::Events::Message.new({:sender => :unknown})
+    gxg_message[:id] = (the_message.id || ::GxG::uuid_generate.to_sym)
+    gxg_message[:sender] = the_message.from.to_s
+    gxg_message[:to] = the_message.to.to_s
+    gxg_message[:type] = the_message.type
+    if the_message.thread.is_a?(::Hash)
+        gxg_message[:context] = the_message.thread.values[0]
+    else
+        gxg_message[:context] = the_message.thread
+    end
+    gxg_message[:subject] = the_message.subject
+    gxg_message[:body] = the_message.body
+    gxg_message
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/XmppAgentConversation.html b/doc/GxG/Networking/XmppAgentConversation.html new file mode 100644 index 0000000..28f86d7 --- /dev/null +++ b/doc/GxG/Networking/XmppAgentConversation.html @@ -0,0 +1,927 @@ + + + + + + +class GxG::Networking::XmppAgentConversation - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::XmppAgentConversation +

+ +
+ +

Conversation Channel

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(parent=nil, details={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2935
+def initialize(parent=nil, details={})
+  if ::GxG::valid_uuid?(details[:context])
+      @uuid = details[:context].to_s.to_sym
+  else
+      @uuid = ::GxG::uuid_generate.to_sym
+  end
+  @parent = parent
+  # @details should have a JID for the sender-jid/room-jid
+  # @details should indicate whether a private direct message or MUC ROOM message (:chat? or :groupchat?)
+  @details = details
+  #
+  @received = []
+  @received_thread_safety = ::Mutex.new
+  # Add call backs
+  # @parent.notify({:event => :new_message, :conversation => @uuid, :message => message.id})
+  # @parent.notify({:event => :new_private_message, :conversation => @uuid, :message => message.id})
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](the_key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2970
+def [](the_key=nil)
+  result = nil
+  if the_key.is_a?(::Symbol)
+      result = @details[(the_key)]
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ add_message(the_message=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3019
+def add_message(the_message=nil)
+  if the_message
+      @received_thread_safety.synchronize { @received << the_message }
+      true
+  else
+      false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ change_configuration(the_configuration=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3120
+def change_configuration(the_configuration=nil)
+  result = false
+  begin
+      # Review : TODO - alter ROOM configuration if admin/owner
+      #   if @handler
+      #     if @handler.owner?
+      #       unless the_configuration.is_a?(::Hash)
+      #         raise ArgumentError, "You MUST provide a configuration Hash."
+      #       end
+      #       the_configuration = this().default_room_configuration(the_configuration)
+      #       @handler.submit_room_configuration(the_configuration)
+      #       result = true
+      #     else
+      #       raise Exception, "You are not the owner of this conversation room."
+      #     end
+      #   end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:configuration => the_configuration}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ chat_buddies() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3049
+def chat_buddies()
+  result = []
+  begin
+      # Review : TODO - like XmppAgent.buddies but for this ROOM only.
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ closed?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2988
+def closed?()
+  # Review : integrate with new system
+  false
+end
+
+ +
+ + + + +
+ + +
+ +
+ configuration() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3086
+def configuration()
+  result = nil
+  begin
+      # Review : TODO - get ROOM configuration
+      #   if @handler
+      #     if @handler.owner?
+      #       current_configuration = {}
+      #       iq = ::Jabber::Iq.new(:get, @handler.jid.to_s)
+      #       iq.to = @handler.jid.to_s
+      #       iq.from = @handler.my_jid.to_s
+      #       iq.add(::Jabber::MUC::IqQueryMUCOwner.new)
+      #       field_keys = this().default_room_configuration.keys
+      #       response = @stream.send_with_id(iq)
+      #       if response
+      #         if (response.query && response.query.x(::Jabber::Dataforms::XData))
+      #           field_keys.each do |the_key|
+      #             field = response.query.x(::Jabber::Dataforms::XData).field(the_key.to_s)
+      #             if field.is_a?(::Jabber::Dataforms::XDataField)
+      #               current_configuration[(the_key.to_s)] = field.value
+      #             end
+      #           end
+      #         end
+      #       end
+      #       result = current_configuration
+      # else
+      #   raise Exception, "You are not the owner of this conversation room."
+      # end
+      #   end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ default_room_configuration(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2903
+def default_room_configuration(options={})
+  unless options.is_a?(::Hash)
+    options = {}
+  end
+  default_config = {
+    'FORM_TYPE' => 'http://jabber.org/protocol/muc#roomconfig',
+    'form' => 'config',
+    'muc#roomconfig_roomname' => nil,
+    'muc#roomconfig_roomdesc' => nil,
+    'leave' => 'has left',
+    'join' => 'has become available',
+    'rename' => 'is now known as',
+    'muc#roomconfig_changesubject' => 0,
+    'muc#roomconfig_maxusers' => 0,
+    'privacy' => 0,
+    'muc#roomconfig_publicroom' => 1,
+    'muc#roomconfig_persistentroom' => 1,
+    'legacy' => 0,
+    'muc#roomconfig_moderatedroom' => 0,
+    'defaulttype' => 0,
+    'privmsg' => 0,
+    'muc#roomconfig_membersonly' => 0,
+    'muc#roomconfig_allowinvites' => 1,
+    'muc#roomconfig_passwordprotectedroom' => 0,
+    'muc#roomconfig_roomsecret' => nil,
+    'muc#roomconfig_whois' => 'moderators',
+    'muc#roomconfig_enablelogging' => 0,
+    'logformat' => 'xml'
+  }
+  default_config.merge(options)
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2984
+def inspect()
+  {:jid => this().jid, :title => this().title}.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ jid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2958
+def jid()
+  @details[:sender].to_s
+end
+
+ +
+ + + + +
+ + +
+ +
+ join(the_jid=nil, password=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3028
+def join(the_jid=nil, password=nil, options={})
+  result = false
+  begin
+      # Review : TODO - MUC join
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:the_jid => the_jid, :options => options}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ leave(exit_string=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3038
+def leave(exit_string=nil)
+  result = false
+  begin
+      # Review : TODO - MUC leave
+      # @parent.refresh_conversations
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:exit_string => exit_string}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ my_jid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2962
+def my_jid()
+  result = nil
+  if @parent
+    result = @parent.jid().to_s
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process_received(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2993
+def process_received(&block)
+  begin
+      if block.respond_to?(:call)
+          count = 0
+          @received_thread_safety.synchronize {
+              count = @received.size
+          }
+          record = nil
+          count.times do
+              @received_thread_safety.synchronize {
+                  record = @received.shift
+              }
+              if record
+                  block.call(record)
+              end
+          end
+          true
+      else
+          false
+      end
+  rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+      false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ say_something(the_message=nil, to_jid=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3059
+def say_something(the_message=nil, to_jid=nil)
+  result = false
+  begin
+      if @details[:type] == :chat
+          recipient = (to_jid || the_message[:to]).to_s
+          @parent.say(::Blather::JID.new(recipient), @parent.gxg_message_to_xmpp(the_message))
+      else
+          # Review : TODO - send chat message to ROOM
+      end
+      result = true
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:message => the_message, :to => to_jid}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ send_invitations(the_recipients=nil, reason=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 3075
+def send_invitations(the_recipients=nil, reason=nil)
+  result = false
+  begin
+      # Review : TODO - ROOM invitation list processing list.each { |i| send i }
+      # "You MUST provide an Array of JID Strings."
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {:recipients => the_recipients, :message => reason}})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ title() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2978
+def title()
+  result = nil
+  # Review : TODO - return ROOM title
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2954
+def uuid()
+  @uuid
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/XmppAgentFileTransfer.html b/doc/GxG/Networking/XmppAgentFileTransfer.html new file mode 100644 index 0000000..23446a3 --- /dev/null +++ b/doc/GxG/Networking/XmppAgentFileTransfer.html @@ -0,0 +1,1048 @@ + + + + + + +class GxG::Networking::XmppAgentFileTransfer - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::XmppAgentFileTransfer +

+ +
+ +

File Transfer

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(manager, direction, details, parent) + + click to toggle source + +
+ + +
+ +

Review : totally rewrite this

+ + + + +
+
# File lib/gxg/net_clients.rb, line 2538
+def initialize(manager, direction, details, parent)
+  @reference = ::GxG::uuid_generate.to_sym
+  @session_id = nil
+  @pending = true
+  @inprogress = false
+  @percent_completed = 0
+  @status_thread_safety = ::Mutex.new
+  @manager = manager
+  @direction = direction
+  @info = {:reference => @reference}
+  @bytestream = nil
+  @file_details = nil
+  @cancel = false
+  the_value = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid")
+  if the_value.is_a?(::Range)
+    @valid_write_buffer_sizes = the_value
+  else
+    @valid_write_buffer_sizes = (4096..65536)
+  end
+  @buffer_size = 4096
+  @client = parent
+  if @direction == :download
+    @invitation = details[:iq]
+    @sender = details[:iq].from
+    @info[:sender] = @sender.to_s
+    @info[:recipient] = @client.jid.to_s
+    @file_details = {:filename => details[:file_info].fname, :path => (details[:download_directory] + "/" + details[:file_info].fname), :size => details[:file_info].size, :md5 => details[:file_info].hash, :description => details[:file_info].description}
+    @download_directory = details[:download_directory]
+    @info[:details] = @file_details
+    @recipient = nil
+    #
+    #
+  end
+  if @direction == :upload
+    @info[:sender] = @client.jid.to_s
+    @info[:recipient] = details[:jid].to_s
+    @recipient = ::Jabber::JID.new(details[:jid])
+    @file_details = {:filename => ::File.basename(details[:path]), :path => details[:path], :size => ::File.size(details[:path]), :md5 => ::Digest::MD5.file(details[:path]), :description => details[:description]}
+    @info[:details] = @file_details
+    @invitation = nil
+    @download_directory = nil
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ accept() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2651
+def accept()
+  if @client
+    if @status_thread_safety.synchronize { @pending == true && @direction == :download }
+      Thread.new {
+        begin
+          bytes_received = 0
+          total_size = @file_details[:size]
+          @bytestream = @manager.accept(@invitation)
+          if @bytestream
+            @bytestream.accept()
+            @status_thread_safety.synchronize {
+              @pending = false
+              @inprogress = true
+              @session_id = @bytestream.session_id.to_s
+            }
+            #
+            cancel_callback = Proc.new do |the_iq|
+              if the_iq.type == :error
+                the_error = ::Jabber::ServerError.new(the_iq.error)
+                if @invitation.id.to_s == the_iq.id.to_s
+                  the_transfer = @client.find_file_transfer(@reference)
+                  if the_transfer
+                    the_transfer.cancel
+                  end
+                end
+                log_error({:error => the_error, :parameters => {:iq => the_iq}})
+              end
+            end
+            @client.set_xfr_callback(@session_id,&cancel_callback)
+            #
+            the_file = ::File.open(@file_details[:path], "w+b",0664)
+            the_file.pos = 0
+            buffer = @bytestream.read()
+            while buffer && @status_thread_safety.synchronize { @cancel == false } do
+              the_file.write(buffer.to_s)
+              bytes_received += buffer.to_s.size
+              @status_thread_safety.synchronize { @percent_completed = ((bytes_received.to_f / total_size.to_f) * 100.0).to_i }
+              @client.notify({:event => :file_transfer_progress, :transfer => @reference, :progress => @status_thread_safety.synchronize { @percent_completed }})
+              buffer = @bytestream.read()
+            end
+            the_file.close
+            @bytestream.close
+            @status_thread_safety.synchronize {
+              @inprogress = false
+              @pending = false
+            }
+            the_path = @file_details[:path]
+            if @status_thread_safety.synchronize { @cancel == true }
+              ::File.delete(the_path)
+              @client.notify({:event => :file_transfer_cancelled, :transfer => @reference})
+            else
+              if @file_details[:md5]
+                unless @file_details[:md5] == ::Digest::MD5.file(the_path)
+                  raise Exception, "MD5 Checksum Failure - file corruption on file: #{the_path}"
+                end
+                @client.notify({:event => :file_transfer_complete, :transfer => @reference})
+              end
+            end
+          else
+            @status_thread_safety.synchronize {
+              @inprogress = false
+              @pending = false
+            }
+            the_file = nil
+            raise Exception, "Failed to negotiate a transfer method."
+          end
+        rescue Exception => the_error
+          @status_thread_safety.synchronize {
+            @inprogress = false
+            @pending = false
+          }
+          if the_file
+            the_file.close
+          end
+          if @bytestream
+            @bytestream.close
+          end
+          log_error({:error => the_error, :parameters => {}})
+          @client.notify({:event => :file_transfer_error, :transfer => @reference})
+        end
+        if @session_id
+          @client.clear_xfr_callback(@session_id)
+        end
+        @client.refresh_file_transfers
+      }
+    end
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ buffer_size() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2603
+def buffer_size()
+  @status_thread_safety.synchronize { @buffer_size }
+end
+
+ +
+ + + + +
+ + +
+ +
+ buffer_size=(the_size=4096) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2607
+def buffer_size=(the_size=4096)
+  begin
+    unless the_size.is_a?(::Integer)
+      raise ArgumentError, "You MUST provide an Integer to set buffer size."
+    end
+    if @status_thread_safety.synchronize { @direction == :upload }
+      unless @valid_write_buffer_sizes.include?(the_size)
+        raise ArgumentError, "You MUST provide an Integer between #{@valid_read_buffer_sizes.first} and #{@valid_read_buffer_sizes.last}."
+        @status_thread_safety.synchronize { @buffer_size = the_size }
+      end
+    else
+      log_warn("Buffer size is only set on upload file transfers.")
+    end
+    the_size
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ cancel() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2631
+def cancel()
+  if @client
+    begin
+      if @status_thread_safety.synchronize { @pending == true }
+        @status_thread_safety.synchronize { @pending = false }
+      end
+      if @status_thread_safety.synchronize { @inprogress == true }
+        @status_thread_safety.synchronize { @inprogress = false }
+      end
+      @status_thread_safety.synchronize { @cancel = true }
+      @client.refresh_file_transfers
+      true
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+    end
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ cancelled?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2627
+def cancelled?()
+  @status_thread_safety.synchronize { @cancel }
+end
+
+ +
+ + + + +
+ + +
+ +
+ decline() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2743
+def decline()
+  if @client
+    if @status_thread_safety.synchronize { @pending == true && @direction == :download }
+      begin
+        @manager.decline(@invitation)
+        @status_thread_safety.synchronize { @pending = false }
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {}})
+      end
+    end
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ download_directory() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2771
+def download_directory()
+  @status_thread_safety.synchronize { @download_directory.clone }
+end
+
+ +
+ + + + +
+ + +
+ +
+ download_directory=(the_path=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2775
+def download_directory=(the_path=nil)
+  begin
+    unless the_path.is_a?(::String)
+      raise ArgumentError, "You MUST supply a String as valid directory path."
+    end
+    the_path = ::File.expand_path(the_path)
+    unless the_path.valid_path?()
+      raise ArgumentError, "You MUST supply a valid directory path."
+    end
+    unless ::File.exist?(the_path)
+      raise ArgumentError, "You MUST supply a valid directory path - Directory does not exist."
+    end
+    @status_thread_safety.synchronize {
+      unless @inprogress == true
+        if @direction == :download
+          @download_directory = ::File.expand_path(the_path)
+          @file_details[:path] = (@download_directory + "/" + @file_details[:filename])
+        end
+      end
+      }
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {}})
+    end
+end
+
+ +
+ + + + +
+ + +
+ +
+ file_details() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2767
+def file_details()
+  @status_thread_safety.synchronize { @file_details.clone }
+end
+
+ +
+ + + + +
+ + +
+ +
+ in_progress?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2759
+def in_progress?()
+  @status_thread_safety.synchronize { @inprogress }
+end
+
+ +
+ + + + +
+ + +
+ +
+ info() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2587
+def info()
+  @info.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2583
+def inspect()
+  "<Transfer: #{@direction.inspect} - #{@reference}, with Session ID: #{@session_id} for File #{@file_details[:filename]}>"
+end
+
+ +
+ + + + +
+ + +
+ +
+ pending?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2599
+def pending?()
+  @status_thread_safety.synchronize { @pending }
+end
+
+ +
+ + + + +
+ + +
+ +
+ percent_completed() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2763
+def percent_completed()
+  @status_thread_safety.synchronize { @percent_completed.clone }
+end
+
+ +
+ + + + +
+ + +
+ +
+ reference() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2591
+def reference()
+  @reference.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ session_id() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2595
+def session_id()
+  @session_id.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ upload_file(proxy=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2800
+def upload_file(proxy=nil)
+  if @client
+    if @direction == :upload
+      unless @status_thread_safety.synchronize { @inprogress == true }
+        Thread.new {
+          begin
+            bytes_sent = 0
+            buffer_size = @status_thread_safety.synchronize { @buffer_size }
+            the_file_source = ::Jabber::FileTransfer::FileSource.new(@file_details[:path])
+            the_file_source.length = buffer_size
+            total_size = the_file_source.size
+            @bytestream = nil
+            response = @manager.offer(@recipient,the_file_source,@file_details[:description],nil,@reference.to_s)
+            if response.is_a?(::Hash)
+              @bytestream = response[:bytestream]
+              the_iq_id = response[:iq_id]
+              if @bytestream
+                if @bytestream.is_a?(::Jabber::Bytestreams::SOCKS5BytestreamsInitiator)
+                  if proxy.is_any?(::String, ::Jabber::JID)
+                    @bytestream.add_streamhost(proxy)
+                  end
+                end
+                if @bytestream.is_a?(::Jabber::Bytestreams::IBBInitiator)
+                  @bytestream.block_size = buffer_size
+                end
+                #
+                @status_thread_safety.synchronize {
+                  @pending = false
+                  @inprogress = true
+                }
+                open_response = @bytestream.open()
+                if open_response
+                  puts "Got: (open_response) - #{open_response.inspect}"
+                  if open_response.type == :result
+                    #
+                    @session_id = @bytestream.session_id.to_s
+                    #
+                    cancel_callback = Proc.new do |the_iq|
+                      if the_iq.type == :error
+                        the_error = ::Jabber::ServerError.new(the_iq.error)
+                        if the_iq_id.to_s == the_iq.id.to_s
+                          the_transfer = @client.find_file_transfer(@reference)
+                          if the_transfer
+                            the_transfer.cancel
+                          end
+                        end
+                        log_error({:error => the_error, :parameters => {:iq => the_iq}})
+                      end
+                    end
+                    @client.set_xfr_callback(@session_id,&cancel_callback)
+                    #
+                    ::GxG.apportioned_ranges(total_size,buffer_size).each do |the_range|
+                      if @status_thread_safety.synchronize { @cancel == true }
+                        the_file_source.close
+                        break
+                      end
+                      the_file_source.seek(the_range.first)
+                      bytes_sent += @bytestream.write(the_file_source.read(the_range.size)).to_i
+                      @status_thread_safety.synchronize { @percent_completed = ((bytes_sent.to_f / total_size.to_f) * 100.0).to_i }
+                      #
+                    end
+                    @bytestream.flush
+                    @bytestream.close
+                    @status_thread_safety.synchronize { @inprogress = false }
+                  else
+                    # Error?
+                    @status_thread_safety.synchronize {
+                      @pending = false
+                      @inprogress = false
+                    }
+                  end
+                else
+                  raise Exception, "Failed to open the stream."
+                end
+              end
+            else
+              @status_thread_safety.synchronize {
+                @pending = false
+                @inprogress = false
+              }
+            end
+          rescue Exception => the_error
+            the_file_source.close
+            @bytestream.close
+            @status_thread_safety.synchronize { @inprogress = false }
+            log_error({:error => the_error, :parameters => {:proxy => proxy}})
+          end
+          @client.clear_xfr_callback(@reference.to_s)
+          if @session_id
+            @client.clear_xfr_callback(@session_id)
+          end
+          @client.refresh_file_transfers
+        }
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/XmppAgentInvitation.html b/doc/GxG/Networking/XmppAgentInvitation.html new file mode 100644 index 0000000..9fb186a --- /dev/null +++ b/doc/GxG/Networking/XmppAgentInvitation.html @@ -0,0 +1,470 @@ + + + + + + +class GxG::Networking::XmppAgentInvitation - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::XmppAgentInvitation +

+ +
+ +

XMPP Classes: Review : Blather switchover: require ::File.expand_path(“./xmpp_client.rb”,::File.dirname(__FILE__)) ### Invitations

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(parent=nil, invitation=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2474
+def initialize(parent=nil, invitation=nil)
+  @uuid = ::GxG::uuid_generate.to_sym
+  @pending = true
+  @parent = parent
+  @invitation = invitation
+  #
+  if @invitation.is_a?(::Blather::Stanza::Presence)
+      @details = {:sender => @invitation.from.to_s, :type => :association}
+  else
+      @details = {:sender => @invitation.from.to_s, :type => :unkown}
+  end
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ [](the_key) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2509
+def [](the_key)
+  @details[(the_key)]
+end
+
+ +
+ + + + +
+ + +
+ +
+ accept() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2513
+def accept()
+  if @invitation
+      @parent.write(@invitation.approve!)
+      @pending = false
+      @parent.refresh_invitations()
+      true
+  else
+      false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ decline() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2524
+def decline()
+  if @invitation
+      @parent.write(@invitation.cancel!)
+      @pending = false
+      @parent.refresh_invitations()
+      true
+  else
+      false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2497
+def inspect()
+  @details.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ invitation() + + click to toggle source + +
+ + +
+ +

Review : debug only

+ + + + +
+
# File lib/gxg/net_clients.rb, line 2489
+def invitation()
+  @invitation
+end
+
+ +
+ + + + +
+ + +
+ +
+ keys() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2505
+def keys()
+  @details.keys()
+end
+
+ +
+ + + + +
+ + +
+ +
+ pending?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2501
+def pending?()
+  @pending
+end
+
+ +
+ + + + +
+ + +
+ +
+ uuid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/net_clients.rb, line 2493
+def uuid()
+  @uuid
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ.html b/doc/GxG/Networking/ZMQ.html new file mode 100644 index 0000000..dcc1db7 --- /dev/null +++ b/doc/GxG/Networking/ZMQ.html @@ -0,0 +1,187 @@ + + + + + + +module GxG::Networking::ZMQ - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Networking::ZMQ +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ zmq_default_context() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 25
+def self.zmq_default_context()
+  @@details[:zmq_context]
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_supported_protocols() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 29
+def self.zmq_supported_protocols()
+  if ::GxG::SYSTEM.platform()[:platform] == :windows
+    ["inproc", "tcp", "tcp4", "tcp6", "pgm", "epgm"]
+  else
+    ["inproc", "ipc", "tcp", "tcp4", "tcp6", "pgm", "epgm"]
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqAdapter.html b/doc/GxG/Networking/ZMQ/ZmqAdapter.html new file mode 100644 index 0000000..e199518 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqAdapter.html @@ -0,0 +1,503 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqAdapter - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqAdapter +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ close() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 101
+def close()
+  if @open == true
+    @connector.socket.close
+    @open = false
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ context() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 111
+def context()
+  if @open == true
+    @connector.context()
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ identity() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 123
+def identity()
+  if @open == true
+    @connector.socket.identity
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ listen(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 129
+def listen(&block)
+  if @open == true
+    @connector.listen(&block)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ open?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 97
+def open?()
+  @open
+end
+
+ +
+ + + + +
+ + +
+ +
+ receive(**options, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 135
+def receive(**options, &block)
+  if @open == true
+    @connector.receive(options, &block)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ send(message="", **options) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 141
+def send(message="", **options)
+  if @open == true
+    @connector.send(message.to_s,options)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ socket() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 117
+def socket()
+  if @open == true
+    @connector.socket()
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ vetted_parameters(params={}) + + click to toggle source + +
+ + +
+ +
Proposed: {:bind/connect => <URI>, :context => <Context>, :encode => <Proc>, :decode => <Proc>}
+mode (:bind, :connect) — a mode for the socket.
+Options Hash:
+ +

context (ZMQ::Context) — a context to use for this socket (one will be created if not provided). encode (lambda) — how to encode messages. decode (lambda) — how to decode messages. transport (Symbol) — default: :tcp — transport for transport. address (String) — default: '127.0.0.1' — address for endpoint. port (Fixnum) — default: 5555 — port for endpoint.

+ + + + +
+
# File lib/gxg/gxg_zmq.rb, line 47
+def vetted_parameters(params={})
+  unless params.is_a?(::Hash)
+    raise ArgumentError, "You MUST provide a Hash as parameters."
+  end
+  if params[:bind]
+    # bind
+    mode = :bind
+  else
+    # connect
+    mode = :connect
+  end
+  options = {}
+  if params[:context].is_a?(::ZMQ::Context)
+    options[:context] = params[:context]
+  end
+  uri = params[(mode)].clone
+  unless uri.port
+    uri.port = 5555
+  end
+  options[:port] = uri.port
+  unless uri.is_a?(::URI::Generic)
+    raise ArgumentError, "You MUST provide a valid URI to bind or connect to."
+  end
+  if ::GxG::Networking::ZMQ::zmq_supported_protocols().include?(uri.scheme())
+    if mode == :bind
+      if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(uri.scheme())
+        uri.resolve_host()
+        if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(uri))
+          raise Errno::EADDRINUSE, "Address and port already in use: #{uri.to_s}"
+        end
+      end
+    end
+  else
+    raise Exception, "Unsupported networking protocol: #{uri.scheme().inspect}"
+  end
+  options[:transport] = uri.scheme.to_s.to_sym
+  options[:address] = uri.hostname
+  if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(uri.scheme())
+    options[:port] = (uri.port || 5555)
+  end
+  options[:transport] = uri.scheme.to_s.to_sym
+  if params[:encode].respond_to?(:call)
+    options[:encode] = params[:encode]
+  end
+  if params[:decode].respond_to?(:call)
+    options[:decode] = params[:decode]
+  end
+  {:mode => mode, :options => options}
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqClient.html b/doc/GxG/Networking/ZMQ/ZmqClient.html new file mode 100644 index 0000000..8fcb205 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqClient.html @@ -0,0 +1,202 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqClient - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqClient +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 184
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Client.new(params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ request(message="", **options) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 192
+def request(message="", **options)
+  @connector.request(message.to_s,options)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqContext.html b/doc/GxG/Networking/ZMQ/ZmqContext.html new file mode 100644 index 0000000..00b6123 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqContext.html @@ -0,0 +1,104 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqContext - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqContext +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqPair.html b/doc/GxG/Networking/ZMQ/ZmqPair.html new file mode 100644 index 0000000..af7d5ef --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqPair.html @@ -0,0 +1,159 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqPair - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqPair +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 151
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Pair.new(params[:mode], params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqPublisher.html b/doc/GxG/Networking/ZMQ/ZmqPublisher.html new file mode 100644 index 0000000..281c75c --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqPublisher.html @@ -0,0 +1,204 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqPublisher - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqPublisher +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 209
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Publisher.new(params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ send(message="", topic='', **options) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 217
+def send(message="", topic='', **options)
+  if @open == true
+    @connector.send(message.to_s, topic.to_s,options)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqPuller.html b/doc/GxG/Networking/ZMQ/ZmqPuller.html new file mode 100644 index 0000000..3545845 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqPuller.html @@ -0,0 +1,159 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqPuller - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqPuller +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 173
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Puller.new(params[:mode], params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqPusher.html b/doc/GxG/Networking/ZMQ/ZmqPusher.html new file mode 100644 index 0000000..ac81877 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqPusher.html @@ -0,0 +1,159 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqPusher - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqPusher +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 162
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Pusher.new(params[:mode], params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqServer.html b/doc/GxG/Networking/ZMQ/ZmqServer.html new file mode 100644 index 0000000..b24d635 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqServer.html @@ -0,0 +1,159 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqServer - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqServer +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 198
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Server.new(params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZMQ/ZmqSubscriber.html b/doc/GxG/Networking/ZMQ/ZmqSubscriber.html new file mode 100644 index 0000000..a8b5173 --- /dev/null +++ b/doc/GxG/Networking/ZMQ/ZmqSubscriber.html @@ -0,0 +1,241 @@ + + + + + + +class GxG::Networking::ZMQ::ZmqSubscriber - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZMQ::ZmqSubscriber +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 226
+def initialize(params={})
+  params = self.vetted_parameters(params)
+  @connector = ::EZMQ::Subscriber.new(params[:options])
+  @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s
+  @open = true
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ subscribe(topic="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 234
+def subscribe(topic="")
+  if @open == true
+    @connector.subscribe(topic)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ unsubscribe(topic="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 240
+def unsubscribe(topic="")
+  if @open == true
+    @connector.unsubscribe(topic)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Networking/ZmqNode.html b/doc/GxG/Networking/ZmqNode.html new file mode 100644 index 0000000..4c27e3a --- /dev/null +++ b/doc/GxG/Networking/ZmqNode.html @@ -0,0 +1,1536 @@ + + + + + + +class GxG::Networking::ZmqNode - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Networking::ZmqNode +

+ +
+ +

xxx

+ +
class AbstractZMQSocket
+  # Abstract class, never instantiated
+  def self.supported_protocols()
+    if ::GxG::SYSTEM.platform()[:platform] == :windows
+      ["inproc", "tcp", "tcp4", "tcp6", "pgm", "epgm"]
+    else
+      ["inproc", "ipc", "tcp", "tcp4", "tcp6", "pgm", "epgm"]
+    end
+  end
+  #
+  def initialize(settings={})
+    # {:context => nil, :type => nil, :bind => {:thisone => "", :thatone => ""}, :connect => nil}
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    @thread_safety = ::Mutex.new
+    if settings[:context].is_a?(::ZMQ::Context)
+      @context = settings[:context]
+    else
+      @context = ::GxG::Networking::zmq_context()
+    end
+    if settings[:type].is_a?(::Numeric)
+      @socket = @context.socket(settings[:type])
+    else
+      raise ArgumentError, "you must supply a type for the socket"
+    end
+    @name = ::Celluloid::UUID.random_generate().to_sym
+    socket_id = (settings[:identity] || @name).to_s.dup
+    if socket_id.bytesize() > 255
+      socket_id.force_encoding(::Encoding::ASCII_8BIT)
+      socket_id = socket_id[(0..255)]
+    end
+    @socket.identity = socket_id
+    #
+    @recv_buffer_size = 1024
+    @send_buffer_size = 1024
+    #
+    @bindings = {}
+    if settings[:bind].is_a?(::Hash)
+      #
+      settings[:bind].keys.to_enum(:each).each do |binding_key|
+        if settings[:bind][(binding_key)].is_a?(::URI::Generic)
+          address = settings[:bind][(binding_key)]
+        else
+          address = ::URI::parse(settings[:bind][(binding_key)])
+        end
+        if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme())
+          address.resolve_host()
+          if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(address.scheme())
+            if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address))
+              raise Errno::EADDRINUSE, "Address and port already in use: #{address.to_s}"
+            end
+          end
+          begin
+            @socket.bind(address.to_s)
+            @bindings[(binding_key)] = address
+          rescue Exception
+            @socket.close
+            raise
+          end
+        else
+          raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol"
+        end
+      end
+      #
+    end
+    #
+    @connections = {}
+    if settings[:connect].is_a?(::Hash)
+      settings[:connect].keys.to_enum(:each).each do |connection_key|
+        if settings[:connect][(connection_key)].is_a?(::URI::Generic)
+          address = settings[:connect][(connection_key)]
+        else
+          address = ::URI::parse(settings[:connect][(connection_key)])
+        end
+        # address = ::URI::parse(settings[:connect][(connection_key)])
+        if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme())
+          address.resolve_host()
+          unless ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address))
+            raise Errno::EADDRNOTAVAIL, "Address and port not bound: #{address.to_s}"
+          end
+          begin
+            @socket.connect(address.to_s)
+            @connections[(connection_key)] = address
+          rescue Exception
+            @socket.close
+            raise
+          end
+        else
+          raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol"
+        end
+      end
+    end
+    #
+    @recv_buffer_size = this.recv_buffer_size()
+    @send_buffer_size = this.send_buffer_size()
+    #
+    this
+  end
+  #
+  def context()
+    @thread_safety.synchronize { @context }
+  end
+  #
+  def socket()
+    @thread_safety.synchronize { @socket }
+  end
+  #
+  def identity()
+    @socket.identity()
+  end
+  #
+  def recv_buffer_size(*args)
+    #
+    if args.size > 0
+      if args[0].is_a?(::Numeric)
+        the_size = args.first.to_i
+        if the_size > 0
+          valid_min = [16384]
+          valid_max = [65536]
+          [@bindings, @connections].to_enum(:each).each do |the_set|
+            the_set.keys.to_enum(:each).each do |the_terminus|
+              setting = ::GxG::Networking::buffer_limits(:in, (the_set[(the_terminus)].scheme).to_sym)[:valid]
+              valid_min << (setting.min)
+              valid_max << (setting.max)
+            end
+          end
+          if ((valid_min.min)..(valid_max.max)).include?(the_size)
+            @thread_safety.synchronize {
+              @recv_buffer_size = the_size
+              @socket.setsockopt(::ZMQ::RCVBUF,the_size)
+            }
+          else
+            raise Exception, "New buffer size #{the_size} is outside acceptable range: #{((valid_min.min)..(valid_max.max)).inspect}"
+          end
+        end
+      end
+    else
+      data = []
+      @thread_safety.synchronize { @socket.getsockopt(::ZMQ::RCVBUF,data) }
+      the_size = (data.first || 0).to_i
+      if the_size == 0
+        # find scheme of connection/binding : the_scheme
+        # buffer_size = (::GxG::Networking::buffer_limits(:out, the_scheme.to_sym)[:initial] || 65536)
+        buffer_sizes = [65536]
+        [@bindings, @connections].to_enum(:each).each do |the_set|
+          the_set.keys.to_enum(:each).each do |the_terminus|
+            buffer_sizes << (::GxG::Networking::buffer_limits(:in, (the_set[(the_terminus)].scheme).to_sym)[:valid].min)
+          end
+        end
+        # FORNOW: use the smallest buffer size found across all bindings and/or connections
+        @thread_safety.synchronize { @recv_buffer_size = buffer_sizes.min }
+      else
+        @thread_safety.synchronize { @recv_buffer_size = the_size }
+      end
+    end
+    @thread_safety.synchronize { @recv_buffer_size.dup }
+  end
+  #
+  def send_buffer_size(*args)
+    #
+    if args.size > 0
+      if args[0].is_a?(::Numeric)
+        the_size = args.first.to_i
+        if the_size > 0
+          valid_min = [16384]
+          valid_max = [65536]
+          [@bindings, @connections].to_enum(:each).each do |the_set|
+            the_set.keys.to_enum(:each).each do |the_terminus|
+              setting = ::GxG::Networking::buffer_limits(:out, (the_set[(the_terminus)].scheme).to_sym)[:valid]
+              valid_min << (setting.min)
+              valid_max << (setting.max)
+            end
+          end
+          if ((valid_min.min)..(valid_max.max)).include?(the_size)
+            @thread_safety.synchronize {
+              @send_buffer_size = the_size
+              @socket.setsockopt(::ZMQ::SNDBUF,the_size)
+            }
+          else
+            raise Exception, "New buffer size #{the_size} is outside acceptable range: #{((valid_min.min)..(valid_max.max)).inspect}"
+          end
+        end
+      end
+    else
+      data = []
+      @thread_safety.synchronize { @socket.getsockopt(::ZMQ::SNDBUF,data) }
+      the_size = (data.first || 0).to_i
+      if the_size == 0
+        # find scheme of connection/binding : the_scheme
+        # buffer_size = (::GxG::Networking::buffer_limits(:out, the_scheme.to_sym)[:initial] || 65536)
+        buffer_sizes = [65536]
+        [@bindings, @connections].to_enum(:each).each do |the_set|
+          the_set.keys.to_enum(:each).each do |the_terminus|
+            buffer_sizes << (::GxG::Networking::buffer_limits(:out, (the_set[(the_terminus)].scheme).to_sym)[:valid].min)
+          end
+        end
+        # FORNOW: use the smallest buffer size found across all bindings and/or connections
+        @thread_safety.synchronize { @send_buffer_size = buffer_sizes.min }
+      else
+        @thread_safety.synchronize { @send_buffer_size = the_size }
+      end
+    end
+    @thread_safety.synchronize { @send_buffer_size.dup }
+  end
+  #
+  def binding(binding_key=nil)
+    if binding_key
+      @thread_safety.synchronize { @bindings[(binding_key)] }
+    else
+      @thread_safety.synchronize { @bindings }
+    end
+  end
+  #
+  def bind(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of keyed bind point(s) for the binding"
+    end
+    result = false
+    settings.keys.to_enum(:each).each do |binding_key|
+      if settings[(binding_key)].is_a?(::URI::Generic)
+        address = settings[(binding_key)]
+      else
+        address = ::URI::parse(settings[(binding_key)])
+      end
+      # address = ::URI::parse(settings[(binding_key)])
+      if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme())
+        address.resolve_host()
+        if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(address.scheme())
+          if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address))
+            raise Errno::EADDRINUSE, "Address and port already in use: #{address.to_s}"
+          end
+        end
+        begin
+          @thread_safety.synchronize {
+            @socket.bind(address.to_s)
+            @bindings[(binding_key)] = address
+          }
+          result = true
+        rescue Exception
+          @thread_safety.synchronize { @socket.close }
+          raise
+        end
+      else
+        raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol"
+      end
+    end
+    @recv_buffer_size = this.recv_buffer_size()
+    @send_buffer_size = this.send_buffer_size()
+    #
+    result
+  end
+  #
+  def connection(connection_key=nil)
+    if connection_key
+      @thread_safety.synchronize { @connections[(connection_key)] }
+    else
+      @thread_safety.synchronize { @connections }
+    end
+  end
+  #
+  def connect(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of keyed address(es) for the connection"
+    end
+    result = false
+    settings.keys.to_enum(:each).each do |connection_key|
+      if settings[(connection_key)].is_a?(::URI::Generic)
+        address = settings[(connection_key)]
+      else
+        address = ::URI::parse(settings[(connection_key)])
+      end
+      # address = ::URI::parse(settings[(connection_key)])
+      if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme())
+        address.resolve_host()
+        if ["tcp", "tcp4", "tcp6"].include?(address.scheme())
+          unless ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address))
+            raise Errno::EADDRNOTAVAIL, "Address and port not bound: #{address.to_s}"
+          end
+        end
+        begin
+          @thread_safety.synchronize {
+            @socket.connect(address.to_s)
+            @connections[(connection_key)] = address
+          }
+          result = true
+        rescue Exception
+          @thread_safety.synchronize { @socket.close() }
+          raise
+        end
+      else
+        raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol"
+      end
+    end
+    @recv_buffer_size = this.recv_buffer_size()
+    @send_buffer_size = this.send_buffer_size()
+    #
+    result
+  end
+  #
+  def close()
+    @thread_safety.synchronize { @socket.close() }
+  end
+  #
+  def triage_strings(data=[])
+    # takes raw output from @socket.recv_strings([]) and separates routing from content
+    result = {:route => [], :content => []}
+    prior_element = nil
+    data.to_enum(:each).each do |the_element|
+      # Empty strings terminate routes -- Do NOT use in payload as parse element
+      if the_element.to_s.size > 0
+        if prior_element.to_s.size > 0
+          result[:content] << prior_element
+        end
+        prior_element = the_element
+      else
+        if prior_element.to_s.size > 0
+          result[:route] << prior_element
+        end
+        prior_element = nil
+      end
+    end
+    if prior_element.to_s.size > 0
+      result[:content] << prior_element
+    end
+    result
+  end
+  #
+  def recv(options={:autojoin => true})
+    unless options.is_a?(::Hash)
+      options = {:autojoin => true}
+    end
+    payload = []
+    # @thread_safety.lock
+    status = @socket.recv_strings(payload,0)
+    if ::ZMQ::Util.resultcode_ok?(status)
+      data = this.triage_strings(payload)
+      if (data[:route].size > 0 || data[:content].size > 0)
+        if options[:autojoin]
+          ::GxG::Events::Message.new({:sender => this, :body => data[:content].join(), :route => data[:route]})
+        else
+          ::GxG::Events::Message.new({:sender => this, :body => data[:content], :route => data[:route]})
+        end
+      else
+        nil
+      end
+    else
+      begin
+        raise Exception, ("ZMQ - " + ::ZMQ::Util.error_string())
+      rescue => the_error
+        log_error({:error => the_error})
+      end
+      nil
+    end
+    # @thread_safety.unlock
+  end
+  #
+  def send(data=nil, route=[])
+    if data
+      # FORNOW: use the smallest buffer size found across all bindings and/or connections
+      buffer_size = @thread_safety.synchronize { @send_buffer_size }
+      payload = []
+      messages = []
+      unless route.is_a?(::Array)
+        route = [(route)]
+      end
+      #
+      if data.is_a?(::GxG::Events::Message)
+        if data.body().is_a?(::String)
+          data = [(data.body())]
+        else
+          data = [(data.body().serialize())]
+        end
+      end
+      if data.is_a?(::String)
+        data = [(data)]
+      end
+      #
+      if route.is_a?(::Array)
+        route.flatten.to_enum(:each).each do |the_route|
+          if the_route.is_a?(::String)
+            payload << the_route.slice_bytes(0..255)
+            payload << ""
+          end
+        end
+      end
+      payload << @socket.identity()
+      payload << ""
+      #
+      if data.is_a?(::Array)
+        data.flatten.to_enum(:each).each do |the_element|
+          if the_element.is_a?(::String)
+            if the_element.size > 0
+              # Disallow parsing payload with empty strings : used to parse routes
+              # Break into appropriate buffer size as needed : respect buffer sizes
+              if the_element.bytesize > buffer_size
+                length = the_element.bytesize()
+                passes = ::GxG::Networking::passes_needed(length, buffer_size)
+                total = (length - 1)
+                starting = 0
+                ending = ([length,buffer_size].min - 1)
+                passes.times do
+                  messages << the_element.slice_bytes(starting..ending)
+                  #
+                  starting = ending + 1
+                  ending = (starting + (buffer_size - 1))
+                  if ending > total
+                    ending = ending - (ending - total)
+                  end
+                end
+                #
+              else
+                messages << the_element
+              end
+            end
+          end
+        end
+        if messages.size > 0
+          payload << messages
+          payload.flatten!
+          status = @thread_safety.synchronize { @socket.send_strings(payload) }
+          if status == -1
+            begin
+              raise Exception, "ZMQ - Message could not be enqueued."
+            rescue => the_error
+              log_error({:error => the_error, :parameters => {:data => data, :route => route}})
+            end
+          end
+        end
+      end
+      #
+    end
+  end
+  #
+end
+
+class ZMQRouter < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    unless settings[:context].is_a?(::ZMQ::Context)
+      settings[:context] = ::GxG::Networking::zmq_context()
+    end
+    settings[:type] = ::ZMQ::ROUTER
+    bindings = (settings.delete(:bind) || {})
+    super(settings)
+    unless bindings.keys.size > 0
+      bindings[:inproc] = ::URI::parse("inproc://#{@name}")
+    end
+    this.bind(bindings)
+    #
+    this
+  end
+  #
+end
+
+class ZMQDealer < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::DEALER
+    super(settings)
+    #
+    this
+  end
+  #
+end
+
+class ZMQRequest < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::REQ
+    super(settings)
+    #
+    this
+  end
+  #
+end
+
+class ZMQReply < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::REP
+    super(settings)
+    #
+    this
+  end
+  #
+end
+
+class ZMQPuller < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::PULL
+    super(settings)
+    #
+    this
+  end
+  #
+  def send(*args)
+    # no-op
+  end
+  #
+end
+
+class ZMQPusher < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::PUSH
+    super(settings)
+    #
+    this
+  end
+  #
+  def recv()
+    nil
+  end
+  #
+end
+
+class ZMQPair < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::PAIR
+    super(settings)
+    #
+    this
+  end
+  #
+end
+
+class ZMQSubscriber < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::SUB
+    super(settings)
+    @topics = []
+    #
+    this
+  end
+  #
+  def topics()
+    @thread_safety.synchronize { @topics.dup }
+  end
+  #
+  def subscribe_topic(the_topic=nil)
+    the_topic = the_topic.to_s
+    if the_topic.bytesize > 255
+      the_topic = the_topic.slice_bytes(0..255).to_s
+    end
+    if the_topic.bytesize > 0
+      @thread_safety.synchronize {
+        unless @topics.include?(the_topic)
+          @topics << the_topic
+          @socket.setsockopt(::ZMQ::SUBSCRIBE,the_topic)
+        end
+      }
+    end
+  end
+  #
+  def unsubscribe_topic(the_topic=nil)
+    the_topic = the_topic.to_s
+    if the_topic.bytesize > 255
+      the_topic = the_topic.slice_bytes(0..255).to_s
+    end
+    if the_topic.bytesize > 0
+      @thread_safety.synchronize {
+        if @topics.include?(the_topic)
+          @topics.delete(the_topic)
+          @socket.setsockopt(::ZMQ::UNSUBSCRIBE,the_topic)
+        end
+      }
+    end
+  end
+  #
+  def send(*args)
+    # no-op
+  end
+  #
+end
+
+class ZMQPublisher < ::GxG::Networking::AbstractZMQSocket
+  #
+  def initialize(settings={})
+    #
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "you must supply a hash of settings for the socket"
+    end
+    settings[:type] = ::ZMQ::PUB
+    bindings = (settings.delete(:bind) || {})
+    super(settings)
+    unless bindings.keys.size > 0
+      bindings[:inproc] = ::URI::parse("inproc://#{@name}")
+    end
+    this.bind(bindings)
+    #
+    this
+  end
+  #
+  def recv()
+    nil
+  end
+  #
+  def publish(the_data=nil,the_topic=nil)
+    if the_data
+      the_topic = the_topic.to_s
+      if the_topic.bytesize > 255
+        the_topic = the_topic.slice_bytes(0..255).to_s
+      end
+      if the_topic.bytesize > 0
+        this.send(the_data,[(the_topic)])
+      else
+        this.send(the_data)
+      end
+    end
+  end
+end
+
+class ZMQ_RequestReplyBroker < ::GxG::Events::Actor
+  # class ZMQ_RequestReplyBroker is a derivative work based upon: http://zguide.zeromq.org/rb:rrbroker Copyright (c) 2013 iMatix Corporation
+  # accordingly, class ZMQ_RequestReplyBroker is licensed under this license: http://creativecommons.org/licenses/by-sa/3.0/legalcode
+  def initialize(settings={})
+    unless settings.is_a?(::Hash)
+      raise ArgumentError, "You must provide a hash of settings for the Broker."
+    end
+    unless settings[:frontend].is_a?(::URI::Generic)
+      raise ArgumentError, "You must provide a URI as :frontend."
+    end
+    unless settings[:backend].is_a?(::URI::Generic)
+      raise ArgumentError, "You must provide a URI as :backend."
+    end
+    super()
+    #
+    @context = ::GxG::Networking::zmq_context()
+    @frontend = ::GxG::Networking::ZMQRouter.new({:context => @context, :bind => {:frontend => settings[:frontend]}})
+    @backend = ::GxG::Networking::ZMQDealer.new({:context => @context, :bind => {:backend => settings[:backend]}})
+    # @queue = ::ZMQ::Device.new(::ZMQ::QUEUE, @frontend.socket(), @backend.socket())
+    @queue = ::ZMQ::Poller.new
+    @queue.register(@frontend.socket(), ::ZMQ::POLLIN)
+    @queue.register(@backend.socket(), ::ZMQ::POLLIN)
+    #
+    @circulating = false
+    @is_circulating = false
+    @circulate_safety = ::Mutex.new
+    #
+    this.resume_circulating()
+    ::Celluloid::current_actor()
+  end
+  #
+  def terminate()
+    this.halt_circulating()
+    @queue.deregister(@frontend.socket())
+    @queue.deregister(@backend.socket())
+    @frontend.close()
+    @backend.close()
+    #
+    super()
+  end
+  #
+  def circulating?()
+    @circulate_safety.synchronize { @circulating }
+  end
+  #
+  def halt_circulating()
+    @circulate_safety.synchronize { @circulating = false }
+  end
+  #
+  def resume_circulating()
+    unless this.circulating?()
+      @circulate_safety.synchronize { @circulating = true }
+      this.async.circulate
+    end
+  end
+  #
+  def circulate()
+    # Circulate Data as required
+    if this.alive?()
+      front_socket = @frontend.socket()
+      front_id = @frontend.identity()
+      back_socket = @backend.socket()
+      back_id = @frontend.identity()
+      busy = @circulate_safety.synchronize { @is_circulating }
+      while (this.circulating?() && !busy) do
+        #
+        @circulate_safety.synchronize { @is_circulating = true }
+        @circulate_safety.lock
+        #
+        @queue.poll(:blocking)
+        @queue.readables.each do |the_socket|
+          if the_socket === front_socket
+            data = @frontend.triage_strings(the_socket.recv_strings([]))
+            payload = []
+            if (data[:route].size > 0 || data[:content].size > 0)
+              data[:route].to_enum(:each).each do |the_route|
+                payload << the_route
+                payload << ""
+              end
+              payload << back_id
+              payload << ""
+              payload << data[:content]
+              payload.flatten!
+              if payload.size > 0
+                back_socket.send_strings(payload)
+              end
+            end
+          end
+          if the_socket === back_socket
+            data = @backend.triage_strings(the_socket.recv_strings([]))
+            payload = []
+            if (data[:route].size > 0 || data[:content].size > 0)
+              data[:route].to_enum(:each).each do |the_route|
+                payload << the_route
+                payload << ""
+              end
+              payload << front_id
+              payload << ""
+              payload << data[:content]
+              payload.flatten!
+              if payload.size > 0
+                front_socket.send_strings(payload)
+              end
+            end
+          end
+        end
+        #
+        @circulate_safety.unlock
+        @circulate_safety.synchronize { @is_circulating = false }
+        busy = @circulate_safety.synchronize { @is_circulating }
+        #
+        if this.alive?()
+          if this.circulating?()
+            sleep 0.033
+          end
+        end
+        #
+      end
+    end
+  end
+  #
+end
+
+ +

Deprecated: completely rethink this.

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1026
+def initialize(*args)
+  #
+  @zmq_thread_safety = ::Mutex.new
+  @zmq_processing = false
+  @zmq_is_processing = false
+  @zmq_processing_handler = nil
+  @zmq_processing_error_handler = nil
+  @zmq_inputs = {}
+  @zmq_outputs = {}
+  #
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ add_zmq_input(the_key=nil, the_socket=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1062
+def add_zmq_input(the_key=nil, the_socket=nil)
+  valid = [::GxG::Networking::ZMQPair, ::GxG::Networking::ZMQPuller, ::GxG::Networking::ZMQSubscriber, ::GxG::Networking::ZMQReply, ::GxG::Networking::ZMQRequest]
+  #
+  unless the_key.is_a?(::Symbol)
+    raise ArgumentError, "you need so supply a unique symbol key for the socket"
+  end
+  if (@zmq_thread_safety.synchronize { @zmq_inputs[(the_key)] })
+    raise ArgumentError, "inputs already exists, you need so supply a unique symbol key for the socket"
+  end
+  unless the_socket.is_any?(valid)
+    raise ArgumentError, "you supplied a #{the_socket.class.inspect}, you need so supply one of these as socket type: #{valid.inspect}"
+  end
+  @zmq_thread_safety.synchronize { @zmq_inputs[(the_key)] = the_socket }
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ add_zmq_output(the_key=nil, the_socket=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1100
+def add_zmq_output(the_key=nil, the_socket=nil)
+  valid = [::GxG::Networking::ZMQPair, ::GxG::Networking::ZMQPusher, ::GxG::Networking::ZMQPublisher, ::GxG::Networking::ZMQReply, ::GxG::Networking::ZMQRequest]
+  #
+  unless the_key.is_a?(::Symbol)
+    raise ArgumentError, "you need so supply a unique symbol key for the socket"
+  end
+  if (@zmq_thread_safety.synchronize { @zmq_outputs[(the_key)] })
+    raise ArgumentError, "inputs already exists, you need so supply a unique symbol key for the socket"
+  end
+  unless the_socket.is_any?(valid)
+    raise ArgumentError, "you supplied a #{the_socket.class.inspect}, you need so supply one of these as socket type: #{valid.inspect}"
+  end
+  @zmq_thread_safety.synchronize { @zmq_outputs[(the_key)] = the_socket }
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ halt_zmq_processing() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1167
+def halt_zmq_processing()
+  @zmq_thread_safety.synchronize { @zmq_processing = false;@zmq_is_processing = false }
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_zmq_input(the_key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1078
+def remove_zmq_input(the_key=nil)
+  #
+  result = false
+  the_inputs = this.zmq_inputs()
+  if the_inputs[(the_key)]
+    @zmq_thread_safety.synchronize { @zmq_inputs.delete(the_key) }
+    result = true
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ remove_zmq_output(the_key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1116
+def remove_zmq_output(the_key=nil)
+  #
+  result = false
+  the_outputs = this.zmq_outputs()
+  if the_outputs[(the_key)]
+    @zmq_thread_safety.synchronize { @zmq_outputs.delete(the_key) }
+    result = true
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ resume_zmq_processing() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1171
+def resume_zmq_processing()
+  unless this.processing?()
+    @zmq_thread_safety.synchronize { @zmq_processing = true }
+    this.async.zmq_process
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ terminate() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_zmq.rb, line 1039
+def terminate()
+  # Halt any zmq processing first
+  this.halt_zmq_processing()
+  # Remove any references to input and output sockets
+  # Question: should I close all input/output sockets here???
+  @zmq_thread_safety.synchronize {
+    @zmq_inputs = {}
+    @zmq_outputs = {}
+  }
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_error_handler(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1145
+def zmq_error_handler(params={})
+  result = {:result => false}
+  begin
+    unless params.is_a?(:Hash)
+      raise ArgumentError, "you must provide a Hash as parameter container"
+    end
+    if params[:block].respond_to(:call)
+      @zmq_thread_safety.synchronize { @zmq_processing_error_handler = params[:block] }
+      result[:result] = true
+    else
+      raise ArgumentError, "you must provide something that responds to :call with the :block parameter"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => params})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_handler(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1127
+def zmq_handler(params={})
+  result = {:result => false}
+  begin
+    unless params.is_a?(:Hash)
+      raise ArgumentError, "you must provide a Hash as parameter container"
+    end
+    if params[:block].respond_to(:call)
+      @zmq_thread_safety.synchronize { @zmq_processing_handler = params[:block] }
+      result[:result] = true
+    else
+      raise ArgumentError, "you must provide something that responds to :call with the :block parameter"
+    end
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => params})
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_inputs() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1051
+def zmq_inputs()
+  result = {}
+  @zmq_thread_safety.synchronize {
+    @zmq_inputs.keys.to_enum(:each).each do |the_key|
+      result[(the_key)] = @zmq_inputs[(the_key)]
+    end
+    #
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_outputs() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1089
+def zmq_outputs()
+  result = {}
+  @zmq_thread_safety.synchronize {
+    @zmq_outputs.keys.to_enum(:each).each do |the_key|
+      result[(the_key)] = @zmq_outputs[(the_key)]
+    end
+    #
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_process() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1178
+def zmq_process()
+  # Process items on the zmq inputs and potentially send things via outputs
+  while (this.zmq_processing?() && !(@zmq_thread_safety.synchronize { @zmq_is_processing })) do
+    #
+    @zmq_thread_safety.synchronize { @zmq_is_processing = true }
+    # Process event item
+    begin
+      my_handler = @zmq_thread_safety.synchronize { @zmq_processing_error_handler }
+      begin
+        processor = @zmq_thread_safety.synchronize { @zmq_processing_handler }
+        if processor.respond_to?(:call)
+          processor.call(this, this.zmq_inputs(), this.zmq_outputs())
+        end
+      rescue Exception => the_error
+        # TODO: Revise logging/error-handling system generally, after display manager construction
+        this.halt_zmq_processing()
+        if my_handler.respond_to?(:call)
+          # error handler provided
+          my_handler.call({:error => the_error})
+        else
+          # log error and forget about it
+          log_error({:error => the_error})
+        end
+      end
+    rescue Exception => error_handling
+      log_error({:error => error_handling})
+    end
+    #
+    @zmq_thread_safety.synchronize { @zmq_is_processing = false }
+    if this.alive?()
+      if this.zmq_processing?()
+        sleep 0.033
+      end
+    end
+    #
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ zmq_processing?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_zmq.rb, line 1163
+def zmq_processing?()
+  @zmq_thread_safety.synchronize { @zmq_processing }
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/ReturnResponse.html b/doc/GxG/ReturnResponse.html new file mode 100644 index 0000000..2923e6c --- /dev/null +++ b/doc/GxG/ReturnResponse.html @@ -0,0 +1,411 @@ + + + + + + +class GxG::ReturnResponse - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::ReturnResponse +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(response={}) + + click to toggle source + +
+ + +
+ +

this class allows for characterized muli-part return values for Procs and Methods this was created for a variety of reasons. Hope this isn't a dead end.

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 364
+def initialize(response={})
+  unless response.is_a?(::Hash)
+    raise ArgumentError.new("Response must be passed as a Hash")
+  end
+  @details = response
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ debug() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 385
+def debug()
+  @details[:debug]
+end
+
+ +
+ + + + +
+ + +
+ +
+ error() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 376
+def error()
+  @details[:error]
+end
+
+ +
+ + + + +
+ + +
+ +
+ info() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 382
+def info()
+  @details[:info]
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 388
+def inspect()
+  @details.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ new_selector() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 370
+def new_selector()
+  @details[:new_selector]
+end
+
+ +
+ + + + +
+ + +
+ +
+ result() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 373
+def result()
+  @details[:result]
+end
+
+ +
+ + + + +
+ + +
+ +
+ warning() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 379
+def warning()
+  @details[:warning]
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Storage.html b/doc/GxG/Storage.html new file mode 100644 index 0000000..2d9da50 --- /dev/null +++ b/doc/GxG/Storage.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Storage - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Storage +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Storage/FileSpace.html b/doc/GxG/Storage/FileSpace.html new file mode 100644 index 0000000..3fbb90d --- /dev/null +++ b/doc/GxG/Storage/FileSpace.html @@ -0,0 +1,1894 @@ + + + + + + +class GxG::Storage::FileSpace - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Storage::FileSpace +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 6
+def initialize()
+  @thread_safety = ::Mutex.new
+  @mounted = []
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ copy(source_path=nil,destination_path=nil, options={}) + + click to toggle source + +
+ + +
+ +

Copying support:

+ + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 483
+def copy(source_path=nil,destination_path=nil, options={})
+  # Four scenarios: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS
+  # Objective: handle both file/object and directory/persisted_array in one method!
+  # Does OVERWRITE copy onto existing files/folders of same name.
+  result = false
+  error = false
+  if source_path == destination_path
+    error = true
+  end
+  #
+  source = {:enclosure => nil, :profile => nil, :record => nil, :permissions => nil}
+  if self.valid_path?(source_path.to_s)
+    if source_path.to_s == "/"
+      source[:record] = nil
+    else
+      source[:record] = self.volume_of_path(source_path.to_s)
+    end
+    if source[:record]
+      source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true}))
+      source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true}))
+      source[:permissions] = self.get_permissions(source_path)
+    else
+      error = true
+      log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  else
+    error = true
+    log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+  end
+  # Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path
+  if File.basename(source_path) != File.basename(destination_path)
+    destination_path = File.expand_path(destination_path + "/" + File.basename(source_path))
+  end
+  #
+  destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil, :permissions => source[:permissions].clone}
+  if self.valid_path?(destination_path.to_s)
+    if destination_path.to_s == "/"
+      destination[:record] = nil
+    else
+      destination[:record] = self.volume_of_path(destination_path.to_s)
+    end
+    if destination[:record]
+      destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true}))
+    else
+      error = true
+      log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  else
+    if destination[:enclosure].is_a?(::Hash)
+      unless destination[:enclosure][:permissions][:effective][:create] == true
+        error = true
+        log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+      end
+      unless error == true
+        destination[:record] = self.volume_of_path(File.dirname(destination_path))
+        if destination[:record].is_a?(::Hash)
+          # Note: this won't exist yet, but is here for future reference.
+          destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path))
+        end
+      end
+    else
+      error = true
+      log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  end
+  unless error == true
+    if source[:profile]
+      # determine permissions
+      if source[:profile][:permissions][:effective][:read] == true
+        # Does destination already exist? --> overwrite permissions?
+        if destination[:profile]
+          unless destination[:profile][:permissions][:effective][:write] == true
+            error = true
+            log_error({:error => Exception.new("You do not have sufficient permissions at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})      
+          end
+        end
+        unless error == true
+          # determine copying scenario: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS
+          if source[:record][:volume].file_system?()
+            if destination[:enclosure][:uuid].to_s.size > 0
+              scenario = :FS_to_DB
+            else
+              scenario = :FS_to_FS
+            end
+          else
+            if destination[:enclosure][:uuid].to_s.size > 0
+              scenario = :DB_to_DB
+            else
+              scenario = :DB_to_FS
+            end
+          end
+          # create source/destination path pairings:
+          file_list = []
+          dir_list = []
+          if ["persisted_array","directory","virtual_directory"].include?(source[:profile][:type].to_s)
+            # directory / collection
+            search_queue = [{:source => (source_path), :destination => (destination_path)}]
+            while search_queue.size > 0 do
+              entry = search_queue.shift
+              if entry
+                self.entries(entry[:source]).each do |the_profile|
+                  new_record = {:source => (entry[:source] + "/" + the_profile[:title]), :destination => (entry[:destination] + "/" + the_profile[:title]), :mode => the_profile[:mode], :mime => the_profile[:mime]}
+                  if ["persisted_array","directory","virtual_directory"].include?(the_profile[:type].to_s)
+                    dir_list << new_record
+                    search_queue << new_record
+                  else
+                    file_list << new_record
+                  end
+                end
+              end
+            end
+          else
+            # single object
+            file_list << {:source => (source_path), :destination => (destination_path)}
+          end
+          # lay down dir structure
+          dir_list.each do |the_record|
+            self.mkpath(the_record[:destination])
+          end
+          #
+          case scenario
+          when :FS_to_FS
+            # copy files one at a time.
+            file_list.each do |the_record|
+              if self.exist?(the_record[:destination])
+                self.rmfile(the_record[:destination])
+              end
+              # FIXME: find a way to preserve :mode (via REAL FS path) on new file.
+              begin
+                source_file = self.open(the_record[:source])
+                destination_file = self.mkfile(the_record[:destination])
+                if source_file && destination_file
+                  GxG::apportioned_ranges(source_file.size,65536).each do |the_range|
+                    source_file.seek(the_range.first)
+                    destination_file.write(GxG::ByteArray.new(source_file.read(the_range.size)).to_s)
+                  end
+                  destination_file.close
+                  source_file.close
+                else
+                  raise Exception, "Error opening source or destination files."
+                end
+              rescue Exception => the_error
+                log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})      
+                error = true
+                break
+              end
+            end
+            unless error == true
+              destination[:permissions].each do |the_entry|
+                self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
+              end
+              result = true
+            end
+          when :FS_to_DB
+            file_list.each do |the_record|
+              begin
+                # Question: should I support an object import format from a text file ??
+                if File.extname(the_record[:source]) == ".gxg_export" && (the_record[:mime] == "application/octet-stream" || the_record[:mime] == "application/json")
+                  source_file = self.open(the_record[:source])
+                  #
+                  import_record = source_file.read()
+                  source_file.close
+                  if import_record.to_s.json?
+                    import_record = ::JSON::parse(import_record,{:symbolize_names => true})
+                  end
+                  if import_record.is_a?(::Hash)
+                    database = nil
+                    if destination[:record].is_a?(::Hash)
+                      if destination[:record][:volume].is_a?(GxG::Storage::Volume)
+                        database = destination[:record][:volume].database()
+                      end
+                    end
+                    if database
+                      enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym
+                      destination_uuid = import_record[:record][:uuid].to_s.to_sym
+                      # Import Formats first
+                      if import_record[:formats].is_a?(::Hash)
+                        op_frame = {:operation => :merge_format, :data => []}
+                        import_record[:formats].keys.each do |the_format_key|
+                          op_frame[:data] << import_record[:formats][(the_format_key)]
+                        end
+                        if op_frame[:data].size > 0
+                          database.synchronize_records([(op_frame)],destination[:record][:volume].credential())
+                        end
+                      end
+                      #
+                      database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential())
+                      #
+                      unless self.exist?(the_record[:destination])
+                        the_enclosure = database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential())
+                        the_enclosure.wait_for_reservation
+                        destination_object = database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential())
+                        the_enclosure << destination_object
+                        destination_object.deactivate
+                        the_enclosure.release_reservation
+                        the_enclosure.deactivate
+                      end
+                      #
+                    else
+                      raise Exception, "Error aquiring destination database."
+                    end
+                  else
+                    raise Exception, "Error importing source file."
+                  end
+                else
+                  # Import the FS file into the DB as a org.gxg.file object
+                  source_file = self.open(the_record[:source])
+                  destination_object = self.open(the_record[:destination],{:format => "org.gxg.file"})
+                  if source_file && destination_object
+                    destination_object.wait_for_reservation
+                    destination_object[:mime] = the_record[:mime]
+                    if destination_object[:file_segments].size > 0
+                      (0..(destination_object[:file_segments].size - 1)).to_a.reverse.each do |the_segment_index|
+                        destination_object[:file_segments].delete_at(the_segment_index).destroy
+                      end
+                    end
+                    GxG::apportioned_ranges(source_file.size,65536).each do |the_range|
+                      source_file.seek(the_range.first)
+                      destination_object[:file_segments] << GxG::ByteArray.new(source_file.read(the_range.size))
+                      destination_object[:file_segments].save
+                      destination_object[:file_segments].unload(destination_object[:file_segments].size - 1)
+                    end
+                    destination_object.release_reservation
+                    destination_object.deactivate
+                    source_file.close
+                  else
+                    if source_file
+                      source_file.close
+                      raise Exception, "Error opening destination object."
+                    else
+                      raise Exception, "Error opening source file & destination object."
+                    end
+                  end
+                end
+              rescue Exception => the_error
+                log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})      
+                error = true
+                break
+              end
+            end
+            unless error == true
+              destination[:permissions].each do |the_entry|
+                self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
+              end
+              result = true
+            end
+          when :DB_to_FS
+            file_list.each do |the_record|
+              begin
+                # Unless the object format is org.gxg.file --> export as .gxg_export object (formats + record)
+                source_object = self.open(the_record[:source])
+                database = source_object.db_address[:database]
+                # check if it is an org.gxg.file for export
+                current_format = nil
+                if source_object.format().to_s.size > 0
+                  the_format = database.format_load({:uuid => source_object.format().to_s.to_sym})
+                  if the_format[:ufs].to_s == "org.gxg.file"
+                    current_format = the_format[:ufs].to_s
+                  end
+                end
+                if current_format.to_s == "org.gxg.file"
+                  destination_file = self.open(the_record[:destination])
+                  source_object[:file_segments].each_with_index do |the_segment, segment_index|
+                    destination_file.write(the_segment.to_s)
+                    source_object[:file_segments].unload(segment_index)
+                    # Field no longer exists: the_segment.unload
+                  end
+                  source_object.release_reservation
+                  source_object.deactivate
+                  destination_file.close
+                else
+                  # Review: replace with <db>.sync_export(<uuid-array>).to_json
+                  format_records = {}
+                  object_record = source_object.export
+                  object_record.search do |item,selector,container|
+                      if selector == :format || selector == :constraint
+                          if item.to_s.size > 0
+                              format_uuid = item
+                              unless format_records[(item.to_s.to_sym)].is_a?(::Hash)
+                                  format_sample = database.format_load({:uuid => format_uuid.to_s.to_sym})
+                                  format_sample[:content] = format_sample[:content].gxg_export()
+                                  format_records[(format_uuid.to_s.to_sym)] = format_sample
+                              end
+                          end
+                      end
+                  end
+                  buffer = GxG::ByteArray.new({:formats => format_records, :record => object_record}.to_json)
+                  #
+                  unless File.extname(the_record[:destination]) == ".gxg_export"
+                    the_record[:destination] = (File.dirname(the_record[:destination]) + "/" + File.basename(the_record[:destination]) + ".gxg_export")
+                  end
+                  if self.exist?(the_record[:destination])
+                    self.rmfile(the_record[:destination])
+                  end
+                  destination_file = self.open(the_record[:destination])
+                  destination_file.write(buffer.to_s)
+                  destination_file.close
+                end
+                #
+              rescue Exception => the_error
+                log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})      
+                error = true
+                break
+              end
+            end
+            unless error == true
+              destination[:permissions].each do |the_entry|
+                self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
+              end
+              result = true
+            end
+          when :DB_to_DB
+            source_database = source[:record][:volume].database()
+            destination_database = destination[:record][:volume].database()
+            if source_database && destination_database
+              file_list.each do |the_record|
+                source_object = self.open(the_record[:source])
+                current_format = nil
+                if source_object.format().to_s.size > 0
+                  the_format = source_database.format_load({:uuid => source_object.format().to_s.to_sym})
+                  if the_format[:ufs].to_s == "org.gxg.file"
+                    current_format = the_format[:ufs].to_s
+                  end
+                end
+                #
+                if source_database == destination_database
+                  if current_format.to_s == "org.gxg.file"
+                    import_record = {:formats => {}, :record => source_object.export({:clone => true, :exclude_file_segments => true})}
+                  else
+                    import_record = {:formats => {}, :record => source_object.export({:clone => true})}
+                  end
+                else
+                  if current_format.to_s == "org.gxg.file"
+                    import_record = {:formats => {}, :record => source_object.export({:exclude_file_segments => true})}
+                  else
+                    import_record = {:formats => {}, :record => source_object.export()}
+                  end
+                end
+                # Gather format records for the object.
+                import_record.search do |item,selector,container|
+                    if selector == :format || selector == :constraint
+                        if item.to_s.size > 0
+                            format_uuid = item
+                            unless format_records[(item.to_s.to_sym)].is_a?(::Hash)
+                                format_sample = source_database.format_load({:uuid => format_uuid.to_s.to_sym})
+                                format_sample[:content] = format_sample[:content].gxg_export()
+                                format_records[(format_uuid.to_s.to_sym)] = format_sample
+                            end
+                        end
+                    end
+                end
+                #
+                enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym
+                destination_uuid = import_record[:record][:uuid].to_s.to_sym
+                if source_database != destination_database
+                  # Import Formats first
+                  if import_record[:formats].is_a?(::Hash)
+                    op_frame = {:operation => :merge_format, :data => []}
+                    import_record[:formats].keys.each do |the_format_key|
+                      op_frame[:data] << import_record[:formats][(the_format_key)]
+                    end
+                    if op_frame[:data].size > 0
+                      destination_database.synchronize_records([(op_frame)],destination[:record][:volume].credential())
+                    end
+                  end
+                  #
+                end
+                #
+                if self.exist?(the_record[:destination])
+                  # Why? Because an object of the same NAME, but differing format/content may be present (unacceptable)
+                  self.rmfile(the_record[:destination])
+                  # Risky, but avoids situation where: diff.db, same.uuid, gets deleted, then imported to deleted object (doh!)
+                  destination_database.empty_trash
+                end
+                #
+                destination_database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential())
+                #
+                unless self.exist?(the_record[:destination])
+                  the_enclosure = destination_database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential())
+                  the_enclosure.wait_for_reservation
+                  destination_object = destination_database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential())
+                  if current_format.to_s == "org.gxg.file"
+                    # copy file segments
+                    destination_object.wait_for_reservation
+                    source_object[:file_segments].each_with_index do |the_segment, segment_index|
+                      destination_object[:file_segments] << ByteArray.new(the_segment.to_s)
+                      destination_object[:file_segments].save
+                      destination_object[:file_segments].unload(destination_object[:file_segments].size - 1)
+                      # destination_object[:file_segments][(destination_object[:file_segments].size - 1)].save
+                      # destination_object[:file_segments][(destination_object[:file_segments].size - 1)].unload
+                      source_object[:file_segments].unload(segment_index)
+                      # Field no longer exists : the_segment.unload
+                    end
+                    destination_object.release_reservation
+                  end
+                  the_enclosure << destination_object
+                  destination_object.deactivate
+                  the_enclosure.release_reservation
+                  the_enclosure.deactivate
+                end
+                #
+              end
+              unless error == true
+                destination[:permissions].each do |the_entry|
+                  self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions])
+                end
+                result = true
+              end
+            else
+              log_error({:error => Exception.new("Error aquiring source or destination database."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})      
+              error = true
+            end
+          end
+          #
+        end
+      else
+        error = true
+        log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ entries(the_path="", as_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 191
+def entries(the_path="", as_credential=nil)
+  result = []
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    #
+    if volume_record
+      result = volume_record[:volume].entries(volume_record[:subpath].to_s, as_credential)
+      @mounted.each do |volume_entry|
+        if (volume_entry[:path].to_s).include?(the_path) && volume_entry[:path].to_s != the_path
+          result << self.profile(volume_entry[:path].to_s)
+        end
+      end
+      result = result.sort {|a,b| a[:title] <=> b[:title]}
+    else
+      path_table = []
+      @thread_safety.synchronize {
+        @mounted.reverse.each do |volume_entry|
+          path_entry = volume_entry[:path].to_s.split("/")
+          unless path_table.include?(path_entry)
+            path_table << path_entry
+          end
+        end
+      }
+      if the_path == "/"
+        offset = 1
+      else
+        offset = (the_path.to_s.split("/").size - 1)
+      end
+      #
+      already = []
+      path_table.each do |subpath|
+        if self.path_prefix(the_path.to_s,subpath.join("/").to_s) == the_path.to_s
+          if subpath[(offset)].to_s.size > 0
+            unless already.include?(subpath[(offset)].to_s)
+              the_profile = self.profile((the_path + "/" + subpath[(offset)].to_s).gsub("//","/"), {:with_credential => as_credential})
+              if the_profile
+                the_profile[:title] = (subpath[(offset)].to_s)
+                the_profile[:accessed] = DateTime.now
+                result << the_profile
+              end
+              already << subpath[(offset)].to_s
+            end
+          end
+        end
+      end
+      result = result.sort {|a,b| a[:title] <=> b[:title]}
+      #
+    end
+    #
+  else
+    begin
+      raise Exception, "Path not found: #{the_path}"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ exist?(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 92
+def exist?(the_path="")
+  self.valid_path?(the_path)
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_permissions(the_path="", the_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 96
+def get_permissions(the_path="", the_credential=nil)
+  result = []
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    if volume_record
+      result = volume_record[:volume].get_permissions(volume_record[:subpath].to_s, the_credential)
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mkdir(the_path="", permissions=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 336
+def mkdir(the_path="", permissions=nil)
+  result = false
+  if self.valid_path?(the_path.to_s)
+    result = true
+  else
+    if self.valid_path?(::File::dirname(the_path.to_s))
+      if the_path.to_s == "/"
+        volume_record = nil
+      else
+        volume_record = self.volume_of_path(the_path.to_s)
+      end
+      if volume_record
+        result = volume_record[:volume].mkdir(volume_record[:subpath].to_s, permissions)
+      else
+        begin
+          raise Exception, "You do not have sufficient priviledges to create here.  See the mount method."
+        rescue Exception => the_error
+          result = false
+          log_error({:error => the_error, :parameters => {:path => the_path}})
+        end
+      end
+    else
+      begin
+        raise Exception, "Invalid path to new directory"
+      rescue Exception => the_error
+        result = false
+        log_error({:error => the_error, :parameters => {:path => the_path}})
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mkfile(the_path="", options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 452
+def mkfile(the_path="", options={})
+  result = nil
+  if self.valid_path?(the_path.to_s)
+    result = self.open(the_path,options)
+  else
+    if self.valid_path?(::File::dirname(the_path.to_s))
+      if the_path.to_s == "/"
+        volume_record = nil
+      else
+        volume_record = self.volume_of_path(the_path.to_s)
+      end
+      if volume_record
+        result = volume_record[:volume].open(volume_record[:subpath].to_s, options)
+      else
+        begin
+          raise Exception, "You do not have sufficient priviledges to create here."
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+        end
+      end
+    else
+      begin
+        raise Exception, "Invalid path to new :file"
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mkpath(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 369
+def mkpath(the_path="")
+  result = false
+  if the_path.to_s == ""
+    the_path = "/"
+  end
+  if self.valid_path?(the_path.to_s)
+    result = true
+  else
+    error_flag = false
+    path_array = the_path.to_s.split("/")
+    (0..(path_array.size - 1)).each do |indexer|
+      temp_path = path_array[(0..(indexer))].join("/")
+      if temp_path == ""
+        temp_path = "/"
+      end
+      unless self.valid_path?(temp_path)
+        unless self.mkdir(temp_path)
+          error_flag = true
+          break
+        end
+      end
+    end
+    unless error_flag
+      result = true
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ mount(the_volume = nil, the_path = "") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1019
+def mount(the_volume = nil, the_path = "")
+  # Allow for a path to be mounted over the top of another - design choice
+  if the_volume.is_a?(::GxG::Storage::Volume)
+    @thread_safety.synchronize { @mounted << {:volume => the_volume, :path => ::Pathname.new(the_path.to_s)} }
+    true
+  else
+    @mounted
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ mounted?(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1029
+def mounted?(the_path="")
+  result = false
+  @thread_safety.synchronize {
+    @mounted.reverse.each do |entry|
+      if entry[:path] == the_path
+        result = true
+        break
+      end
+    end
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ move(source_path=nil, destination_path=nil, options={}) + + click to toggle source + +
+ + +
+ +

Moving support

+ + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 907
+def move(source_path=nil, destination_path=nil, options={})
+  result = false
+  error = false
+  if source_path == destination_path
+    error = true
+  end
+  #
+  source = {:enclosure => nil, :profile => nil, :record => nil}
+  if self.valid_path?(source_path.to_s)
+    if source_path.to_s == "/"
+      source[:record] = nil
+    else
+      source[:record] = self.volume_of_path(source_path.to_s)
+    end
+    if source[:record]
+      source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true}))
+      source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true}))
+    else
+      error = true
+      log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  else
+    error = true
+    log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+  end
+  # Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path
+  if File.basename(source_path) != File.basename(destination_path)
+    destination_path = File.expand_path(destination_path + "/" + File.basename(source_path))
+  end
+  #
+  destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil}
+  if self.valid_path?(destination_path.to_s)
+    if destination_path.to_s == "/"
+      destination[:record] = nil
+    else
+      destination[:record] = self.volume_of_path(destination_path.to_s)
+    end
+    if destination[:record]
+      destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true}))
+    else
+      error = true
+      log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  else
+    if destination[:enclosure].is_a?(::Hash)
+      unless destination[:enclosure][:permissions][:effective][:create] == true
+        error = true
+        log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+      end
+      unless error == true
+        destination[:record] = self.volume_of_path(File.dirname(destination_path))
+        if destination[:record].is_a?(::Hash)
+          # Note: this won't exist yet, but is here for future reference.
+          destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path))
+        end
+      end
+    else
+      error = true
+      log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}})
+    end
+  end
+  #
+  unless error == true
+    if source[:record][:volume].file_system?()
+      if destination[:enclosure][:uuid].to_s.size > 0
+        scenario = :FS_to_DB
+      else
+        scenario = :FS_to_FS
+      end
+    else
+      if destination[:enclosure][:uuid].to_s.size > 0
+        scenario = :DB_to_DB
+      else
+        scenario = :DB_to_FS
+      end
+    end
+    if scenario == :DB_to_DB && (source[:record][:volume].database() == destination[:record][:volume].database())
+      database = source[:record][:volume].database()
+      source_enclosure = database.retrieve_by_uuid(source[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential())
+      source_enclosure.wait_for_reservation
+      destination_enclosure = database.retrieve_by_uuid(destination[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential())
+      destination_enclosure.wait_for_reservation
+      #
+      source_enclosure.each_index do |the_index|
+        if source_enclosure[(the_index)].uuid().to_s.to_sym == source[:profile][:uuid].to_s.to_sym
+          destination_enclosure << source_enclosure.delete_at(the_index)
+          break
+        else
+          source_enclosure[(the_index)].deactivate
+        end
+      end
+      source_enclosure.release_reservation
+      destination_enclosure.release_reservation
+      source_enclosure.deactivate
+      destination_enclosure.deactivate
+      result = true
+    else
+      # copy to destination_path
+      if self.copy(source_path, destination_path, options) == true
+        # delete source_path object
+        if ["directory","persisted_array"].include?(source[:profile][:type].to_s)
+          result = self.rmdir(source_path)
+        else
+          result = self.rmfile(source_path)
+        end
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ open(the_path="", options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 398
+def open(the_path="", options={})
+  result = nil
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    if volume_record
+      result = volume_record[:volume].open(volume_record[:subpath].to_s, options)
+    else
+      begin
+        raise Exception, "You do not have sufficient priviledges to open here."
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+      end
+    end
+  else
+    begin
+      raise Exception, "Invalid path"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ path_prefix(src_path="", the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 24
+def path_prefix(src_path="", the_path="")
+  result = ""
+  src_path.each_char do |the_char|
+    if the_char == the_path[0]
+      the_path = the_path[(1..-1)]
+      result << the_char
+    else
+      break
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ profile(the_path="",params=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 142
+def profile(the_path="",params=nil)
+  result = nil
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    if volume_record
+      if volume_record[:subpath].to_s.size > 0
+        if params
+            result = volume_record[:volume].profile(volume_record[:subpath].to_s,params)
+        else
+            result = volume_record[:volume].profile(volume_record[:subpath].to_s)
+        end
+        result[:title] = (the_path.split("/").last.to_s)
+      else
+        # VFS
+        if params
+            base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s,params)
+        else
+            base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s)
+        end
+        if base_profile
+          result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil}
+          result[:title] = (the_path.split("/").last.to_s)
+          result[:accessed] = DateTime.now
+          if base_profile[:permissions][:effective][:write]
+            result[:permissions][:effective][:write] = true
+            result[:permissions][:effective][:create] = true
+          end
+        end
+      end
+    else
+      # VFS Other
+      result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil}
+      result[:title] = (the_path.split("/").last.to_s)
+      result[:accessed] = DateTime.now
+    end
+  else
+    begin
+      raise Exception, "Path not found: #{the_path}"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end         
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rename(the_path="", new_name="", options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 425
+def rename(the_path="", new_name="", options={})
+  result = false
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    if volume_record
+      result = volume_record[:volume].rename(volume_record[:subpath].to_s, new_name, options)
+    else
+      begin
+        raise Exception, "You do not have sufficient priviledges to rename here."
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+      end
+    end
+  else
+    begin
+      raise Exception, "Invalid path"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ revoke_permissions(the_path="", the_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 111
+def revoke_permissions(the_path="", the_credential=nil)
+  result = false
+  if self.valid_path?(the_path.to_s)
+    if the_path.to_s == "/"
+      volume_record = nil
+    else
+      volume_record = self.volume_of_path(the_path.to_s)
+    end
+    if volume_record
+      result = volume_record[:volume].revoke_permissions(volume_record[:subpath].to_s, the_credential)
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rmdir(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 254
+def rmdir(the_path="")
+  result = false
+  if self.valid_path?(the_path.to_s)
+    begin
+      if the_path.to_s == "/"
+        raise Exception, "You do not have destroy permissions here"
+      else
+        volume_record = self.volume_of_path(the_path.to_s)
+      end
+      if volume_record
+        the_profile = self.profile(the_path.to_s)
+        if the_profile
+          if the_profile[:permissions][:effective][:destroy]
+            if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
+              result = volume_record[:volume].rmdir(volume_record[:subpath].to_s)
+            else
+              raise Exception, "Not a directory"
+            end
+          else
+            raise Exception, "You do not have destroy permissions here. See unmount method."
+          end
+        else
+          raise Exception, "Invalid path"
+        end
+        #
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end
+  else
+    begin
+      raise Exception, "Invalid path"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rmfile(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 295
+def rmfile(the_path="")
+  result = false
+  if self.valid_path?(the_path.to_s)
+    begin
+      if the_path.to_s == "/"
+        raise Exception, "You do not have destroy permissions here"
+      else
+        volume_record = self.volume_of_path(the_path.to_s)
+      end
+      if volume_record
+        the_profile = self.profile(the_path.to_s)
+        if the_profile
+          if the_profile[:permissions][:effective][:destroy]
+            if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
+              raise Exception, "Not a valid :file or :persisted_hash"
+            else
+              result = volume_record[:volume].rmfile(volume_record[:subpath].to_s)
+            end
+          else
+            raise Exception, "You do not have destroy permissions here. See unmount method."
+          end
+        else
+          raise Exception, "Invalid path"
+        end
+        #
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end
+  else
+    begin
+      raise Exception, "Invalid path"
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_permissions(the_path="", the_credential=nil, the_permissions={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 127
+def set_permissions(the_path="", the_credential=nil, the_permissions={})
+    result = false
+    if self.valid_path?(the_path.to_s)
+      if the_path.to_s == "/"
+        volume_record = nil
+      else
+        volume_record = self.volume_of_path(the_path.to_s)
+      end
+      if volume_record
+        result = volume_record[:volume].set_permissions(volume_record[:subpath].to_s, the_credential, the_permissions)
+      end
+    end
+    result
+end
+
+ +
+ + + + +
+ + +
+ +
+ subpath(src_path="", the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 11
+def subpath(src_path="", the_path="")
+  if src_path == "/"
+    the_path
+  else
+    src_path.each_char do |the_char|
+      if the_char == the_path[0]
+        the_path = the_path[(1..-1)]
+      end
+    end
+    the_path
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ unmount(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1042
+def unmount(the_path="")
+  # Allow for a path to be mounted over the top of another - design choice
+  result = nil
+  @thread_safety.synchronize {
+    if @mounted.size > 0
+      (0..(@mounted.size - 1)).to_a.reverse.each do |indexer|
+        if the_path.to_s == (@mounted[(indexer)][:path].to_s)
+          result = @mounted[(indexer)][:volume]
+          @mounted.delete_at(indexer)
+          break
+        end
+      end
+    end
+  }
+  # returns the volume object for proper closing
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_path?(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 50
+def valid_path?(the_path="")
+  result = false
+  if the_path.to_s == "/"
+    result = true
+  else
+    # VFS check
+    path_table = []
+    @thread_safety.synchronize {
+        @mounted.reverse.each do |volume_entry|
+        path_entry = volume_entry[:path].to_s.split("/")
+        unless path_table.include?(path_entry)
+          path_table << path_entry
+        end
+      end
+    }
+    path_array = the_path.to_s.split("/")
+    vfs_check = false
+    path_table.each do |entry|
+      (0..(path_array.size - 1)).each do |indexer|
+        if entry == (path_array[(0..(indexer))])
+          vfs_check = true
+          break
+        end
+      end
+      if vfs_check
+        break
+      end
+    end
+    # volume check
+    if vfs_check
+      volume_record = self.volume_of_path(the_path.to_s)
+      if volume_record
+        result = volume_record[:volume].exist?(volume_record[:subpath].to_s)
+      else
+        result = true
+      end
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ volume_of_path(the_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 37
+def volume_of_path(the_path="")
+  result = nil
+  @thread_safety.synchronize {
+    @mounted.reverse.each do |volume_entry|
+      if self.path_prefix(volume_entry[:path].to_s,the_path.to_s) == (volume_entry[:path].to_s)
+        result = {:volume => volume_entry[:volume], :subpath => self.subpath(volume_entry[:path].to_s, the_path.to_s) }
+        break
+      end
+    end
+  }
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Storage/Volume.html b/doc/GxG/Storage/Volume.html new file mode 100644 index 0000000..15703dd --- /dev/null +++ b/doc/GxG/Storage/Volume.html @@ -0,0 +1,2259 @@ + + + + + + +class GxG::Storage::Volume - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Storage::Volume +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1498
+def initialize(options={})
+  # options: :directory => "/path/to/dir", :credential => <UUID>.to_s, :act_as => :owner? || :database => <GxG::Database::Database>, :credential => <UUID>.to_s, :root_uuid => <UUID>.to_s
+  @thread_safety = ::Mutex.new
+  @credential = GxG::DB[:administrator].to_s
+  @database = nil
+  @root_uuid = nil
+  # Acts As: :owner, :group, :other for File System Volumes
+  if [:owner, :group, :other].include?(options[:act_as])
+    @act_as = options[:act_as]
+  else
+    @act_as = nil
+  end
+  @directory = options[:directory]
+  if @directory.to_s.size > 0
+    if ::GxG::valid_uuid?(options[:credential].to_s.to_sym)
+      unless @act_as
+        # set @act_as according to credential Group/Role membership: Administrators=:owner, Developers/Designers=:group, all others=:other
+        if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], options[:credential].to_s.to_sym)
+          @act_as = :owner
+        else
+          if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, options[:credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, options[:credential].to_s.to_sym)
+            @act_as = :group
+          else
+            @act_as = :other
+          end
+        end
+      end
+    end
+    if ::Dir.exist?(@directory.to_s)
+      if ::File.readable_real?(@directory.to_s)
+        @directory = ::Dir.new(@directory.to_s)
+      else
+        raise Exception, "You do not have read permissions to directory #{@directory.inspect}."
+      end
+    else
+      raise Exception, "Directory #{@directory.inspect} does not exist in the filesystem."
+    end
+    #
+  else
+    @credential = (options[:credential] || GxG::DB[:administrator]).to_s
+    if options[:database].is_a?(::GxG::Database::Database) and @credential.size > 0
+      @database = options[:database]
+      if @database.setting_keys.include?(:volume_information)
+        vol_info = @database[:volume_information]
+      else
+        if @database.db_permissions()[:write]
+          new_root = @database.try_persist([], @credential)
+          if new_root
+            new_root.wait_for_reservation()
+            new_root.set_title("Volume Root")
+            vol_info = {:volume_root => new_root.uuid()}
+            new_root.deactivate
+            @database[:volume_information] = vol_info
+          else
+            raise Exception, "Unable to initialize volume root"
+          end
+        else
+          raise Exception, "Cannot initialize Volume Information on read-only database."
+        end
+      end
+      # One can specify a sub-array to the volume as this volume's root array.
+      if GxG::valid_uuid?(options[:root_uuid].to_s)
+          # verify array's existence
+          the_persisted_array = @database.retrieve_by_uuid(options[:root_uuid], @credential)
+          if the_persisted_array.is_a?(::GxG::Database::PersistedArray)
+              the_persisted_array.deactivate
+              @root_uuid = options[:root_uuid].to_s.to_sym
+          else
+              raise Exception, "The root_uuid points to a non-existent or invalid object, or you do not have permissions to read it."
+          end
+          #
+      else
+          @root_uuid = vol_info[:volume_root].to_sym
+      end
+    else
+      raise ArgumentError, "You MUST supply a valid credential and a database object.  :credential => #{@credential.inspect}, :database => #{options[:database].inspect}"
+    end
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ credential() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1069
+def credential()
+  @credential
+end
+
+ +
+ + + + +
+ + +
+ +
+ database() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1073
+def database()
+  @database
+end
+
+ +
+ + + + +
+ + +
+ +
+ db_path(subpath="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1089
+def db_path(subpath="")
+  result = nil
+  unless subpath.to_s[0] == "/"
+    subpath = ("/" << subpath.to_s)
+  end
+  subpath_array = subpath.to_s.split("/")
+  if subpath_array.size == 0
+    subpath_array << "Volume Root"
+  else
+    subpath_array[0] = "Volume Root"
+  end
+  path_array = [{:uuid => @root_uuid, :title => "Volume Root"}]
+  subpath_array.each_with_index do |the_entry, indexer|
+    parent_uuid = path_array.last[:uuid]
+    #
+    @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |item|
+      if [:element_array, :element_hash].include?(item[:element].to_s.to_sym)
+        if item[:element].to_s.to_sym == :element_array
+          record = @database.connector()[:element_array].filter({:uuid => item[:element_array_uuid].to_s}).first
+        else
+          record = @database.connector()[:element_hash].filter({:uuid => item[:element_hash_uuid].to_s}).first
+        end
+        if record
+          if record[:title] == subpath_array[(indexer + 1)].to_s
+            path_array << {:uuid => record[:uuid].to_sym, :title => record[:title]}
+            break
+          end
+        end
+      end
+    end
+    #
+  end
+  if path_array.size == subpath_array.size
+    result = path_array
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ directory() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1077
+def directory()
+  @directory
+end
+
+ +
+ + + + +
+ + +
+ +
+ entries(subpath="", as_credential=nil, options={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1579
+def entries(subpath="", as_credential=nil, options={:follow_symlinks => true})
+  results = []
+  if @directory
+    # FS
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    ::Dir::entries(the_path).each do |item|
+      unless [".", ".."].include?(item)
+        if as_credential
+          the_profile = self.profile((subpath + "/" + item), options.merge({:with_credential => as_credential}))
+        else
+          the_profile = self.profile((subpath + "/" + item), options)
+        end
+        if the_profile
+          the_profile[:title] = item
+          results << the_profile
+        end
+      end
+    end
+    #
+  else
+    # DB
+    unless subpath.to_s[0] == "/"
+      subpath = ("/" << subpath.to_s)
+    end
+    path_array = self.db_path(subpath.to_s)
+    if path_array
+      base_profile = self.profile(subpath.to_s, {:with_credential => as_credential})
+      if base_profile
+        if base_profile[:permissions][:effective][:read]
+          if base_profile[:type] == :persisted_array
+            object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
+            header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
+            if header
+              object_details[:table] = :element_array
+              object_details[:dbid] = header[:dbid]
+            end
+            contained_titles = []
+            @database.connector()[:array_elements].select(:element_hash_uuid, :element_array_uuid).where({:parent_uuid => path_array.last[:uuid].to_s}).each do |link_record|
+              if link_record[:element_hash_uuid].to_s.size > 0
+                header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first
+                if header
+                  contained_titles << header[:title].to_s
+                end
+              else
+                if link_record[:element_array_uuid].to_s.size > 0
+                  header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first
+                  if header
+                    contained_titles << header[:title].to_s
+                  end
+                end
+              end
+            end
+            contained_titles.sort.each do |the_title|
+              if the_title.size > 0
+                results << self.profile((subpath.to_s + "/" + the_title).gsub("//","/"), {:with_credential => as_credential})
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+  results
+end
+
+ +
+ + + + +
+ + +
+ +
+ exist?(subpath="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1127
+def exist?(subpath="")
+  result = false
+  if @directory
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    result = ::File::exist?(the_path)
+  else
+    # DB object exist?
+    if self.db_path(subpath)
+      result = true
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ file_system?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1081
+def file_system?()
+  if self.directory()
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ get_permissions(subpath="", the_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 2359
+def get_permissions(subpath="", the_credential=nil)
+  result = []
+  if @directory
+    if self.exist?(subpath)
+      the_profile = self.profile(subpath)
+      if the_profile[:file_id]
+        result = GxG::DB[:roles][:vfs].vfs_permission_manifest(the_profile[:file_id], the_credential)
+      end
+      # ????
+      # xxx
+      # group_roles = GxG::DB[:authority][:system_credentials]
+      # the_record = {:credential => group_roles[:administrators], :permissions => the_profile[:permissions][:owner], :details => {}}
+      # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
+      # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
+      # the_record[:details][:role_title] = role[:title]
+      # the_record[:details][:group] = group[:uuid].to_s.to_sym
+      # the_record[:details][:group_title] = group[:title]
+      # result << the_record
+      # the_record = {:credential => group_roles[:developers], :permissions => the_profile[:permissions][:group], :details => {}}
+      # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
+      # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
+      # the_record[:details][:role_title] = role[:title]
+      # the_record[:details][:group] = group[:uuid].to_s.to_sym
+      # the_record[:details][:group_title] = group[:title]
+      # result << the_record
+      # the_record = {:credential => group_roles[:designers], :permissions => the_profile[:permissions][:group], :details => {}}
+      # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s})
+      # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s})
+      # the_record[:details][:role_title] = role[:title]
+      # the_record[:details][:group] = group[:uuid].to_s.to_sym
+      # the_record[:details][:group_title] = group[:title]
+      # result << the_record
+      # the_record = {:credential => :"00000000-0000-4000-0000-000000000000", :permissions => the_profile[:permissions][:other], :details => {}}
+      # user = GxG::DB[:authority].user_fetch({:uuid => the_record[:credential].to_s})
+      # the_record[:details][:user_title] = user[:user_id]
+      # result << the_record
+    end
+  else
+    if self.exist?(subpath)
+      the_profile = self.profile(subpath)
+      result = @database.element_permissions_by_uuid(the_profile[:uuid])
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1065
+def inspect()
+  "<Volume: #{(@database || @directory).inspect}>"
+end
+
+ +
+ + + + +
+ + +
+ +
+ mkdir(subpath="", permissions=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 2233
+def mkdir(subpath="", permissions=nil)
+  result = false
+  if @directory
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    perm = 16384
+    if permissions.is_a?(::Hash)
+      # Best if you pass the entire :permissions hash from a profile query
+      "040000".each_char_with_index do |digit, indexer|
+        case indexer
+        when 0
+        when 1
+          # TODO: process other flags
+        when 2
+        when 3
+          if permissions[:owner].is_a?(::Hash)
+            if permissions[:owner][:execute]
+              perm += 256
+            end
+            if permissions[:owner][:write]
+              perm += 128
+            end
+            if permissions[:owner][:read]
+              perm += 64
+            end
+          end
+        when 4
+          if permissions[:group].is_a?(::Hash)
+            if permissions[:group][:execute]
+              perm += 32
+            end
+            if permissions[:group][:write]
+              perm += 16
+            end
+            if permissions[:group][:read]
+              perm += 8
+            end
+          end
+        when 5
+          if permissions[:other].is_a?(::Hash)
+            if permissions[:other][:execute]
+              perm += 4
+            end
+            if permissions[:other][:write]
+              perm += 2
+            end
+            if permissions[:other][:read]
+              perm += 1
+            end
+          end
+        end
+      end
+      #
+    else
+      base_profile = self.profile("/")
+      if base_profile.is_a?(::Hash)
+        if base_profile[:mode].is_a?(::String)
+          perm = base_profile[:mode].to_i(base=8)
+        else
+          perm = 16877
+        end
+      else
+        perm = 16877
+      end
+    end
+    # FS mkdir
+    begin
+      parent_profile = self.profile(::File::dirname(subpath.to_s))
+      if parent_profile
+        if parent_profile[:permissions][:effective][:create]
+          if ::File::exist?(the_path)
+            result = true
+          else
+            if ::Dir::mkdir(the_path, perm) == 0
+              # Generate VFS-DB permissions, return result
+              self.profile(subpath)
+              result = true
+            end
+          end
+        else
+          raise Exception, "You do not have create permissions here"
+        end
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :permissions => permissions}})
+      #
+    end
+  else
+    # DB dir create
+    begin
+      parent_profile = self.profile(::File::dirname(subpath.to_s))
+      if parent_profile
+        if parent_profile[:permissions][:effective][:create] == true
+          new_object = @database.try_persist([], @credential)
+          new_object.wait_for_reservation
+          new_object.set_title(::File::basename(subpath.to_s))
+          # ### Fashion a link under the parent uuid.
+          new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count
+          @database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_array", :element_array_uuid => new_object.uuid.to_s})
+          # ### Wrap up and return new object
+          new_object.release_reservation
+          result = true
+        else
+          raise Exception, "You do not have create permissions here"
+        end
+      else
+        raise Exception, "Invalid path"
+      end
+      #
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => subpath, :permissions => permissions}})
+      #
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ open(subpath="", options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1974
+def open(subpath="", options={})
+  result = nil
+  if @directory
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    flags = 0
+    flag_array = (options[:flags] || [:readwrite])
+    flag_array.each do |the_flag|
+      case the_flag
+      when :append
+        flags += ::File::APPEND
+      when :binary
+        flags += ::File::BINARY
+      when :create
+        flags += ::File::CREAT
+      when :direct
+        flags += ::File::DIRECT
+      when :dsync
+        flags += ::File::DSYNC
+      when :duplex
+        flags += ::File::DUPLEX
+      when :exclusive
+        flags += ::File::EXCL
+      when :casefold
+        flags += ::File::FNM_CASEFOLD
+      when :dotmatch
+        flags += ::File::FNM_DOTMATCH
+      when :no_escape
+        flags += ::File::FNM_NOESCAPE
+      when :pathname
+        flags += ::File::FNM_PATHNAME
+      when :system_case
+        flags += ::File::FNM_SYSCASE
+      when :lock_ex
+        flags += ::File::LOCK_EX
+      when :lock_nb
+        flags += ::File::LOCK_NB
+      when :lock_sh
+        flags += ::File::LOCK_SH
+      when :lock_un
+        flags += ::File::LOCK_UN
+      when :no_atime
+        flags += ::File::NOATIME
+      when :no_ctty
+        flags += ::File::NOCTTY
+      when :no_follow
+        flags += ::File::NOFOLLOW
+      when :nonblock
+        flags += ::File::NONBLOCK
+      when :read
+        flags += ::File::RDONLY
+      when :rsync
+        flags += ::File::RSYNC
+      when :seek_cur
+        flags += ::File::SEEK_CUR
+      when :seek_end
+        flags += ::File::SEEK_END
+      when :seek_set
+        flags += ::File::SEEK_SET
+      when :setenc_by_bom
+        flags += ::File::SETENC_BY_BOM
+      when :sync
+        flags += ::File::SYNC
+      when :text
+        flags += ::File::TEXT
+      when :truncate
+        flags += ::File::TRUNC
+      when :tty
+        flags += ::File::TTY
+      when :write
+        flags += ::File::WRONLY
+      when :readwrite
+        flags += ::File::RDWR
+      when :wsplit
+        flags += ::File::WSPLIT
+      when :wsplit_initialized
+        flags += ::File::WSPLIT_INITIALIZED
+      end
+    end
+    perm = 16384
+    permissions = options[:permissions]
+    if permissions.is_a?(::Hash)
+      # Best if you pass the entire :permissions hash from a profile query
+      "040000".each_char_with_index do |digit, indexer|
+        case indexer
+        when 0
+        when 1
+          # TODO: process other flags
+        when 2
+        when 3
+          if permissions[:owner].is_a?(::Hash)
+            if permissions[:owner][:execute]
+              perm += 256
+            end
+            if permissions[:owner][:write]
+              perm += 128
+            end
+            if permissions[:owner][:read]
+              perm += 64
+            end
+          end
+        when 4
+          if permissions[:group].is_a?(::Hash)
+            if permissions[:group][:execute]
+              perm += 32
+            end
+            if permissions[:group][:write]
+              perm += 16
+            end
+            if permissions[:group][:read]
+              perm += 8
+            end
+          end
+        when 5
+          if permissions[:other].is_a?(::Hash)
+            if permissions[:other][:execute]
+              perm += 4
+            end
+            if permissions[:other][:write]
+              perm += 2
+            end
+            if permissions[:other][:read]
+              perm += 1
+            end
+          end
+        end
+      end
+      #
+    else
+      base_profile = self.profile("/")
+      if base_profile.is_a?(::Hash)
+        if base_profile[:mode].is_a?(::String)
+          perm = (base_profile[:mode][0..2].to_s + "644").to_i(base=8)
+        else
+          perm = 16804
+        end
+      else
+        perm = 16804
+      end
+    end
+    # FS
+    begin
+      parent_profile = self.profile(::File::dirname(subpath.to_s))
+      if parent_profile
+        if ::File::exist?(the_path)
+          if ::File::directory?(the_path)
+            result = ::Dir::new(the_path)
+          else
+            if flag_array.include?(:read)
+              unless parent_profile[:permissions][:effective][:read]
+                raise Exception, "You do not have read permissions here."
+              end
+            end
+            if flag_array.include?(:write)
+              unless parent_profile[:permissions][:effective][:write]
+                raise Exception, "You do not have write permissions here."
+              end
+            end
+            if flag_array.include?(:readwrite)
+              unless parent_profile[:permissions][:effective][:read]
+                raise Exception, "You do not have read permissions here."
+              end
+              unless parent_profile[:permissions][:effective][:write]
+                raise Exception, "You do not have write permissions here."
+              end
+            end
+            if ::GxG::Engine::available_file_descriptors() > 0
+              result = ::File::open(the_path, flags)
+            else
+              raise Exception, "Maximum File Descriptor count exceeded. Aborting"
+            end
+          end
+        else
+          if parent_profile[:permissions][:effective][:create]
+            if ::GxG::Engine::available_file_descriptors() > 0
+              ::File::new(the_path, "w", perm).close
+              result = ::File::open(the_path, flags)
+              # Generate VFS-DB permissions, return result
+              self.profile(subpath)
+            else
+              raise Exception, "Maximum File Descriptor count exceeded. Aborting"
+            end
+          else
+            raise Exception, "You do not have create permissions here"
+          end
+        end
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+      #
+    end
+  else
+    # DB open/create :persisted_hash
+    begin
+      path_array = self.db_path(subpath.to_s)
+      if path_array
+        profile = self.profile(subpath.to_s)
+        if profile[:permissions][:effective][:read]
+          result = @database.retrieve_by_uuid(path_array.last[:uuid], @credential)
+        else
+          # Review : keep? this breaks the 'if you don't have privs you can't see it - no errors, security through obscurity' idea.
+          raise Exception, "You do not have read permissions on this object"
+        end
+      else
+        parent_profile = self.profile(::File::dirname(subpath.to_s))
+        if parent_profile
+          if parent_profile[:permissions][:effective][:create] == true
+            if options[:format]
+              if GxG::valid_uuid?(options[:format])
+                new_object = @database.new_structure_from_format(@credential,{:uuid => options[:format]})
+              else
+                new_object = @database.new_structure_from_format(@credential,{:ufs => options[:format]})
+              end
+            else
+              if options[:ufs]
+                new_object = @database.new_structure_from_format(@credential,{:ufs => options[:ufs]})
+              else
+                new_object = @database.try_persist({}, @credential)
+              end
+            end
+            # ### allow to create the object with a specific UUID.
+            if ::GxG::valid_uuid?(options[:with_uuid])
+              address = new_object.db_address()
+              new_object.deactivate
+              @database.connector()[(address[:table])].filter({:dbid => address[:dbid]}).first.update({:uuid => options[:with_uuid].to_s})
+              new_object = @database.retrieve_by_uuid(options[:with_uuid].to_s.to_sym, @credential)
+            end
+            #
+            new_object.wait_for_reservation
+            new_object.set_title(::File::basename(subpath.to_s))
+            # ### Fashion a link under the parent uuid.
+            new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count
+            @database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_hash", :element_hash_uuid => new_object.uuid.to_s})
+            # ### Wrap up and return new object
+            new_object.release_reservation
+            result = new_object
+          else
+            raise Exception, "You do not have create permissions here"
+          end
+          #
+        else
+          raise Exception, "Invalid path"
+        end
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ profile(subpath="",params={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1148
+def profile(subpath="",params={:follow_symlinks => true})
+  unless params.is_a?(::Hash)
+    params = {:follow_symlinks => true}
+  end
+  unless params[:follow_symlinks]
+    unless params.keys.include?(:follow_symlinks)
+      params[:follow_symlinks] = true
+    end
+  end
+  platform_details = GxG::SYSTEM.platform()
+  result = {:title => "", :type => :unspecified, :owner_type => :unspecified, :uuid => nil, :version => 0.0, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>false}}, :mode=>nil, :mime => nil}
+  if @directory
+    # return FS object profile
+    result[:title] = (subpath.split("/").last.to_s)
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    if params[:follow_symlinks]
+      raw_stat = ::File::stat(the_path.to_s)
+    else
+      raw_stat = ::File::lstat(the_path.to_s)
+    end
+    raw_mode = raw_stat.mode.to_s(base=8)
+    # puts "Got: #{raw_mode}"
+    if raw_mode.size < 6
+      raw_mode = ("0" << raw_mode)
+    end
+    # puts "Then Got: #{raw_mode}"
+    set_user_id = false
+    set_group_id = false
+    set_sticky_bit = false
+    if (raw_mode[2].to_i & 4) == 4
+      set_user_id = true
+    end
+    if (raw_mode[2].to_i & 2) == 2
+      set_group_id = true
+    end
+    if (raw_mode[2].to_i & 1) ==  1
+      set_sticky_bit = true
+    end
+    raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
+    case raw_stat.ftype.to_s.downcase.to_sym
+    when :link
+      # are .lnk files handled properly on win32 for this?
+      result[:type] =  :symlink
+    when :file
+       the_extension = ::File::basename(the_path.to_s).split(".")[-1].to_s.downcase
+       if the_extension == "lnk"
+         result[:type] =  :symlink
+       else
+         if ["so","la","a","dylib","dll","class"].include?(the_extension)
+           result[:type] =  :library
+         else
+           if (["out","exe","misc","java","jar", "sh", "rb", "py", "pyc"].include?(the_extension) || raw_stat.executable_real?)
+             result[:type] =  :application
+           else
+             # TODO: find a way to externally sense if a tty
+             result[:type] = :file
+           end
+         end
+       end
+     when :fifo
+       result[:type] =  :fifo
+     when :characterspecial
+       result[:type] =  :character_device
+     when :blockspecial
+       result[:type] =  :block_device
+     when :socket
+       result[:type] =  :socket
+     when :directory
+       result[:type] =  :directory
+     when :unknown
+       result[:type] =  :unknown
+       if raw_stat.pipe?
+         result[:type] =  :pipe
+       end
+    else
+       if raw_stat.pipe?
+         result[:type] =  :pipe
+       end
+    end
+    #
+    result[:owner_type] = result[:type]
+    result[:on_device] = raw_stat.dev.to_s(base=10).to_i
+    if result[:on_device]
+      result[:on_device_major] = raw_stat.dev_major
+      result[:on_device_minor] = raw_stat.dev_minor
+    end
+    result[:is_device] = raw_stat.rdev
+    if result[:is_device]
+      result[:is_device_major] = raw_stat.rdev_major
+      result[:is_device_minor] = raw_stat.rdev_minor
+    end
+    result[:inode] = raw_stat.ino.to_i
+    # ### :file_id
+    result[:file_id] = "#{(result[:on_device] || 0).to_s}-#{(result[:on_device_major] || 0).to_s}-#{(result[:on_device_minor] || 0).to_s}-#{(result[:inode] || 0).to_s}"
+    result[:flags] = []
+    if ::File::writable_real?(the_path.to_s)
+      result[:flags] << :write
+    end
+    if ::File::readable_real?(the_path.to_s)
+      result[:flags] << :read
+    end
+    #
+    result[:hardlinks_to] = raw_stat.nlink.to_i
+    result[:user_id] = raw_stat.uid.to_i
+    result[:group_id] = raw_stat.gid.to_i
+    result[:size] = raw_stat.size
+    result[:block_size] = raw_stat.blksize
+    result[:blocks] = raw_stat.blocks
+    result[:version] = raw_stat.mtime.to_f
+    result[:accessed] = DateTime::parse(raw_stat.atime.to_s)
+    result[:modified] = DateTime::parse(raw_stat.mtime.to_s)
+    result[:status_modified] = DateTime::parse(raw_stat.ctime.to_s)
+    # File permissions work in an ACL style, even if based only upon Unix-style permissions.
+    #
+    result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+    if set_user_id
+      result[:flags] << :user_id_on_execute
+    end
+    if set_group_id
+      result[:flags] << :group_id_on_execute
+    end
+    if set_sticky_bit
+      result[:flags] << :sticky_bit
+    end
+    #
+    # owner permissions
+    if (raw_mode[3].to_i & 4) == 4
+      result[:permissions][:owner][:read] = true
+    end
+    if (raw_mode[3].to_i & 2) == 2
+      result[:permissions][:owner][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:owner][:rename] = true
+        result[:permissions][:owner][:move] = true
+        result[:permissions][:owner][:destroy] = true
+        result[:permissions][:owner][:create] = true
+      end
+    end
+    if (raw_mode[3].to_i & 1) == 1
+      result[:permissions][:owner][:execute] = true
+    end
+    # group permissions
+    if (raw_mode[4].to_i & 4) == 4
+       result[:permissions][:group][:read] = true
+    end
+    if (raw_mode[4].to_i & 2) == 2
+      result[:permissions][:group][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:group][:rename] = true
+        result[:permissions][:group][:move] = true
+        result[:permissions][:group][:destroy] = true
+        result[:permissions][:group][:create] = true
+      end
+    end
+    if (raw_mode[4].to_i & 1) == 1
+      result[:permissions][:group][:execute] = true
+    end
+    # other permissions
+    if (raw_mode[5].to_i & 4) == 4
+      result[:permissions][:other][:read] = true
+    end
+    if (raw_mode[5].to_i & 2) == 2
+      result[:permissions][:other][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:other][:rename] = true
+        result[:permissions][:other][:move] = true
+        result[:permissions][:other][:destroy] = true
+        result[:permissions][:other][:create] = true
+      end
+    end
+    if (raw_mode[5].to_i & 1) == 1
+      result[:permissions][:other][:execute] = true
+    end
+    # effective (this user) permissions (while opened, supercedes normal effective file system permissions)
+    if result[:flags].index(:read)
+      result[:permissions][:effective][:read] = true
+      #
+    end
+    # ### db-based virtualized permission system keyed by :file_id
+    # ### on non-linux systems - make sure to support the :file_id format subsystem, and map to local-arch-platform equivilents.
+    # search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]})
+    if ::GxG::valid_uuid?(params[:with_credential] || @credential)
+      acts_as = :group
+      current_roles = GxG::DB[:authority].user_roles(params[:with_credential] || @credential)
+      current_roles.each do |role_record|
+        unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], role_record[:credential])
+          if role_record[:credential] == GxG::DB[:authority][:system_credentials][:designers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:developers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:administrators]
+            GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:owner])
+            acts_as = :owner
+          else
+            GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:group])
+          end
+          #
+        end
+      end
+      #
+      unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], :"00000000-0000-4000-0000-000000000000")
+        GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], :"00000000-0000-4000-0000-000000000000", result[:permissions][:other])
+      end
+      # ### Review : should I support individual permissions at all, or rather confine it to roles and the public id?
+      # unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], (params[:with_credential] || @credential))
+      #   if acts_as == :owner
+      #     GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:owner])
+      #   else
+      #     GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:group])
+      #   end
+      # end
+      #
+      result[:permissions][:effective] = GxG::DB[:roles][:vfs].effective_vfs_permission(result[:file_id], (params[:with_credential] || @credential))
+      #
+    else
+      log_error({:error => Exception.new("Invalid credential: #{params[:with_credential] || @credential}")})
+    end
+    result[:permissions].delete(:owner)
+    result[:permissions].delete(:group)
+    result[:permissions].delete(:other)
+    #
+    #
+    # if @act_as.is_a?(::Symbol) || params[:with_credential]
+    #   if ::GxG::valid_uuid?(params[:with_credential])
+    #     if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], params[:with_credential].to_s.to_sym)
+    #       now_act_as = :owner
+    #     else
+    #       if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, params[:with_credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, params[:with_credential].to_s.to_sym)
+    #         now_act_as = :group
+    #       else
+    #         now_act_as = :other
+    #       end
+    #     end
+    #     result[:permissions][:effective] = result[:permissions][(now_act_as)].clone
+    #   else
+    #     result[:permissions][:effective] = result[:permissions][(@act_as)].clone
+    #   end
+    # else
+    #   if result[:flags].index(:write)
+    #     result[:permissions][:effective][:write] = true
+    #     if platform_details[:platform] == :windows
+    #       # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+    #     else
+    #       result[:permissions][:effective][:rename] = true
+    #       result[:permissions][:effective][:move] = true
+    #       result[:permissions][:effective][:destroy] = true
+    #       result[:permissions][:effective][:create] = true
+    #     end
+    #   end
+    #   if raw_stat.executable?
+    #     result[:permissions][:effective][:execute] = true
+    #   end
+    # end
+    #
+    result[:mode] = raw_mode
+    # Mime type identification:
+    if File.directory?(the_path)
+      mime_type = "inode/directory"
+    else
+      mime_type = MimeMagic.by_extension(File.extname(result[:title]))
+      unless mime_type
+          mime_type = MimeMagic.by_path(the_path)
+      end
+      unless mime_type
+        handle = ::File.open(the_path,"rb")
+        handle.rewind
+        mime_type = ::MimeMagic.by_magic(handle)
+        handle.close
+      end
+      if mime_type
+          mime_type = mime_type.type()
+      else
+          mime_type = "application/octet-stream"
+      end
+    end
+    result[:mime] = mime_type
+    #
+    #
+  else
+    # return DB object profile
+    unless subpath.to_s[0] == "/"
+      subpath = ("/" << subpath.to_s)
+    end
+    #
+    path_array = self.db_path(subpath.to_s)
+    if path_array
+      object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
+      header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first
+      if header
+        object_details[:table] = :element_hash
+        object_details[:dbid] = header[:dbid]
+      else
+        header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
+        if header
+          object_details[:table] = :element_array
+          object_details[:dbid] = header[:dbid]
+        end
+      end
+      #
+      result = {:title => path_array.last[:title], :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => path_array.last[:uuid], :version => header[:version], :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :file_id => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil, :mime => nil}
+      if object_details[:table] == :element_array
+        result[:type] = :persisted_array
+        result[:owner_type] = :persisted_array
+      else
+        result[:type] = :persisted_hash
+        result[:owner_type] = :persisted_hash
+      end
+      if ::GxG::valid_uuid?(params[:with_credential])
+        result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], params[:with_credential])
+      else
+        result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], @credential)
+      end
+      # Object Size in DB:
+      if result[:type] == :persisted_hash
+        # Review : DB overhaul - ensure @database.byte_size_by_uuid supports new arch.
+        # result[:size] = @database.byte_size_by_uuid(object_details[:uuid])
+      else
+        # result[:size] = 0
+      end
+      # Pseudo-mime for objects:
+      mime_type = nil
+      if header[:format].to_s.size > 0
+        the_format = @database.format_list({:uuid => header[:format]})[0]
+        if the_format
+          mime_type = "vnd.gxg/#{the_format[:ufs].to_s.gsub('_','-')}"
+        else
+          mime_type = "vnd.gxg/org.gxg.hash.persisted"
+        end
+      else
+        if object_details[:table] == :element_array
+          mime_type = "vnd.gxg/org.gxg.array.persisted"
+        end
+      end
+      result[:mime] = mime_type
+    else
+      # raise Exception, "Invalid path"
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rename(subpath="", new_name="", options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1905
+def rename(subpath="", new_name="", options={})
+  result = false
+  if @directory
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    # FS
+    begin
+      if options[:with_credential]
+        item_profile = self.profile(subpath.to_s,options.merge({:follow_symlinks => true}))
+      else
+        item_profile = self.profile(subpath.to_s)
+      end
+      if item_profile.is_a?(::Hash)
+        if item_profile[:permissions][:effective][:rename] == true
+          ::File.rename(the_path,(File.dirname(the_path) + "/" + new_name))
+          # Review : check for file_id changes and update the VFS-DB permission records.
+          result = true
+        else
+          raise Exception, "You do not have rename permissions here"
+        end
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+      #
+    end
+  else
+    # DB open/create :persisted_hash or open :persisted_array
+    begin
+      path_array = self.db_path(subpath.to_s)
+      if path_array
+        if options[:with_credential]
+          profile = self.profile(subpath.to_s, options)
+        else
+          profile = self.profile(subpath.to_s)
+        end
+        profile = self.profile(subpath.to_s)
+        if profile[:permissions][:effective][:rename] == true
+          if profile[:type] == :persisted_hash
+            @database.connector()[:element_hash].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name})
+            result = true
+          else
+            if profile[:type] == :persisted_array
+              @database.connector()[:element_array].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name})
+              result = true
+            end
+          end
+          #
+        else
+          raise Exception, "You do not have rename permissions here"
+        end
+        #
+      else
+        raise Exception, "Invalid path"
+      end
+    rescue Exception => the_error
+      log_error({:error => the_error, :parameters => {:path => the_path, :options => options}})
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ revoke_permissions(subpath="", the_credential=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 2405
+def revoke_permissions(subpath="", the_credential=nil)
+  # revoke_element_permissions(table=:unspecified, dbid=0, credential=nil)
+  result = false
+  if @directory
+    # Review - all I need is the :file_id : make more efficient
+    profile = self.profile(subpath)
+    if profile[:file_id]
+      result = GxG::DB[:roles][:vfs].destroy_vfs_permission(profile[:file_id], the_credential)
+    end
+  else
+    if self.exist?(subpath)
+      the_profile = self.profile(subpath)
+      result = @database.revoke_permissions_by_uuid(the_profile[:uuid], the_credential)
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rmdir(subpath="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1651
+def rmdir(subpath="")
+  result = false
+  if @directory
+    # FS rmdir
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    #
+    if ::File::exist?(the_path)
+      begin
+        the_profile = self.profile(subpath.to_s)
+        if the_profile
+          if the_profile[:permissions][:effective][:destroy]
+            # Recursively delete any content files and subdirs first
+            file_list = []
+            dir_list = []
+            vfs_list = [ (the_profile[:file_id]) ]
+            # Gather up all VFS-DB :file_id entries:
+            search_queue = [(subpath)]
+            while search_queue.size > 0 do
+              entry = search_queue.shift
+              self.entries(entry).each do |the_subitem_profile|
+                if the_subitem_profile[:file_id].to_s.size > 0
+                  vfs_list << the_subitem_profile[:file_id]
+                end
+                if [:virtual_directory, :persisted_array, :directory].include?(the_subitem_profile[:type])
+                  search_queue << (entry + "/" + the_subitem_profile[:title])
+                end
+              end
+            end
+            # Do the FS work:
+            search_queue = [(the_path)]
+            while search_queue.size > 0 do
+              entry = search_queue.shift
+              if entry
+                Dir.entries(entry).each do |the_item_name|
+                  unless [".",".."].include?(the_item_name)
+                    if File.directory?(entry + "/" + the_item_name)
+                      dir_list << (entry + "/" + the_item_name)
+                      search_queue << (entry + "/" + the_item_name)
+                    else
+                      file_list << (entry + "/" + the_item_name)
+                    end
+                  end
+                end
+              end
+            end
+            file_list.each do |the_file_path|
+              File.delete(the_file_path)
+            end
+            dir_list.each do |the_dir_path|
+              ::Dir::rmdir(the_dir_path)
+            end
+            if ::Dir::rmdir(the_path) == 0
+              vfs_list.each do |the_file_id|
+                GxG::DB[:roles][:vfs].destroy_vfs_permission(the_file_id)
+              end
+              result = true
+            end
+          else
+            raise Exception, "You do not have destroy permissions to do this"
+          end
+        else
+          raise Exception, "Inavalid path"
+        end
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path}})
+      end
+    else
+      begin
+        raise Exception, "Invaid path"
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path}})
+      end
+    end
+    #
+  else
+    # DB rmdir
+    unless subpath.to_s[0] == "/"
+      subpath = ("/" << subpath.to_s)
+    end
+    path_array = self.db_path(subpath.to_s)
+    if path_array
+      if path_array.size > 1
+        the_profile = self.profile(subpath.to_s)
+        if the_profile
+          if the_profile[:permissions][:effective][:destroy]
+            parent_uuid = (path_array[-2] || {})[:uuid]
+            link_uuids = []
+            found_link = nil
+            if parent_uuid
+              @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record|
+                header = nil
+                if link_record[:element_array_uuid].to_s.size > 0
+                  header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first
+                end
+                if header
+                  if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title]
+                    found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]}
+                  else
+                    link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]}
+                  end
+                end
+              end
+              if found_link
+                # Review : how to deal with lots of users deleting objects at once??
+                if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true
+                  # ### Delete array_elements link of id ...
+                  @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete
+                  # ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results)
+                  link_uuids.each_with_index do |link_record, ordinal|
+                    @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal})
+                  end
+                end
+              end
+            end
+          else
+            begin
+              raise Exception, "You do not have permissions to destroy this object"
+            rescue Exception => the_error
+              log_error({:error => the_error, :parameters => {:path => subpath}})
+            end
+          end
+        else
+          begin
+            raise Exception, "Invalid path"
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:path => subpath}})
+          end
+        end
+      else
+        begin
+          raise Exception, "You do not have permissions to destroy this object"
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:path => subpath}})
+        end
+      end
+    else
+      begin
+        raise Exception, "Invalid path"
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => subpath}})
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ rmfile(subpath="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 1804
+def rmfile(subpath="")
+  result = false
+  if @directory
+    # FS rmfile
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    #
+    if ::File::exist?(the_path)
+      begin
+        the_profile = self.profile(subpath.to_s)
+        if the_profile
+          if the_profile[:permissions][:effective][:destroy]
+            if ::File::delete(the_path)
+              GxG::DB[:roles][:vfs].destroy_vfs_permission(the_profile[:file_id])
+              result = true
+            end
+          else
+            raise Exception, "You do not have destroy permissions to do this"
+          end
+        else
+          raise Exception, "Inavalid path"
+        end
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path}})
+      end
+    else
+      begin
+        raise Exception, "Invaid path"
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => the_path}})
+      end
+    end
+    #
+  else
+    # DB rmfile
+    path_array = self.db_path(subpath.to_s)
+    if path_array
+      the_profile = self.profile(subpath.to_s)
+      if the_profile
+        if the_profile[:permissions][:effective][:destroy]
+          parent_uuid = (path_array[-2] || {})[:uuid]
+          link_uuids = []
+          found_link = nil
+          if parent_uuid
+            @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record|
+              header = nil
+              if link_record[:element_hash_uuid].to_s.size > 0
+                header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first
+              end
+              if header
+                if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title]
+                  found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]}
+                else
+                  link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]}
+                end
+              end
+            end
+            if found_link
+              # Review : how to deal with lots of users deleting objects at once??
+              if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true
+                # ### Delete array_elements link of id ...
+                @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete
+                # ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results)
+                link_uuids.each_with_index do |link_record, ordinal|
+                  @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal})
+                end
+              end
+            end
+          end
+          #
+        else
+          begin
+            raise Exception, "You do not have destroy permissions to do this"
+          rescue Exception => the_error
+            log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
+          end
+        end
+      else
+        begin
+          raise Exception, "Invaid path"
+        rescue Exception => the_error
+          log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
+        end
+      end
+    else
+      begin
+        raise Exception, "Invaid path"
+      rescue Exception => the_error
+        log_error({:error => the_error, :parameters => {:path => subpath.to_s}})
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_permissions(subpath="", the_credential=nil, the_permissions={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 2423
+def set_permissions(subpath="", the_credential=nil, the_permissions={})
+  result = false
+  if @directory
+    if subpath.to_s == "" or subpath.to_s == "/"
+      the_path = (@directory.path)
+    else
+      unless subpath.to_s[0] == "/"
+        subpath = ("/" << subpath.to_s)
+      end
+      the_path = (@directory.path + subpath.to_s).gsub("//", "/")
+    end
+    if ::File::exist?(the_path)
+      # Review - all I need is the :file_id : make more efficient
+      profile = self.profile(subpath, the_credential)
+      the_credentials = []
+      owner_roles = [(GxG::DB[:authority][:system_credentials][:administrators]), (GxG::DB[:authority][:system_credentials][:developers]), (GxG::DB[:authority][:system_credentials][:designers])].flatten!
+      user_roles = GxG::DB[:authority].user_roles(the_credential).collect {|record| record[:credential]}
+      the_role = :other
+      user_roles.each do |the_role_credential|
+        if owner_roles.include?(the_role_credential)
+          the_role = :owner
+          the_credentials = owner_roles
+          break
+        end
+      end
+      unless the_role == :owner
+        if the_credential.to_s == "00000000-0000-4000-0000-000000000000"
+          the_role = :other
+          the_credentials = [ :"00000000-0000-4000-0000-000000000000" ]
+        else
+          the_role = :group
+          the_credentials = user_roles
+        end
+      end
+      # translate to unix permissions
+      old_permission = ::File.stat(the_path).mode.to_s(base=2)
+      x = nil
+      if ::File.directory?(the_path)
+        x = 1
+      else
+        if the_permissions.keys.include?(:execute)
+          if the_permissions[:execute] == true
+            x = 1
+          else
+            x = 0
+          end
+        end
+      end
+      w = nil
+      if the_permissions.keys.include?(:write)
+        if the_permissions[:write] == true
+          w = 1
+        else
+          w = 0
+        end
+      else
+        [:rename, :move, :destroy, :create].each do |the_action|
+          if the_permissions.keys.include?(the_action)
+            if the_permissions[(the_action)] == true
+              w = 1
+              break
+            end
+          end
+        end
+      end
+      if the_permissions.keys.include?(:read)
+        if the_permissions[:read] == true
+          r = 1
+        else
+          r = 0
+        end
+      else
+        r = nil
+      end
+      case the_role
+      when :owner
+        if x
+          old_permission[-7] = x.to_s
+        end
+        if w
+          old_permission[-8] = w.to_s
+        end
+        if r
+          old_permission[-9] = r.to_s
+        end
+      when :group
+        if x
+          old_permission[-4] = x.to_s
+        end
+        if w
+          old_permission[-5] = w.to_s
+        end
+        if r
+          old_permission[-6] = r.to_s
+        end
+      when :other
+        if x
+          old_permission[-1] = x.to_s
+        end
+        if w
+          old_permission[-2] = w.to_s
+        end
+        if r
+          old_permission[-3] = r.to_s
+        end
+      end
+      ::File.chmod(old_permission.to_i(2), the_path)
+      if profile[:file_id]
+        the_credentials.each do |credential_uuid|
+          GxG::DB[:roles][:vfs].update_vfs_permission(profile[:file_id], credential_uuid, the_permissions)
+        end
+      end
+      result = true
+    end
+  else
+    # DB object exist?
+    path_array = self.db_path(subpath.to_s)
+    if path_array
+      object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0}
+      header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first
+      if header
+        object_details[:table] = :element_hash
+        object_details[:dbid] = header[:dbid]
+      else
+        header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first
+        if header
+          object_details[:table] = :element_array
+          object_details[:dbid] = header[:dbid]
+        end
+      end
+      if object_details[:uuid].to_s.size > 0
+        if object_details[:table] = :element_array
+          manifest = [{:table => object_details[:table], :dbid => object_details[:dbid]}]
+        else
+          # Note: for an element_hash - ensure all sub-objects are extended this permission.
+          manifest = []
+          structures = [{:table => object_details[:table], :dbid => object_details[:dbid]}]
+          while structures.size > 0 do
+            entry = structures[0]
+            @database.element_manifest(entry[:table],entry[:dbid]).each do |record|
+              if [:element_hash, :element_array].include?(record[:table])
+                unless structures.include?(record)
+                  structures << record
+                end
+              end
+            end
+            manifest << structures.shift
+          end
+        end
+        #
+        manifest.each do |record|
+          @database.assign_element_permission(record[:table], record[:dbid], credential, the_permissions)
+        end
+        result = true
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_permissions_recursive(subpath="", the_credential=nil, the_permissions={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_dbfs.rb, line 2583
+def set_permissions_recursive(subpath="", the_credential=nil, the_permissions={})
+  the_profile = self.profile((File.expand_path(subpath)).to_s)
+  if the_profile
+    if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type])
+      paths = [(File.expand_path(subpath))]
+      while paths.size > 0 do
+        the_path = paths.shift
+        the_path_profile = self.profile(the_path.to_s)
+        if [:virtual_directory, :directory, :persisted_array].include?(the_path_profile[:type])
+          self.entries(the_path, the_credential).each do |a_profile|
+            paths << File.expand_path(the_path + "/" + a_profile[:title].to_s)
+          end
+          self.set_permissions(the_path, the_credential, the_permissions)
+        else
+          self.set_permissions(the_path, the_credential, the_permissions)
+        end
+      end
+    else
+      self.set_permissions(subpath, the_credential, the_permissions)
+    end
+  end
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support.html b/doc/GxG/Support.html new file mode 100644 index 0000000..911cd3d --- /dev/null +++ b/doc/GxG/Support.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Support - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Support/Library.html b/doc/GxG/Support/Library.html new file mode 100644 index 0000000..98f43d6 --- /dev/null +++ b/doc/GxG/Support/Library.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Support::Library - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Support/Library/CommonIOPrepare.html b/doc/GxG/Support/Library/CommonIOPrepare.html new file mode 100644 index 0000000..1c5c75d --- /dev/null +++ b/doc/GxG/Support/Library/CommonIOPrepare.html @@ -0,0 +1,376 @@ + + + + + + +module GxG::Support::Library::CommonIOPrepare - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::CommonIOPrepare +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ process_parameters(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 769
+def process_parameters(*args)
+  default_object = nil
+  if args[0].is_a?(Hash)
+    # GxG style parameters
+    raw_params = args[0]
+    unless raw_params[:object].is_any?(self.process_params_classes_valid())
+      unless raw_params[:file_descriptor].is_any?(self.process_params_classes_valid())
+        default_object = self.process_params_default_object((raw_params[:object] || raw_params[:file_descriptor]))
+        if default_object
+          raw_params[:object] = default_object
+        else
+          raise ArgumentError, (self.class::process_params_classes_error())
+        end
+      end
+    end
+    unless raw_params[:mode].is_a?(Array)
+      raise ArgumentError, "you MUST specify an Array of :mode flags (as symbols)"
+    end
+    raw_params[:mode].to_enum.each do |the_mode_flag|
+      unless [:read, :write, :readwrite, :create, :overwrite, :truncate, :append, :text, :binary].include?(the_mode_flag)
+        raw_params[:mode].delete(the_mode_flag)
+      end
+    end
+    unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text))
+      raw_params[:mode] << :text
+    end
+  else
+    # standard MRI style parameters
+    raw_params = {:mode => []}
+    if args[0].is_any?(self.process_params_classes_valid())
+      if args[0].is_a?(::Numeric)
+        raw_params[:file_descriptor] = args[0].to_i
+      else
+        raw_params[:object] = args[0]
+      end
+    else
+      default_object = self.process_params_default_object(args[0])
+      if default_object
+        raw_params[:object] = default_object
+      else
+        raise ArgumentError, (self.class::process_params_classes_error())
+      end
+    end
+    if args[2].is_a?(Hash)
+      if args[2][:mode]
+        raw_params[:raw_mode] = args[2][:mode]
+      end
+      if args[2][:binmode]
+        raw_params[:mode] << :binary
+      else
+        unless raw_params[:mode].index(:text)
+          raw_params[:mode] << :text
+        end
+      end
+      extern_encode = nil
+      intern_encode = nil
+      if args[2][:encoding]
+        extern_encode = args[2][:encoding]
+        intern_encode = args[2][:encoding]
+      end
+      if args[2][:external_encoding]
+        unless extern_encode
+          extern_encode = args[2][:external_encoding]
+        end
+      end
+      if args[2][:internal_encoding]
+        unless intern_encode
+          intern_encode = args[2][:internal_encoding]
+        end
+      end
+      if extern_encode
+        raw_params[:external_encoding] = extern_encode
+      end
+      if intern_encode
+        raw_params[:internal_encoding] = intern_encode
+      end
+      if args[2][:autoclose]
+        raw_params[:autoclose] = args[2][:autoclose]
+      end
+    end
+    if args[1].is_any?(String,Numeric)
+      if raw_params[:raw_mode]
+        raise ArgumentError, "you specify :mode as: 2nd parameter (String || Numeric) or an option in 3rd parameter Hash, but not in both"
+      else
+        raw_params[:raw_mode] = args[1]
+      end
+      if raw_params[:raw_mode].is_a?(String)
+        mode_parse = raw_params[:raw_mode].split(":")
+        mode_spec = mode_parse[0].numeric_values()
+        if mode_spec.is_a?(Hash)
+          raw_params[:raw_mode] = mode_spec[:integer]
+        else
+          raw_params[:raw_mode] = mode_parse[0]
+        end
+        #
+        if mode_parse.size > 1
+          extern_encode = mode_parse[1]
+          if mode_parse.size > 2
+            intern_encode = mode_parse[2]
+          else
+            intern_encode = nil
+          end
+        else
+          extern_encode = nil
+          intern_encode = nil
+        end
+        # if specified also in options, it is overwritten here by mode inclusion.
+        if (extern_encode == intern_encode)
+          if extern_encode
+            # raw_params[:encoding] = extern_encode
+            raw_params[:external_encoding] = extern_encode
+            raw_params[:internal_encoding] = extern_encode
+          end
+        else
+          if extern_encode
+            raw_params[:external_encoding] = extern_encode
+          end
+          if intern_encode
+            raw_params[:internal_encoding] = intern_encode
+          end
+        end
+        #
+      else
+        # numeric mode arg
+        raw_params[:raw_mode] = args[1].to_i
+      end
+    end
+    #
+    unless (raw_params[:object] || raw_params[:file_descriptor])
+      raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum"
+    end
+    #
+    if raw_params[:raw_mode]
+      # decipher :raw_mode
+      if raw_params[:raw_mode].is_a?(Numeric)
+        # Numeric
+        if raw_params[:file_descriptor]
+          if (raw_params[:raw_mode] & ::File::RDWR) == ::File::RDWR
+            raw_params[:mode] << :read
+            raw_params[:mode] << :write
+          else
+            if (raw_params[:raw_mode] &  ::File::WRONLY) ==  ::File::WRONLY
+              raw_params[:mode] << :write
+            else
+              if (raw_params[:raw_mode] & ::File::RDONLY) == ::File::RDONLY
+                raw_params[:mode] << :read
+              end
+            end
+          end
+          if (raw_params[:raw_mode] & ::File::TRUNC) == ::File::TRUNC
+            raw_params[:mode] << :overwrite
+          end
+          if (raw_params[:raw_mode] & ::File::APPEND) == ::File::APPEND
+            raw_params[:mode] << :append
+          end
+          if (raw_params[:raw_mode] & ::File::CREAT) == ::File::CREAT
+            raw_params[:mode] << :append
+          end
+        else
+          if (raw_params[:raw_mode] & ::IO::RDWR) == ::IO::RDWR
+            raw_params[:mode] << :read
+            raw_params[:mode] << :write
+          else
+            if (raw_params[:raw_mode] &  ::IO::WRONLY) ==  ::IO::WRONLY
+              raw_params[:mode] << :write
+            else
+              if (raw_params[:raw_mode] & ::IO::RDONLY) == ::IO::RDONLY
+                raw_params[:mode] << :read
+              end
+            end
+          end
+          if (raw_params[:raw_mode] & ::IO::TRUNC) == ::IO::TRUNC
+            raw_params[:mode] << :overwrite
+          end
+          if (raw_params[:raw_mode] & ::IO::APPEND) == ::IO::APPEND
+            raw_params[:mode] << :append
+          end
+          if (raw_params[:raw_mode] & ::IO::CREAT) == ::IO::CREAT
+            raw_params[:mode] << :create
+          end
+        end
+      else
+        # String
+        unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text))
+          if raw_params[:raw_mode].to_s.include?("b")
+            raw_params[:mode] << :binary
+          else
+            raw_params[:mode] << :text
+          end
+        end
+        if raw_params[:raw_mode].to_s.include?("r")
+          raw_params[:mode] << :read
+          if raw_params[:raw_mode].to_s.include?("+")
+            raw_params[:mode] << :write
+          end
+        else
+          if raw_params[:raw_mode].to_s.include?("w")
+            raw_params[:mode] << :write
+            raw_params[:mode] << :overwrite
+            if raw_params[:raw_mode].to_s.include?("+")
+              raw_params[:mode] << :read
+            end
+          else
+            if raw_params[:raw_mode].to_s.include?("a")
+              raw_params[:mode] << :write
+              raw_params[:mode] << :append
+              if raw_params[:raw_mode].to_s.include?("+")
+                raw_params[:mode] << :read
+              end
+            end
+          end
+        end
+      end
+      raw_params.delete(:raw_mode)
+    end
+    #
+  end
+  #
+  if raw_params[:external_encoding]
+    unless ::Encoding::constants.include?(raw_params[:external_encoding].to_s.to_sym)
+      raise ArgumentError, ":external_encoding :#{raw_params[:external_encoding].to_s} unsupported"
+    end
+  end
+  if raw_params[:internal_encoding]
+    unless ::Encoding::constants.include?(raw_params[:internal_encoding].to_s.to_sym)
+      raise ArgumentError, ":internal_encoding :#{raw_params[:internal_encoding].to_s} unsupported"
+    end
+  end
+  #
+  raw_params
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/IOPrepare.html b/doc/GxG/Support/Library/IOPrepare.html new file mode 100644 index 0000000..ad430ac --- /dev/null +++ b/doc/GxG/Support/Library/IOPrepare.html @@ -0,0 +1,218 @@ + + + + + + +module GxG::Support::Library::IOPrepare - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::IOPrepare +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ process_params_classes_error() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 761
+def process_params_classes_error()
+  "you MUST specify an IO :object or a :file_descriptor Fixnum"
+end
+
+ +
+ + + + +
+ + +
+ +
+ process_params_classes_valid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 758
+def process_params_classes_valid()
+  [::IO, ::GxG::IO::IO, ::StringIO, ::GxG::IO::StringIO, ::Numeric]
+end
+
+ +
+ + + + +
+ + +
+ +
+ process_params_default_object(the_object=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 755
+def process_params_default_object(the_object=nil)
+  the_object
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/SocketIO.html b/doc/GxG/Support/Library/SocketIO.html new file mode 100644 index 0000000..bcb5080 --- /dev/null +++ b/doc/GxG/Support/Library/SocketIO.html @@ -0,0 +1,669 @@ + + + + + + +module GxG::Support::Library::SocketIO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::SocketIO +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ recv(bytecount=nil, flags=0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 340
+def recv(bytecount=nil, flags=0)
+  # for now, flags ignored.  LATER: study Socket.recv message flags in detail
+  unless self.instance_variable_defined?(:@before_receive_data)
+    @before_receive_data = nil
+  end
+  unless self.instance_variable_defined?(:@after_receive_data)
+    @after_receive_data = nil
+  end
+  unless bytecount.is_a?(::Numeric)
+    bytecount = self.buffer_limits(:in,:socket)[:initial]
+  end
+  unless flags.is_a?(::Numeric)
+    flags = 0
+  end
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    # on Winderz, just to a buffer sized spoon full until done ??
+    #
+    if self.closed?()
+      raise IOError, "attempting to recv on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      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
+      buffer_size = self.buffer_limits(:in,whatfor)[:initial]
+      unless bytecount.is_a?(::Numeric)
+        bytecount = buffer_size
+      end
+      unless flags.is_a?(::Numeric)
+        flags = 0
+      end
+      # millisecond: 1/1000th of a second (reminder)
+      read_bytes = 0
+      result = ""
+      result.force_encoding(::Encoding::ASCII_8BIT)
+      chunk = "abc"
+      #
+      until (read_bytes == bytecount || chunk.bytesize() == 0)
+        begin
+          if ((bytecount - read_bytes) < buffer_size)
+            chunk = self.recv_nonblock((bytecount - read_bytes),flags).to_s
+          else
+            chunk = self.recv_nonblock(buffer_size,flags).to_s
+          end
+          read_bytes += chunk.bytesize()
+          if chunk.bytesize() > 0
+            result << chunk
+          end
+          #
+        rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable
+          pause
+          begin
+            # about 10 ms
+            selected = GxG::IO::IO::select([self],nil,nil,0.010)
+            until selected
+              pause
+              selected = GxG::IO::IO::select([self],nil,nil,0.010)
+            end
+          rescue Exception
+            pause
+            retry
+          end
+          retry
+        end
+        #
+        pause
+      end
+      #
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call([result,nil,0,nil])
+      end
+      result
+    end
+  else
+    if self.closed?()
+      raise IOError, "attempting to recv on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      result = self.original_recv(bytecount.to_i,flags).to_s
+      result.force_encoding(::Encoding::ASCII_8BIT)
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call([result,nil,0,nil])
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ recvmsg(max_mesg_length=nil, flags=0, max_control_length=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 281
+def recvmsg(max_mesg_length=nil, flags=0, max_control_length=nil, options={})
+  # for now, flags ignored.  LATER: study Socket.recv message flags in detail
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    # on Winderz, just to a buffer sized spoon full until done ??
+    if self.closed?()
+      raise IOError, "attempting to recvmsg on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      begin
+        result = self.recvmsg_nonblock(max_mesg_length, flags, max_control_length, options)
+        #
+      rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable
+        pause
+        begin
+          # about 10 ms
+          selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          until selected
+            pause
+            selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          end
+        rescue Exception
+          pause
+          retry
+        end
+        retry
+      end
+      #
+      if result.is_a?(::Array)
+        if result[0].is_a?(::String)
+          result[0].force_encoding(::Encoding::ASCII_8BIT)
+        end
+      end
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call(result)
+      end
+    end
+  else
+    if self.closed?()
+      raise IOError, "attempting to recvmsg on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      result = self.original_recvmsg(max_mesg_length, flags, max_control_length, options)
+      if result.is_a?(::Array)
+        if result[0].is_a?(::String)
+          result[0].force_encoding(::Encoding::ASCII_8BIT)
+        end
+      end
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call(result)
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ send(data="", flags=0, destination_socket_address=nil, *controls) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 181
+def send(data="", flags=0, destination_socket_address=nil, *controls)
+  # for now, flags ignored.  LATER: study Socket.send message flags in detail
+  unless self.instance_variable_defined?(:@remote_closed)
+    @remote_closed = nil
+  end
+  unless self.instance_variable_defined?(:@write_latency)
+    @write_latency = {:low => nil, :last => nil, :high => nil}
+  end
+  unless self.instance_variable_defined?(:@before_send_data)
+    @before_send_data = nil
+  end
+  unless self.instance_variable_defined?(:@after_send_data)
+    @after_send_data = nil
+  end
+  if self.closed?()
+    raise IOError, "attempting to send on a closed stream"
+  else
+    if @remote_closed
+      raise Errno::EPIPE
+    else
+      data = data.to_s
+      length = 0
+      if data.bytesize() > 0
+        length = data.bytesize()
+        # process controls:
+        if controls.size > 0
+          if controls[0].is_a?(::Socket::AncillaryData)
+            controls = controls[0]
+          else
+            if controls[0].is_a?(::Hash)
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
+            else
+              controls.flatten!
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
+            end
+          end
+        else
+          controls = nil
+        end
+        #
+        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
+        buffer_size = self.buffer_limits(:out,whatfor)[:initial]
+        if length >= buffer_size
+          passes = self.passes_needed(length, buffer_size)
+        else
+          passes = 1
+        end
+        #
+        total = (length - 1)
+        starting = 0
+        ending = ([length,buffer_size].min - 1)
+        #
+        passes.times do
+          # if remote end closes during write, raise error.
+          if controls
+            reading = self.latency_reading(self.write_latency()) do
+              self.sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true
+            end
+          else
+            if destination_socket_address
+              reading = self.latency_reading(self.write_latency()) do
+                self.original_send(data.slice_bytes(starting..ending),flags,destination_socket_address) or @remote_closed=true
+              end
+            else
+              reading = self.latency_reading(self.write_latency()) do
+                self.original_send(data.slice_bytes(starting..ending),flags) or @remote_closed=true
+              end
+            end
+          end
+          if @remote_closed
+            break
+          else
+            @write_latency = reading[:reading]
+          end
+          starting = ending + 1
+          ending = (starting + (buffer_size - 1))
+          if ending > total
+            ending = ending - (ending - total)
+          end
+        end
+        #
+        if @remote_closed
+          raise Errno::EPIPE
+        end
+      end
+      length
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ sendmsg(data="", flags=0, destination_socket_address=nil, *controls) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 8
+def sendmsg(data="", flags=0, destination_socket_address=nil, *controls)
+  unless self.instance_variable_defined?(:@remote_closed)
+    @remote_closed = nil
+  end
+  unless self.instance_variable_defined?(:@write_latency)
+    @write_latency = {:low => nil, :last => nil, :high => nil}
+  end
+  unless self.instance_variable_defined?(:@before_send_data)
+    @before_send_data = nil
+  end
+  unless self.instance_variable_defined?(:@after_send_data)
+    @after_send_data = nil
+  end
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    # on Winderz, just to a buffer sized spoon full until done ??
+    if self.closed?()
+      raise IOError, "attempting to sendmsg on a closed stream"
+    else
+      if @remote_closed
+        raise Errno::EPIPE
+      else
+        data = data.to_s
+        written_bytes = data.bytesize()
+        # process controls:
+        if controls.size > 0
+          if controls[0].is_a?(::Socket::AncillaryData)
+            controls = controls[0]
+          else
+            if controls[0].is_a?(::Hash)
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
+            else
+              controls.flatten!
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
+            end
+          end
+        else
+          raise ArgumentError, "you MUST supply ancillary data as a control"
+        end
+        #
+        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
+        buffer_size = self.buffer_limits(:out,whatfor)[:initial]
+        if written_bytes >= buffer_size
+          passes = self.passes_needed(written_bytes, buffer_size)
+        else
+          passes = 1
+        end
+        #
+        total = (written_bytes - 1)
+        starting = 0
+        ending = ([written_bytes,buffer_size].min - 1)
+        #
+        passes.times do
+          #
+          begin
+            # if remote end closes during write, raise error by setting @remote_closed and breaking loop.
+            reading = self.latency_reading(self.write_latency()) do
+              self.sendmsg_nonblock(data.slice_bytes(starting..ending),flags,destination_socket_address,controls)
+            end
+            @write_latency = reading[:reading]
+            #
+          rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN
+            pause
+            begin
+              selected = ::IO::select(nil,[self],nil,0.010)
+              until selected
+                pause
+                selected = ::IO::select(nil,[self],nil,0.010)
+              end
+            rescue Exception
+              pause
+              retry
+            end
+            retry
+          rescue ::Errno::EPIPE
+            @remote_closed=true
+            break
+          end
+          #
+          starting = ending + 1
+          ending = (starting + (buffer_size - 1))
+          if ending > total
+            ending = ending - (ending - total)
+          end
+        end
+        if @remote_closed
+          raise Errno::EPIPE
+        end
+        written_bytes
+      end
+    end
+  else
+    if self.closed?()
+      raise IOError, "attempting to sendmsg on a closed stream"
+    else
+      if @remote_closed
+        raise Errno::EPIPE
+      else
+        data = self.transcode_to_external(data)
+        written_bytes = data.bytesize()
+        # process controls:
+        if controls.size > 0
+          if controls[0].is_a?(::Socket::AncillaryData)
+            controls = controls[0]
+          else
+            if controls[0].is_a?(::Hash)
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno))
+            else
+              controls.flatten!
+              controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno))
+            end
+          end
+        else
+          raise ArgumentError, "you MUST supply ancillary data as a control"
+        end
+        #
+        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
+        buffer_size = self.buffer_limits(:out,whatfor)[:initial]
+        if written_bytes >= buffer_size
+          passes = self.passes_needed(written_bytes, buffer_size)
+        else
+          passes = 1
+        end
+        #
+        total = (written_bytes - 1)
+        starting = 0
+        ending = ([written_bytes,buffer_size].min - 1)
+        #
+        passes.times do
+          # if remote end closes during write, raise error.
+          reading = self.latency_reading(self.write_latency()) do
+            self.original_sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true
+          end
+          if @remote_closed
+            break
+          else
+            @write_latency = reading[:reading]
+          end
+          starting = ending + 1
+          ending = (starting + (buffer_size - 1))
+          if ending > total
+            ending = ending - (ending - total)
+          end
+        end
+        if @remote_closed
+          raise Errno::EPIPE
+        end
+        #
+      end
+      written_bytes
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/SocketIORecvFrom.html b/doc/GxG/Support/Library/SocketIORecvFrom.html new file mode 100644 index 0000000..698ca1f --- /dev/null +++ b/doc/GxG/Support/Library/SocketIORecvFrom.html @@ -0,0 +1,203 @@ + + + + + + +module GxG::Support::Library::SocketIORecvFrom - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::SocketIORecvFrom +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ recvfrom(max_mesg_length=nil, flags=0) + + click to toggle source + +
+ + +
+ +

Note: Allow aliases or existing recvfrom_nonblock to catch method references. Do not define recvfrom_nonblock here.

+ + + + +
+
# File lib/gxg/gxg_io.rb, line 445
+def recvfrom(max_mesg_length=nil, flags=0)
+  # for now, flags ignored.  LATER: study Socket.recv message flags in detail
+  if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK
+    # on Winderz, just to a buffer sized spoon full until done ??
+    if self.closed?()
+      raise IOError, "attempting to recvmsg on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      begin
+        result = self.recvfrom_nonblock(max_mesg_length, flags)
+        #
+      rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable
+        pause
+        begin
+          # about 10 ms
+          selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          until selected
+            pause
+            selected = GxG::IO::IO::select([self],nil,nil,0.010)
+          end
+        rescue Exception
+          pause
+          retry
+        end
+        retry
+      end
+      #
+      if result.is_a?(::Array)
+        if result[0].is_a?(::String)
+          result[0].force_encoding(::Encoding::ASCII_8BIT)
+        end
+      end
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call(result)
+      end
+    end
+  else
+    if self.closed?()
+      raise IOError, "attempting to recvmsg on a closed stream"
+    else
+      if @before_receive_data.respond_to?(:call)
+        @before_receive_data.call()
+      end
+      result = self.original_recvfrom(max_mesg_length, flags)
+      if result.is_a?(::Array)
+        if result[0].is_a?(::String)
+          result[0].force_encoding(::Encoding::ASCII_8BIT)
+        end
+      end
+      if @after_receive_data.respond_to?(:call)
+        @after_receive_data.call(result)
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/SocketRW.html b/doc/GxG/Support/Library/SocketRW.html new file mode 100644 index 0000000..35c6b91 --- /dev/null +++ b/doc/GxG/Support/Library/SocketRW.html @@ -0,0 +1,1054 @@ + + + + + + +module GxG::Support::Library::SocketRW - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::SocketRW +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ <<(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 726
+def <<(*args)
+  self.write(*args)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_char(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 595
+def each_char(&block)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.each_char(&block)
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_line(*args, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 608
+def each_line(*args, &block)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.each_line(*args, &block)
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ eof?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 508
+def eof?()
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.eof?()
+end
+
+ +
+ + + + +
+ + +
+ +
+ getbyte() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 556
+def getbyte()
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.getbyte()
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ getc(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 569
+def getc(*args)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.getc(*args)
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ gets(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 582
+def gets(*args)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.gets(*args)
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ lineno() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 532
+def lineno()
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.lineno()
+end
+
+ +
+ + + + +
+ + +
+ +
+ lineno=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 540
+def lineno=(*args)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.lineno = args[0]
+end
+
+ +
+ + + + +
+ + +
+ +
+ pos() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 516
+def pos()
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.pos()
+end
+
+ +
+ + + + +
+ + +
+ +
+ pos=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 524
+def pos=(*args)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.pos = args[0]
+end
+
+ +
+ + + + +
+ + +
+ +
+ read(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 621
+def read(*args)
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  result = @in_buffer.read(*args)
+  if @in_buffer.eof?()
+    @in_buffer.rewind
+    @in_buffer.string.clear
+  end
+  result
+end
+
+ +
+ + +
+ Also aliased as: read_nonblock, sysread +
+ + + +
+ + +
+ +
+ read_nonblock(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: read +
+ +
+ + +
+ +
+ readpartial(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 636
+def readpartial(*args)
+  #
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      result = ""
+      result.force_encoding(self.internal_encoding)
+      data = self.read(*args)
+      if data
+        # non-nil
+        if data.is_a?(::String)
+          # 'blocking' or not?
+          if (self.eof?() || (data && data.to_s.size > 0))
+            # no pseudo-blocking
+            result << data
+            data = nil
+          else
+            # pseudo-blocking
+            until (self.eof?() || (data && data.to_s.size > 0))
+              data = self.read(*args)
+              # string, buffer, or nil
+              if data
+                #
+                if data.is_a?(::String)
+                  if (data && data.to_s.size > 0)
+                    result << data
+                    data = nil
+                    break
+                  else
+                    if self.eof?()
+                      raise EOFError, "end of stream reached"
+                    end
+                  end
+                else
+                  # buffer
+                  result = data
+                  data = nil
+                  break
+                end
+              else
+                if self.eof?()
+                  raise EOFError, "end of stream reached"
+                end
+              end
+              #
+              pause
+              #
+            end
+          end
+        else
+          # buffer
+          result = data
+        end
+      else
+        if self.eof?()
+          raise EOFError, "end of stream reached"
+        end
+      end
+      #
+      if result
+        if result.is_a?(::String)
+          if result.bytesize > 0
+            result
+          else
+            nil
+          end
+        else
+          # buffer
+          result
+        end
+      else
+        nil
+      end
+      #
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ rewind() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 548
+def rewind()
+  unless self.instance_variable_defined?(:@in_buffer)
+    @in_buffer = ::StringIO.new("",::IO::RDWR)
+    @in_buffer.set_encoding(::Encoding::ASCII_8BIT)
+  end
+  @in_buffer.rewind()
+end
+
+ +
+ + + + +
+ + +
+ +
+ sysread(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: read +
+ +
+ + +
+ +
+ syswrite(data="") + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: write +
+ +
+ + +
+ +
+ ungetbyte() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 731
+def ungetbyte()
+  raise NotImplementedError, "unget operations not supported on this type of object"
+end
+
+ +
+ + +
+ Also aliased as: ungetbytes, ungetc, ungets +
+ + + +
+ + +
+ +
+ ungetbytes() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: ungetbyte +
+ +
+ + +
+ +
+ ungetc() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: ungetbyte +
+ +
+ + +
+ +
+ ungets() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: ungetbyte +
+ +
+ + +
+ +
+ write(data="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 717
+def write(data="")
+  data = self.transcode_to_external(data)
+  written_bytes = data.bytesize()
+  self.send(data,0)
+  written_bytes
+end
+
+ +
+ + +
+ Also aliased as: write_nonblock, syswrite +
+ + + +
+ + +
+ +
+ write_nonblock(data="") + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: write +
+ +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/StringIOPrepare.html b/doc/GxG/Support/Library/StringIOPrepare.html new file mode 100644 index 0000000..5b4a754 --- /dev/null +++ b/doc/GxG/Support/Library/StringIOPrepare.html @@ -0,0 +1,218 @@ + + + + + + +module GxG::Support::Library::StringIOPrepare - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::StringIOPrepare +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ process_params_classes_error() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 748
+def process_params_classes_error()
+  "you MUST specify a String"
+end
+
+ +
+ + + + +
+ + +
+ +
+ process_params_classes_valid() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 745
+def process_params_classes_valid()
+  [::String]
+end
+
+ +
+ + + + +
+ + +
+ +
+ process_params_default_object(the_object) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_io.rb, line 742
+def process_params_default_object(the_object)
+  ""
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/Transcoding.html b/doc/GxG/Support/Library/Transcoding.html new file mode 100644 index 0000000..509b57f --- /dev/null +++ b/doc/GxG/Support/Library/Transcoding.html @@ -0,0 +1,723 @@ + + + + + + +module GxG::Support::Library::Transcoding - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::Transcoding +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ convert_to_string(the_object=nil,encoding=::Encoding.default_external,conversion_options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 214
+def convert_to_string(the_object=nil,encoding=::Encoding.default_external,conversion_options={})
+  # return the supplied string, or supported object string data, or UTF_8 codepoint, un-transcoded
+  # encoding is used for when a byte value integer is passed to know what encoding to set.
+  the_string = ""
+  the_string.force_encoding(encoding || ::Encoding.default_external)
+  if the_object
+    if the_object.is_a?(::Array)
+      dataset = the_object.flatten
+    else
+      dataset = [(the_object)]
+    end
+    dataset.to_enum(:each).each do |object|
+      buffer = ""
+      if object.is_a?(::String)
+        # find the string content of these classes and pass them for transcoding: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray
+        buffer = object.dup
+      else
+        if object.is_any?(::IO, ::StringIO)
+          # Note: what issues arise when the_object is in binary mode?  Also, how does this scale to very large things or files?
+          buffer = object.gets().to_s
+        end
+        if object.is_any?(::GxG::ByteArray)
+          buffer = object.to_s
+        end
+        if object.is_a?(::Numeric)
+          if object.to_i >= 0
+            data = ""
+            if (0xFFFFFFFF && object.to_i) > 255
+              # interprets as unicode code point
+              data.force_encoding(::Encoding::UTF_8)
+            else
+              # interprets as a char value in its own encoding, no transcoding needed.
+              data.force_encoding(encoding || ::Encoding.default_external)
+            end
+            buffer = (data << object.to_i)
+            data = nil
+          else
+            # signed integers not supported for char values or codepoints at this time.
+          end
+        end
+      end
+      #
+      if buffer.size > 0
+        if (buffer.encoding == (encoding || ::Encoding.default_external) && ! self.binmode?())
+          the_string << buffer
+        else
+          # ::String::transcode_options((encoding || ::Encoding.default_external),buffer.encoding, (conversion_options || {}))
+          if self.binmode?()
+            buffer.to_enum(:chars).each do |the_char|
+              the_char.to_enum(:bytes).each do |the_byte|
+                the_string << the_byte
+              end
+            end
+          else
+            nl_op = self.newline_option_used(conversion_options)
+            options = {}
+            if nl_op
+              options[(nl_op)] = true
+            end
+            #
+            the_string << buffer.transcode!((encoding || ::Encoding.default_external),options)
+          end
+        end
+      end
+      #
+    end
+  end
+  #
+  the_string
+end
+
+ +
+ + + + +
+ + +
+ +
+ field_separator_used(io_channel=:external) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 162
+def field_separator_used(io_channel=:external)
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  unless self.instance_variable_defined?(:@internal_field_separator)
+    @internal_field_separator = nil
+  end
+  unless self.instance_variable_defined?(:@external_field_separator)
+    @external_field_separator = nil
+  end
+  field_separator = ""
+  if io_channel == :internal
+    field_separator.force_encoding((@internal_encoding || self.external_encoding))
+    field_separator << (@internal_field_separator || ($,).to_s)
+  else
+    io_channel = :external
+    field_separator.force_encoding(self.external_encoding)
+    field_separator << (@external_field_separator || ($,).to_s)
+  end
+  if field_separator.size > 0
+    field_separator
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ newline_option_used(io_channel=:external) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 103
+def newline_option_used(io_channel=:external)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  # determine the correct new_line trancoding option used
+  if io_channel.is_a?(::Hash)
+    options = io_channel
+  else
+    options = (@conversion_options[(io_channel.to_sym)] || {})
+  end
+  if options[:cr_newline]
+    :cr_newline
+  else
+    if options[:crlf_newline]
+      :crlf_newline
+    else
+      if options[:universal_newline]
+        :universal_newline
+      else
+        # use nil if none specified
+        nil
+      end
+    end
+  end
+  #
+end
+
+ +
+ + + + +
+ + +
+ +
+ newline_used(io_channel=:external) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 130
+def newline_used(io_channel=:external)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  newline = ""
+  if io_channel == :internal
+    newline.force_encoding((@internal_encoding || self.external_encoding))
+  else
+    io_channel = :external
+    newline.force_encoding(self.external_encoding)
+  end
+  # determine the correct new_line_separator
+  if @conversion_options[(io_channel)][:cr_newline]
+    newline << "\r"
+  else
+    if @conversion_options[(io_channel)][:crlf_newline]
+      newline << "\r\n"
+    else
+      if @conversion_options[(io_channel)][:universal_newline]
+        newline << "\n"
+      else
+        # use global default if none specified
+        newline << $/
+      end
+    end
+  end
+  newline
+end
+
+ +
+ + + + +
+ + +
+ +
+ prepare_conversion_options(options={}) + + click to toggle source + +
+ + +
+ +

Instance Methods

+ + + + +
+
# File lib/gxg/gxg_transcode.rb, line 8
+def prepare_conversion_options(options={})
+  # obsoleted by ::String::transcode_options
+  unless options.is_a?(::Hash)
+    raise ArgumentError, "You must pass conversion options parameter as a Hash"
+  end
+  #
+  new_options = {}
+  #
+  valid_options = [:invalid,:undef,:replace,:fallback,:xml,:cr_newline,:crlf_newline,:universal_newline]
+  #
+  for the_option_key in valid_options
+    #
+    if options[(the_option_key)]
+      # Note Source: http://ruby-doc.org/core-1.9.3/String.html#method-i-encode
+      if the_option_key == :fallback
+        #    Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method,
+        #    or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder.
+        #    Its value can be any encoding until it can be converted into the destination encoding of the transcoder.
+        unless new_options[:replace]
+          if (options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || options[:fallback].respond_to?(:[]))
+            # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition.
+            new_options[:fallback] = options[:fallback]
+          end
+        end
+      end
+      #
+      if the_option_key == :invalid
+        #    If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to
+        #    raise the Encoding::InvalidByteSequenceError exception
+        unless new_options[:fallback]
+          if options[:invalid] == :replace
+            new_options[:invalid] = :replace
+          end
+        end
+      end
+      #
+      if (the_option_key == :undef)
+        #    If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character.
+        #    The default is to raise the Encoding::UndefinedConversionError.
+        unless new_options[:fallback]
+          if options[:undef] == :replace
+            new_options[:undef] = :replace
+          end
+        end
+      end
+      if (the_option_key == :replace)
+        #    Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise.
+        unless new_options[:fallback]
+          if options[:replace].is_a?(::String)
+            new_options[:replace] = options[:replace]
+          end
+        end
+      end
+      if the_option_key == :xml
+        #    The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character
+        #    references. ‘&’, ‘<’, and ‘>’ are converted to “&amp;”, “&lt;”, and “&gt;”, respectively. If the value is :attr, encode also quotes the replacement
+        #    result (using ‘“’), and replaces ‘”’ with “&quot;”.
+        if [:text, :attr].include?(options[:xml])
+          new_options[:xml] = options[:xml]
+        end
+      end
+      if the_option_key == :cr_newline
+        #    Replaces LF (“n”) with CR (“r”) if value is true.
+        unless (new_options[:crlf_newline] || new_options[:universal_newline])
+          if options[(the_option_key)] == true
+            new_options[(the_option_key)] = true
+          end
+        end
+      end
+      #
+      if the_option_key == :crlf_newline
+        #    Replaces LF (“n”) with CRLF (“rn”) if value is true.
+        unless (new_options[:cr_newline] || new_options[:universal_newline])
+          if options[(the_option_key)] == true
+            new_options[(the_option_key)] = true
+          end
+        end
+      end
+      #
+      if the_option_key == :universal_newline
+        #    Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true.
+        unless (new_options[:cr_newline] || new_options[:crlf_newline])
+          if options[(the_option_key)] == true
+            new_options[(the_option_key)] = true
+          end
+        end
+      end
+      #
+    end
+    #
+    pause
+  end
+  new_options
+end
+
+ +
+ + + + +
+ + +
+ +
+ record_separator_used(io_channel=:external) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 188
+def record_separator_used(io_channel=:external)
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  unless self.instance_variable_defined?(:@internal_record_separator)
+    @internal_record_separator = nil
+  end
+  unless self.instance_variable_defined?(:@external_record_separator)
+    @external_record_separator = nil
+  end
+  record_separator = ""
+  if io_channel == :internal
+    record_separator.force_encoding((@internal_encoding || self.external_encoding))
+    record_separator << (@internal_record_separator || ($\).to_s)
+  else
+    io_channel = :external
+    record_separator.force_encoding(self.external_encoding)
+    record_separator << (@external_record_separator || ($\).to_s)
+  end
+  if record_separator.size > 0
+    record_separator
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ transcode_to_external(the_string="") + + click to toggle source + +
+ + +
+ +

TODO: GxG::StringIO : transcoding methods : tie into conversion-options-set-selector supports (dst-encoding,src-encoding). Hash deep-merge method needed.

+ + + + +
+
# File lib/gxg/gxg_transcode.rb, line 287
+def transcode_to_external(the_string="")
+  # return the supplied string, or supported object string data, or UTF_8 codepoint, transcoded
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  #
+  unless the_string.is_a?(::String)
+    # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding
+    the_string = self.convert_to_string(the_string,self.external_encoding,@conversion_options[:external])
+  end
+  if the_string.size > 0
+    if self.binmode?()
+      the_string.force_encoding(::Encoding::ASCII_8BIT)
+    else
+      if (the_string.encoding != self.external_encoding)
+        if the_string.encoding == @internal_encoding
+          options = ::String::transcode_options(self.external_encoding,the_string.encoding,@conversion_options[:external])
+        else
+          # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding
+          nl_op = self.newline_option_used(:external)
+          options = {}
+          if nl_op
+            options[(nl_op)] = true
+          end
+          options = ::String::transcode_options(self.external_encoding,the_string.encoding,options)
+        end
+        the_string.transcode!(self.external_encoding,options)
+      end
+    end
+  end
+  #
+  the_string
+end
+
+ +
+ + + + +
+ + +
+ +
+ transcode_to_internal(the_string="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 324
+def transcode_to_internal(the_string="")
+  # return the supplied string, or supported object string data, or UTF_8 codepoint, transcoded
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  #
+  if (@internal_encoding && ! self.binmode?())
+    unless the_string.is_a?(::String)
+      the_string = self.convert_to_string(the_string,@internal_encoding,@conversion_options[:internal])
+    end
+    if the_string.size > 0
+      if @internal_encoding == ::Encoding::ASCII_8BIT
+        # for cases where the external encoding is anything but binary, but the internal is set to BINARY.
+        # instead of actually transcoding something char by char into binary - just sets the encoding and filters for newline settings.
+        #
+        the_string.force_encoding(::Encoding::ASCII_8BIT)
+        nl_op = self.newline_option_used(:internal)
+        options = {}
+        if nl_op
+          options[(nl_op)] = true
+        end
+        the_string = the_string.encode(::Encoding::ASCII_8BIT,::Encoding::ASCII_8BIT,options)
+      else
+        if (the_string.encoding != @internal_encoding)
+          if the_string.encoding == self.external_encoding
+            options = ::String::transcode_options(@internal_encoding,the_string.encoding,@conversion_options[:internal])
+          else
+            nl_op = self.newline_option_used(:internal)
+            options = {}
+            if nl_op
+              options[(nl_op)] = true
+            end
+            options = ::String::transcode_options(@internal_encoding,the_string.encoding,options)
+          end
+          the_string.transcode!(@internal_encoding,options)
+        end
+      end
+    end
+  else
+    the_string = self.transcode_to_external(the_string)
+  end
+  #
+  the_string
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Support/Library/TranscodingIO.html b/doc/GxG/Support/Library/TranscodingIO.html new file mode 100644 index 0000000..785f5d9 --- /dev/null +++ b/doc/GxG/Support/Library/TranscodingIO.html @@ -0,0 +1,2346 @@ + + + + + + +module GxG::Support::Library::TranscodingIO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Support::Library::TranscodingIO +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ bytes(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_byte +
+ +
+ + +
+ +
+ chars(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_char +
+ +
+ + +
+ +
+ codepoints(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_codepoint +
+ +
+ + +
+ +
+ each(separator=$/, limit=nil,&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_line +
+ +
+ + +
+ +
+ each_byte(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 668
+def each_byte(&block)
+  if block.respond_to?(:call)
+    if self.closed_read?
+      raise IOError, "not open for reading"
+    else
+      until self.eof?()
+        data = self.getbyte()
+        if data
+          block.call(data)
+        end
+        pause
+      end
+      #
+      self
+    end
+  else
+    # Note: when each_byte alias is called, will be cosmetic off, but will work.
+    self.to_enum(:each_byte)
+  end
+end
+
+ +
+ + +
+ Also aliased as: bytes +
+ + + +
+ + +
+ +
+ each_char(&block) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 647
+def each_char(&block)
+  if block.respond_to?(:call)
+    if self.closed_read?
+      raise IOError, "not open for reading"
+    else
+      #
+      # Note: this approach burns another 20 or 40 bytes (depending on arch-bits) for extra cooperative Enumerator encapsulation,
+      # but avoids stack-too-deep errors..
+      super().to_enum.each do |the_character|
+        block.call(self.transcode_to_internal(the_character))
+      end
+      #
+      self
+    end
+  else
+    # Note: when each_char alias is called, will be cosmetic off, but will work.
+    self.to_enum(:each_char)
+  end
+end
+
+ +
+ + +
+ Also aliased as: chars +
+ + + +
+ + +
+ +
+ each_codepoint(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 712
+def each_codepoint(&block)
+  if block.respond_to?(:call)
+    if self.closed_read?
+      raise IOError, "not open for reading"
+    else
+      # transcodes at the character level prior to codepoint passage to the block.
+      self.each_char do |the_character|
+        block.call(the_character.codepoints.first)
+      end
+      #
+      self
+    end
+  else
+    # Note: when each_codepoint alias is called, will be cosmetic off, but will work.
+    self.to_enum(:each_codepoint)
+  end
+end
+
+ +
+ + +
+ Also aliased as: codepoints +
+ + + +
+ + +
+ +
+ each_line(separator=$/, limit=nil,&block) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 731
+def each_line(separator=$/, limit=nil,&block)
+  # should not do an *args param capture for interface behavior compatibility.
+  # use of limit and separator appear exclusive in this method.
+  if separator.is_a?(Numeric)
+    limit = separator.to_i
+    separator = nil
+  end
+  # if transcoding is set, overwrite default separator according to translation flag settings.
+  if (self.internal_encoding() && self.internal_encoding() != self.external_encoding)
+    if separator == $/
+      #Note : line separator supplied as method parameter will be applied to the transcoded string, not the StringIO data itself.
+      separator = self.newline_used(:internal)
+    end
+  end
+  #
+  if limit
+    limit = limit.to_i
+    unless limit > 0
+      # Note: a work-around : irb FREAKS entire system when you call readlines with limit equal to 0 (memleak: IO lock up)
+      limit = 1
+    end
+    args = [(separator),(limit)]
+  else
+    args = [(separator)]
+  end
+  #
+  if block.respond_to?(:call)
+    if self.closed_read?
+      raise IOError, "not open for reading"
+    else
+      # Note: this approach burns another 20 or 40 bytes (depending on arch-bits) for extra cooperative Enumerator encapsulation,
+      # but avoids stack-too-deep errors..
+      super(*args) do |the_line|
+        block.call(self.transcode_to_internal(the_line))
+        pause
+      end
+      #
+      self
+    end
+  else
+    # Note: when each_line alias is called, will be cosmetic off, but will work.
+    self.to_enum(:each_line,*args)
+  end
+end
+
+ +
+ + +
+ Also aliased as: each, lines +
+ + + +
+ + +
+ +
+ each_transcoded_byte(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 690
+def each_transcoded_byte(&block)
+  if block.respond_to?(:call)
+    if self.closed_read?
+      raise IOError, "not open for reading"
+    else
+      # transcodes at the character level prior to byte passage to the block.
+      self.each_char do |the_character|
+        # the_character is pre-transcoded by each_char where appropriate.
+        the_character.to_enum(:bytes).each do |the_byte|
+          block.call(the_byte)
+        end
+      end
+      #
+      self
+    end
+  else
+    # Note: when each_transcoded_byte alias is called, will be cosmetic off, but will work.
+    self.to_enum(:each_transcoded_byte)
+  end
+end
+
+ +
+ + +
+ Also aliased as: transcoded_bytes +
+ + + +
+ + +
+ +
+ external_conversion() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 377
+def external_conversion()
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  @conversion_options[:external].clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_conversion=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 384
+def external_conversion=(*args)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  @conversion_options[:external] = self.prepare_conversion_options(*args)
+  @conversion_options[:external].clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_field_separator() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 408
+def external_field_separator()
+  unless self.instance_variable_defined?(:@external_field_separator)
+    @external_field_separator = nil
+  end
+  @external_field_separator.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_field_separator=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 415
+def external_field_separator=(*args)
+  unless self.instance_variable_defined?(:@external_field_separator)
+    @external_field_separator = nil
+  end
+  if args[0].is_a?(::String)
+    @external_field_separator = args[0]
+  else
+    case args[0]
+    when :globalcopy
+      @external_field_separator = ($,).dup
+    when :global
+      @external_field_separator = $,
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_newline() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 392
+def external_newline()
+  self.newline_option_used(:external)
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_newline=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 396
+def external_newline=(*args)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  if [:cr_newline, :crlf_newline, :universal_newline].include?(args[0])
+    @conversion_options[:external].delete(:cr_newline)
+    @conversion_options[:external].delete(:crlf_newline)
+    @conversion_options[:external].delete(:universal_newline)
+    @conversion_options[:external][(args[0])] = true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_record_separator() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 431
+def external_record_separator()
+  unless self.instance_variable_defined?(:@external_record_separator)
+    @external_record_separator = nil
+  end
+  @external_record_separator.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+ external_record_separator=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 438
+def external_record_separator=(*args)
+  unless self.instance_variable_defined?(:@external_record_separator)
+    @external_record_separator = nil
+  end
+  if args[0].is_a?(::String)
+    @external_record_separator = args[0]
+  else
+    case args[0]
+    when :globalcopy
+      @external_record_separator = ($\).dup
+    when :global
+      @external_record_separator = $\
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ getbyte() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 586
+def getbyte()
+  # regarding getbyte : I don't see any practical way to do auto-transcoding ... leaving that one alone for now.
+  super()
+end
+
+ +
+ + + + +
+ + +
+ +
+ getbytes() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 591
+def getbytes()
+  result = GxG::ByteArray.new
+  until self.eof?()
+    data = self.getbyte()
+    if data
+      result << data
+    end
+    pause
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ getc() + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 603
+def getc()
+  data = super()
+  if data.is_a?(::String)
+    data = self.transcode_to_internal(data)
+  end
+  data
+end
+
+ +
+ + + + +
+ + +
+ +
+ gets(*args) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 566
+def gets(*args)
+  # Reads the next “line” from the I/O stream; lines are separated by sep. A separator of nil reads the entire contents,
+  # and a zero-length separator reads the input a paragraph at a time (two successive newlines in the input separate paragraphs).
+  # The stream must be opened for reading or an IOError will be raised. The line read in will be returned and also assigned to $_.
+  # Returns nil if called at end of file. If the first argument is an integer, or optional second argument is given, the returning string
+  # would not be longer than the given value in bytes.
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    data = super(*args)
+    if data.is_a?(::String)
+      data = self.transcode_to_internal(data)
+      $_ = data.dup
+      data
+    else
+      nil
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_conversion() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 489
+def internal_conversion()
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  @conversion_options[:internal].clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_conversion=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 496
+def internal_conversion=(*args)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  @conversion_options[:internal] = self.prepare_conversion_options(*args)
+  @conversion_options[:internal].clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_encoding() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 454
+def internal_encoding()
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  @internal_encoding
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_encoding=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 461
+def internal_encoding=(*args)
+  unless self.instance_variable_defined?(:@internal_encoding)
+    @internal_encoding = nil
+  end
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  unless self.binmode?()
+    if args[0].is_any?(::Encoding, ::NilClass)
+      if args[0].is_a?(::Encoding)
+        @internal_encoding = args[0]
+        # preserve newline settings
+        nl_op = self.newline_option_used(:internal)
+        options = {}
+        if nl_op
+          options[(nl_op)] = true
+        end
+        @conversion_options[:internal] = ::String::transcoding_options(@internal_encoding,self.external_encoding,options)
+      else
+        @internal_encoding = nil
+        @conversion_options[:internal] = {}
+      end
+    else
+      raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}"
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_field_separator() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 520
+def internal_field_separator()
+  unless self.instance_variable_defined?(:@internal_field_separator)
+    @internal_field_separator = nil
+  end
+  @internal_field_separator.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_field_separator=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 527
+def internal_field_separator=(*args)
+  unless self.instance_variable_defined?(:@internal_field_separator)
+    @internal_field_separator = nil
+  end
+  if args[0].is_a?(::String)
+    @internal_field_separator = args[0]
+  else
+    case args[0]
+    when :globalcopy
+      @internal_field_separator = ($,).dup
+    when :global
+      @internal_field_separator = $,
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_newline() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 504
+def internal_newline()
+  self.newline_option_used(:internal)
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_newline=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 508
+def internal_newline=(*args)
+  unless self.instance_variable_defined?(:@conversion_options)
+    @conversion_options = {:internal => {}, :external => {}}
+  end
+  if [:cr_newline, :crlf_newline, :universal_newline].include?(args[0])
+    @conversion_options[:internal].delete(:cr_newline)
+    @conversion_options[:internal].delete(:crlf_newline)
+    @conversion_options[:internal].delete(:universal_newline)
+    @conversion_options[:internal][(args[0])] = true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_record_separator() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 543
+def internal_record_separator()
+  unless self.instance_variable_defined?(:@internal_record_separator)
+    @internal_record_separator = nil
+  end
+  @internal_record_separator.dup
+end
+
+ +
+ + + + +
+ + +
+ +
+ internal_record_separator=(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 550
+def internal_record_separator=(*args)
+  unless self.instance_variable_defined?(:@internal_record_separator)
+    @internal_record_separator = nil
+  end
+  if args[0].is_a?(::String)
+    @internal_record_separator = args[0]
+  else
+    case args[0]
+    when :globalcopy
+      @internal_record_separator = ($\).dup
+    when :global
+      @internal_record_separator = $\
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ lines(separator=$/, limit=nil,&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_line +
+ +
+ + +
+ +
+ print(*args) + + click to toggle source + +
+ + +
+ + + + + + + + +
+ + + + +
+ + +
+ +
+ printf(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 848
+def printf(*args)
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    formatting = ""
+    args = args.flatten
+    if args[0].is_a?(::String)
+      formatting = args.delete_at(0)
+    end
+    args.to_enum(:each_with_index) do |element,index|
+      if element.is_any?(::String, ::Numeric, ::IO, ::StringIO, ::GxG::ByteArray)
+        args[(index)] = self.transcode_to_external(element)
+      else
+        args[(index)] = self.transcode_to_external(element.inspect)
+      end
+    end
+    data = ""
+    data.force_encoding(self.external_encoding)
+    data << formatting.%(*args)
+    #
+    if data.size > 0
+      self.write(data)
+    end
+    #
+  end
+  #
+  nil
+end
+
+ +
+ + + + +
+ + +
+ +
+ put_codepoint(the_object=nil) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: putc +
+ +
+ + +
+ +
+ putc(the_object=nil) + + click to toggle source + +
+ + +
+ +

Transcoding 'write' methods:

+ + + + +
+
# File lib/gxg/gxg_transcode.rb, line 792
+def putc(the_object=nil)
+  # make multi-byte safe version of putc for StringIO. (alternate3)
+  # unlike stock putc : multi-byte char safe, can accept arrays of objects : where this will read the first char available on each object and write it.
+  if the_object
+    if the_object.is_a?(::Array)
+      dataset = the_object.flatten
+    else
+      dataset = [(the_object)]
+    end
+    dataset.to_enum(:each).each do |object|
+      buffer = ""
+      if object.is_a?(::String)
+        # find the string content of these classes and pass them for transcoding: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray
+        buffer = object.chars.first
+      else
+        if object.is_any?(::IO, ::StringIO)
+          # Note: what issues arise when the_object is in binary mode?  Also, how does this scale to very large things or files?
+          buffer = object.getc.to_s
+        end
+        if object.is_any?(::GxG::ByteArray)
+          buffer = object.to_s.chars.first
+        end
+        if object.is_a?(::Numeric)
+          if object.to_i >= 0
+            data = ""
+            if (0xFFFFFFFF && object.to_i) > 255
+              # interprets as unicode code point
+              data.force_encoding(::Encoding::UTF_8)
+            else
+              # interprets as a char value in its own encoding, no transcoding needed.
+              data.force_encoding(self.external_encoding)
+            end
+            buffer = (data << object.to_i)
+            data = nil
+          else
+            # signed integers not supported for char values or codepoints at this time.
+          end
+        end
+      end
+      #
+      if buffer.size > 0
+        if buffer.encoding == (self.external_encoding)
+          self.write(buffer.chars.first)
+        else
+          self.write(self.transcode_to_external(buffer).chars.first)
+        end
+      end
+      #
+    end
+  end
+  #
+  nil
+end
+
+ +
+ + +
+ Also aliased as: put_codepoint, putwc +
+ + + +
+ + +
+ +
+ puts(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 914
+def puts(*args)
+  # Writes the given objects to ios as with IO#print. Writes a record separator (typically a newline) after any
+  # that do not already end with a newline sequence. If called with an array argument, writes each element on a new line.
+  # If called without arguments, outputs a single record separator.
+  #  ###
+  # determine the correct new_line_separator
+  newline = self.newline_used(:external)
+  #
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    args = args.flatten
+    if args.size > 0
+      args.to_enum(:each_with_index) do |element,index|
+        if element.is_any?(::String, ::Numeric, ::IO, ::StringIO, ::GxG::ByteArray)
+          args[(index)] = self.transcode_to_external(element)
+        else
+          args[(index)] = self.transcode_to_external(element.to_s)
+        end
+      end
+      #
+      data = ""
+      data.force_encoding(self.external_encoding)
+      args.to_enum.each do |element|
+        if newline.size > 0
+          # sense if newline is already present, add if not
+          if element.size >= newline.size
+            if element.slice((newline.size * -1)..-1) == newline
+              data << element
+            else
+              data << element
+              data << newline
+            end
+          else
+            data << element
+            data << newline
+          end
+        else
+          data << element
+        end
+      end
+      if data.size > 0
+        self.write(data)
+      end
+      #
+    else
+      self.write(newline)
+    end
+  end
+  #
+  nil
+end
+
+ +
+ + + + +
+ + +
+ +
+ putwc(the_object=nil) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: putc +
+ +
+ + +
+ +
+ readbyte() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 611
+def readbyte()
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      self.getbyte()
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ readchar(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 623
+def readchar(*args)
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      self.transcode_to_internal(self.read(1))
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ readline(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 635
+def readline(*args)
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    if self.eof?
+      raise EOFError, "end of stream reached"
+    else
+      self.gets(*args)
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ readlines(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 778
+def readlines(*args)
+  # Reads all of the lines in ios, and returns them in anArray. Lines are separated by the optional sep.
+  # If sep is nil, the rest of the stream is returned as a single record. If the first argument is an integer,
+  # or optional second argument is given, the returning string would not be longer than the given value in bytes.
+  # The stream must be opened for reading or an IOError will be raised.
+  if self.closed_read?
+    raise IOError, "not open for reading"
+  else
+    data = []
+    self.each_line(*args) { |the_line|  data << the_line }
+    data
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ transcoded_bytes(&block) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: each_transcoded_byte +
+ +
+ + +
+ +
+ unget_transcoded_byte(the_byte=nil) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: ungetbyte +
+ +
+ + +
+ +
+ unget_transcoded_bytes(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: ungetbytes +
+ +
+ + +
+ +
+ ungetbyte(the_byte=nil) + + click to toggle source + +
+ + +
+ + + + +
+ Calls superclass method + +
+ + + +
+
# File lib/gxg/gxg_transcode.rb, line 967
+def ungetbyte(the_byte=nil)
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    #
+    if the_byte.is_a?(::Numeric)
+      if (0..255).include?(the_byte.to_i)
+        super(the_byte.to_i)
+      else
+        self.ungetbytes(the_byte)
+      end
+    else
+      self.ungetbytes(the_byte)
+    end
+    #
+  end
+  nil
+end
+
+ +
+ + +
+ Also aliased as: unget_transcoded_byte +
+ + + +
+ + +
+ +
+ ungetbytes(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 987
+def ungetbytes(*args)
+  # unget the string content of these classes and pre-pend them to the StringIO: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    args = args.flatten
+    args.to_enum.each do |the_object|
+      #
+      the_string = self.transcode_to_external(the_object)
+      if the_string.size > 0
+        the_string.reverse.each_char do |the_character|
+          bytes = []
+          the_character.each_byte do |the_byte_value|
+            bytes.unshift(the_byte_value)
+          end
+          bytes.to_enum.each do |the_byte_value|
+            self.ungetbyte(the_byte_value)
+          end
+        end
+      end
+      #
+    end
+  end
+  nil
+end
+
+ +
+ + +
+ Also aliased as: unget_transcoded_bytes +
+ + + +
+ + +
+ +
+ ungetc(the_character="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 1014
+def ungetc(the_character="")
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    unless the_character.is_a?(::String)
+      the_character = the_character.to_s.chars.first
+    end
+    if the_character.size > 1
+      the_character = the_character.chars.first
+    end
+    self.ungetbytes(the_character)
+  end
+  nil
+end
+
+ +
+ + + + +
+ + +
+ +
+ ungets(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_transcode.rb, line 1029
+def ungets(*args)
+  # unget the string content of these classes and pre-pend them to the StringIO: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray
+  if self.closed_write?
+    raise IOError, "not open for writing"
+  else
+    args = args.flatten
+    args.to_enum.each do |the_object|
+      #
+      the_string = self.transcode_to_external(the_object)
+      if the_string.size > 0
+        the_string.reverse.to_enum(:each_char).each do |the_character|
+          self.ungetc(the_character)
+        end
+      end
+      #
+    end
+  end
+  nil
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Tuple.html b/doc/GxG/Tuple.html new file mode 100644 index 0000000..8c057fc --- /dev/null +++ b/doc/GxG/Tuple.html @@ -0,0 +1,419 @@ + + + + + + +class GxG::Tuple - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Tuple +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(key=nil,value=nil) + + click to toggle source + +
+ + +
+ +

Inspired by Python's Tuple class, in this case: allows any object to be a key for any other object Monkey-patch notes: stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i (Array integration)

+ + + + +
+
# File lib/gxg/gxg_elements.rb, line 397
+def initialize(key=nil,value=nil)
+  @thread_safety = ::Mutex.new
+  @key = key
+  @value = value
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <=>(other_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 417
+def <=>(other_value=nil)
+  if other_value.respone_to?(:<=>)
+    if other_value.is_a?(GxG::Tuple)
+      @thread_safety.synchronize { @value <=> other_value.value }
+    else
+      @thread_safety.synchronize { @value <=> other_value }
+    end
+  else
+    # taking liberties here, if other object does not support compare, then this obj is construed as greater than. (bias)
+    1
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 432
+def inspect()
+  @thread_safety.synchronize { "#{@key.inspect} => #{@value.inspect}" }
+end
+
+ +
+ + + + +
+ + +
+ +
+ key() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 403
+def key()
+  @thread_safety.synchronize { @key }
+end
+
+ +
+ + + + +
+ + +
+ +
+ key=(new_key=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 406
+def key= (new_key=nil)
+  @thread_safety.synchronize { @key = new_key }
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_s() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 429
+def to_s()
+  @thread_safety.synchronize { @value.to_s }
+end
+
+ +
+ + + + +
+ + +
+ +
+ value() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 410
+def value()
+  @thread_safety.synchronize { @value }
+end
+
+ +
+ + + + +
+ + +
+ +
+ value=(new_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 413
+def value= (new_value=nil)
+  @thread_safety.synchronize { @value = new_value }
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Units.html b/doc/GxG/Units.html new file mode 100644 index 0000000..e54436e --- /dev/null +++ b/doc/GxG/Units.html @@ -0,0 +1,266 @@ + + + + + + +module GxG::Units - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Units +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ interpret_units(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_units.rb, line 40
+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
+
+ +
+ + + + +
+ + +
+ +
+ refresh_units_registry() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_units.rb, line 35
+def self.refresh_units_registry()
+  # SOMEDAY: Thread-safety?
+  @@interpreters = self.unit_interpreters()
+end
+
+ +
+ + + + +
+ + +
+ +
+ unit_interpreters(categories=:any) + + click to toggle source + +
+ + +
+ +

this module is used for interpreting text into denominations and multipliers., and general support for various units of measure

+ + + + +
+
# File lib/gxg/gxg_units.rb, line 10
+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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Units/Bits.html b/doc/GxG/Units/Bits.html new file mode 100644 index 0000000..1c8f1b6 --- /dev/null +++ b/doc/GxG/Units/Bits.html @@ -0,0 +1,97 @@ + + + + + + +module GxG::Units::Bits - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Units::Bits +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/GxG/Units/Bytes.html b/doc/GxG/Units/Bytes.html new file mode 100644 index 0000000..eda9ad0 --- /dev/null +++ b/doc/GxG/Units/Bytes.html @@ -0,0 +1,221 @@ + + + + + + +module GxG::Units::Bytes - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module GxG::Units::Bytes +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ categories() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_units.rb, line 73
+def self.categories()
+  [:numeric, :byte, :memory, :storage, :computer]
+end
+
+ +
+ + + + +
+ + +
+ +
+ interpret(quanta_string="",interpret_for_locale=:en_US,interpret_for_numeric_base=10) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_units.rb, line 76
+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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/GxG/Version.html b/doc/GxG/Version.html new file mode 100644 index 0000000..1d810e4 --- /dev/null +++ b/doc/GxG/Version.html @@ -0,0 +1,471 @@ + + + + + + +class GxG::Version - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class GxG::Version +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ new(params={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 7
+def initialize(params={})
+  unless params.is_a?(::Hash)
+    params = {:interpret => (params.to_s)}
+  end
+  @encoded_value = encode(params)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ <=>(other) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 19
+def <=>(other)
+  return @encoded_value <=> other.encoded_value()
+end
+
+ +
+ + + + +
+ + +
+ +
+ encoded_value() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 16
+def encoded_value()
+  @encoded_value
+end
+
+ +
+ + + + +
+ + +
+ +
+ increment(params={:revision => 1}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 28
+def increment(params={:revision => 1})
+  current = decode({:value => @encoded_value})
+  if current[:phase].is_a?(Hash)
+    numeric_phase = current[:phase][:candidate].to_i + 2
+  else
+    case current[:phase]
+    when :alpha
+      numeric_phase = 0
+    when :beta
+      numeric_phase = 1
+    when :release
+      numeric_phase = 99
+    end
+  end
+  if params[:phase].is_a?(Numeric)
+    phase_increment = params[:phase]
+  else
+    phase_increment = 0
+  end
+  new_value = {}
+  #
+  if params[:revision].is_a?(Numeric)
+    if (params[:revision].to_i + current[:revision]) > 99999
+      params[:patch_level] = params[:patch_level].to_i + 1
+      new_value[:revision] = 0
+    else
+      new_value[:revision] = (params[:revision].to_i + current[:revision])
+    end
+  else
+    new_value[:revision] = current[:revision]
+  end
+  #
+  if params[:patch_level].is_a?(Numeric)
+    if (params[:patch_level].to_i + current[:patch_level]) > 9999
+      phase_increment = phase_increment + 1
+      new_value[:patch_level] = 0
+    else
+      new_value[:patch_level] = (params[:patch_level].to_i + current[:patch_level])
+    end
+  else
+    new_value[:patch_level] = current[:patch_level]
+  end
+  #
+  if phase_increment > 0
+    if (phase_increment + numeric_phase) > 99
+      params[:tiny] = params[:tiny].to_i + 1
+      new_value[:phase] = 99
+    else
+      new_value[:phase] = (phase_increment + numeric_phase)
+    end
+  else
+    new_value[:phase] = numeric_phase
+  end
+  #
+  if params[:tiny].is_a?(Numeric)
+    if (params[:tiny].to_i + current[:tiny]) > 999
+      params[:minor] = params[:minor].to_i + 1
+      new_value[:tiny] = 0
+    else
+      new_value[:tiny] = (params[:tiny].to_i + current[:tiny])
+    end
+  else
+    new_value[:tiny] = current[:tiny]
+  end
+  #
+  if params[:minor].is_a?(Numeric)
+    if (params[:minor].to_i + current[:minor]) > 999
+      params[:major] = params[:major].to_i + 1
+      new_value[:minor] = 0
+    else
+      new_value[:minor] = (params[:minor].to_i + current[:minor])
+    end
+  else
+    new_value[:minor] = current[:minor]
+  end
+  #
+  if params[:major].is_a?(Numeric)
+    new_value[:major] = current[:major] + params[:major].to_i
+  else
+    new_value[:major] = current[:major]
+  end
+  # convert phase numeric value to Symbol / Hash
+  if (2..98).include?(new_value[:phase])
+    new_value[:phase] = {:candidate => (new_value[:phase] - 2)}
+  else
+    case new_value[:phase]
+    when 0
+      new_value[:phase] = :alpha
+    when 1
+      new_value[:phase] = :beta
+    when 99
+      new_value[:phase] = :release
+    end
+  end
+  #
+  @encoded_value = encode(new_value)
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ inspect() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 25
+def inspect()
+  value.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_s() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 22
+def to_s()
+  value.inspect
+end
+
+ +
+ + + + +
+ + +
+ +
+ value() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_elements.rb, line 13
+def value()
+  decode({:value => @encoded_value})
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Hash.html b/doc/Hash.html new file mode 100644 index 0000000..23ec6ad --- /dev/null +++ b/doc/Hash.html @@ -0,0 +1,911 @@ + + + + + + +class Hash - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Hash +

+ +
+ +

Alteration to Hash and Array for support of structural processing: additional methods to address elements of, and process, structural combinations of Hashes, Arrays, and ByteArrays SOMEDAY: @Hash extend these to the pain-in-the-ass Struct class as well.

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ gxg_import(the_exported_record=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3579
+def self.gxg_import(the_exported_record=nil)
+  result = nil
+  if the_exported_record.is_a?(Hash)
+    if the_exported_record[:type] == "Array"
+      result = ::Array::gxg_import(the_exported_record)
+    else
+      import_value = Proc.new do |type,value|
+        if value.is_a?(String)
+          begin
+            the_class = eval(type)
+            if the_class.respond_to?(:parse)
+              value = the_class.parse(value)
+            else
+              if the_class.respond_to?(:new)
+                value = the_class.new(value)
+              else
+                if the_class.respond_to?(:try_convert)
+                  value = the_class.try_convert(value)
+                end
+              end
+            end
+          rescue Exception => the_error
+          end
+        end
+        value
+      end
+      if the_exported_record[:type] == "Hash"
+        result = {}
+        import_db = [{:parent => nil, :parent_selector => nil, :object => result, :record => the_exported_record}]
+        while import_db.size > 0 do
+          entry = import_db.shift
+          if entry[:record][:content].is_a?(Hash)
+            entry[:record][:content].each_pair do |selector, value|
+              if value[:type] == "Hash"
+                entry[:object][(selector)] = {}
+                import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+              else
+                if value[:type] == "Array"
+                  entry[:object][(selector)] = []
+                  import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                else
+                  entry[:object][(selector)] = import_value.call(value[:type], value[:content])
+                end
+              end
+            end
+          else
+            if entry[:record][:content].is_a?(Array)
+              entry[:record][:content].each_with_index do |value, selector|
+                if value[:type] == "Hash"
+                  entry[:object][(selector)] = {}
+                  import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                else
+                  if value[:type] == "Array"
+                    entry[:object][(selector)] = []
+                    import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value}
+                  else
+                    entry[:object][(selector)] = import_value.call(value[:type], value[:content])
+                  end
+                end
+              end
+            end
+          end
+        end
+      else
+        result = import_value.call(the_exported_record[:type], the_exported_record[:content])
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(the_hash={},&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3291
+def self.process(the_hash={},&block)
+  new_hash = {}
+  if block.respond_to?(:call)
+    if the_hash.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+      new_hash = the_hash.process!(&block)
+    else
+      raise ArgumentError, "you must pass a Hash or an Array, or a ByteArray"
+    end
+  end
+  new_hash
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ get_at_path(the_path="/") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3466
+def get_at_path(the_path="/")
+  result = nil
+  if the_path == "/"
+    result = self
+  else
+    object_stack = [(self)]
+    path_stack = the_path.split("/")
+    path_stack.to_enum.each do |path_element|
+      element = nil
+      if path_element.size > 0
+        if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          element = path_element.to_i
+        else
+          element = path_element
+          element.gsub!("%2f","/")
+          if element[0] == ":"
+            element = element[(1..-1)].to_sym
+          end
+        end
+      end
+      if element
+        result = object_stack.first[(element)]
+        if result.is_a?(NilClass)
+          break
+        else
+          object_stack.unshift(result)
+        end
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ gxg_export() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3533
+def gxg_export()
+  result = {:type => "Hash", :content => {}}
+  export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}]
+  children_of = Proc.new do |the_parent=nil|
+    list = []
+    export_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  export_record = Proc.new do |the_value|
+    if the_value.is_any?(Integer, Float, String)
+      {:type => (the_value.class.to_s), :content => the_value}
+    else
+      {:type => (the_value.class.to_s), :content => the_value.to_s}
+    end
+  end
+  # Build up export_db:
+  self.search do |the_value, the_selector, the_container|
+    if the_value.is_a?(::Hash)
+      export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Hash", :content => {}}}
+    else
+      if the_value.is_a?(::Array)
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Array", :content => []}}
+      else
+        export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => export_record.call(the_value)}
+      end
+    end
+  end
+  # Collect children export content:
+  link_db =[(export_db[0])]
+  while link_db.size > 0 do
+    entry = link_db.shift
+    children_of.call(entry[:object]).each do |the_child|
+      entry[:record][:content][(the_child[:parent_selector])] = the_child[:record]
+      if the_child[:object].is_any?(Hash, Array)
+        link_db << the_child
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ iterative(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3315
+def iterative(&block)
+  result = []
+  visit = Proc.new do |the_node=nil, accumulator=[]|
+    node_stack = []
+    if the_node
+      node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)})
+      while (node_stack.size > 0) do
+        a_node = node_stack.shift
+        #
+        if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash)
+          a_node[:object].each_pair do |the_key, the_value|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value})
+          end
+        end
+        if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray)
+          a_node[:object].each_with_index do |the_value, the_index|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value})
+          end
+        end
+        #
+        accumulator << a_node
+      end
+    end
+    accumulator
+  end
+  #
+  children_of = Proc.new do |the_db=[], the_parent=nil|
+    list = []
+    the_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  #
+  begin
+    database = visit.call(self,[])
+    link_db = children_of.call(database, self)
+    if block.respond_to?(:call)
+      while (link_db.size > 0) do
+        entry = link_db.shift
+        unless entry[:object].object_id == self.object_id
+          # calls with parameters: the_value, the_key/the_index (the_selector), the_container
+          raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent])
+          if raw_result
+            result << raw_result
+          end
+        end
+        if entry[:object].object_id != nil.object_id
+          children = children_of.call(database, entry[:object])
+          children.each do |child|
+            link_db << child
+          end
+        end
+      end
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ paths_to(the_object=nil,base_path="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3399
+def paths_to(the_object=nil,base_path="")
+  # new idea here:
+  search_results = []
+  unless base_path[0] == "/"
+    base_path = ("/" << base_path)
+  end
+  if base_path.size > 1
+    path_stack = base_path.split("/")[1..-1].reverse
+  else
+    path_stack = []
+  end
+  origin = self.get_at_path(base_path)
+  container_stack = [{:selector => nil, :container => origin, :prefix => "/"}]
+  find_container = Proc.new do |the_container|
+    result = nil
+    container_stack.each_with_index do |entry, index|
+      if entry[:container] == the_container
+        result = entry
+        break
+      end
+    end
+    result
+  end
+  last_container = origin
+  found = false
+  # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}}
+  if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+    origin.process! do |the_value, selector, container|
+      if last_container.object_id != container.object_id
+        container_record = find_container.call(container)
+        if container_record
+          path_stack = container_record[:prefix].split("/").reverse
+          if path_stack.size == 0
+            path_stack << ""
+          end
+        end
+        last_container = container
+      end
+      if selector.is_a?(Symbol)
+        safe_key = (":" + selector.to_s)
+      else
+        safe_key = selector.to_s
+      end
+      safe_key = safe_key.gsub("/","%2f")
+      path_stack.unshift(safe_key)
+      # compare the_value
+      found = false
+      if (the_value == the_object)
+        found = true
+      end
+      if found
+        search_results << ("/" << path_stack.reverse.join("/"))
+      end
+      #
+      if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+        container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))})
+      end
+      path_stack.shift
+      #
+      nil
+    end
+  else
+    search_results << ("/" << path_stack.join("/"))
+  end
+  search_results
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3380
+def process(&block)
+  result = self.clone
+  result.iterative(&block)
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3386
+def process!(&block)
+  self.iterative(&block)
+  self
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+ set_at_path(the_path="/",the_value=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3502
+def set_at_path(the_path="/",the_value=nil)
+  result = nil
+  if the_path != "/"
+    container = self.get_at_path(::File::dirname(the_path))
+    if container
+      raw_selector = ::File::basename(the_path)
+      selector = nil
+      if raw_selector.size > 0
+        if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0
+          selector = raw_selector.to_i
+        else
+          selector = raw_selector
+          selector.gsub!("%2f","/")
+          if selector[0] == ":"
+            selector = selector[(1..-1)].to_sym
+          end
+        end
+      end
+      if selector
+        container[(selector)] = the_value
+        result = container[(selector)]
+      else
+        # ignore double slashes? '//'
+        # break
+      end
+      #
+    end
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ symbolize_keys() + + click to toggle source + +
+ + +
+ +

def to_xml_simple(options={})

+ +
#      keyattr keeproot contentkey noattr rootname
+#      xmldeclaration outputfile noescape suppressempty
+#      anonymoustag indent grouptags noindent attrprefix
+the_options = {:xmldeclaration => true, :rootname => :root, :keeproot => false}
+if options.is_a?(::Hash)
+  if options[:XmlDeclaration].is_any?(::TrueClass, ::FalseClass)
+    the_options[:xmldeclaration] = options[:XmlDeclaration]
+  end
+  if options[:xmldeclaration].is_any?(::TrueClass, ::FalseClass)
+    the_options[:xmldeclaration] = options[:xmldeclaration]
+  end
+  if options[:RootName].is_any?(::String, ::Symbol)
+    the_options[:rootname] = options[:RootName]
+  end
+  if options[:rootname].is_any?(::String, ::Symbol)
+    the_options[:rootname] = options[:rootname]
+  end
+  if options[:KeepRoot].is_any?(::TrueClass, ::FalseClass)
+    the_options[:keeproot] = options[:KeepRoot]
+  end
+  if options[:keeproot].is_any?(::TrueClass, ::FalseClass)
+    the_options[:keeproot] = options[:keeproot]
+  end
+else
+  raise ArgumentError, "You MUST provide options in the form of a Hash."
+end
+::XmlSimple.xml_out(self, the_options).gsub("\n", "")
+
+ +

end

+ +

def to_json()

+ +
::JSON.generate(self)
+
+ +

end

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3684
+def symbolize_keys()
+  self.process! do |value, selector, container|
+    if container.is_a?(::Hash)
+      unless selector.is_a?(::Symbol)
+        container[(selector.to_sym)] = container.delete(selector)
+      end
+    end
+    nil
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/IO.html b/doc/IO.html new file mode 100644 index 0000000..ed7f818 --- /dev/null +++ b/doc/IO.html @@ -0,0 +1,756 @@ + + + + + + +class IO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class IO +

+ +
+ +
IO/StringIO/File alterations
+Fill in missing constants according to platform.
+### win32 stuff
+ +

006 define F_SETFL 1 007 define F_SETFD 2 008 define F_GETFL 3 009 010 define _O_RDONLY 0x0000 open for reading only

+ +

011 define _O_WRONLY 0x0001 open for writing only

+ +

012 define _O_RDWR 0x0002 /* open for reading and writing */ 013 014 define _O_NONBLOCK 0x0004 015 016 define _O_APPEND 0x0008 /* writes done at eof */ 017 define _O_CREAT 0x0100 /* create and open file */ 018 define _O_TRUNC 0x0200 /* open and truncate */ 019 define _O_EXCL 0x0400 /* open only if file doesn't already exist */ 020 define _O_TEXT 0x4000 /* file mode is text (translated) */ 021 define _O_BINARY 0x8000 /* file mode is binary (untranslated) */ 022 define _O_ACCMODE 0x10000 023 024 define _O_NOINHERIT 0 025 define O_NOINHERIT _O_NOINHERIT 026 027 define O_RDONLY _O_RDONLY 028 define O_WRONLY _O_WRONLY 029 define O_RDWR _O_RDWR 030 031 define O_NONBLOCK _O_NONBLOCK 032 033 define O_APPEND _O_APPEND 034 define O_CREAT _O_CREAT 035 define O_TRUNC _O_TRUNC 036 define O_EXCL _O_EXCL 037 define O_TEXT _O_TEXT 038 define O_BINARY _O_BINARY 039 define O_ACCMODE _O_ACCMODE

+ +
### end win32 stuff
+     @@valid_modes[:flags][:io][:tty] = 0x00000010
+     @@valid_modes[:flags][:io][:noctty] = ::IO::NOCTTY
+     @@valid_modes[:flags][:io][:duplex] = 0x00000020
+     @@valid_modes[:flags][:io][:append] = ::IO::APPEND
+     @@valid_modes[:flags][:io][:create] = ::IO::CREAT
+     @@valid_modes[:flags][:io][:exclusive] = ::IO::EXCL
+     @@valid_modes[:flags][:io][:wsplit] = 0x00000200
+     @@valid_modes[:flags][:io][:wsplit_initialized] = 0x00000400
+     @@valid_modes[:flags][:io][:trunc] = ::IO::TRUNC
+     @@valid_modes[:flags][:io][:text] = ::IO::TEXT
+     @@valid_modes[:flags][:io][:setenc_by_bom] = 0x00100000
+See : http://boop.ankhcraft.com/library/ruby-api/dc/dac/io_8h.html
+Also : http://boop.ankhcraft.com/library/ruby-api/d2/d4d/file_8h.html
+also : http://boop.ankhcraft.com/library/ruby-api/d6/d13/file_8c.html
+ +
+ + +
+ + + + + +
+
+

Constants

+
+
+ +
DSYNC + +

Note: need to verify this int value before using:

+ + +
DUPLEX + +

Note: need to verify this int value before using:

+ + +
NOATIME + +

Note: need to verify this int value before using:

+ + +
NOFOLLOW + +

Note: need to verify this int value before using:

+ + +
RSYNC + +

Note: need to verify this int value before using:

+ + +
SETENC_BY_BOM + +
Note: need to verify this int value before using:
+ +

SETENC_BY_BOM = 0x00100000

+ + +
TEXT + +

What is the flag value under Win32?

+ + +
TTY + +

Note: need to verify this int value before using:

+ + +
WSPLIT + +

Note: need to verify this int value before using:

+ + +
WSPLIT_INITIALIZED + +

Note: need to verify this int value before using:

+ + +
+
+ + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ flags() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 275
+def flags()
+  result = []
+  current = self.fcntl(::Fcntl::F_GETFL)
+  flags = {}
+  if self.binmode?()
+    result << :binary
+    if self.external_encoding == ::Encoding::ASCII_8BIT
+      result << :setenc_by_bom
+    end
+  else
+    result << :text
+    result << :wsplit
+  end
+  if self.tty?
+    result << :tty
+  end
+  #
+  #flag[:read] = 0x00000001
+  #flag[:write] = 0x00000002
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  #flag[:binmode] = 0x00000004
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  flags[:read] = ::Fcntl::O_RDONLY
+  flags[:write] = ::Fcntl::O_WRONLY
+  flags[:readwrite] = ::Fcntl::O_RDWR
+  #flags[:binary] = 0x00000004
+  flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
+  flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata.
+  flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
+  #flags[:tty] = 0x00000010
+  flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY
+  flags[:duplex] = 0x00000020
+  flags[:append] = ::Fcntl::O_APPEND
+  flags[:create] = ::Fcntl::O_CREAT
+  flags[:exclusive] = ::Fcntl::O_EXCL
+  # flags[:wsplit] = 0x00000200
+  flags[:wsplit_initialized] = 0x00000400
+  flags[:trunc] = ::Fcntl::O_TRUNC
+  #flags[:text] = 0x00001000
+  #flags[:setenc_by_bom] = 0x00100000
+  flags[:seek_set] = ::File::SEEK_SET
+  flags[:seek_current] = ::File::SEEK_CUR
+  flags[:seek_end] = ::File::SEEK_END
+  # ### file region locking:
+  flags[:shared_lock] = ::File::LOCK_SH # for reading
+  flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking)
+  flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
+  flags[:unlock] = ::File::LOCK_UN
+  #
+  flags[:nonblocking] = ::Fcntl::O_NONBLOCK
+  flags[:ndelay] = ::Fcntl::O_NDELAY
+  flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks.
+  flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file.
+  flags[:match_noescape] = ::File::FNM_NOESCAPE #
+  flags[:match_pathname] = ::File::FNM_PATHNAME #
+  flags[:match_dotmatch] = ::File::FNM_DOTMATCH #
+  flags[:match_casefold] = ::File::FNM_CASEFOLD #
+  flags[:match_systemcase] = ::File::FNM_SYSCASE #
+  #
+  flags.keys.to_enum.each do |the_flag_key|
+    if (current & flags[(the_flag_key)]) == flags[(the_flag_key)]
+      case the_flag_key
+      when :readwrite
+        if result.index(:write)
+          result << :read
+        else
+          if result.index(:read)
+            result << :write
+          end
+        end
+      when :wsplit_initialized
+        if result.index(:wsplit)
+          result[(result.index(:wsplit))] = :wsplit_initialized
+        else
+          result << :wsplit_initialized
+        end
+      else
+        result << the_flag_key
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ latency_reading(old_reading=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 246
+def latency_reading(old_reading=nil,&block)
+  unless old_reading.is_a?(::Hash)
+    old_reading = {:low => nil, :last => nil, :high => nil}
+  end
+  new_reading = {:low => nil, :last => nil, :high => nil}
+  reading = millisecond_latency(&block)
+  if old_reading[:low]
+    if reading[:milliseconds] < old_reading[:low]
+      new_reading[:low] = reading[:milliseconds].dup
+    else
+      new_reading[:low] = old_reading[:low]
+    end
+  else
+    new_reading[:low] = reading[:milliseconds].dup
+  end
+  new_reading[:last] = reading[:milliseconds].dup
+  if old_reading[:high]
+    if reading[:milliseconds] > old_reading[:high]
+      new_reading[:high] = reading[:milliseconds].dup
+    else
+      new_reading[:high] = old_reading[:high]
+    end
+  else
+    new_reading[:high] = reading[:milliseconds].dup
+  end
+  #
+  {:result => reading[:result], :reading => new_reading}
+end
+
+ +
+ + + + +
+ + +
+ +
+ profile(params={:follow_symlinks => true}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 360
+def profile(params={:follow_symlinks => true})
+  platform_details = GxG::SYSTEM.platform()
+  result = {}
+  #
+  if params[:follow_symlinks]
+    raw_stat = self.to_io.stat
+  else
+    raw_stat = self.to_io.lstat
+  end
+  raw_mode = raw_stat.mode.to_s(base=8)
+  if raw_mode.size < 7
+    raw_mode = ("0" << raw_mode)
+  end
+  set_user_id = false
+  set_group_id = false
+  set_sticky_bit = false
+  if (raw_mode[3].to_i & 4) == 4
+    set_user_id = true
+  end
+  if (raw_mode[3].to_i & 2) == 2
+    set_group_id = true
+  end
+  if (raw_mode[3].to_i & 1) ==  1
+    set_sticky_bit = true
+  end
+  #
+  #
+  raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }
+  #
+  file_handle = nil
+  GxG::Engine::file_descriptors.to_enum.each do |the_handle|
+    if self.fileno == the_handle.fileno
+      file_handle = the_handle
+      break
+    end
+  end
+  # LATER: GxG::IO::IO.profile : patch into file/stream type recognition engine.
+  case raw_stat.ftype.to_s.downcase.to_sym
+  when :link
+    # are .lnk files handled properly on win32 for this?
+    result[:type] =  :symlink
+  when :file
+    if file_handle
+      if self.tty?
+        result[:type] =  :tty
+      else
+        the_extension = ::File::basename(file_handle.to_path).split(".")[-1].to_s.downcase
+        if the_extension == "lnk"
+          result[:type] =  :symlink
+        else
+          if ["so","la","a","dylib","dll","class"].include?(the_extension)
+            result[:type] =  :library
+          else
+            if (["out","exe","misc","java","jar", "sh", "rb", "py", "pyc"].include?(the_extension) || raw_stat.executable_real?)
+              result[:type] =  :application
+            else
+              result[:type] = :file
+            end
+          end
+        end
+      end
+    end
+  when :fifo
+    result[:type] =  :fifo
+  when :characterspecial
+    result[:type] =  :character_device
+  when :blockspecial
+    result[:type] =  :block_device
+  when :socket
+    result[:type] =  :socket
+  when :directory
+    result[:type] =  :directory
+  when :unknown
+    result[:type] =  :unknown
+    if raw_stat.pipe?
+      result[:type] =  :pipe
+    end
+  else
+    if raw_stat.pipe?
+      result[:type] =  :pipe
+    end
+  end
+  result[:owner_type] = result[:type]
+  result[:on_device] = raw_stat.dev.to_s(base=10).to_i
+  if result[:on_device]
+    result[:on_device_major] = raw_stat.dev_major
+    result[:on_device_minor] = raw_stat.dev_minor
+  end
+  result[:is_device] = raw_stat.rdev
+  if result[:is_device]
+    result[:is_device_major] = raw_stat.rdev_major
+    result[:is_device_minor] = raw_stat.rdev_minor
+  end
+  result[:inode] = raw_stat.ino.to_i
+  #
+  result[:flags] = self.flags()
+  #
+  if file_handle
+    # man fstat : mode decoder-ring table:
+    #
+    #           S_IFMT     0170000   bit mask for the file type bit fields
+    #           S_IFSOCK   0140000   socket
+    #           S_IFLNK    0120000   symbolic link
+    #           S_IFREG    0100000   regular file
+    #           S_IFBLK    0060000   block device
+    #           S_IFDIR    0040000   directory
+    #           S_IFCHR    0020000   character device
+    #           S_IFIFO    0010000   FIFO
+    #
+    #           S_ISUID    0004000   set UID bit
+    #           S_ISGID    0002000   set-group-ID bit (see below)
+    #           S_ISVTX    0001000   sticky bit (see below)
+    #
+    #           S_IRWXU    00700     mask for file owner permissions
+    #           S_IRUSR    00400     owner has read permission
+    #           S_IWUSR    00200     owner has write permission
+    #           S_IXUSR    00100     owner has execute permission
+    #
+    #           S_IRWXG    00070     mask for group permissions
+    #           S_IRGRP    00040     group has read permission
+    #           S_IWGRP    00020     group has write permission
+    #           S_IXGRP    00010     group has execute permission
+    #
+    #           S_IRWXO    00007     mask for permissions for others (not in group)
+    #           S_IROTH    00004     others have read permission
+    #           S_IWOTH    00002     others have write permission
+    #           S_IXOTH    00001     others have execute permission
+    #
+    # <file>.stat table: (mode example : 0100664)
+    #      0700       rwx mask for owner
+    #      0400       r for owner
+    #      0200       w for owner
+    #      0100       x for owner
+    #      0070       rwx mask for group
+    #      0040       r for group
+    #      0020       w for group
+    #      0010       x for group
+    #      0007       rwx mask for other
+    #      0004       r for other
+    #      0002       w for other
+    #      0001       x for other
+    #      4000       Set user ID on execution
+    #      2000       Set group ID on execution
+    #      1000       Save swapped text, even after use
+    result[:hardlinks_to] = raw_stat.nlink.to_i
+    result[:user_id] = raw_stat.uid.to_i
+    result[:group_id] = raw_stat.gid.to_i
+    result[:size] = raw_stat.size
+    result[:block_size] = raw_stat.blksize
+    result[:blocks] = raw_stat.blocks
+    result[:accessed] = DateTime::parse(raw_stat.atime.to_s)
+    result[:modified] = DateTime::parse(raw_stat.mtime.to_s)
+    result[:status_modified] = DateTime::parse(raw_stat.ctime.to_s)
+    # File permissions work in an ACL style, even if based only upon Unix-style permissions.
+    #
+    result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+    if set_user_id
+      result[:flags] << :user_id_on_execute
+    end
+    if set_group_id
+      result[:flags] << :group_id_on_execute
+    end
+    if set_sticky_bit
+      result[:flags] << :sticky_bit
+    end
+    # owner permissions
+    if (raw_mode[4].to_i & 4) == 4
+      result[:permissions][:owner][:read] = true
+    end
+    if (raw_mode[4].to_i & 2) == 2
+      result[:permissions][:owner][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:owner][:rename] = true
+        result[:permissions][:owner][:move] = true
+        result[:permissions][:owner][:destroy] = true
+        result[:permissions][:owner][:create] = true
+      end
+    end
+    if (raw_mode[4].to_i & 1) == 1
+      result[:permissions][:owner][:execute] = true
+    end
+    # group permissions
+    if (raw_mode[5].to_i & 4) == 4
+      result[:permissions][:owner][:read] = true
+    end
+    if (raw_mode[5].to_i & 2) == 2
+      result[:permissions][:group][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:group][:rename] = true
+        result[:permissions][:group][:move] = true
+        result[:permissions][:group][:destroy] = true
+        result[:permissions][:group][:create] = true
+      end
+    end
+    if (raw_mode[5].to_i & 1) == 1
+      result[:permissions][:group][:execute] = true
+    end
+    # other permissions
+    if (raw_mode[6].to_i & 4) == 4
+      result[:permissions][:other][:read] = true
+    end
+    if (raw_mode[6].to_i & 2) == 2
+      result[:permissions][:other][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:other][:rename] = true
+        result[:permissions][:other][:move] = true
+        result[:permissions][:other][:destroy] = true
+        result[:permissions][:other][:create] = true
+      end
+    end
+    if (raw_mode[6].to_i & 1) == 1
+      result[:permissions][:other][:execute] = true
+    end
+    # effective (this user) permissions (while opened, supercedes normal effective file system permissions)
+    if result[:flags].index(:read)
+      result[:permissions][:effective][:read] = true
+      #
+    end
+    if result[:flags].index(:write)
+      result[:permissions][:effective][:write] = true
+      if platform_details[:platform] == :windows
+        # :windows way of reading :rename, :move, and :destroy permission for file/directory.
+      else
+        result[:permissions][:effective][:rename] = true
+        result[:permissions][:effective][:move] = true
+        result[:permissions][:effective][:destroy] = true
+        result[:permissions][:effective][:create] = true
+      end
+    end
+    if raw_stat.executable?
+      result[:permissions][:effective][:execute] = true
+    end
+    #
+  else
+    # in-memory object - no fs involvement
+    # ok ... why? Well, I want objects to be able to pass through my unwritten system stuff : experimental, w/o special handling.
+    result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone}
+    #
+    if result[:flags].index(:read)
+      result[:permissions][:effective][:read] = true
+      result[:permissions][:owner][:read] = true
+      result[:permissions][:group][:read] = true
+      result[:permissions][:other][:read] = true
+      #
+    end
+    #
+    if result[:flags].index(:write)
+      result[:permissions][:effective][:write] = true
+      result[:permissions][:owner][:write] = true
+      result[:permissions][:group][:write] = true
+      result[:permissions][:other][:write] = true
+    end
+    #
+  end
+  #
+  result[:mode] = {}
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ read_latency() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 239
+def read_latency()
+  unless self.instance_variable_defined?(:@read_latency)
+    @read_latency= {:low => nil, :last => nil, :high => nil}
+  end
+  @read_latency.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ write_latency() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 232
+def write_latency()
+  unless self.instance_variable_defined?(:@write_latency)
+    @write_latency= {:low => nil, :last => nil, :high => nil}
+  end
+  @write_latency.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Integer.html b/doc/Integer.html new file mode 100644 index 0000000..61534c3 --- /dev/null +++ b/doc/Integer.html @@ -0,0 +1,106 @@ + + + + + + +class Integer - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Integer +

+ +
+ +

Alteration to <Integer>.times block call loop

+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Kernel.html b/doc/Kernel.html new file mode 100644 index 0000000..f7daa04 --- /dev/null +++ b/doc/Kernel.html @@ -0,0 +1,345 @@ + + + + + + +module Kernel - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Kernel +

+ +
+ +

shell command:

+
+
+ +

Additional alternations to Kernel/Object class: Data element class support, quota support functions

+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ content_size_used(counted=[], options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 243
+def content_size_used(counted=[], options={})
+  # returns how many bytes are consumed by this *instance* (and elements) and its instance variables (and elements) all the way down.
+  unless counted.is_a?(Array)
+    if counted.is_a?(Hash)
+      options = counted
+    end
+    counted = []
+  end
+  exclusions = [nil]
+  if options[:exclude]
+    unless options[:exclude].is_a?(Array)
+      options[:exclude] = [(options[:exclude])]
+    end
+    exclusions = (options[:exclude] << nil)
+  end
+  count = 0
+  unless (counted.include?(self) || exclusions.include?(self))
+    counted << self
+    if self.is_any?(::Array,::Hash,::Struct)
+      self.search do |entry,selector,container|
+        unless counted.include?(entry)
+          unless selector.is_a?(Numeric)
+            count += selector.content_size_used(counted, {:exclude => exclusions})
+          end
+          count += entry.content_size_used(counted, {:exclude => exclusions})
+        end
+      end
+    else
+      unless self.is_a?(::GxG::ByteArray)
+        if self.respond_to?(:bytesize)
+          count = self.bytesize
+        else
+          if self.respond_to?(:size)
+            count = self.size
+          else
+            # TODO: cull the size of more exotic classes by class:
+            count = 0
+          end
+        end
+      end
+    end
+  end
+  #
+  self.instance_variables.to_enum.each do |ivar|
+    # must decode from symbol to actual instance var
+    ivar = self.instance_eval(ivar.to_s)
+    unless (counted.include?(ivar) || exclusions.include?(ivar))
+      if ivar.is_any?(::Array,::Hash,::Struct)
+        ivar.search do |entry,selector,container|
+          unless (counted.include?(entry) || exclusions.include?(entry))
+            unless selector.is_a?(Numeric)
+              # Symbols and Strings in Hashes take up heap space, so lets count it.
+              count += selector.content_size_used(counted, {:exclude => exclusions})
+            end
+            count += entry.content_size_used(counted, {:exclude => exclusions})
+          end
+        end
+      else
+        unless (counted.include?(ivar) || exclusions.include?(ivar))
+          count += ivar.content_size_used(counted, {:exclude => exclusions}).to_i
+          # Subsequent references to this object will only incur an RVALUE slot count (me thinks)
+          counted << ivar
+        end
+      end
+    end
+  end
+  count
+end
+
+ +
+ + + + +
+ + +
+ +
+ shell(command=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 3
+def shell(command=nil,&block)
+  if command.to_s.size > 0
+    block.call(`#{command.to_s}`)
+    true
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ slots_used(counted=[], options={}) + + click to toggle source + +
+ + +
+ +

alias :stock_enum_for :enum_for alias :stock_to_enum :to_enum def enum_for(method=:each,*args)

+ +
GxG::Enumerator.new(self,method,*args)
+
+ +

end alias :to_enum :enum_for

+ + + + +
+
# File lib/gxg.rb, line 191
+def slots_used(counted=[], options={})
+  # returns how many heap slots are consumed by this *instance* (and elements) and its instance variables (and elements) all the way down.
+  # General Research: see memprof for how much is consumed by a Module or Class or Method.
+  # TODO: ::Kernel#slots_used : find the byte-size of a given :method and add it to slot_count initial value.
+  unless counted.is_a?(Array)
+    if counted.is_a?(Hash)
+      options = counted
+    end
+    counted = []
+  end
+  # warning: assumes references to nil incur no RVALUE allocation.
+  exclusions = [nil]
+  if options[:exclude]
+    unless options[:exclude].is_a?(Array)
+      options[:exclude] = [(options[:exclude])]
+    end
+    exclusions = (options[:exclude] << nil)
+  end
+  slot_count = 0
+  unless (counted.include?(self) || exclusions.include?(self))
+    counted << self
+    slot_count = 1
+    if self.is_any?(::Array,::Hash,::Set,::Struct)
+      self.search do |entry,selector,container|
+        unless (counted.include?(entry) || exclusions.include?(entry))
+          unless selector.is_a?(Numeric)
+            slot_count += selector.slots_used(counted, {:exclude => exclusions})
+          end
+          slot_count += entry.slots_used(counted, {:exclude => exclusions})
+        end
+      end
+    end
+  end
+  self.instance_variables.to_enum.each do |ivar|
+    unless (counted.include?(ivar) || exclusions.include?(ivar))
+      if ivar.is_any?(::Array,::Hash,::Set,::Struct)
+        ivar.search do |entry,selector,container|
+          unless (counted.include?(entry) || exclusions.include?(entry))
+            unless selector.is_a?(Numeric)
+              slot_count += selector.slots_used(counted, {:exclude => exclusions})
+            end
+            slot_count += entry.slots_used(counted, {:exclude => exclusions})
+          end
+        end
+      else
+        slot_count += ivar.slots_used(counted, {:exclude => exclusions})
+      end
+    end
+  end
+  slot_count
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/NilClass.html b/doc/NilClass.html new file mode 100644 index 0000000..a9fa0f5 --- /dev/null +++ b/doc/NilClass.html @@ -0,0 +1,258 @@ + + + + + + +class NilClass - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class NilClass +

+ +
+ +

require facet classes here (prior to overrides), and ensure smooth integration with overrides.

+ +

### dup and clone support for Fixnum, Integer, Float, (and BigDecimal at some point) : don't even bother to tell me I gain nothing – I've heard this before.

+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 44
+def clone()
+  initialize_clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 39
+def initialize_clone
+  nil
+end
+
+ +
+ + +
+ Also aliased as: initialize_dup, dup +
+ + + +
+ + +
+ +
+ initialize_dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+
+ + + + diff --git a/doc/Object.html b/doc/Object.html new file mode 100644 index 0000000..dc566a6 --- /dev/null +++ b/doc/Object.html @@ -0,0 +1,434 @@ + + + + + + +class Object - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Object +

+ +
+
+ +

Preliminary alternations to Kernel/Object class: Actor/Event support

+
+ +

Alterations to Object class: GxG::Engine dependency - quota supports

+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ actor?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 134
+def actor?()
+  false
+end
+
+ +
+ + + + +
+ + +
+ +
+ alive?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 129
+def alive?()
+  # Why: adjusting the entire object space to dealing with possibilities introduced by Celluloid.
+  true
+end
+
+ +
+ + + + +
+ + +
+ +
+ handle_error(the_error={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 138
+def handle_error(the_error={})
+  if the_error[:error].is_a?(::Exception)
+    log_error(the_error)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ heap_used(options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 436
+def heap_used(options={})
+  unless options.is_a?(Hash)
+    options={}
+  end
+  (self.content_size_used(options).to_i + (self.slots_used(options).to_i * GxG::Engine::profile[:slot_size]).to_i)
+end
+
+ +
+ + + + +
+ + +
+ +
+ is_any?(*args) + + click to toggle source + +
+ + +
+ +

public methods

+ + + + +
+
# File lib/gxg.rb, line 116
+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
+
+ +
+ + + + +
+ + +
+ +
+ millisecond_latency(*args,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 408
+def millisecond_latency(*args,&block)
+  if block
+    starting = Time.now.to_f
+    result = block.call(*args)
+    ending = Time.now.to_f
+    # Results are approximate: does not include the time ruby needs to return from the call,
+    # assign the result RVALUE, collect a time, convert it to a Float, and assign ending RVALUE..
+    # However, it is pretty darn close.
+    {:result => result, :milliseconds => (ending - starting)}
+  else
+    # If :milliseconds are nil then the block was not passed and since it never ran, there is no latency to count up.
+    # Simply do a return_var[:milliseconds].to_f for auto-accumulators w/o post-call comparison.
+    {:result => nil, :milliseconds => 0.0}
+  end
+  # Attribution : http://stackoverflow.com/questions/2289381/how-to-time-an-operation-in-milliseconds-in-ruby
+end
+
+ +
+ + + + +
+ + +
+ +
+ serialize() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg.rb, line 144
+def serialize()
+  if self.is_any?(::Array, ::Hash, ::Set, ::Struct)
+    data = self.process do |entry, selector|
+      entry.serialize()
+    end
+    begin
+      ("structure:" + ::Marshal.dump(data).encode64())
+    rescue Exception => the_error
+      "marshal:BAgw"
+    end
+  else
+    if self.is_a?(::String)
+      if self.serialized?()
+        self
+      else
+        ("marshal:" + ::Marshal.dump(self).encode64())
+      end
+    else
+      # by default, or upon error, returns marshaled nil, must override to get other serialization
+      begin
+        ("marshal:" + ::Marshal.dump(self).encode64())
+      rescue Exception => the_error
+        "marshal:BAgw"
+      end
+    end
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Padrino.html b/doc/Padrino.html new file mode 100644 index 0000000..0bc3965 --- /dev/null +++ b/doc/Padrino.html @@ -0,0 +1,97 @@ + + + + + + +module Padrino - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Padrino +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Padrino/Helpers.html b/doc/Padrino/Helpers.html new file mode 100644 index 0000000..3a8377a --- /dev/null +++ b/doc/Padrino/Helpers.html @@ -0,0 +1,97 @@ + + + + + + +module Padrino::Helpers - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Padrino::Helpers +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+ + + + diff --git a/doc/Padrino/Helpers/TagHelpers.html b/doc/Padrino/Helpers/TagHelpers.html new file mode 100644 index 0000000..7ec743f --- /dev/null +++ b/doc/Padrino/Helpers/TagHelpers.html @@ -0,0 +1,728 @@ + + + + + + +module Padrino::Helpers::TagHelpers - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module Padrino::Helpers::TagHelpers +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ accordian_tag(the_id=nil, sections=nil) + + click to toggle source + +
+ + +
+ +

See: github.com/padrino/padrino-framework/blob/master/padrino-helpers/lib/padrino-helpers/tag_helpers.rb

+ + + + +
+
# File lib/gxg/tag_helpers.rb, line 7
+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
+
+ +
+ + + + +
+ + +
+ +
+ autocomplete_tag(the_id=nil, the_terms=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 28
+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
+
+ +
+ + + + +
+ + +
+ +
+ control_group(content="", options={:vertical => false}, &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 119
+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
+
+ +
+ + + + +
+ + +
+ +
+ currency_string(amount=0.0) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 240
+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
+
+ +
+ + + + +
+ + +
+ +
+ datepicker_tag(the_id=nil,title="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 141
+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
+
+ +
+ + + + +
+ + +
+ +
+ dialog_tag(the_id=nil, title="", content="", &block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 151
+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
+
+ +
+ + + + +
+ + +
+ +
+ gxg_button_tag(the_id=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 50
+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
+
+ +
+ + + + +
+ + +
+ +
+ radio_group(legend=nil,button_names=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 107
+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
+
+ +
+ + + + +
+ + +
+ +
+ radio_tag(the_id=nil,title=nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 97
+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
+
+ +
+ + + + +
+ + +
+ +
+ slider_tag(the_id=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 167
+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
+
+ +
+ + + + +
+ + +
+ +
+ spinner_tab(the_id=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 185
+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
+
+ +
+ + + + +
+ + +
+ +
+ tabs_tag(the_id=nil, sections=nil, options={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/tag_helpers.rb, line 206
+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
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/README_md.html b/doc/README_md.html new file mode 100644 index 0000000..61879f1 --- /dev/null +++ b/doc/README_md.html @@ -0,0 +1,89 @@ + + + + + + +README - RDoc Documentation + + + + + + + + + + + + + + + + + +
+ +

gxg-framework

+ +

GxG Framework for JRuby

+
+ + + + + diff --git a/doc/String.html b/doc/String.html new file mode 100644 index 0000000..9584256 --- /dev/null +++ b/doc/String.html @@ -0,0 +1,2649 @@ + + + + + + +class String - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class String +

+ +
+ +

Alteration to <string> class

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ transcode_options(destination_encoding, source_encoding, options={}) + + click to toggle source + +
+ + +
+ +

:macos text handling notes: hints.macworld.com/article.php?story=20060825071728278 Also, research UTF8_MAC <-> UTF8_HFS <-> UTF8 normalization conversions.

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1850
+def self.transcode_options(destination_encoding, source_encoding, options={})
+  # TODO: ::String::transcode_options : complete transcoding back-end supports.
+  # select a default encoding_option set given the two encodings, then merge options over that and return.  Explore Hash deep-merge ideas.
+  #
+  new_options = {}
+  # fallback set/method or replace char
+  if options[:fallback]
+    # Note: I *confirmed* (thanks VGoff) that :invalid/:undef/:replace and :fallback are exclusive - So maybe this is really an optimized :fallback thing?
+    #    Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method,
+    #    or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder.
+    #    Its value can be any encoding until it can be converted into the destination encoding of the transcoder.
+    if (options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || options[:fallback].respond_to?(:[]))
+      # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition.
+      new_options[:fallback] = options[:fallback].dup
+    end
+  else
+    #
+    if options[:invalid] == :replace
+      #    If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to
+      #    raise the Encoding::InvalidByteSequenceError exception
+      new_options[:invalid] = :replace
+    end
+    #
+    if options[:undef] == :replace
+      #    If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character.
+      #    The default is to raise the Encoding::UndefinedConversionError.
+      new_options[:undef] = :replace
+    end
+    #
+    if options[:replace]
+      #    Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise.
+      new_options[:replace] = options[:replace].dup
+    end
+    #
+  end
+  # xml
+  if options[:xml]
+    #    The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character
+    #    references. ‘&’, ‘<’, and ‘>’ are converted to “&amp;”, “&lt;”, and “&gt;”, respectively. If the value is :attr, encode also quotes the replacement
+    #    result (using ‘“’), and replaces ‘”’ with “&quot;”.
+    if [:text, :attr].include?(options[:xml])
+      new_options[:xml] = options[:xml].dup
+    end
+  end
+  #
+  if options[:universal_newline]
+    #    Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true.
+    unless (new_options[:cr_newline] || new_options[:crlf_newline])
+      if options[:universal_newline] == true
+        new_options[:universal_newline] = true
+      end
+    end
+  else
+    if options[:crlf_newline]
+      #    Replaces LF (“n”) with CRLF (“rn”) if value is true.
+      unless (new_options[:cr_newline] || new_options[:universal_newline])
+        if options[:crlf_newline] == true
+          new_options[:crlf_newline] = true
+        end
+      end
+    else
+      if options[:cr_newline]
+        #    Replaces LF (“n”) with CR (“r”) if value is true.
+        unless (new_options[:crlf_newline] || new_options[:universal_newline])
+          if options[:cr_newline] == true
+            new_options[:cr_newline] = true
+          end
+        end
+      else
+        # default
+      end
+    end
+  end
+  #
+  default_options = {}
+  # find the default conversion options given the destination and source encodings
+  # then ...
+  new_options
+  # deep-merge new_options into a provided default_options
+  # then return default_options
+  # default_options
+end
+
+ +
+ + + + +
+ + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ base64?() + + click to toggle source + +
+ + +
+ +

Base64 Stuff:

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2932
+def base64?()
+  # RFC 4648
+  # SOMEDAY: use regex based detection, current is open to some bugs.
+  # See: http://ruby.about.com/od/advancedruby/ss/Base64-In-Ruby.htm
+  # See: http://www.perlmonks.org/?node_id=775820
+  # See: http://mattfaus.com/blog/2007/02/14/base64-regular-expression/
+  # See: http://stackoverflow.com/questions/475074/regex-to-parse-or-validate-base64-data
+  # Regex Testing: http://www.myregexp.com/signedJar.html
+  # For now:
+  begin
+    # Review : So, for a work-around - keep adding exclusion tests to this conditional set as you discover them.
+    if (self.size > 0) && (not ["html","form","page","head","body"].include?(self.downcase))
+      # Review : *almost perfect* - words like 'html' 'form' 'page' get mangled because this regex senses they are base64 when they are not! (consult regex expert for remedy)
+      if self.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/)
+        true
+      else
+        false
+      end
+    else
+      false
+    end
+  rescue Exception => the_error
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ byte_at(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2733
+def byte_at(*args)
+  # Random-access indexed byte value retrieval. Returns a Integer or nil.
+  # why?  This is to pluck a single byte value out of a string as if it were merely a string of bytes. 1.8.7 used to provide a [] method for this purpose.
+  if args[0].is_a?(Numeric)
+    args[0] = args[0].to_i
+  else
+    raise ArgumentError.new("The parameter needs to be a Numeric, you provided #{args[0].inspect}")
+  end
+  if self.bytesize > 0
+    if self.encoding == ::Encoding::ASCII_8BIT
+      self[(args[0])].ord
+    else
+      self.slice_bytes(args[0])[0].ord
+    end
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ bytes_at(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2752
+def bytes_at(*args)
+  # Returns GxG::ByteArray
+  unless args.size > 0
+    args = [(0..-1)]
+  end
+  GxG::ByteArray.new(1,self.slice_bytes(*args))
+end
+
+ +
+ + + + +
+ + +
+ +
+ camel_case?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2278
+def camel_case?()
+  # self.match(/([A-Z][a-z]+[A-Z][a-zA-Z]+)/)
+  if self.match(/[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*/)      
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ decode64() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2967
+def decode64()
+  # RFC 4648
+  if self.base64?()
+    ::Base64::strict_decode64(self)
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ decode64!() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2984
+def decode64!()
+  data = self.decode64()
+  if data != self
+    self.replace(data)
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ decrypt(withkey="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3029
+def decrypt(withkey="")
+  #
+  if withkey.to_s.size > 0
+    the_encoding = self.encoding()
+    keybytes = ::GxG::ByteArray.new(withkey.to_s)
+    container = ::GxG::ByteArray.new(self)
+    #
+    container.each_index do |index|
+      the_value = container[(index)]
+      #
+      keybytes.each do |the_byte|
+        [127,63,31,15,7,3,1,0].each do |the_bit|
+          if the_byte > the_bit
+            the_value -= 1
+            if the_value < 0
+              the_value = 255
+            end
+          else
+            the_value += 1
+            if the_value > 255
+              the_value = 0
+            end
+          end
+        end
+      end
+      #
+      container[(index)] = the_value
+    end
+    #
+    result = container.to_s
+    result.force_encoding(the_encoding)
+    result
+  else
+    self.dup
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_byte(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2818
+def each_byte(&block)
+  enumerator = self.to_enum(:bytes)
+  if block.respond_to?(:call)
+    enumerator.each do |item|
+      block.call(item)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_byte_with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2830
+def each_byte_with_index(offset=0,&block)
+  # Thanks to Hanmac from IRC :)
+  enumerator = self.to_enum(:bytes).with_index(offset)
+  if block.respond_to?(:call)
+    enumerator.each do |item,index|
+      block.call(item,index)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_char(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2793
+def each_char(&block)
+  enumerator = self.to_enum(:chars)
+  if block.respond_to?(:call)
+    enumerator.each do |item|
+      block.call(item)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_char_with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2805
+def each_char_with_index(offset=0,&block)
+  # Thanks to Hanmac from IRC :)
+  enumerator = self.to_enum(:chars).with_index(offset)
+  if block.respond_to?(:call)
+    enumerator.each do |item,index|
+      block.call(item,index)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_codepoint(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2843
+def each_codepoint(&block)
+  # String.codepoint will not return a GxG::Enumerator (non-blocking), so I'm providing these methods.
+  enumerator = self.to_enum(:codepoints)
+  if block.respond_to?(:call)
+    enumerator.each do |item|
+      block.call(item)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_codepoint_with_index(offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2856
+def each_codepoint_with_index(offset=0,&block)
+  enumerator = self.to_enum(:codepoints).with_index(offset)
+  if block.respond_to?(:call)
+    enumerator.each do |item,index|
+      block.call(item,index)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_line(separator=$/,&block) + + click to toggle source + +
+ + +
+ +

Note: documentation: recommend use of these methods and NOT those which they call to ensure cooperative event processing. LATER: String: look into c code : is there a way to pluck a char off a string w/o using chars,bytes,codepoints,lines ? This would afford an opportunity to override chars,bytes,codepoints and lines so that cooperative event processing was truly and invisibly supported. This would be slower than the c code, but would let you load up gems w/o overriding another author's code to make it cooperative. (i.e. facets, etc)

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2769
+def each_line(separator=$/,&block)
+  enumerator = self.lines(separator).to_enum
+  if block.respond_to?(:call)
+    enumerator.each do |item|
+      block.call(item)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ each_line_with_index(separator=$/,offset=0,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2781
+def each_line_with_index(separator=$/,offset=0,&block)
+  enumerator = self.lines(separator).to_enum.with_index(offset)
+  if block.respond_to?(:call)
+    enumerator.each do |item,index|
+      block.call(item,index)
+    end
+    self
+  else
+    enumerator
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ encode64() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2958
+def encode64()
+  # RFC 4648
+  if self.base64?()
+    self
+  else
+    ::Base64::strict_encode64(self)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ encode64!() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2976
+def encode64!()
+  data = self.encode64()
+  if data != self
+    self.replace(data)
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ encrypt(withkey="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2992
+def encrypt(withkey="")
+  #
+  if withkey.to_s.size > 0
+    the_encoding = self.encoding()
+    keybytes = ::GxG::ByteArray.new(withkey.to_s)
+    container = ::GxG::ByteArray.new(self)
+    #
+    container.each_index do |index|
+      the_value = container[(index)]
+      #
+      keybytes.each do |the_byte|
+        [127,63,31,15,7,3,1,0].each do |the_bit|
+          if the_byte > the_bit
+            the_value += 1
+            if the_value > 255
+              the_value = 0
+            end
+          else
+            the_value -= 1
+            if the_value < 0
+              the_value = 255
+            end
+          end
+        end
+      end
+      #
+      container[(index)] = the_value
+    end
+    #
+    result = container.to_s
+    result.force_encoding(the_encoding)
+    result
+  else
+    self.dup
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ from_json(symbolize_names = true) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2896
+def from_json(symbolize_names = true)
+  if self.json?
+    the_object = ::JSON::parse(self,{:symbolize_names => symbolize_names})
+    if the_object.is_any?(::Hash, ::Array)
+      the_object.process! do |value, selector, container|
+        if value.is_a?(::String)
+          item = value.numeric_values()
+          if item.is_a?(::Hash)
+            if item[:integer]
+              item = item[:integer]
+              container[(selector)] = item
+            else
+              if item[:float]
+                item = item[:float]
+                container[(selector)] = item
+              end
+            end
+          else
+            if (value.valid_datetime? || value.valid_datetime_nolocale?)
+              container[(selector)] = ::DateTime::parse(value)
+            end
+          end
+          #
+          if value[0..6] == "binary:" && value[7..-1].base64?
+            container[(selector)] = GxG::ByteArray.new(value[7..-1].decode64)
+          end
+        end
+        nil
+      end
+      #
+    end
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ html?() + + click to toggle source + +
+ + +
+ +

def from_xml(options={})

+ +
#
+unless options[:no_test] == true
+  unless self.xml?()
+    raise Exception, "String is NOT formatted as XML."
+  end
+end
+result = nil
+visit = Proc.new do |the_node=nil, accumulator=[]|
+  node_stack = []
+  if the_node
+    node_stack.push(the_node)
+    while (node_stack.size > 0) do
+      a_node = node_stack.pop()
+      # process(a_node)
+      if a_node.is_a?(::Oga::XML::Text)
+        accumulator << a_node.text()
+      end
+      if a_node.is_any?(::Oga::XML::Element, ::Oga::XML::Document)
+        record = {:name => "document_root", :attributes => {}, :parent => nil, :object => nil, :path => "/", :assembled => false}
+        if a_node.respond_to?(:name)
+          record[:name] = a_node.name()
+        end
+        if a_node.respond_to?(:attributes)
+          a_node.attributes.each do |the_attr|
+            record[:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value
+          end
+        end
+        if a_node.respond_to?(:parent)
+          record[:parent] = a_node.parent()
+        else
+          record[:parent] = a_node
+        end
+        record[:object] = a_node
+        unless a_node.is_a?(::Oga::XML::Document)
+          # set accumulator path
+          #
+        end
+        accumulator << record
+      end
+      if a_node.children.size > 0
+        a_node.children.each do |entry|
+          node_stack.push(entry)
+        end
+      end
+    end
+  end
+  accumulator
+end
+#
+begin
+  if options[:sax] == true
+    database = visit.call(::Oga::sax_parse_xml(self.transcode({:replace => "."},::Encoding::UTF_8)),[])
+  else
+    database = visit.call(::Oga::parse_xml(self.transcode({:replace => "."},::Encoding::UTF_8)),[])
+  end
+  link_db = []
+  accumulator = []
+  # Build link_db
+  database.each do |the_node|
+    if the_node.is_a?(::Hash)
+      node_record = {}
+      as_key = the_node[:name].to_s.to_sym
+      if the_node[:object].is_a?(::Oga::XML::Document)
+        node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => "", :children => []}
+      else
+        node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => (the_node[:object].text), :children => []}
+      end
+      link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])}
+      #
+      database.each do |a_node|
+        if a_node.is_a?(::Hash)
+          if a_node[:parent].object_id == the_node[:object].object_id
+            link_record[:desendents] << a_node[:object]
+          end
+        end
+      end
+      link_db << link_record
+      if the_node[:parent].is_a?(::Oga::XML::Document)
+        accumulator << node_record
+      end
+    else
+      accumulator << the_node
+    end
+  end
+  #
+  while (link_db.size > 0) do
+    entry = link_db.shift
+    if entry.is_a?(::Hash)
+      entry[:desendents].each do |node|
+        unless node.is_a?(::Oga::XML::Document)
+          if node.is_a?(::Oga::XML::Text)
+            node_record = node.text
+          else
+            if node.is_a?(::Oga::XML::Comment)
+              node_record = {:comment => node.text}
+            else
+              node_record = {}
+              as_key = node.name.to_s.to_sym
+              node_record[(as_key)] = {:attributes => {}, :text => (node.text), :children => []}
+              node.attributes.each do |the_attr|
+                node_record[(as_key)][:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value
+              end
+              link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])}
+              node.children.each do |node_descendent|
+                link_record[:desendents] << node_descendent
+              end
+              if link_record[:desendents].size > 0
+                link_db.push(link_record)
+              end
+            end
+          end
+          #
+          entry[:accumulator] << node_record
+        end
+      end
+      #
+    end
+  end
+  #
+  result = accumulator
+rescue Exception => the_error
+end
+#
+result
+
+ +

end

+ +

def from_xml_simple()

+ +
if self.xml?
+  the_hash = ::XmlSimple.xml_in(self)
+  if the_hash.is_a?(::Hash)
+    the_hash.symbolize_keys
+    the_hash.process! do |value, selector, container|
+      if value.is_a?(::String)
+        item = value.numeric_values()
+        if item.is_a?(::Hash)
+          if item[:integer]
+            item = item[:integer]
+            container[(selector)] = item
+          else
+            if item[:float]
+              item = item[:float]
+              container[(selector)] = item
+            end
+          end
+        else
+          # Cast all time elements to ISO-8601
+          if (value.valid_time? || value.valid_date? || value.valid_datetime? || value.valid_datetime_nolocale?)
+            container[(selector)] = ::DateTime::parse(value.to_s)
+          end
+        end
+      end
+      nil
+    end
+  end
+  the_hash
+else
+  nil
+end
+
+ +

end

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2482
+def html?()
+  declaration_test = (self.include?('<!DOCTYPE HTML') || self.include?('<!DOCTYPE html') || self.include?('<!DOCTYPE xhtml'))
+  parse_test = false
+  if declaration_test
+    begin
+      mime = self.mime_type()
+      if mime.is_a?(::Hash)
+        if ["html", "xhtml", "xhtml+xml"].include?(mime[:subtype])
+          parse_test = true
+        end
+      end
+    rescue Exception => the_error
+    end
+  end
+  (declaration_test && parse_test)
+end
+
+ +
+ + + + +
+ + +
+ +
+ json?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2892
+def json?()
+  self.json_1?() || self.json_2?()
+end
+
+ +
+ + + + +
+ + +
+ +
+ json_1?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2884
+def json_1?()
+  self.slice(0,1) == '{'
+end
+
+ +
+ + + + +
+ + +
+ +
+ json_2?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2888
+def json_2?()
+  (self.json_1?() && (self.match('"apiVersion"\s?:\s?"2.0"') || false))
+end
+
+ +
+ + + + +
+ + +
+ +
+ mime_type() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2295
+def mime_type()
+  result = nil
+  raw = ::MimeMagic.by_magic(::StringIO.new(self.clone))
+  if raw
+    result = {:type => (raw.type), :mediatype => (raw.mediatype), :subtype => (raw.subtype)}
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ numeric_values(categories=:any,delimiter="\t",locale=:en_US, numeric_base=10) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2108
+def numeric_values(categories=:any,delimiter="\t",locale=:en_US, numeric_base=10)
+  results = []
+  prepared = []
+  # SOMEDAY: <string>.numeric_values: formulate regex patterns per locale and numeric_base
+  quanta_pattern = /[a-zA-Z]*/
+  # proposed: /^-{0,1}\d*\.{0,1}\d+$/
+  # /^[-+]{0,1}\d*\.{0,1}\d+$/
+  # old: /[0-9.,]*/
+  numerical_pattern = /[-+0-9.,]*/
+  self.to_enum(:each_line).each do |row_text|
+    # ?? allow empty lines ??  I'm just not sure if I really *want* to attempt to preserve the structure that much.
+    row_prep = []
+    row_text.split(delimiter).to_enum.each do |column_text|
+      if column_text.size > 0
+        column_entry = {:text => column_text, :denomination => nil, :multiplier => nil}
+        column_text.scan(quanta_pattern).to_enum.each do |quanta|
+          if quanta.size > 0
+            #
+            interpretation = ::GxG::Units::interpret_units({:text => quanta,:categories => categories,:locale => locale, :base => numeric_base})
+            #
+            if interpretation[:error]
+              raise interpretation[:error]
+            else
+              # SOMEDAY: <string>.numeric_values: a bit more discernment here than just the first interpretation.
+              interpretation = interpretation[:result]
+              if interpretation[0].is_a?(Hash)
+                if interpretation[0][:quantum].is_a?(Hash)
+                  denomination = interpretation[0][:quantum].keys[0]
+                  multiplier = interpretation[0][:quantum][(denomination)]
+                  column_entry = {:text => column_text, :denomination => denomination, :multiplier => multiplier}
+                  if interpretation[0][:attributes].is_a?(Hash)
+                    column_entry[:attributes] = interpretation[0][:attributes]
+                  end
+                  break
+                end
+              end
+            end
+            #
+          end
+          #
+        end
+        #
+        row_prep << column_entry
+        #
+      else
+        row_prep << nil
+      end
+    end
+    prepared << row_prep
+    #
+  end
+  # process prepared array
+  prepared.to_enum.each do |row|
+    results << row.to_enum.map do |column|
+      result = nil
+      if column.is_a?(Hash)
+        column[:text].scan(numerical_pattern).to_enum.each do |text|
+          text.gsub!(",","")
+          if text.size > 0
+            denomination = column[:denomination]
+            multiplier = column[:multiplier]
+            if text.include?(".")
+              # Note: defaults *assume* base-10 numbers
+              unless denomination
+                denomination = :float
+              end
+              unless multiplier
+                multiplier = 1.0
+              end
+              result = {}
+              result[(denomination)] = text.to_f * multiplier.to_f
+            else
+              # Note: defaults *assume* base-10 numbers
+              unless denomination
+                denomination = :integer
+              end
+              unless multiplier
+                multiplier = 1
+              end
+              result = {}
+              result[(denomination)] = text.to_i * multiplier.to_i
+            end
+            if column[:attributes].is_a?(Hash)
+              result.merge(column[:attributes])
+            end
+            break
+          end
+        end
+      end
+      result
+    end
+  end
+  # unless it is a multi-line, or multi-column result, pass only the found denomination/value Hash.
+  if results.size == 1
+    # single row
+    results = results[0]
+    if results.size == 1
+      # single column
+      results = results[0]
+    end
+  end
+  #
+  results
+end
+
+ +
+ + + + +
+ + +
+ +
+ serialized?() + + click to toggle source + +
+ + +
+ +

Encoding format detection and handling: Serialized, JSON & binhex(base64)

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2868
+def serialized?()
+  if ((self[0..7].to_s == "marshal:" || self[0..9].to_s == "structure:") && (self[8..-1].to_s.base64?() || self[10..-1].to_s.base64?()))
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ slice_bytes(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2641
+def slice_bytes(*args)
+  # Return an ASCII_8BIT encoded sub-string
+  result = nil
+  the_range = nil
+  if args[0].is_a?(Numeric)
+    args[0] = args[0].to_i
+    if args[0] < 0
+      args[0] = self.bytesize + args[0]
+    end
+    the_range = ((args[0])..(args[0]))
+    if args[1].is_a?(Numeric)
+      args[1] = args[1].to_i
+      if args[1] < 1
+        raise ArgumentError.new("Second parameter needs to be a Numeric greater than 0, you provided #{args[1].inspect}")
+      else
+        the_range = ((args[0])..(args[0] + (args[1] - 1)))
+      end
+    end
+  else
+    if args[0].is_a?(Range)
+      if args[0].first < 0
+        args[1] = self.bytesize + args[0].first
+      else
+        args[1] = args[0].first
+      end
+      if args[0].last < 0
+        args[2] = self.bytesize + args[0].last
+      else
+        args[2] = args[0].last
+      end
+      if args[1] <= args[2]
+        the_range = ((args[1])..(args[2]))
+      else
+        the_range = ((args[2])..(args[1]))
+      end
+    else
+      raise ArgumentError.new("First parameter needs to be a Numeric or a Range, you provided #{args[0].inspect}")
+    end
+  end
+  #
+  if the_range
+    result = ""
+    result.force_encoding(::Encoding::ASCII_8BIT)
+    #
+    if self.encoding == ::Encoding::ASCII_8BIT
+      if self.bytesize > 0
+        if the_range.min != the_range.max
+          result << self.slice(the_range)
+        else
+          result << self[(the_range.min)]
+        end
+      end
+    else
+      # based upon memory-load and processing-load of Ruby Engine, select space-vs-speed or speed-vs-space techniques.
+      if (GxG::Engine::memory_load_high?() && (self.to_enum(:bytes).heap_used() < self.heap_used()))
+        # trade off speed for space considerations
+        # long-way 'round
+        position = 0
+        index = 0
+        #
+        self.to_enum(:bytes).each do |the_byte|
+          if the_range.include?(position)
+            if the_range.min == the_range.max
+              result << the_byte.chr
+              break
+            else
+              result[(index)] = the_byte.chr
+            end
+            index += 1
+          end
+          position += 1
+        end
+        #
+      else
+        # trade off space for speed considerations
+        if self.bytesize > 0
+          raw_text = self.clone
+          raw_text.force_encoding(::Encoding::ASCII_8BIT)
+          if the_range.min != the_range.max
+            result << raw_text.slice(the_range)
+          else
+            result << raw_text[(the_range.min)]
+          end
+        end
+        #
+      end
+    end
+    #
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ split_camelcase() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2287
+def split_camelcase()
+  if self.camel_case?()
+    self.split(/(?=[A-Z])/)
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_uri() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2637
+def to_uri()
+  ::URI::parse(self)
+end
+
+ +
+ + + + +
+ + +
+ +
+ transcode(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1933
+def transcode(*args)
+  destination_encoding = nil
+  conversion_options = nil
+  if args.size > 0
+    if args[0].is_a?(::Hash)
+      conversion_options = args[0]
+      if args[1].is_a?(::Encoding)
+        destination_encoding = args[1]
+      else
+        destination_encoding = ::Encoding.default_external
+      end
+    else
+      #
+      if args[0].is_a?(::Encoding)
+        destination_encoding = args[0]
+        if args[1].is_a?(::Hash)
+          conversion_options = args[1]
+        else
+          conversion_options = {}
+        end
+      end
+    end
+    args = nil
+  end
+  #
+  unless destination_encoding.is_a?(::Encoding)
+    raise ArgumentError, "You must provide a valid Encoding, instead you provided #{destination_encoding.class}"
+  end
+  unless conversion_options.is_a?(::Hash)
+    raise ArgumentError, "You must provide a Hash for conversion options, instead you provided #{conversion_options.class}"
+  end
+  in_options = {}
+  out_options = {}
+  #
+  if (conversion_options[:invalid] || conversion_options[:undef] || conversion_options[:replace])
+    # Note: I *confirmed* (thanks VGoff) that :invalid/:undef/:replace and :fallback are exclusive.
+    #
+    # If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to
+    #    raise the Encoding::InvalidByteSequenceError exception
+    if conversion_options[:invalid] == :replace
+      in_options[:invalid] = :replace
+      out_options[:invalid] = :replace
+    end
+    #    If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character.
+    #    The default is to raise the Encoding::UndefinedConversionError.
+    if conversion_options[:undef] == :replace
+      in_options[:undef] = :replace
+      out_options[:undef] = :replace
+    end
+    #
+    #    Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise.
+    if conversion_options[:replace].is_a?(::String)
+      in_options[:replace] = conversion_options[:replace]
+      out_options[:replace] = conversion_options[:replace]
+    end
+    #
+  else
+    #    Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method,
+    #    or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder.
+    #    Its value can be any encoding until it can be converted into the destination encoding of the transcoder.
+    if (conversion_options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || conversion_options[:fallback].respond_to?(:[]))
+      # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition.
+      if conversion_options[:fallback].is_any?(::Proc, ::Method)
+        # TODO: study arguments and output expected, and construct one in and one out proc as wrapper.
+        # FIX: Odd Bug : MRI 1.9.3 will attempt to convert a Proc into a Hash directly and raise exception, instead of calling the proc.
+        # Also, when a method is supplied: it does call it, but passes no data and I don't think even collects a response.  (wtf?) what is the point then??
+        # So ... as a work around (FORNOW) I will simply construct a Hash and use that resultant Hash as fallback.
+        # LATER: I think I'd like to add the above comparison to simply check for a .respond_to?(:call) on action objects.
+        in_fallback = {}
+        out_fallback = {}
+        self.each_char do |the_char|
+          dst_defined_char = conversion_options[:fallback].call(the_char).to_s.chars.first
+          if (dst_defined_char.size > 0 && the_char != dst_defined_char)
+            utf_bridge = the_char.encode(::Encoding::UTF_8)
+            in_fallback[(the_char)] = utf_bridge
+            out_fallback[(utf_bridge)] = dst_defined_char
+          end
+        end
+        in_options[:fallback] = in_fallback
+        out_options[:fallback] = out_fallback
+      else
+        if conversion_options[:fallback].is_any?(::Hash, ::Struct)
+          #
+          in_fallback = {}
+          out_fallback = {}
+          #
+          conversion_options[:fallback].to_enum(:each_pair).each do |src_undefined_char,dst_defined_char|
+            utf_bridge = src_undefined_char.encode(::Encoding::UTF_8)
+            in_fallback[(src_undefined_char)] = utf_bridge
+            out_fallback[(utf_bridge)] = dst_defined_char
+          end
+          #
+          in_options[:fallback] = in_fallback
+          out_options[:fallback] = out_fallback
+        else
+          if (conversion_options[:fallback].respond_to?(:[]) && conversion_options[:fallback].respond_to?(:to_a))
+            in_fallback = {}
+            out_fallback = {}
+            the_data = conversion_options[:fallback].to_a.flatten
+            the_size = the_data.size
+            if (the_size.even? && the_size > 0)
+              (0..(the_size)).step(2) do |index|
+                src_undefined_char = the_data[(index)]
+                dst_defined_char = the_data[(index + 1)]
+                utf_bridge = src_undefined_char.encode(::Encoding::UTF_8)
+                in_fallback[(src_undefined_char)] = utf_bridge
+                out_fallback[(utf_bridge)] = dst_defined_char
+              end
+            else
+              # raise error?  this really requires matched pairs of things
+            end
+            in_options[:fallback] = in_fallback
+            out_options[:fallback] = out_fallback
+          end
+        end
+      end
+    end
+  end
+  #
+  #    The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character
+  #    references. ‘&’, ‘<’, and ‘>’ are converted to “&amp;”, “&lt;”, and “&gt;”, respectively. If the value is :attr, encode also quotes the replacement
+  #    result (using ‘“’), and replaces ‘”’ with “&quot;”.
+  if [:text, :attr].include?(conversion_options[:xml])
+    in_options[:xml] = conversion_options[:xml]
+    out_options[:xml] = conversion_options[:xml]
+  end
+  #
+  #    Replaces LF (“n”) with CR (“r”) if value is true.
+  unless (conversion_options[:crlf_newline] || conversion_options[:universal_newline])
+    if conversion_options[:cr_newline] == true
+      in_options[:cr_newline] = true
+      out_options[:cr_newline] = true
+    end
+  end
+  #
+  #    Replaces LF (“n”) with CRLF (“rn”) if value is true.
+  unless (conversion_options[:cr_newline] || conversion_options[:universal_newline])
+    if conversion_options[:crlf_newline] == true
+      in_options[:crlf_newline] = true
+      out_options[:crlf_newline] = true
+    end
+  end
+  #
+  #    Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true.
+  unless (conversion_options[:cr_newline] || conversion_options[:crlf_newline])
+    if conversion_options[:universal_newline] == true
+      in_options[:universal_newline] = true
+      out_options[:universal_newline] = true
+    end
+  end
+  # ###
+  # the_string = self.encode(::Encoding::UTF_8,self.external_encoding,in_options)
+  # the_string.encode!(destination_encoding,::Encoding::UTF_8,out_options)
+  # --or--
+  if self.encoding == ::Encoding::UTF_8
+    the_string = self.dup
+  else
+    the_string = self.encode(::Encoding::UTF_8,self.encoding,in_options)
+  end
+  if destination_encoding != ::Encoding::UTF_8
+    the_string.encode!(destination_encoding,::Encoding::UTF_8,out_options)
+  end
+  #
+  the_string
+end
+
+ +
+ + + + +
+ + +
+ +
+ transcode!(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2099
+def transcode!(*args)
+  data = self.transcode(*args)
+  if data.is_a?(::String)
+    self.force_encoding(data.encoding)
+    self.replace(data)
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ unpack_bytes(format_template_string="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2760
+def unpack_bytes(format_template_string="")
+  # See : http://ruby-doc.org/core-1.9.3/String.html#method-i-byteslice#This%20table%20summarizes
+  GxG::ByteArray.new(self.unpack(format_template_string))
+end
+
+ +
+ + + + +
+ + +
+ +
+ unserialize() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2876
+def unserialize()
+  if self.serialized?()
+    ::GxG::reconstitute(self)
+  else
+    self
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_date?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2227
+def valid_date?()
+  # Match for Date pattern
+  if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/.match(self))
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_datetime?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2236
+def valid_datetime?()
+  # Match for ISO 8601 pattern
+  if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9][+-][0-9][0-9]:[0-9][0-9]/.match(self))
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_datetime_nolocale?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2245
+def valid_datetime_nolocale?()
+  # Match for not-so-much ISO 8601-ish pattern
+  if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/.match(self))
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_jid?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2262
+def valid_jid?()
+  # From Blather: /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/
+  # From xmpp4r: /^(?:([^@\/<>\'\"]+)@)?([^@\/<>\'\"]+)(?:\/([^<>\'\"]*))?$/
+  # Note: for now - using the one from Blather
+  if /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.match(self)
+    true
+  else
+    false
+  end
+  # if /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.match(self)
+  #   true
+  # else
+  #   false
+  # end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_path?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2254
+def valid_path?()
+  if (/.*(?:\\|\/)(.+)$/.match(self) || (self.split(" ").size == 1 || self.split("/").size > 1 || self == "/"))
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_time?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2213
+def valid_time?()
+  # Parse for Time class
+  instance = nil
+  begin
+    instance = ::Chronic::parse(self)
+  rescue Exception
+  end
+  if instance.is_a?(::Time)
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ valid_uri?() + + click to toggle source + +
+ + +
+ +

def from_html(options={})

+ +
#
+unless options[:no_test] == true
+  unless self.html?()
+    raise Exception, "String is NOT formatted as HTML."
+  end
+end
+result = nil
+visit = Proc.new do |the_node=nil, accumulator=[]|
+  node_stack = []
+  if the_node
+    node_stack.push(the_node)
+    while (node_stack.size > 0) do
+      a_node = node_stack.pop()
+      # process(a_node)
+      if a_node.is_a?(::Oga::XML::Text)
+        accumulator << a_node.text()
+      end
+      if a_node.is_any?(::Oga::XML::Element, ::Oga::XML::Document)
+        record = {:name => "document_root", :attributes => {}, :parent => nil, :object => nil, :path => "/", :assembled => false}
+        if a_node.respond_to?(:name)
+          record[:name] = a_node.name()
+        end
+        if a_node.respond_to?(:attributes)
+          a_node.attributes.each do |the_attr|
+            record[:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value
+          end
+        end
+        if a_node.respond_to?(:parent)
+          record[:parent] = a_node.parent()
+        else
+          record[:parent] = a_node
+        end
+        record[:object] = a_node
+        unless a_node.is_a?(::Oga::XML::Document)
+          # set accumulator path
+          #
+        end
+        accumulator << record
+      end
+      if a_node.children.size > 0
+        a_node.children.each do |entry|
+          node_stack.push(entry)
+        end
+      end
+    end
+  end
+  accumulator
+end
+#
+begin
+  if options[:sax] == true
+    database = visit.call(::Oga::sax_parse_html(self.transcode({:replace => "."},::Encoding::UTF_8)),[])
+  else
+    database = visit.call(::Oga::parse_html(self.transcode({:replace => "."},::Encoding::UTF_8)),[])
+  end
+  link_db = []
+  accumulator = []
+  # Build link_db
+  database.each do |the_node|
+    if the_node.is_a?(::Hash)
+      node_record = {}
+      as_key = the_node[:name].to_s.to_sym
+      if the_node[:object].is_a?(::Oga::XML::Document)
+        node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => "", :children => []}
+      else
+        node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => (the_node[:object].text), :children => []}
+      end
+      link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])}
+      #
+      database.each do |a_node|
+        if a_node.is_a?(::Hash)
+          if a_node[:parent].object_id == the_node[:object].object_id
+            link_record[:desendents] << a_node[:object]
+          end
+        end
+      end
+      link_db << link_record
+      if the_node[:parent].is_a?(::Oga::XML::Document)
+        accumulator << node_record
+      end
+    else
+      accumulator << the_node
+    end
+  end
+  #
+  while (link_db.size > 0) do
+    entry = link_db.shift
+    if entry.is_a?(::Hash)
+      entry[:desendents].each do |node|
+        unless node.is_a?(::Oga::XML::Document)
+          if node.is_a?(::Oga::XML::Text)
+            node_record = node.text
+          else
+            if node.is_a?(::Oga::XML::Comment)
+              node_record = {:comment => node.text}
+            else
+              node_record = {}
+              as_key = node.name.to_s.to_sym
+              node_record[(as_key)] = {:attributes => {}, :text => (node.text), :children => []}
+              node.attributes.each do |the_attr|
+                node_record[(as_key)][:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value
+              end
+              link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])}
+              node.children.each do |node_descendent|
+                link_record[:desendents] << node_descendent
+              end
+              if link_record[:desendents].size > 0
+                link_db.push(link_record)
+              end
+            end
+          end
+          #
+          entry[:accumulator] << node_record
+        end
+      end
+      #
+    end
+  end
+  #
+  result = accumulator
+rescue Exception => the_error
+end
+#
+result
+
+ +

end

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2626
+def valid_uri?()
+  result = false
+  begin
+    if ::URI::parse(self).is_a?(::URI::Generic)
+      result = true
+    end
+  rescue Exception
+  end
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ xml?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 2304
+def xml?()
+  declaration_test = self.include?("<?xml version=")
+  parse_test = false
+  if declaration_test
+    begin
+      mime = self.mime_type()
+      if mime.is_a?(::Hash)
+        if mime[:subtype] == "xml" || mime[:subtype] = "xhtml+xml"
+          parse_test = true
+        end
+      end
+    rescue Exception => the_error
+    end
+  end
+  (declaration_test && parse_test)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/StringIO.html b/doc/StringIO.html new file mode 100644 index 0000000..c325a9c --- /dev/null +++ b/doc/StringIO.html @@ -0,0 +1,622 @@ + + + + + + +class StringIO - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class StringIO +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ binmode() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 640
+def binmode()
+  # Question: ok, so the cosmetics are that it is in binmode ... but what will be its actual behavior??
+  if self.binmode?()
+    self
+  else
+    self.set_encoding(::Encoding::ASCII_8BIT)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ binmode?() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 632
+def binmode?()
+  if self.external_encoding == ::Encoding::ASCII_8BIT
+    true
+  else
+    false
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ fcntl(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 695
+def fcntl(*args)
+  if args[0] == ::Fcntl::F_GETFL
+    current = ::Fcntl::O_NONBLOCK
+    if self.closed_read?()
+      unless self.closed_write?()
+        current = (current | ::Fcntl::O_WRONLY)
+      end
+    else
+      if self.closed_write?()
+        current = (current | ::Fcntl::O_RDONLY)
+      else
+        current = (current | ::Fcntl::O_RDWR)
+      end
+    end
+    current
+  else
+    0
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ flags() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 715
+def flags()
+  result = []
+  current = self.fcntl(::Fcntl::F_GETFL)
+  flags = {}
+  if self.binmode?()
+    result << :binary
+    if self.external_encoding == ::Encoding::ASCII_8BIT
+      result << :setenc_by_bom
+    end
+  else
+    result << :text
+    result << :wsplit
+  end
+  if self.tty?
+    result << :tty
+  end
+  #
+  #flag[:read] = 0x00000001
+  #flag[:write] = 0x00000002
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  #flag[:binmode] = 0x00000004
+  #flag[:readwrite] = (flag[:read] | flag[:write])
+  flags[:read] = ::Fcntl::O_RDONLY
+  flags[:write] = ::Fcntl::O_WRONLY
+  flags[:readwrite] = ::Fcntl::O_RDWR
+  #flags[:binary] = 0x00000004
+  flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk.
+  flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata.
+  flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC
+  #flags[:tty] = 0x00000010
+  flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY
+  flags[:duplex] = 0x00000020
+  flags[:append] = ::Fcntl::O_APPEND
+  flags[:create] = ::Fcntl::O_CREAT
+  flags[:exclusive] = ::Fcntl::O_EXCL
+  # flags[:wsplit] = 0x00000200
+  flags[:wsplit_initialized] = 0x00000400
+  flags[:trunc] = ::Fcntl::O_TRUNC
+  #flags[:text] = 0x00001000
+  #flags[:setenc_by_bom] = 0x00100000
+  flags[:seek_set] = ::File::SEEK_SET
+  flags[:seek_current] = ::File::SEEK_CUR
+  flags[:seek_end] = ::File::SEEK_END
+  # ### file region locking:
+  flags[:shared_lock] = ::File::LOCK_SH # for reading
+  flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking)
+  flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing.
+  flags[:unlock] = ::File::LOCK_UN
+  #
+  flags[:nonblocking] = ::Fcntl::O_NONBLOCK
+  flags[:ndelay] = ::Fcntl::O_NDELAY
+  # these do not apply to in-memory IO-like things at all:
+  #    flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks.
+  #    flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file.
+  #    flags[:match_noescape] = ::File::FNM_NOESCAPE #
+  #    flags[:match_pathname] = ::File::FNM_PATHNAME #
+  #    flags[:match_dotmatch] = ::File::FNM_DOTMATCH #
+  #    flags[:match_casefold] = ::File::FNM_CASEFOLD #
+  #    flags[:match_systemcase] = ::File::FNM_SYSCASE #
+  #
+  flags.keys.to_enum.each do |the_flag_key|
+    if (current & flags[(the_flag_key)]) == flags[(the_flag_key)]
+      case the_flag_key
+      when :readwrite
+        if result.index(:write)
+          result << :read
+        else
+          if result.index(:read)
+            result << :write
+          end
+        end
+      when :wsplit_initialized
+        if result.index(:wsplit)
+          result[(result.index(:wsplit))] = :wsplit_initialized
+        else
+          result << :wsplit_initialized
+        end
+      else
+        result << the_flag_key
+      end
+    end
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ latency_reading(old_reading=nil,&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 665
+def latency_reading(old_reading=nil,&block)
+  # Just in StringIO for interface compatibility.
+  unless old_reading.is_a?(::Hash)
+    old_reading = {:low => nil, :last => nil, :high => nil}
+  end
+  new_reading = {:low => nil, :last => nil, :high => nil}
+  reading = millisecond_latency(&block)
+  if old_reading[:low]
+    if reading[:milliseconds] < old_reading[:low]
+      new_reading[:low] = reading[:milliseconds].dup
+    else
+      new_reading[:low] = old_reading[:low]
+    end
+  else
+    new_reading[:low] = reading[:milliseconds].dup
+  end
+  new_reading[:last] = reading[:milliseconds].dup
+  if old_reading[:high]
+    if reading[:milliseconds] > old_reading[:high]
+      new_reading[:high] = reading[:milliseconds].dup
+    else
+      new_reading[:high] = old_reading[:high]
+    end
+  else
+    new_reading[:high] = reading[:milliseconds].dup
+  end
+  #
+  {:result => reading[:result], :reading => new_reading}
+end
+
+ +
+ + + + +
+ + +
+ +
+ lstat(*args) + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: stat +
+ +
+ + +
+ +
+ profile() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 806
+def profile()
+  current = self.flags()
+  result = {:type => :memory, :flags => current, :permissions => {:effective=>{:execute=>false, :rename=>false, :move=>false, :destroy=>false, :create=>false, :write=>false, :read=>false}}}
+  #
+  if current.include?(:read)
+    result[:permissions][:effective][:read] = true
+  end
+  if current.include?(:write)
+    result[:permissions][:effective][:write] = true
+    result[:permissions][:effective][:destroy] = true
+    result[:permissions][:effective][:create] = true
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ read_latency() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 657
+def read_latency()
+  # Just in StringIO for interface compatibility.
+  unless self.instance_variable_defined?(:@read_latency)
+    @read_latency= {:low => nil, :last => nil, :high => nil}
+  end
+  @read_latency.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ stat(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 801
+def stat(*args)
+  nil
+end
+
+ +
+ + +
+ Also aliased as: lstat +
+ + + +
+ + +
+ +
+ write_latency() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 649
+def write_latency()
+  # Just in StringIO for interface compatibility.
+  unless self.instance_variable_defined?(:@write_latency)
+    @write_latency= {:low => nil, :last => nil, :high => nil}
+  end
+  @write_latency.clone
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/Struct.html b/doc/Struct.html new file mode 100644 index 0000000..3b7ddda --- /dev/null +++ b/doc/Struct.html @@ -0,0 +1,578 @@ + + + + + + +class Struct - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Struct +

+ +
+ +

Alteration to Struct for support of structural processing:

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ process(the_struct=::Struct.new(nil),&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3162
+def self.process(the_struct=::Struct.new(nil),&block)
+  new_struct = ::Struct.new(nil)
+  if block.respond_to?(:call)
+    if the_struct.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray)
+      new_struct = the_struct.process!(&block)
+    else
+      raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Set, or a Struct"
+    end
+  end
+  new_struct
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+
+

Public Instance Methods

+
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3158
+def clone()
+  initialize_clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3149
+def initialize_clone
+  result = self.class.new
+  self.each_pair do |key, value|
+    result[(key)] = value.clone
+  end
+  result
+end
+
+ +
+ + +
+ Also aliased as: initialize_dup, dup +
+ + + +
+ + +
+ +
+ initialize_dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ iterative(&block) + + click to toggle source + +
+ + +
+ +

def each_pair(&block)

+ +
# ok, bizarre : this method is listed in instance_methods but is missing when called.  hrm.
+collection = {}
+self.members.to_enum.each do |key|
+  collection[(key)] = (self[(key)])
+end
+if block.respond_to?(:call)
+  collection.to_enum(:each_pair).each do |key,value|
+    block.call(key,value)
+  end
+else
+  collection.to_enum(:each_pair)
+end
+
+ +

end

+ + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3201
+def iterative(&block)
+  result = []
+  visit = Proc.new do |the_node=nil, accumulator=[]|
+    node_stack = []
+    if the_node
+      node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)})
+      while (node_stack.size > 0) do
+        a_node = node_stack.shift
+        #
+        if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash)
+          a_node[:object].each_pair do |the_key, the_value|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value})
+          end
+        end
+        if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray)
+          a_node[:object].each_with_index do |the_value, the_index|
+            node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value})
+          end
+        end
+        #
+        accumulator << a_node
+      end
+    end
+    accumulator
+  end
+  #
+  children_of = Proc.new do |the_db=[], the_parent=nil|
+    list = []
+    the_db.each do |node|
+      if node[:parent].object_id == the_parent.object_id
+        list << node
+      end
+    end
+    list
+  end
+  #
+  begin
+    database = visit.call(self,[])
+    link_db = children_of.call(database, self)
+    if block.respond_to?(:call)
+      while (link_db.size > 0) do
+        entry = link_db.shift
+        unless entry[:object].object_id == self.object_id
+          # calls with parameters: the_value, the_key/the_index (the_selector), the_container
+          raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent])
+          if raw_result
+            result << raw_result
+          end
+        end
+        if entry[:object].object_id != nil.object_id
+          children = children_of.call(database, entry[:object])
+          children.each do |child|
+            link_db << child
+          end
+        end
+      end
+    end
+    #
+  rescue Exception => the_error
+    log_error({:error => the_error, :parameters => {}})
+  end
+  #
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3266
+def process(&block)
+  result = self.clone
+  result.iterative(&block)
+  result
+end
+
+ +
+ + + + +
+ + +
+ +
+ process!(&block) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 3272
+def process!(&block)
+  self.iterative(&block)
+  self
+end
+
+ +
+ + + + +
+ + + + + +
+ +
+
+ + + + diff --git a/doc/Symbol.html b/doc/Symbol.html new file mode 100644 index 0000000..8c9d7d8 --- /dev/null +++ b/doc/Symbol.html @@ -0,0 +1,254 @@ + + + + + + +class Symbol - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Symbol +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 91
+def clone()
+  initialize_clone
+end
+
+ +
+ + + + +
+ + +
+ +
+ dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+ initialize_clone() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 86
+def initialize_clone
+  (self.to_s.dup.to_sym)
+end
+
+ +
+ + +
+ Also aliased as: initialize_dup, dup +
+ + + +
+ + +
+ +
+ initialize_dup() + +
+ + +
+ + + + + + +
+ + + + +
+ Alias for: initialize_clone +
+ +
+ + +
+ +
+
+ + + + diff --git a/doc/Time.html b/doc/Time.html new file mode 100644 index 0000000..e6cf01a --- /dev/null +++ b/doc/Time.html @@ -0,0 +1,155 @@ + + + + + + +class Time - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class Time +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ to_datetime() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 97
+def to_datetime()
+  ::DateTime::parse(self.to_s)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/URI.html b/doc/URI.html new file mode 100644 index 0000000..7ecada4 --- /dev/null +++ b/doc/URI.html @@ -0,0 +1,151 @@ + + + + + + +module URI - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ module URI +

+ +
+ +

URI alterations

+ +
+ + +
+ + + + + + + + + +
+
+

Public Class Methods

+
+ + +
+ +
+ parse(*args) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1290
+def self.parse(*args)
+  #::URI::Generic.new(*(::URI::split(*args)))
+  ::URI::Generic::parse(*args)
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/URI/Generic.html b/doc/URI/Generic.html new file mode 100644 index 0000000..5458e97 --- /dev/null +++ b/doc/URI/Generic.html @@ -0,0 +1,1626 @@ + + + + + + +class URI::Generic - RDoc Documentation + + + + + + + + + + + + + + + + + +
+

+ class URI::Generic +

+ +
+ +
+ + +
+ + + + + + + + + +
+
+

Public Instance Methods

+
+ + +
+ +
+ address_info() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1703
+def address_info()
+  ::Addrinfo::parse(self)
+end
+
+ +
+ + + + +
+ + +
+ +
+ fragment() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1553
+def fragment
+        if @fragment.is_a?(String)
+                URI::unescape(@fragment)
+        else
+                @fragment
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ fragment=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1545
+def fragment=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_fragment(v)
+        self.fragment
+end
+
+ +
+ + + + +
+ + +
+ +
+ from_hash(the_hash={}) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1601
+def from_hash(the_hash={})
+                    if the_hash[:scheme]
+    self.scheme = the_hash[:scheme]
+  else
+    @scheme = nil
+                    end
+                    if the_hash[:opaque]
+    self.opaque = the_hash[:opaque]
+    @registry = nil
+    @user = nil
+    @password = nil
+    @host = nil
+    @port = nil
+    @path = nil
+    @query = nil
+                    else
+    @opaque = nil
+                            if the_hash[:registry]
+      self.registry = the_hash[:registry]
+      @user = nil
+      @password = nil
+      @host = nil
+      @port = nil
+                            else
+      @registry = nil
+      if the_hash[:user]
+        self.user = the_hash[:user]
+      else
+        @user = nil
+      end
+      if the_hash[:password]
+        self.password = the_hash[:password]
+      else
+        @password = nil
+      end
+      if the_hash[:host]
+        self.host = the_hash[:host]
+      else
+        @host = nil
+      end
+      if the_hash[:port] && the_hash[:port] != self.default_port
+        self.port = the_hash[:port].to_i
+      else
+        @port = nil
+      end
+                            end
+    if the_hash[:path]
+      self.path = the_hash[:path]
+    else
+      @path = nil
+    end
+    if the_hash[:query]
+      self.query = the_hash[:query]
+    else
+      @query = nil
+    end
+                    end
+  if the_hash[:fragment]
+    self.fragment = the_hash[:fragment]
+  else
+    @fragment = nil
+  end
+  self
+end
+
+ +
+ + + + +
+ + +
+ +
+ host() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1411
+def host()
+        if @host.is_a?(String)
+                URI::unescape(@host)
+        else
+                @host
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ host=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1403
+def host=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_host(v)
+        self.host
+end
+
+ +
+ + + + +
+ + +
+ +
+ hostname() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1419
+def hostname()
+  if @host.to_s.size > 0
+    self.host().to_s.dup.gsub("[","").gsub("]","")
+  else
+    nil
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ hostname=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1427
+def hostname=(v)
+  if (v.to_s.include?(":") || (v.to_s.include?("[") && v.to_s.include?("]")))
+    # ipv6 ??
+    if v.to_s.include?("[") && v.to_s.include?("]")
+      self.host = v.to_s
+    else
+      self.host = "[" << v.to_s << "]"
+    end
+  else
+    self.host = v.to_s
+  end
+  self.host()
+end
+
+ +
+ + + + +
+ + +
+ +
+ opaque() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1505
+def opaque
+        if @opaque.is_a?(String)
+                URI::unescape(@opaque)
+        else
+                @opaque
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ opaque=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1497
+def opaque=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_opaque(v)
+        self.opaque
+end
+
+ +
+ + + + +
+ + +
+ +
+ password() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1387
+def password
+        if @password.is_a?(String)
+                URI::unescape(@password)
+        else
+                @password
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ password=(password) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1379
+def password=(password)
+        if password.is_a?(String)
+                password = (URI::escape(password))
+        end
+        set_password(password)
+        # returns password
+end
+
+ +
+ + + + +
+ + +
+ +
+ path() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1481
+def path
+        if @path.is_a?(String)
+                URI::unescape(@path)
+        else
+                @path
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ path=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1473
+def path=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_path(v)
+        self.path
+end
+
+ +
+ + + + +
+ + +
+ +
+ query() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1529
+def query
+        if @query.is_a?(String)
+                URI::unescape(@query)
+        else
+                @query
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ query=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1521
+def query=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_query(v)
+        self.query
+end
+
+ +
+ + + + +
+ + +
+ +
+ registry() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1457
+def registry
+        if @registry.is_a?(String)
+                URI::unescape(@registry)
+        else
+                @registry
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ registry=(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1449
+def registry=(v)
+        if v.is_a?(String)
+                v = (URI::escape(v))
+        end
+        set_registry(v)
+        self.registry
+end
+
+ +
+ + + + +
+ + +
+ +
+ resolve_host() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1707
+def resolve_host()
+  case @scheme
+  when "inproc"
+    # no-op
+  when "ipc", "unix"
+    # no-op
+  when "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"
+    self.hostname = ::TCPSocket.getaddress(self.hostname)
+  end
+end
+
+ +
+ + + + +
+ + +
+ +
+ scheme() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1309
+          def scheme()
+@scheme
+          end
+
+ +
+ + + + +
+ + +
+ +
+ scheme=(scheme="") + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1304
+def scheme=(scheme="")
+        set_scheme(scheme)
+        scheme
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_fragment(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1537
+def set_fragment(v)
+        if v.is_a?(String)
+                @fragment = URI::escape(v)
+        else
+                @fragment = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_host(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1395
+def set_host(v)
+        if v.is_a?(String)
+                @host = URI::escape(v)
+        else
+                @host = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_opaque(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1489
+def set_opaque(v)
+        if v.is_a?(String)
+                @opaque = URI::escape(v)
+        else
+                @opaque = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_password(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1368
+def set_password(v)
+        if v.is_a?(String)
+                @password = URI::escape(v)
+        end
+        if @password.is_a?(String)
+                URI::unescape(@password)
+        else
+                @password
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_path(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1465
+def set_path(v)
+        if v.is_a?(String)
+                @path = URI::escape(v)
+        else
+                @path = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_query(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1513
+def set_query(v)
+        if v.is_a?(String)
+                @query = URI::escape(v)
+        else
+                @query = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_registry(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1441
+def set_registry(v)
+        if v.is_a?(String)
+                @registry = URI::escape(v)
+        else
+                @registry = v
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ set_scheme(v) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1297
+          def set_scheme(v)
+                  if v.is_a?(String)
+  @scheme = v
+                  end
+v
+          end
+
+ +
+ + + + +
+ + +
+ +
+ set_userinfo(user, password = nil) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1313
+def set_userinfo(user, password = nil)
+        unless password 
+                user, password = split_userinfo(user)
+        end
+        if user.is_a?(String)
+                @user     = URI::escape(user)
+        else
+                @user = user
+        end
+        if password.is_a?(String)
+                @password = URI::escape(password)
+        else
+                @password = password
+        end
+        result = []
+        if @user.is_a?(String)
+                result << URI::unescape(@user)
+        else
+                result << @user
+        end
+        if @password.is_a?(String)
+                result << URI::unescape(@password)
+        else
+                result << @password
+        end
+        result
+end
+
+ +
+ + + + +
+ + +
+ +
+ to_hash() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1666
+          def to_hash()
+                  result = {}
+                  if @scheme
+                          result[:scheme] = @scheme
+                  end
+                  if @opaque
+                          result[:opaque] = URI::unescape(@opaque)
+                  else
+                          if @registry
+    result[:registry] = URI::unescape(@registry)
+                          else
+    if @user
+      result[:user] = URI::unescape(@user)
+    end
+    if @password
+      result[:password] = URI::unescape(@password)
+    end
+    if @host
+      result[:host] = URI::unescape(@host)
+    end
+    if @port && @port != self.default_port
+      result[:port] = URI::unescape(@port).to_i
+    end
+                          end
+  if @path
+    result[:path] = URI::unescape(@path)
+  end
+  if @query
+    result[:query] = URI::unescape(@query)
+  end
+                  end
+if @fragment
+  result[:fragment] = URI::unescape(@fragment)
+end
+result
+          end
+
+ +
+ + + + +
+ + +
+ +
+ to_s() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1561
+        def to_s
+                str = ''
+                if @scheme
+                        str << @scheme
+                        str << ':'
+                end
+                if @opaque
+                        str << @opaque
+                else
+                        if @registry
+                                str << @registry
+                        else
+  if @host
+    str << '//'
+  end
+  if self.userinfo
+    str << self.userinfo
+    str << '@'
+  end
+  if @host
+    str << @host
+  end
+  if @port && @port != self.default_port
+    str << ':'
+    str << @port.to_s
+  end
+                        end
+str << @path
+if @query
+  str << '?'
+  str << @query
+end
+                end
+                if @fragment
+                        str << '#'
+                        str << @fragment
+                end
+                URI::escape(str)
+        end
+
+ +
+ + + + +
+ + +
+ +
+ user() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1360
+def user
+        if @user.is_a?(String)
+                URI::unescape(@user)
+        else
+                @user
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+ user=(user) + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1351
+def user=(user)
+        if user.is_a?(String)
+                user = (URI::escape(user))
+        end
+        set_user(user)
+        self.user
+        # returns user
+end
+
+ +
+ + + + +
+ + +
+ +
+ userinfo() + + click to toggle source + +
+ + +
+ + + + + + +
+
# File lib/gxg/gxg_augmented.rb, line 1341
+def userinfo
+        if @user.nil?
+                nil
+        elsif @password.nil?
+                URI::unescape(@user)
+        else
+                URI::unescape(@user) + ':' + URI::unescape(@password)
+        end
+end
+
+ +
+ + + + +
+ + +
+ +
+
+ + + + diff --git a/doc/created.rid b/doc/created.rid new file mode 100644 index 0000000..7a60b67 --- /dev/null +++ b/doc/created.rid @@ -0,0 +1,23 @@ +Thu, 15 Oct 2020 09:37:49 -0700 +./lib/gxg/bootstrap.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_database_persistedarray.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/net_tools.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_io.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_database.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_communications.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_augmented.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_engine.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_events.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_database_persistedhash.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_elements.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_dbfs.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_transcode.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/net_clients.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/tag_helpers.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_net.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_zmq.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_units.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg/gxg_entities.rb Thu, 15 Oct 2020 06:19:22 -0700 +./lib/gxg.rb Thu, 15 Oct 2020 08:30:26 -0700 +./README.md Thu, 15 Oct 2020 07:02:08 -0700 +./gxg.gemspec Thu, 15 Oct 2020 08:25:30 -0700 diff --git a/doc/css/fonts.css b/doc/css/fonts.css new file mode 100644 index 0000000..57302b5 --- /dev/null +++ b/doc/css/fonts.css @@ -0,0 +1,167 @@ +/* + * Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), + * with Reserved Font Name "Source". All Rights Reserved. Source is a + * trademark of Adobe Systems Incorporated in the United States and/or other + * countries. + * + * This Font Software is licensed under the SIL Open Font License, Version + * 1.1. + * + * This license is copied below, and is also available with a FAQ at: + * http://scripts.sil.org/OFL + */ + +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + src: local("Source Code Pro"), + local("SourceCodePro-Regular"), + url("../fonts/SourceCodePro-Regular.ttf") format("truetype"); +} + +@font-face { + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 700; + src: local("Source Code Pro Bold"), + local("SourceCodePro-Bold"), + url("../fonts/SourceCodePro-Bold.ttf") format("truetype"); +} + +/* + * Copyright (c) 2010, Łukasz Dziedzic (dziedzic@typoland.com), + * with Reserved Font Name Lato. + * + * This Font Software is licensed under the SIL Open Font License, Version + * 1.1. + * + * This license is copied below, and is also available with a FAQ at: + * http://scripts.sil.org/OFL + */ + +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 300; + src: local("Lato Light"), + local("Lato-Light"), + url("../fonts/Lato-Light.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: italic; + font-weight: 300; + src: local("Lato Light Italic"), + local("Lato-LightItalic"), + url("../fonts/Lato-LightItalic.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: normal; + font-weight: 700; + src: local("Lato Regular"), + local("Lato-Regular"), + url("../fonts/Lato-Regular.ttf") format("truetype"); +} + +@font-face { + font-family: "Lato"; + font-style: italic; + font-weight: 700; + src: local("Lato Italic"), + local("Lato-Italic"), + url("../fonts/Lato-RegularItalic.ttf") format("truetype"); +} + +/* + * ----------------------------------------------------------- + * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + * ----------------------------------------------------------- + * + * PREAMBLE + * The goals of the Open Font License (OFL) are to stimulate worldwide + * development of collaborative font projects, to support the font creation + * efforts of academic and linguistic communities, and to provide a free and + * open framework in which fonts may be shared and improved in partnership + * with others. + * + * The OFL allows the licensed fonts to be used, studied, modified and + * redistributed freely as long as they are not sold by themselves. The + * fonts, including any derivative works, can be bundled, embedded, + * redistributed and/or sold with any software provided that any reserved + * names are not used by derivative works. The fonts and derivatives, + * however, cannot be released under any other type of license. The + * requirement for fonts to remain under this license does not apply + * to any document created using the fonts or their derivatives. + * + * DEFINITIONS + * "Font Software" refers to the set of files released by the Copyright + * Holder(s) under this license and clearly marked as such. This may + * include source files, build scripts and documentation. + * + * "Reserved Font Name" refers to any names specified as such after the + * copyright statement(s). + * + * "Original Version" refers to the collection of Font Software components as + * distributed by the Copyright Holder(s). + * + * "Modified Version" refers to any derivative made by adding to, deleting, + * or substituting -- in part or in whole -- any of the components of the + * Original Version, by changing formats or by porting the Font Software to a + * new environment. + * + * "Author" refers to any designer, engineer, programmer, technical + * writer or other person who contributed to the Font Software. + * + * PERMISSION & CONDITIONS + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of the Font Software, to use, study, copy, merge, embed, modify, + * redistribute, and sell modified and unmodified copies of the Font + * Software, subject to the following conditions: + * + * 1) Neither the Font Software nor any of its individual components, + * in Original or Modified Versions, may be sold by itself. + * + * 2) Original or Modified Versions of the Font Software may be bundled, + * redistributed and/or sold with any software, provided that each copy + * contains the above copyright notice and this license. These can be + * included either as stand-alone text files, human-readable headers or + * in the appropriate machine-readable metadata fields within text or + * binary files as long as those fields can be easily viewed by the user. + * + * 3) No Modified Version of the Font Software may use the Reserved Font + * Name(s) unless explicit written permission is granted by the corresponding + * Copyright Holder. This restriction only applies to the primary font name as + * presented to the users. + * + * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + * Software shall not be used to promote, endorse or advertise any + * Modified Version, except to acknowledge the contribution(s) of the + * Copyright Holder(s) and the Author(s) or with their explicit written + * permission. + * + * 5) The Font Software, modified or unmodified, in part or in whole, + * must be distributed entirely under this license, and must not be + * distributed under any other license. The requirement for fonts to + * remain under this license does not apply to any document created + * using the Font Software. + * + * TERMINATION + * This license becomes null and void if any of the above conditions are + * not met. + * + * DISCLAIMER + * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL + * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM + * OTHER DEALINGS IN THE FONT SOFTWARE. + */ + diff --git a/doc/css/rdoc.css b/doc/css/rdoc.css new file mode 100644 index 0000000..1bdb6e6 --- /dev/null +++ b/doc/css/rdoc.css @@ -0,0 +1,611 @@ +/* + * "Darkfish" Rdoc CSS + * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $ + * + * Author: Michael Granger + * + */ + +/* vim: ft=css et sw=2 ts=2 sts=2 */ +/* Base Green is: #6C8C22 */ + +.hide { display: none !important; } + +* { padding: 0; margin: 0; } + +body { + background: #fafafa; + font-family: Lato, sans-serif; + font-weight: 300; +} + +h1 span, +h2 span, +h3 span, +h4 span, +h5 span, +h6 span { + position: relative; + + display: none; + padding-left: 1em; + line-height: 0; + vertical-align: baseline; + font-size: 10px; +} + +h1 span { top: -1.3em; } +h2 span { top: -1.2em; } +h3 span { top: -1.0em; } +h4 span { top: -0.8em; } +h5 span { top: -0.5em; } +h6 span { top: -0.5em; } + +h1:hover span, +h2:hover span, +h3:hover span, +h4:hover span, +h5:hover span, +h6:hover span { + display: inline; +} + +h1:target, +h2:target, +h3:target, +h4:target, +h5:target, +h6:target { + margin-left: -10px; + border-left: 10px solid #f1edba; +} + +:link, +:visited { + color: #6C8C22; + text-decoration: none; +} + +:link:hover, +:visited:hover { + border-bottom: 1px dotted #6C8C22; +} + +code, +pre { + font-family: "Source Code Pro", Monaco, monospace; +} + +/* @group Generic Classes */ + +.initially-hidden { + display: none; +} + +#search-field { + width: 98%; + background: white; + border: none; + height: 1.5em; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + text-align: left; +} +#search-field:focus { + background: #f1edba; +} +#search-field:-moz-placeholder, +#search-field::-webkit-input-placeholder { + font-weight: bold; + color: #666; +} + +.missing-docs { + font-size: 120%; + background: white url(images/wrench_orange.png) no-repeat 4px center; + color: #ccc; + line-height: 2em; + border: 1px solid #d00; + opacity: 1; + padding-left: 20px; + text-indent: 24px; + letter-spacing: 3px; + font-weight: bold; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +.target-section { + border: 2px solid #dcce90; + border-left-width: 8px; + padding: 0 1em; + background: #fff3c2; +} + +/* @end */ + +/* @group Index Page, Standalone file pages */ +.table-of-contents ul { + margin: 1em; + list-style: none; +} + +.table-of-contents ul ul { + margin-top: 0.25em; +} + +.table-of-contents ul :link, +.table-of-contents ul :visited { + font-size: 16px; +} + +.table-of-contents li { + margin-bottom: 0.25em; +} + +.table-of-contents li .toc-toggle { + width: 16px; + height: 16px; + background: url(images/add.png) no-repeat; +} + +.table-of-contents li .toc-toggle.open { + background: url(images/delete.png) no-repeat; +} + +/* @end */ + +/* @group Top-Level Structure */ + +nav { + float: left; + width: 260px; + font-family: Helvetica, sans-serif; + font-size: 14px; +} + +main { + display: block; + margin: 0 2em 5em 260px; + padding-left: 20px; + min-width: 340px; + font-size: 16px; +} + +main h1, +main h2, +main h3, +main h4, +main h5, +main h6 { + font-family: Helvetica, sans-serif; +} + +.table-of-contents main { + margin-left: 2em; +} + +#validator-badges { + clear: both; + margin: 1em 1em 2em; + font-size: smaller; +} + +/* @end */ + +/* @group navigation */ +nav { + margin-bottom: 1em; +} + +nav .nav-section { + margin-top: 2em; + border-top: 2px solid #aaa; + font-size: 90%; + overflow: hidden; +} + +nav h2 { + margin: 0; + padding: 2px 8px 2px 8px; + background-color: #e8e8e8; + color: #555; + font-size: 125%; + text-align: center; +} + +nav h3, +#table-of-contents-navigation { + margin: 0; + padding: 2px 8px 2px 8px; + text-align: right; + background-color: #e8e8e8; + color: #555; +} + +nav ul, +nav dl, +nav p { + padding: 4px 8px 0; + list-style: none; +} + +#project-navigation .nav-section { + margin: 0; + border-top: 0; +} + +#home-section h2 { + text-align: center; +} + +#table-of-contents-navigation { + font-size: 1.2em; + font-weight: bold; + text-align: center; +} + +#search-section { + margin-top: 0; + border-top: 0; +} + +#search-field-wrapper { + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; + padding: 3px 8px; + background-color: #e8e8e8; + color: #555; +} + +ul.link-list li { + white-space: nowrap; + line-height: 1.4em; +} + +ul.link-list .type { + font-size: 8px; + text-transform: uppercase; + color: white; + background: #969696; + padding: 2px 4px; + -webkit-border-radius: 5px; +} + +.calls-super { + background: url(images/arrow_up.png) no-repeat right center; +} + +/* @end */ + +/* @group Documentation Section */ +main { + color: #333; +} + +main > h1:first-child, +main > h2:first-child, +main > h3:first-child, +main > h4:first-child, +main > h5:first-child, +main > h6:first-child { + margin-top: 0px; +} + +main sup { + vertical-align: super; + font-size: 0.8em; +} + +/* The heading with the class name */ +main h1[class] { + margin-top: 0; + margin-bottom: 1em; + font-size: 2em; + color: #6C8C22; +} + +main h1 { + margin: 2em 0 0.5em; + font-size: 1.7em; +} + +main h2 { + margin: 2em 0 0.5em; + font-size: 1.5em; +} + +main h3 { + margin: 2em 0 0.5em; + font-size: 1.2em; +} + +main h4 { + margin: 2em 0 0.5em; + font-size: 1.1em; +} + +main h5 { + margin: 2em 0 0.5em; + font-size: 1em; +} + +main h6 { + margin: 2em 0 0.5em; + font-size: 1em; +} + +main p { + margin: 0 0 0.5em; + line-height: 1.4em; +} + +main pre { + margin: 1.2em 0.5em; + padding: 1em; + font-size: 0.8em; +} + +main hr { + margin: 1.5em 1em; + border: 2px solid #ddd; +} + +main blockquote { + margin: 0 2em 1.2em 1.2em; + padding-left: 0.5em; + border-left: 2px solid #ddd; +} + +main ol, +main ul { + margin: 1em 2em; +} + +main li > p { + margin-bottom: 0.5em; +} + +main dl { + margin: 1em 0.5em; +} + +main dt { + margin-bottom: 0.5em; + font-weight: bold; +} + +main dd { + margin: 0 1em 1em 0.5em; +} + +main header h2 { + margin-top: 2em; + border-width: 0; + border-top: 4px solid #bbb; + font-size: 130%; +} + +main header h3 { + margin: 2em 0 1.5em; + border-width: 0; + border-top: 3px solid #bbb; + font-size: 120%; +} + +.documentation-section-title { + position: relative; +} +.documentation-section-title .section-click-top { + position: absolute; + top: 6px; + left: 12px; + font-size: 10px; + color: #9b9877; + visibility: hidden; + padding-left: 0.5px; +} + +.documentation-section-title:hover .section-click-top { + visibility: visible; +} + +.constants-list > dl { + margin: 1em 0 2em; + border: 0; +} + +.constants-list > dl dt { + margin-bottom: 0.75em; + padding-left: 0; + font-family: "Source Code Pro", Monaco, monospace; + font-size: 110%; +} + +.constants-list > dl dt a { + color: inherit; +} + +.constants-list > dl dd { + margin: 0 0 2em 0; + padding: 0; + color: #666; +} + +.documentation-section h2 { + position: relative; +} + +.documentation-section h2 a { + position: absolute; + top: 8px; + right: 10px; + font-size: 12px; + color: #9b9877; + visibility: hidden; +} + +.documentation-section h2:hover a { + visibility: visible; +} + +/* @group Method Details */ + +main .method-source-code { + max-height: 0; + overflow: hidden; + transition-duration: 200ms; + transition-delay: 0ms; + transition-property: all; + transition-timing-function: ease-in-out; +} + +main .method-source-code.active-menu { + max-height: 100vh; +} + +main .method-description .method-calls-super { + color: #333; + font-weight: bold; +} + +main .method-detail { + margin-bottom: 2.5em; + cursor: pointer; +} + +main .method-detail:target { + margin-left: -10px; + border-left: 10px solid #f1edba; +} + +main .method-heading { + position: relative; + font-family: "Source Code Pro", Monaco, monospace; + font-size: 110%; + font-weight: bold; + color: #333; +} +main .method-heading :link, +main .method-heading :visited { + color: inherit; +} +main .method-click-advice { + position: absolute; + top: 2px; + right: 5px; + font-size: 12px; + color: #9b9877; + visibility: hidden; + padding-right: 20px; + line-height: 20px; + background: url(images/zoom.png) no-repeat right top; +} +main .method-heading:hover .method-click-advice { + visibility: visible; +} + +main .method-alias .method-heading { + color: #666; +} + +main .method-description, +main .aliases { + margin-top: 0.75em; + color: #333; +} + +main .aliases { + padding-top: 4px; + font-style: italic; + cursor: default; +} +main .method-description ul { + margin-left: 1.5em; +} + +main #attribute-method-details .method-detail:hover { + background-color: transparent; + cursor: default; +} +main .attribute-access-type { + text-transform: uppercase; + padding: 0 1em; +} +/* @end */ + +/* @end */ + +/* @group Source Code */ + +pre { + margin: 0.5em 0; + border: 1px dashed #999; + padding: 0.5em; + background: #262626; + color: white; + overflow: auto; +} + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #dc0000; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } + +/* @end */ + + +/* @group search results */ +#search-results { + font-family: Lato, sans-serif; + font-weight: 300; +} + +#search-results .search-match { + font-family: Helvetica, sans-serif; + font-weight: normal; +} + +#search-results .search-selected { + background: #e8e8e8; + border-bottom: 1px solid transparent; +} + +#search-results li { + list-style: none; + border-bottom: 1px solid #aaa; + margin-bottom: 0.5em; +} + +#search-results li:last-child { + border-bottom: none; + margin-bottom: 0; +} + +#search-results li p { + padding: 0; + margin: 0.5em; +} + +#search-results .search-namespace { + font-weight: bold; +} + +#search-results li em { + background: yellow; + font-style: normal; +} + +#search-results pre { + margin: 0.5em; + font-family: "Source Code Pro", Monaco, monospace; +} + +/* @end */ + diff --git a/doc/fonts/Lato-Light.ttf b/doc/fonts/Lato-Light.ttf new file mode 100644 index 0000000..b49dd43 Binary files /dev/null and b/doc/fonts/Lato-Light.ttf differ diff --git a/doc/fonts/Lato-LightItalic.ttf b/doc/fonts/Lato-LightItalic.ttf new file mode 100644 index 0000000..7959fef Binary files /dev/null and b/doc/fonts/Lato-LightItalic.ttf differ diff --git a/doc/fonts/Lato-Regular.ttf b/doc/fonts/Lato-Regular.ttf new file mode 100644 index 0000000..839cd58 Binary files /dev/null and b/doc/fonts/Lato-Regular.ttf differ diff --git a/doc/fonts/Lato-RegularItalic.ttf b/doc/fonts/Lato-RegularItalic.ttf new file mode 100644 index 0000000..bababa0 Binary files /dev/null and b/doc/fonts/Lato-RegularItalic.ttf differ diff --git a/doc/fonts/SourceCodePro-Bold.ttf b/doc/fonts/SourceCodePro-Bold.ttf new file mode 100644 index 0000000..61e3090 Binary files /dev/null and b/doc/fonts/SourceCodePro-Bold.ttf differ diff --git a/doc/fonts/SourceCodePro-Regular.ttf b/doc/fonts/SourceCodePro-Regular.ttf new file mode 100644 index 0000000..85686d9 Binary files /dev/null and b/doc/fonts/SourceCodePro-Regular.ttf differ diff --git a/doc/gxg_gemspec.html b/doc/gxg_gemspec.html new file mode 100644 index 0000000..971fd06 --- /dev/null +++ b/doc/gxg_gemspec.html @@ -0,0 +1,149 @@ + + + + + + +gxg.gemspec - RDoc Documentation + + + + + + + + + + + + + + + + + +
+ +

Gem::Specification.new do |s|

+ +
s.name        = 'gxg'
+s.version     = '0.0.40'
+s.licenses    = ['UNLICENSED']
+s.summary     = "GxG Framework for JRuby"
+s.description = "GxG Framework for JRuby"
+s.authors     = ["G. Gibson"]
+s.email       = 'mistergibson@disroot.org'
+s.files       = ['gxg.gemspec'] + ['lib/gxg.rb'] + Dir['lib/gxg/*']
+s.homepage    = 'https://cdc.csproject.org/c0de'
+s.metadata    = { "source_code_uri" => "https://cdc.csproject.org/c0de" }
+s.require_path = "lib"
+# ### Requirements:
+s.add_runtime_dependency 'bcrypt'
+s.add_runtime_dependency 'sequel'
+s.add_runtime_dependency 'ffi'
+s.add_runtime_dependency 'chronic'
+s.add_runtime_dependency 'rufus-scheduler', '~> 3.0'
+s.add_runtime_dependency 'ffi-rzmq'
+s.add_runtime_dependency 'ezmq'
+s.add_runtime_dependency 'nokogiri'
+s.add_runtime_dependency 'mimemagic'
+s.add_runtime_dependency 'rubysl-securerandom'
+s.add_runtime_dependency 'net-ssh'
+s.add_runtime_dependency 'net-scp'
+s.add_runtime_dependency 'net-sftp'
+s.add_runtime_dependency 'xmpp4r'
+s.add_runtime_dependency 'blather'
+s.add_runtime_dependency 'rubysl-xmlrpc'
+s.add_runtime_dependency 'handsoap'
+s.add_runtime_dependency 'rest-client'
+s.add_runtime_dependency 'gmail_xoauth'
+s.add_runtime_dependency 'mail'
+s.add_runtime_dependency 'matrix_sdk'
+# ### Database Adapters:
+s.add_runtime_dependency 'jdbc-sqlite3'
+s.add_runtime_dependency 'jdbc-mysql'
+s.add_runtime_dependency 'jdbc-postgresql'
+s.add_runtime_dependency 'jdbc-as400'
+s.add_runtime_dependency 'jdbc-cassandra'
+s.add_runtime_dependency 'jdbc-crate'
+s.add_runtime_dependency 'jdbc-derby'
+s.add_runtime_dependency 'jdbc-filemaker'
+s.add_runtime_dependency 'jdbc-firebird'
+s.add_runtime_dependency 'jdbc-h2'
+s.add_runtime_dependency 'jdbc-hive2'
+s.add_runtime_dependency 'jdbc-hsqldb'
+s.add_runtime_dependency 'jdbc-jt400'
+s.add_runtime_dependency 'jdbc-jtds'
+s.add_runtime_dependency 'jdbc-luciddb'
+s.add_runtime_dependency 'jdbc-mssql'
+s.add_runtime_dependency 'jdbc-nuodb'
+# ### FIX : openedge requires external .jars in classpath -- exclude for now.
+# s.add_runtime_dependency 'jdbc-openedge'
+s.add_runtime_dependency 'jdbc-orientdb'
+s.add_runtime_dependency 'jdbc-phoenix'
+s.add_runtime_dependency 'jdbc-redshift'
+s.add_runtime_dependency 'jdbc-splice'
+s.add_runtime_dependency 'jdbc-vertica'
+
+ +

end

+
+ + + + + diff --git a/doc/images/add.png b/doc/images/add.png new file mode 100644 index 0000000..6332fef Binary files /dev/null and b/doc/images/add.png differ diff --git a/doc/images/arrow_up.png b/doc/images/arrow_up.png new file mode 100644 index 0000000..1ebb193 Binary files /dev/null and b/doc/images/arrow_up.png differ diff --git a/doc/images/brick.png b/doc/images/brick.png new file mode 100644 index 0000000..7851cf3 Binary files /dev/null and b/doc/images/brick.png differ diff --git a/doc/images/brick_link.png b/doc/images/brick_link.png new file mode 100644 index 0000000..9ebf013 Binary files /dev/null and b/doc/images/brick_link.png differ diff --git a/doc/images/bug.png b/doc/images/bug.png new file mode 100644 index 0000000..2d5fb90 Binary files /dev/null and b/doc/images/bug.png differ diff --git a/doc/images/bullet_black.png b/doc/images/bullet_black.png new file mode 100644 index 0000000..5761970 Binary files /dev/null and b/doc/images/bullet_black.png differ diff --git a/doc/images/bullet_toggle_minus.png b/doc/images/bullet_toggle_minus.png new file mode 100644 index 0000000..b47ce55 Binary files /dev/null and b/doc/images/bullet_toggle_minus.png differ diff --git a/doc/images/bullet_toggle_plus.png b/doc/images/bullet_toggle_plus.png new file mode 100644 index 0000000..9ab4a89 Binary files /dev/null and b/doc/images/bullet_toggle_plus.png differ diff --git a/doc/images/date.png b/doc/images/date.png new file mode 100644 index 0000000..783c833 Binary files /dev/null and b/doc/images/date.png differ diff --git a/doc/images/delete.png b/doc/images/delete.png new file mode 100644 index 0000000..08f2493 Binary files /dev/null and b/doc/images/delete.png differ diff --git a/doc/images/find.png b/doc/images/find.png new file mode 100644 index 0000000..1547479 Binary files /dev/null and b/doc/images/find.png differ diff --git a/doc/images/loadingAnimation.gif b/doc/images/loadingAnimation.gif new file mode 100644 index 0000000..82290f4 Binary files /dev/null and b/doc/images/loadingAnimation.gif differ diff --git a/doc/images/macFFBgHack.png b/doc/images/macFFBgHack.png new file mode 100644 index 0000000..c6473b3 Binary files /dev/null and b/doc/images/macFFBgHack.png differ diff --git a/doc/images/package.png b/doc/images/package.png new file mode 100644 index 0000000..da3c2a2 Binary files /dev/null and b/doc/images/package.png differ diff --git a/doc/images/page_green.png b/doc/images/page_green.png new file mode 100644 index 0000000..de8e003 Binary files /dev/null and b/doc/images/page_green.png differ diff --git a/doc/images/page_white_text.png b/doc/images/page_white_text.png new file mode 100644 index 0000000..813f712 Binary files /dev/null and b/doc/images/page_white_text.png differ diff --git a/doc/images/page_white_width.png b/doc/images/page_white_width.png new file mode 100644 index 0000000..1eb8809 Binary files /dev/null and b/doc/images/page_white_width.png differ diff --git a/doc/images/plugin.png b/doc/images/plugin.png new file mode 100644 index 0000000..6187b15 Binary files /dev/null and b/doc/images/plugin.png differ diff --git a/doc/images/ruby.png b/doc/images/ruby.png new file mode 100644 index 0000000..f763a16 Binary files /dev/null and b/doc/images/ruby.png differ diff --git a/doc/images/tag_blue.png b/doc/images/tag_blue.png new file mode 100644 index 0000000..3f02b5f Binary files /dev/null and b/doc/images/tag_blue.png differ diff --git a/doc/images/tag_green.png b/doc/images/tag_green.png new file mode 100644 index 0000000..83ec984 Binary files /dev/null and b/doc/images/tag_green.png differ diff --git a/doc/images/transparent.png b/doc/images/transparent.png new file mode 100644 index 0000000..d665e17 Binary files /dev/null and b/doc/images/transparent.png differ diff --git a/doc/images/wrench.png b/doc/images/wrench.png new file mode 100644 index 0000000..5c8213f Binary files /dev/null and b/doc/images/wrench.png differ diff --git a/doc/images/wrench_orange.png b/doc/images/wrench_orange.png new file mode 100644 index 0000000..565a933 Binary files /dev/null and b/doc/images/wrench_orange.png differ diff --git a/doc/images/zoom.png b/doc/images/zoom.png new file mode 100644 index 0000000..908612e Binary files /dev/null and b/doc/images/zoom.png differ diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..504ae39 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,331 @@ + + + + + + +RDoc Documentation + + + + + + + + + + + + + + + + + +
+

This is the API documentation for RDoc Documentation. +

+ + + + + diff --git a/doc/js/darkfish.js b/doc/js/darkfish.js new file mode 100644 index 0000000..089813f --- /dev/null +++ b/doc/js/darkfish.js @@ -0,0 +1,85 @@ +/** + * + * Darkfish Page Functions + * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $ + * + * Author: Michael Granger + * + */ + +/* Provide console simulation for firebug-less environments */ +/* +if (!("console" in window) || !("firebug" in console)) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +}; +*/ + + +function showSource( e ) { + var target = e.target; + while (!target.classList.contains('method-detail')) { + target = target.parentNode; + } + if (typeof target !== "undefined" && target !== null) { + target = target.querySelector('.method-source-code'); + } + if (typeof target !== "undefined" && target !== null) { + target.classList.toggle('active-menu') + } +}; + +function hookSourceViews() { + document.querySelectorAll('.method-heading').forEach(function (codeObject) { + codeObject.addEventListener('click', showSource); + }); +}; + +function hookSearch() { + var input = document.querySelector('#search-field'); + var result = document.querySelector('#search-results'); + result.classList.remove("initially-hidden"); + + var search_section = document.querySelector('#search-section'); + search_section.classList.remove("initially-hidden"); + + var search = new Search(search_data, input, result); + + search.renderItem = function(result) { + var li = document.createElement('li'); + var html = ''; + + // TODO add relative path to + + + + + + + + + + + + + +
+

Table of Contents - RDoc Documentation

+ +

Pages

+ + +

Classes and Modules

+ + +

Methods

+ +
+ + + + diff --git a/gxg-framework.gemspec b/gxg-framework.gemspec new file mode 100644 index 0000000..3f988f3 --- /dev/null +++ b/gxg-framework.gemspec @@ -0,0 +1,66 @@ +Gem::Specification.new do |s| + s.name = 'gxg-framework' + s.version = '0.0.52' + s.licenses = ['UNLICENSED'] + s.summary = "GxG Framework for JRuby" + s.description = "GxG Framework for JRuby" + s.authors = ["G. Gibson"] + s.email = 'mistergibson@disroot.org' + s.files = ['gxg-framework.gemspec'] + ['lib/gxg-framework.rb'] + Dir['lib/gxg/*'] + s.homepage = 'https://cdc.csproject.org/c0de' + s.metadata = { "source_code_uri" => "https://cdc.csproject.org/c0de" } + s.require_path = "lib" + # ### Requirements: + 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 'bcrypt' + s.add_runtime_dependency 'sequel' + s.add_runtime_dependency 'ffi' + s.add_runtime_dependency 'chronic' + s.add_runtime_dependency 'rufus-scheduler', '~> 3.0' + # FIX : big hassle with windows - exclude for now + # s.add_runtime_dependency 'ffi-rzmq' + # s.add_runtime_dependency 'ezmq' + s.add_runtime_dependency 'nokogiri' + s.add_runtime_dependency 'mimemagic' + s.add_runtime_dependency 'rubysl-securerandom' + s.add_runtime_dependency 'net-ssh' + s.add_runtime_dependency 'net-scp' + s.add_runtime_dependency 'net-sftp' + s.add_runtime_dependency 'rubysl-xmlrpc' + s.add_runtime_dependency 'handsoap' + s.add_runtime_dependency 'rest-client' + s.add_runtime_dependency 'gmail_xoauth' + s.add_runtime_dependency 'mail' + s.add_runtime_dependency 'matrix_sdk' + s.add_runtime_dependency 'state_machines' + s.add_runtime_dependency 'net-ldap' + # ### Database Adapters: + s.add_runtime_dependency 'jdbc-sqlite3' + s.add_runtime_dependency 'jdbc-mysql' + s.add_runtime_dependency 'jdbc-postgresql' + s.add_runtime_dependency 'jdbc-as400' + s.add_runtime_dependency 'jdbc-cassandra' + s.add_runtime_dependency 'jdbc-crate' + s.add_runtime_dependency 'jdbc-derby' + s.add_runtime_dependency 'jdbc-filemaker' + s.add_runtime_dependency 'jdbc-firebird' + s.add_runtime_dependency 'jdbc-h2' + s.add_runtime_dependency 'jdbc-hive2' + s.add_runtime_dependency 'jdbc-hsqldb' + s.add_runtime_dependency 'jdbc-jt400' + s.add_runtime_dependency 'jdbc-jtds' + s.add_runtime_dependency 'jdbc-luciddb' + s.add_runtime_dependency 'jdbc-mssql' + s.add_runtime_dependency 'jdbc-nuodb' + # ### FIX : openedge requires external .jars in classpath -- exclude for now. + # s.add_runtime_dependency 'jdbc-openedge' + s.add_runtime_dependency 'jdbc-orientdb' + s.add_runtime_dependency 'jdbc-phoenix' + # ### FIX : redshift is missing the RedshiftJDBC4.jar file -- exclude for now. + # s.add_runtime_dependency 'jdbc-redshift' + s.add_runtime_dependency 'jdbc-splice' + s.add_runtime_dependency 'jdbc-vertica' +end diff --git a/lib/gxg-framework.rb b/lib/gxg-framework.rb new file mode 100644 index 0000000..2327f88 --- /dev/null +++ b/lib/gxg-framework.rb @@ -0,0 +1,907 @@ +# shell command: +module Kernel + def shell(command=nil,&block) + if command.to_s.size > 0 + block.call(`#{command.to_s}`) + true + end + end +end +# bootstrap.rb is a separate file so other external scripts can avail themselves of it. +require(::File.expand_path(::File.dirname(__FILE__) << "/gxg/bootstrap.rb")) +# Load base requirements: +requirements = [] +requirements.push({:requirement => "ostruct", :gem => nil}) +requirements.push({:requirement => "logger", :gem => nil}) +requirements.push({:requirement => "fileutils", :gem => nil}) +requirements.push({:requirement => "bigdecimal", :gem => nil}) +requirements.push({:requirement => "singleton", :gem => nil}) +requirements.push({:requirement => "set", :gem => nil}) +requirements.push({:requirement => "csv", :gem => nil}) +requirements.push({:requirement => "uri", :gem => nil}) +requirements.push({:requirement => "socket", :gem => nil}) +requirements.push({:requirement => "resolv", :gem => nil}) +requirements.push({:requirement => "stringio", :gem => nil}) +requirements.push({:requirement => "date", :gem => nil}) +requirements.push({:requirement => "ffi", :gem => "ffi"}) +requirements.push({:requirement => "base64", :gem => nil}) +requirements.push({:requirement => "digest/md5", :gem => nil}) +requirements.push({:requirement => "digest/sha2", :gem => nil}) +requirements.push({:requirement => "securerandom", :gem => nil}) +requirements.push({:requirement => "json", :gem => nil}) +requirements.push({:requirement => "sys-cpu", :gem => "sys-cpu"}) +requirements.push({:requirement => "sys-proctable", :gem => "sys-proctable"}) +requirements.push({:requirement => "chronic", :gem => "chronic"}) +# FIX : big hassle with windows - exclude for now +# requirements.push({:requirement => "ffi-rzmq", :gem => "ffi-rzmq"}) +# requirements.push({:requirement => "ezmq", :gem => "ezmq"}) +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 => "bcrypt", :gem => "bcrypt"}) +requirements.push({:requirement => "matrix_sdk", :gem => "matrix_sdk"}) +requirements.push({:requirement => "state_machines", :gem => "state_machines"}) +# consider adding facets later - esp. if file io needs extension +# ### Database Adapter for JRuby: +if ::RUBY_ENGINE == "jruby" + requirements.push({:requirement => "jdbc/sqlite3", :gem => "jdbc-sqlite3"}) + requirements.push({:requirement => "jdbc/mysql", :gem => "jdbc-mysql"}) + requirements.push({:requirement => "jdbc-postgresql", :gem => "jdbc-postgresql"}) + requirements.push({:requirement => "jdbc/as400", :gem => "jdbc-as400"}) + requirements.push({:requirement => "jdbc/cassandra", :gem => "jdbc-cassandra"}) + requirements.push({:requirement => "jdbc/crate", :gem => "jdbc-crate"}) + requirements.push({:requirement => "jdbc/derby", :gem => "jdbc-derby"}) + requirements.push({:requirement => "jdbc/filemaker", :gem => "jdbc-filemaker"}) + requirements.push({:requirement => "jdbc/firebird", :gem => "jdbc-firebird"}) + requirements.push({:requirement => "jdbc/h2", :gem => "jdbc-h2"}) + requirements.push({:requirement => "jdbc/hive2", :gem => "jdbc-hive2"}) + requirements.push({:requirement => "jdbc/hsqldb", :gem => "jdbc-hsqldb"}) + requirements.push({:requirement => "jdbc/jt400", :gem => "jdbc-jt400"}) + requirements.push({:requirement => "jdbc/jtds", :gem => "jdbc-jtds"}) + requirements.push({:requirement => "jdbc/luciddb", :gem => "jdbc-luciddb"}) + # ### FIX : MS-SQL Server driver not in OpenJDK 14 - exclude on THAT JDK for now + if ENV_JAVA.is_a?(::Hash) + if ENV_JAVA['java.specification.version'] == "1.8" + requirements.push({:requirement => "jdbc/mssql", :gem => "jdbc-mssql"}) + end + end + requirements.push({:requirement => "jdbc/nuodb", :gem => "jdbc-nuodb"}) + # ### FIX : openedge requires external .jars in classpath -- exclude for now. + # requirements.push({:requirement => "jdbc/openedge", :gem => "jdbc-openedge"}) + requirements.push({:requirement => "jdbc/orientdb", :gem => "jdbc-orientdb"}) + requirements.push({:requirement => "jdbc/phoenix", :gem => "jdbc-phoenix"}) + # ### FIX : redshift is missing the RedshiftJDBC4.jar file + # requirements.push({:requirement => "redshift", :gem => "jdbc-redshift"}) + requirements.push({:requirement => "jdbc/splice", :gem => "jdbc-splice"}) + requirements.push({:requirement => "jdbc/vertica", :gem => "jdbc-vertica"}) +end +# ### Load Requirements +load_error = nil +requirements.to_enum.each do |entry| + begin + require entry[:requirement] + rescue LoadError => the_error + puts "Error: Missing Requirement\n" + the_error.to_s + "\n" + if entry[:gem] + puts "You need to install " + (entry[:gem]) + ", like so:\n" + puts "sudo gem install '" + (entry[:gem]) + "'" + else + puts "You need to install " + (entry[:requirement]) + "." + end + load_error = true + break + end +end +unless load_error + ## --------------------------------------------------------------------------------------------------------------------- + # Various preliminary patches to dependencies: + # --------------------------------------------------------------------------------------------------------------------- + # Various preliminary patches to standard classes: + # + # --------------------------------------------------------------------------------------------------------------------- + # main module declaration: + module GxG + # + def self.uuid_generate() + ::SecureRandom::uuid.to_s + end + # LOG = $LOG + LOG = ::Logger.new(STDOUT) + # + end + # + # --------------------------------------------------------------------------------------------------------------------- + # Various data element classes: + require File.expand_path("./gxg/gxg_elements.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # Preliminary alternations to Kernel/Object class: Actor/Event support + class Object + # private methods + private + def this() + self + end + # + def pause(params={}) + # + end + # public methods + 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 + # + def alive?() + # Why: adjusting the entire object space to dealing with possibilities introduced by Celluloid. + true + end + # + def actor?() + false + end + # + def handle_error(the_error={}) + if the_error[:error].is_a?(::Exception) + log_error(the_error) + end + end + # + def serialize() + if self.is_any?(::Array, ::Hash, ::Set, ::Struct) + data = self.process do |entry, selector| + entry.serialize() + end + begin + ("structure:" + ::Marshal.dump(data).encode64()) + rescue Exception => the_error + "marshal:BAgw" + end + else + if self.is_a?(::String) + if self.serialized?() + self + else + ("marshal:" + ::Marshal.dump(self).encode64()) + end + else + # by default, or upon error, returns marshaled nil, must override to get other serialization + begin + ("marshal:" + ::Marshal.dump(self).encode64()) + rescue Exception => the_error + "marshal:BAgw" + end + end + end + end + # + end + # --------------------------------------------------------------------------------------------------------------------- + # Units and support element classes: + require File.expand_path("./gxg/gxg_units.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # Event classes: + require File.expand_path("./gxg/gxg_events.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # --------------------------------------------------------------------------------------------------------------------- + # Additional alternations to Kernel/Object class: Data element class support, quota support functions + module Kernel + # + # alias :stock_enum_for :enum_for + # alias :stock_to_enum :to_enum + # def enum_for(method=:each,*args) + # GxG::Enumerator.new(self,method,*args) + # end + # alias :to_enum :enum_for + # + def slots_used(counted=[], options={}) + # returns how many heap slots are consumed by this *instance* (and elements) and its instance variables (and elements) all the way down. + # General Research: see memprof for how much is consumed by a Module or Class or Method. + # TODO: ::Kernel#slots_used : find the byte-size of a given :method and add it to slot_count initial value. + unless counted.is_a?(Array) + if counted.is_a?(Hash) + options = counted + end + counted = [] + end + # warning: assumes references to nil incur no RVALUE allocation. + exclusions = [nil] + if options[:exclude] + unless options[:exclude].is_a?(Array) + options[:exclude] = [(options[:exclude])] + end + exclusions = (options[:exclude] << nil) + end + slot_count = 0 + unless (counted.include?(self) || exclusions.include?(self)) + counted << self + slot_count = 1 + if self.is_any?(::Array,::Hash,::Set,::Struct) + self.search do |entry,selector,container| + unless (counted.include?(entry) || exclusions.include?(entry)) + unless selector.is_a?(Numeric) + slot_count += selector.slots_used(counted, {:exclude => exclusions}) + end + slot_count += entry.slots_used(counted, {:exclude => exclusions}) + end + end + end + end + self.instance_variables.to_enum.each do |ivar| + unless (counted.include?(ivar) || exclusions.include?(ivar)) + if ivar.is_any?(::Array,::Hash,::Set,::Struct) + ivar.search do |entry,selector,container| + unless (counted.include?(entry) || exclusions.include?(entry)) + unless selector.is_a?(Numeric) + slot_count += selector.slots_used(counted, {:exclude => exclusions}) + end + slot_count += entry.slots_used(counted, {:exclude => exclusions}) + end + end + else + slot_count += ivar.slots_used(counted, {:exclude => exclusions}) + end + end + end + slot_count + end + # + def content_size_used(counted=[], options={}) + # returns how many bytes are consumed by this *instance* (and elements) and its instance variables (and elements) all the way down. + unless counted.is_a?(Array) + if counted.is_a?(Hash) + options = counted + end + counted = [] + end + exclusions = [nil] + if options[:exclude] + unless options[:exclude].is_a?(Array) + options[:exclude] = [(options[:exclude])] + end + exclusions = (options[:exclude] << nil) + end + count = 0 + unless (counted.include?(self) || exclusions.include?(self)) + counted << self + if self.is_any?(::Array,::Hash,::Struct) + self.search do |entry,selector,container| + unless counted.include?(entry) + unless selector.is_a?(Numeric) + count += selector.content_size_used(counted, {:exclude => exclusions}) + end + count += entry.content_size_used(counted, {:exclude => exclusions}) + end + end + else + unless self.is_a?(::GxG::ByteArray) + if self.respond_to?(:bytesize) + count = self.bytesize + else + if self.respond_to?(:size) + count = self.size + else + # TODO: cull the size of more exotic classes by class: + count = 0 + end + end + end + end + end + # + self.instance_variables.to_enum.each do |ivar| + # must decode from symbol to actual instance var + ivar = self.instance_eval(ivar.to_s) + unless (counted.include?(ivar) || exclusions.include?(ivar)) + if ivar.is_any?(::Array,::Hash,::Struct) + ivar.search do |entry,selector,container| + unless (counted.include?(entry) || exclusions.include?(entry)) + unless selector.is_a?(Numeric) + # Symbols and Strings in Hashes take up heap space, so lets count it. + count += selector.content_size_used(counted, {:exclude => exclusions}) + end + count += entry.content_size_used(counted, {:exclude => exclusions}) + end + end + else + unless (counted.include?(ivar) || exclusions.include?(ivar)) + count += ivar.content_size_used(counted, {:exclude => exclusions}).to_i + # Subsequent references to this object will only incur an RVALUE slot count (me thinks) + counted << ivar + end + end + end + end + count + end + # + end + # + class Object + include Kernel + # + private + # + # logging hooks + def log_unknown(message = nil, progname = nil, &block) + # a.k.a 'unknown' + if message.is_a?(::Hash) + if message[:trace] + message = (message[:trace].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + else + message = (message[:unknown].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + end + ::GxG::LOG.unknown(message.to_s) + end + alias :log_trace :log_unknown + def log_fatal(message = nil, progname = nil, &block) + if message.is_a?(::Hash) + if message[:error].is_a?(::Exception) || message[:fatal].is_a?(::Exception) + if message[:error] + message = (message[:error].to_s + "\n Parameters: #{message[:parameters].inspect.to_s},\n Backtrace: " + message[:error].backtrace.join("\n")) + else + message = (message[:fatal].to_s + "\n Parameters: #{message[:parameters].inspect.to_s},\n Backtrace: " + message[:fatal].backtrace.join("\n")) + end + else + if message[:error] + message = (message[:error].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + else + message = (message[:fatal].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + end + end + ::GxG::LOG.fatal(message.to_s) + end + def log_error(message = nil, progname = nil, &block) + if message.is_a?(::Hash) + if message[:error].is_a?(::Exception) + message = (message[:error].to_s + "\n Parameters: #{message[:parameters].inspect.to_s},\n Backtrace: " + (message[:error].backtrace || []).join("\n")) + else + message = (message[:error].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + end + ::GxG::LOG.error(message.to_s) + end + def log_warn(message = nil, progname = nil, &block) + if message.is_a?(::Hash) + if message[:warning] + message = (message[:warning].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + else + message = (message[:warn].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + end + ::GxG::LOG.warn(message.to_s) + end + alias :log_warning :log_warn + def log_info(message = nil, progname = nil, &block) + if message.is_a?(::Hash) + message = (message[:info].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + ::GxG::LOG.info(message.to_s) + end + def log_debug(message = nil, progname = nil, &block) + if message.is_a?(::Hash) + if message[:dev] || message[:development] + if message[:dev] + message = (message[:dev].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + else + message = (message[:development].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + else + message = (message[:debug].to_s + "\n Parameters: #{message[:parameters].inspect.to_s}") + end + end + ::GxG::LOG.debug(message.to_s) + end + alias :log_dev :log_debug + alias :log_development :log_debug + # + def bytes(*args) + GxG::ByteArray::try_convert(args) + end + # + def new_message(*args) + if this.actor?() + ::GxG::Events::Message.new({:sender => this().to_uri(), :subject => args[1], :body => args[0]}) + else + ::GxG::Events::Message.new({:sender => this(), :subject => args[1], :body => args[0]}) + end + end + # + public + # + def millisecond_latency(*args,&block) + if block + starting = Time.now.to_f + result = block.call(*args) + ending = Time.now.to_f + # Results are approximate: does not include the time ruby needs to return from the call, + # assign the result RVALUE, collect a time, convert it to a Float, and assign ending RVALUE.. + # However, it is pretty darn close. + {:result => result, :milliseconds => (ending - starting)} + else + # If :milliseconds are nil then the block was not passed and since it never ran, there is no latency to count up. + # Simply do a return_var[:milliseconds].to_f for auto-accumulators w/o post-call comparison. + {:result => nil, :milliseconds => 0.0} + end + # Attribution : http://stackoverflow.com/questions/2289381/how-to-time-an-operation-in-milliseconds-in-ruby + end + end + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_augmented.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_entities.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + require 'jruby' + JRuby.objectspace = true + require File.expand_path("./gxg/gxg_engine.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # Alterations to Object class: GxG::Engine dependency - quota supports + class Object + public + # + def heap_used(options={}) + unless options.is_a?(Hash) + options={} + end + (self.content_size_used(options).to_i + (self.slots_used(options).to_i * GxG::Engine::profile[:slot_size]).to_i) + end + # + end + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_transcode.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + if GxG::SYSTEM.platform[:platform] == :windows + # Fixes an issue where IO::SYNC and File::SYNC are not defined on Windows. + class IO + SYNC = DSYNC + end + class File + SYNC = DSYNC + end + end + require File.expand_path("./gxg/gxg_io.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_net.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # FIX : big hassle with windows - exclude for now + # require File.expand_path("./gxg/gxg_zmq.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # Set GxG Version and instantiate GxG::SYSTEM object: + module GxG + # setup constants, dispose of BOOTSTRAP data. + VERSION = GxG::Version.new({:phase => :alpha, :revision => 50}) + VERSION.freeze + # + def self.shutdown() + [::GxG::Networking::SshClient, ::GxG::Networking::ImapClient, ::GxG::Networking::RestClient, ::GxG::Networking::SoapClient, ::GxG::Networking::SmtpClient, ::GxG::Networking::XmlrpcClient, ::GxG::Networking::SftpClient, ::GxG::Networking::Pop3Client, ::GxG::Networking::HttpsClient, ::GxG::Networking::HttpClient, ::GxG::Networking::MatrixClient, ::GxG::Networking::FtpClient].each do |the_class| + ::GxG::Engine::instance_process(the_class) do |the_object| + if the_object.respond_to?(:logout) + the_object.logout() + end + end + end + $Dispatcher.shutdown() + # FOR NOW : ZMQ is excluded awaiting dep fix. + # ::GxG::Networking::ZMQ::zmq_default_context.terminate() + end + end + # First attempt at object serialization/reconstitution: + module GxG + def self.reconstitute(raw_data="") + # See: http://stackoverflow.com/questions/5758464/ruby-how-do-i-check-if-a-class-is-defined + # + if raw_data.is_a?(::String) + if raw_data.serialized?() + result = nil + if raw_data.include?("marshal:") + begin + result = ::Marshal.load(raw_data[(8..-1)].decode64()) + rescue Exception + # Question : what to do here. + end + else + if raw_data.include?("structure:") + data = ::Marshal.load(raw_data[(10..-1)].decode64()) + if data.is_any?(::Array, ::Hash, ::Set, ::Struct) + result = data.process do |entry, selector| + if entry.is_a?(::String) + if entry.serialized?() + ::GxG::reconstitute(entry) + # entry.unserialize() + else + entry + end + else + entry + end + end + end + else + raise Exception, "unrecognized serialization format" + end + end + else + result = raw_data + end + else + result = raw_data + end + result + # + end + # + # Generic toolbox of methods + 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.sql_statement?(the_string) + # See: https://larrysteinle.com/2011/02/20/use-regular-expressions-to-detect-sql-code-injection/ + if /('(''|[^'])*')|(;)|(\\x08(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})\\x08)/.match(the_string) + true + else + false + end + end + # + def self.valid_domain_name?(the_string) + if /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/.match(the_string) + true + else + false + end + end + # + def self.status() + result = nil + # Review : do some planning on tasks --> how does that impact status object cloning?? + $ThreadSafety.synchronize { result = $Status.clone } + result + end + # + def self.set_mode(the_mode=nil) + if the_mode + $ThreadSafety.synchronize { $Status[:mode] = the_mode.to_s.to_sym } + end + end + # + end + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_database.rb",File.dirname(__FILE__)) + require File.expand_path("./gxg/gxg_dbfs.rb",File.dirname(__FILE__)) + require File.expand_path("./gxg/net_clients.rb",File.dirname(__FILE__)) + # Database record retrieval methods: + class Object + # private methods + private + # + def fetch(reference=nil) + result = nil + # + if ::GxG::valid_uuid?(reference) + record = ::GxG::DB[:roles][:data].retrieve_by_uuid(reference, ::GxG::DB[:administrator]) + if record + result = record + end + else + if reference.to_s.valid_path? + result = ::GxG::VFS.open(reference) + else + criteria = {} + if reference.is_a?(::Hash) + criteria = reference + else + criteria[:title] = reference.to_s + end + list = [] + ::GxG::DB[:roles].keys.each do |db_role| + list << ::GxG::DB[:roles][(db_role)].search_database(::GxG::DB[:administrator], criteria) + end + list = list.flatten! + if list[0].is_a?(::Hash) + result = ::GxG::DB[:roles][:data].retrieve_by_uuid(list[0][:uuid], ::GxG::DB[:administrator]) + end + end + end + # + result + end + # + def fetch_structure(reference=nil) + record = fetch(reference) + if record + record.as_structure + else + nil + end + end + # + def fetch_detached(reference=nil) + record = nil + if ::GxG::valid_uuid?(reference) + if ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache][(reference.to_s.to_sym)].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) } + record = ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache][(reference.to_s.to_sym)] } + end + else + found = nil + ::GxG::DB_SAFETY.synchronize { + ::GxG::DB[:cache].values.each do |the_record| + if the_record.title == reference + found = the_record.uuid + break + end + end + } + if found + record = ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache][(found)] } + end + end + unless record.is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) + record = fetch(reference) + if record.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) + record = record.detach + ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache][(record.uuid)] = record } + else + record = nil + end + end + record + end + # + def new_structure(credential=nil, ufs_criteria=nil, the_role=:data) + result = nil + if ::GxG::valud_uuid?(credential) + unless ufs_criteria.is_a?(::Hash) + ufs_criteria = nil + end + unless the_role.is_a?(::Symbol) + the_role = :data + end + the_database = nil + if ::GxG::DB[:roles][(the_role)] + the_database = ::GxG::DB[:roles][(the_role)] + end + if the_database.is_a?(::GxG::Database::Database) + if ufs_criteria + result = the_database.new_structure_from_format(credential, ufs_criteria) + else + result = the_database.try_persist({}, credential) + end + end + end + result + end + # + end + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/net_tools.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + require File.expand_path("./gxg/gxg_communications.rb",File.dirname(__FILE__)) + # --------------------------------------------------------------------------------------------------------------------- + # Register Database as supported protocol classes + ::GxG::CLIENTS.register_client("sqlite", ::GxG::Database::Database) + ::GxG::CLIENTS.register_client("mysql", ::GxG::Database::Database) + # ### Define runtime constants + module GxG + DOCUMENTS = {} + end + # + $ThreadSafety = ::Mutex.new + $Status = {:mode => :loading, :tasks => :undefined, :load => nil} + $Dispatcher = ::GxG::Events::EventDispatcher.new(0.333) + $Dispatcher.startup + $Dispatcher.every("5 seconds") do + load = GxG::Engine.determine_loads + $ThreadSafety.synchronize { $Status[:load] = load } + end + ::GxG::LOG[:default] = ::Logger.new(STDOUT) + ::GxG::LOG[:default].level = ::Logger::ERROR + $Dispatcher.every("0.333 seconds") do + ::GxG::LOG.process_messages() + end + # ### Defer long-runnign cpu info gather on Winderz + if ::GxG::SYSTEM.platform[:platform] == :windows + $Dispatcher.post_event(:root) do + 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=>"" + } + # + local_mapping = { + :AddressWidth=>"64", + :Architecture=>"9 ", + :AssetTag=>"", + :Availability=>"3 ", + :Caption=>"AMD64 Family 21 Model 2 Stepping 0", + :Characteristics=>" ", + :ConfigManagerErrorCode=>"", + :ConfigManagerUserConfig=>" ", + :CpuStatus=>"0", + :CreationClassName=>"Win32_Processor", + :CurrentClockSpeed=>:cpu_mhz, + :CurrentVoltage=>"", + :DataWidth=>"64 ", + :Description=>"AMD64 Family 21 Model 2 Stepping 0", + :DeviceID=>"CPU0", + :ErrorCleared=>"", + :ErrorDescription=>"", + :ExtClock=>"", + :Family=>:cpu_family, + :InstallDate=>" ", + :L2CacheSize=>" ", + :L2CacheSpeed=>"", + :L3CacheSize=>:cache_size, + :L3CacheSpeed=>"0 ", + :LastErrorCode=>" ", + :Level=>:cpuid_level, + :LoadPercentage=>"6 ", + :Manufacturer=>:vendor_id, + :MaxClockSpeed=>"4013 ", + :Name=>:model_name, + :NumberOfCores=>:cpu_cores, + :NumberOfEnabledCore=>" ", + :NumberOfLogicalProcessors=>"4", + :OtherFamilyDescription=>"", + :PartNumber=>"", + :PNPDeviceID=>" ", + :PowerManagementCapabilities=>" ", + :PowerManagementSupported=>"FALSE ", + :ProcessorId=>:physical_id, + :ProcessorType=>" ", + :Revision=>"512 ", + :Role=>"CPU ", + :SecondLevelAddressTranslationExtensions=>"FALSE", + :SerialNumber=>"", + :SocketDesignation=>" ", + :Status=>"OK", + :StatusInfo=>"3 ", + :Stepping=>:stepping, + :SystemCreationClassName=>"Win32_ComputerSystem ", + :SystemName=>"DESKTOP-D87M3K5", + :ThreadCount=>" ", + :UniqueId=>"", + :UpgradeMethod=>:model, + :Version=>"Model 2, Stepping 0", + :VirtualizationFirmwareEnabled=>"TRUE ", + :VMMonitorModeExtensions=>"FALSE", + :VoltageCaps=>" " + } + headers = ["AddressWidth", "Architecture", "AssetTag", "Availability", "Caption", "Characteristics", "ConfigManagerErrorCode", "ConfigManagerUserConfig", "CpuStatus", "CreationClassName", "CurrentClockSpeed", "CurrentVoltage", "DataWidth", "Description", "DeviceID", "ErrorCleared", "ErrorDescription", "ExtClock", "Family", "InstallDate", "L2CacheSize", "L2CacheSpeed", "L3CacheSize", "L3CacheSpeed", "LastErrorCode", "Level", "LoadPercentage", "Manufacturer", "MaxClockSpeed", "Name", "NumberOfCores", "NumberOfEnabledCore", "NumberOfLogicalProcessors", "OtherFamilyDescription", "PartNumber", "PNPDeviceID", "PowerManagementCapabilities", "PowerManagementSupported", "ProcessorId", "ProcessorType", "Revision", "Role", "SecondLevelAddressTranslationExtensions", "SerialNumber", "SocketDesignation", "Status", "StatusInfo", "Stepping", "SystemCreationClassName", "SystemName", "ThreadCount", "UniqueId", "UpgradeMethod", "Version", "VirtualizationFirmwareEnabled", "VMMonitorModeExtensions", "VoltageCaps"] + local_data = {} + headers.each do |the_header| + raw_data = `wmic cpu get #{the_header}`.split("\r\n") + data = raw_data[1].gsub(" ","") + # + if ["AddressWidth", "Architecture", "Availability", "CpuStatus","CurrentClockSpeed","DataWidth","Family","L2CacheSize","L2CacheSpeed","L3CacheSize","L3CacheSpeed","Level","LoadPercentage","MaxClockSpeed","NumberOfCores","NumberOfEnabledCore","NumberOfLogicalProcessors","StatusInfo","Stepping","ThreadCount","UpgradeMethod"].include?(the_header) + if data.to_s.size > 0 && data.to_s != " " + data = data.to_i + else + data = 0 + end + end + if ["PowerManagementSupported","SecondLevelAddressTranslationExtensions","VirtualizationFirmwareEnabled","VMMonitorModeExtensions"].include?(the_header) + if data.to_s.size > 0 && (data.to_s.include?("TRUE") || data.to_s.include?("true")) + data = true + else + data = false + end + end + if ["CurrentVoltage","VoltageCaps"].include?(the_header) + if data.to_s.size > 0 && data.to_s != " " + data = data.to_f + else + data = 0.0 + end + end + # + local_data[(raw_data[0].gsub(" ","").to_sym)] = data + end + local_data[:NumberOfLogicalProcessors].times do |the_cpu_selector| + cpu_record = gxg_template.clone + cpu_record[:processor] = the_cpu_selector + local_mapping.keys.each do |the_key| + if local_mapping[(the_key)].is_a?(::Symbol) + cpu_record[(local_mapping[(the_key)])] = local_data[(the_key)] + end + if the_key == :AddressWidth + cpu_record[:address_sizes][:physical_bits] = local_data[:AddressWidth] + cpu_record[:address_sizes][:virtual_bits] = local_data[:AddressWidth] + end + end + ::GxG::SYSTEM.gxg_cpu_add(cpu_record) + end + end + end + # + ::GxG::set_mode(:running) + # +end +# \ No newline at end of file diff --git a/lib/gxg/bootstrap.rb b/lib/gxg/bootstrap.rb new file mode 100644 index 0000000..5753c78 --- /dev/null +++ b/lib/gxg/bootstrap.rb @@ -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 diff --git a/lib/gxg/gxg_augmented.rb b/lib/gxg/gxg_augmented.rb new file mode 100644 index 0000000..7f3a25a --- /dev/null +++ b/lib/gxg/gxg_augmented.rb @@ -0,0 +1,4366 @@ +# To overcome load-order issues: +module GxG + module Database + class Database + # + end + class PersistedHash + # + end + class PersistedArray + # + end + end +end +# --------------------------------------------------------------------------------------------------------------------- +# Additional alternations to Standard element classes: expanded functionality +# (Tier 1 Augmentation) +# +# Class extensions: +# +class Class + # Attribution : http://stackoverflow.com/questions/2393697/look-up-all-descendants-of-a-class-in-ruby + def descendants + result = [] + ObjectSpace.each_object(::Class) do |the_class| + if the_class < self + result << the_class + end + end + result + end + # +end +# require facet classes here (prior to overrides), and ensure smooth integration with overrides. +# +# ### dup and clone support for Fixnum, Integer, Float, (and BigDecimal at some point) : don't even bother to tell me I gain nothing -- I've heard this before. +class NilClass + public + def initialize_clone + nil + end + alias :initialize_dup :initialize_clone + alias :dup :initialize_clone + def clone() + initialize_clone + end +end +# +#class FalseClass +# public +# def initialize_clone +# false +# end +# alias :initialize_dup :initialize_clone +# alias :dup :initialize_clone +# def clone() +# initialize_clone +# end +#end +# +#class TrueClass +# public +# def initialize_clone +# true +# end +# alias :initialize_dup :initialize_clone +# alias :dup :initialize_clone +# def clone() +# initialize_clone +# end +#end +# +class Float + public + def initialize_clone + (self + 0.0) + end + alias :initialize_dup :initialize_clone + alias :dup :initialize_clone + def clone() + initialize_clone + end + def to_d() + ::BigDecimal.new(self.to_s) + end +end +# +class Symbol + def initialize_clone + (self.to_s.dup.to_sym) + end + alias :initialize_dup :initialize_clone + alias :dup :initialize_clone + def clone() + initialize_clone + end +end +# +class DateTime + def to_d() + self.to_time.to_d + end +end +# +class Time + def to_datetime() + ::DateTime::parse(self.to_s) + end + def to_d() + self.to_f.to_d + end +end +# IO/StringIO/File alterations +# Fill in missing constants according to platform. +# ### win32 stuff +#006 #define F_SETFL 1 +#007 #define F_SETFD 2 +#008 #define F_GETFL 3 +#009 +#010 #define _O_RDONLY 0x0000 /* open for reading only */ +#011 #define _O_WRONLY 0x0001 /* open for writing only */ +#012 #define _O_RDWR 0x0002 /* open for reading and writing */ +#013 +#014 #define _O_NONBLOCK 0x0004 +#015 +#016 #define _O_APPEND 0x0008 /* writes done at eof */ +#017 #define _O_CREAT 0x0100 /* create and open file */ +#018 #define _O_TRUNC 0x0200 /* open and truncate */ +#019 #define _O_EXCL 0x0400 /* open only if file doesn't already exist */ +#020 #define _O_TEXT 0x4000 /* file mode is text (translated) */ +#021 #define _O_BINARY 0x8000 /* file mode is binary (untranslated) */ +#022 #define _O_ACCMODE 0x10000 +#023 +#024 #define _O_NOINHERIT 0 +#025 #define O_NOINHERIT _O_NOINHERIT +#026 +#027 #define O_RDONLY _O_RDONLY +#028 #define O_WRONLY _O_WRONLY +#029 #define O_RDWR _O_RDWR +#030 +#031 #define O_NONBLOCK _O_NONBLOCK +#032 +#033 #define O_APPEND _O_APPEND +#034 #define O_CREAT _O_CREAT +#035 #define O_TRUNC _O_TRUNC +#036 #define O_EXCL _O_EXCL +#037 #define O_TEXT _O_TEXT +#038 #define O_BINARY _O_BINARY +#039 #define O_ACCMODE _O_ACCMODE +# ### end win32 stuff +# @@valid_modes[:flags][:io][:tty] = 0x00000010 +# @@valid_modes[:flags][:io][:noctty] = ::IO::NOCTTY +# @@valid_modes[:flags][:io][:duplex] = 0x00000020 +# @@valid_modes[:flags][:io][:append] = ::IO::APPEND +# @@valid_modes[:flags][:io][:create] = ::IO::CREAT +# @@valid_modes[:flags][:io][:exclusive] = ::IO::EXCL +# @@valid_modes[:flags][:io][:wsplit] = 0x00000200 +# @@valid_modes[:flags][:io][:wsplit_initialized] = 0x00000400 +# @@valid_modes[:flags][:io][:trunc] = ::IO::TRUNC +# @@valid_modes[:flags][:io][:text] = ::IO::TEXT +# @@valid_modes[:flags][:io][:setenc_by_bom] = 0x00100000 +# See : http://boop.ankhcraft.com/library/ruby-api/dc/dac/io_8h.html +# Also : http://boop.ankhcraft.com/library/ruby-api/d2/d4d/file_8h.html +# also : http://boop.ankhcraft.com/library/ruby-api/d6/d13/file_8c.html +# +class IO + ## TRACK: io.h + #define FMODE_READABLE 0x00000001 + #@@valid_modes[:flags][:io][:read] = 0x00000001 + #define FMODE_WRITABLE 0x00000002 + #@@valid_modes[:flags][:io][:write] = 0x00000002 + #define FMODE_READWRITE (FMODE_READABLE|FMODE_WRITABLE) + #@@valid_modes[:flags][:io][:readwrite] = (@@valid_modes[:flags][:io][:read] | @@valid_modes[:flags][:io][:write]) + #define FMODE_BINMODE 0x00000004 + #@@valid_modes[:flags][:io][:binary] = 0x00000004 + #define FMODE_SYNC 0x00000008 + #@@valid_modes[:flags][:io][:sync] = 0x00000008 + #define FMODE_TTY 0x00000010 + #@@valid_modes[:flags][:io][:tty] = 0x00000010 + #define FMODE_DUPLEX 0x00000020 + #@@valid_modes[:flags][:io][:duplex] = 0x00000020 + #define FMODE_APPEND 0x00000040 + #@@valid_modes[:flags][:io][:append] = 0x00000040 + #define FMODE_CREATE 0x00000080 + #@@valid_modes[:flags][:io][:create] = 0x00000080 + #define FMODE_WSPLIT 0x00000200 + # related to io object's buffer management (position offsets and buffer lengths) + #@@valid_modes[:flags][:io][:wsplit] = 0x00000200 + #define FMODE_WSPLIT_INITIALIZED 0x00000400 + #@@valid_modes[:flags][:io][:wsplit_initialized] = 0x00000400 + #define FMODE_TRUNC 0x00000800 + #@@valid_modes[:flags][:io][:trunc] = 0x00000800 + #define FMODE_TEXTMODE 0x00001000 + #@@valid_modes[:flags][:io][:text] = 0x00001000 + #define FMODE_SETENC_BY_BOM 0x00100000 + #@@valid_modes[:flags][:io][:setenc_by_bom] = 0x00100000 + # [:SEEK_SET, :SEEK_CUR, :SEEK_END, :LOCK_SH, :LOCK_EX, :LOCK_UN, :LOCK_NB, :RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT, :EXCL, :NONBLOCK, :TRUNC, :NOCTTY, :BINARY, :SYNC, :DSYNC, :RSYNC, :NOFOLLOW, :NOATIME, :FNM_NOESCAPE, :FNM_PATHNAME, :FNM_DOTMATCH, :FNM_CASEFOLD, :FNM_SYSCASE] + unless defined?(TTY) + # Note: need to verify this int value before using: + TTY = 0x00000010 + end + unless defined?(DUPLEX) + # Note: need to verify this int value before using: + DUPLEX = 0x00000020 + end + unless defined?(WSPLIT) + # Note: need to verify this int value before using: + WSPLIT = 0x00000200 + end + # Not included in jruby + unless defined?(DSYNC) + # Note: need to verify this int value before using: + DSYNC = 4096 + end + unless defined?(RSYNC) + # Note: need to verify this int value before using: + RSYNC = 1052672 + end + unless defined?(NOATIME) + # Note: need to verify this int value before using: + NOATIME = 262144 + end + # + # Not included in rbx + unless defined?(NOFOLLOW) + # Note: need to verify this int value before using: + NOFOLLOW = 131072 + end + # + unless defined?(WSPLIT_INITIALIZED) + # Note: need to verify this int value before using: + WSPLIT_INITIALIZED = 0x00000400 + end + unless defined?(TEXT) + # What is the flag value under Win32? + TEXT = 0 + end + unless defined?(SETENC_BY_BOM) + # Note: need to verify this int value before using: + #SETENC_BY_BOM = 0x00100000 + SETENC_BY_BOM = 0 + end + # + def write_latency() + unless self.instance_variable_defined?(:@write_latency) + @write_latency= {:low => nil, :last => nil, :high => nil} + end + @write_latency.clone + end + # + def read_latency() + unless self.instance_variable_defined?(:@read_latency) + @read_latency= {:low => nil, :last => nil, :high => nil} + end + @read_latency.clone + end + # + def latency_reading(old_reading=nil,&block) + unless old_reading.is_a?(::Hash) + old_reading = {:low => nil, :last => nil, :high => nil} + end + new_reading = {:low => nil, :last => nil, :high => nil} + reading = millisecond_latency(&block) + if old_reading[:low] + if reading[:milliseconds] < old_reading[:low] + new_reading[:low] = reading[:milliseconds].dup + else + new_reading[:low] = old_reading[:low] + end + else + new_reading[:low] = reading[:milliseconds].dup + end + new_reading[:last] = reading[:milliseconds].dup + if old_reading[:high] + if reading[:milliseconds] > old_reading[:high] + new_reading[:high] = reading[:milliseconds].dup + else + new_reading[:high] = old_reading[:high] + end + else + new_reading[:high] = reading[:milliseconds].dup + end + # + {:result => reading[:result], :reading => new_reading} + end + # + def flags() + result = [] + current = self.fcntl(::Fcntl::F_GETFL) + flags = {} + if self.binmode?() + result << :binary + if self.external_encoding == ::Encoding::ASCII_8BIT + result << :setenc_by_bom + end + else + result << :text + result << :wsplit + end + if self.tty? + result << :tty + end + # + #flag[:read] = 0x00000001 + #flag[:write] = 0x00000002 + #flag[:readwrite] = (flag[:read] | flag[:write]) + #flag[:binmode] = 0x00000004 + #flag[:readwrite] = (flag[:read] | flag[:write]) + flags[:read] = ::Fcntl::O_RDONLY + flags[:write] = ::Fcntl::O_WRONLY + flags[:readwrite] = ::Fcntl::O_RDWR + #flags[:binary] = 0x00000004 + flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk. + flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata. + flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC + #flags[:tty] = 0x00000010 + flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY + flags[:duplex] = 0x00000020 + flags[:append] = ::Fcntl::O_APPEND + flags[:create] = ::Fcntl::O_CREAT + flags[:exclusive] = ::Fcntl::O_EXCL + # flags[:wsplit] = 0x00000200 + flags[:wsplit_initialized] = 0x00000400 + flags[:trunc] = ::Fcntl::O_TRUNC + #flags[:text] = 0x00001000 + #flags[:setenc_by_bom] = 0x00100000 + flags[:seek_set] = ::File::SEEK_SET + flags[:seek_current] = ::File::SEEK_CUR + flags[:seek_end] = ::File::SEEK_END + # ### file region locking: + flags[:shared_lock] = ::File::LOCK_SH # for reading + flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking) + flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing. + flags[:unlock] = ::File::LOCK_UN + # + flags[:nonblocking] = ::Fcntl::O_NONBLOCK + flags[:ndelay] = ::Fcntl::O_NDELAY + flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks. + flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file. + flags[:match_noescape] = ::File::FNM_NOESCAPE # + flags[:match_pathname] = ::File::FNM_PATHNAME # + flags[:match_dotmatch] = ::File::FNM_DOTMATCH # + flags[:match_casefold] = ::File::FNM_CASEFOLD # + flags[:match_systemcase] = ::File::FNM_SYSCASE # + # + flags.keys.to_enum.each do |the_flag_key| + if (current & flags[(the_flag_key)]) == flags[(the_flag_key)] + case the_flag_key + when :readwrite + if result.index(:write) + result << :read + else + if result.index(:read) + result << :write + end + end + when :wsplit_initialized + if result.index(:wsplit) + result[(result.index(:wsplit))] = :wsplit_initialized + else + result << :wsplit_initialized + end + else + result << the_flag_key + end + end + end + # + result + end + # + def profile(params={:follow_symlinks => true}) + platform_details = GxG::SYSTEM.platform() + result = {} + # + if params[:follow_symlinks] + raw_stat = self.to_io.stat + else + raw_stat = self.to_io.lstat + end + raw_mode = raw_stat.mode.to_s(base=8) + if raw_mode.size < 7 + raw_mode = ("0" << raw_mode) + end + set_user_id = false + set_group_id = false + set_sticky_bit = false + if (raw_mode[3].to_i & 4) == 4 + set_user_id = true + end + if (raw_mode[3].to_i & 2) == 2 + set_group_id = true + end + if (raw_mode[3].to_i & 1) == 1 + set_sticky_bit = true + end + # + # + raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false } + # + file_handle = nil + GxG::Engine::file_descriptors.to_enum.each do |the_handle| + if self.fileno == the_handle.fileno + file_handle = the_handle + break + end + end + # LATER: GxG::IO::IO.profile : patch into file/stream type recognition engine. + case raw_stat.ftype.to_s.downcase.to_sym + when :link + # are .lnk files handled properly on win32 for this? + result[:type] = :symlink + when :file + if file_handle + if self.tty? + result[:type] = :tty + else + the_extension = ::File::basename(file_handle.to_path).split(".")[-1].to_s.downcase + if the_extension == "lnk" + result[:type] = :symlink + else + if ["so","la","a","dylib","dll","class"].include?(the_extension) + result[:type] = :library + else + if (["out","exe","misc","java","jar", "sh", "rb", "py", "pyc"].include?(the_extension) || raw_stat.executable_real?) + result[:type] = :application + else + result[:type] = :file + end + end + end + end + end + when :fifo + result[:type] = :fifo + when :characterspecial + result[:type] = :character_device + when :blockspecial + result[:type] = :block_device + when :socket + result[:type] = :socket + when :directory + result[:type] = :directory + when :unknown + result[:type] = :unknown + if raw_stat.pipe? + result[:type] = :pipe + end + else + if raw_stat.pipe? + result[:type] = :pipe + end + end + result[:owner_type] = result[:type] + result[:on_device] = raw_stat.dev.to_s(base=10).to_i + if result[:on_device] + result[:on_device_major] = raw_stat.dev_major + result[:on_device_minor] = raw_stat.dev_minor + end + result[:is_device] = raw_stat.rdev + if result[:is_device] + result[:is_device_major] = raw_stat.rdev_major + result[:is_device_minor] = raw_stat.rdev_minor + end + result[:inode] = raw_stat.ino.to_i + # + result[:flags] = self.flags() + # + if file_handle + # man fstat : mode decoder-ring table: + # + # S_IFMT 0170000 bit mask for the file type bit fields + # S_IFSOCK 0140000 socket + # S_IFLNK 0120000 symbolic link + # S_IFREG 0100000 regular file + # S_IFBLK 0060000 block device + # S_IFDIR 0040000 directory + # S_IFCHR 0020000 character device + # S_IFIFO 0010000 FIFO + # + # S_ISUID 0004000 set UID bit + # S_ISGID 0002000 set-group-ID bit (see below) + # S_ISVTX 0001000 sticky bit (see below) + # + # S_IRWXU 00700 mask for file owner permissions + # S_IRUSR 00400 owner has read permission + # S_IWUSR 00200 owner has write permission + # S_IXUSR 00100 owner has execute permission + # + # S_IRWXG 00070 mask for group permissions + # S_IRGRP 00040 group has read permission + # S_IWGRP 00020 group has write permission + # S_IXGRP 00010 group has execute permission + # + # S_IRWXO 00007 mask for permissions for others (not in group) + # S_IROTH 00004 others have read permission + # S_IWOTH 00002 others have write permission + # S_IXOTH 00001 others have execute permission + # + # .stat table: (mode example : 0100664) + # 0700 rwx mask for owner + # 0400 r for owner + # 0200 w for owner + # 0100 x for owner + # 0070 rwx mask for group + # 0040 r for group + # 0020 w for group + # 0010 x for group + # 0007 rwx mask for other + # 0004 r for other + # 0002 w for other + # 0001 x for other + # 4000 Set user ID on execution + # 2000 Set group ID on execution + # 1000 Save swapped text, even after use + result[:hardlinks_to] = raw_stat.nlink.to_i + result[:user_id] = raw_stat.uid.to_i + result[:group_id] = raw_stat.gid.to_i + result[:size] = raw_stat.size + result[:block_size] = raw_stat.blksize + result[:blocks] = raw_stat.blocks + result[:accessed] = DateTime::parse(raw_stat.atime.to_s) + result[:modified] = DateTime::parse(raw_stat.mtime.to_s) + result[:status_modified] = DateTime::parse(raw_stat.ctime.to_s) + # File permissions work in an ACL style, even if based only upon Unix-style permissions. + # + result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + if set_user_id + result[:flags] << :user_id_on_execute + end + if set_group_id + result[:flags] << :group_id_on_execute + end + if set_sticky_bit + result[:flags] << :sticky_bit + end + # owner permissions + if (raw_mode[4].to_i & 4) == 4 + result[:permissions][:owner][:read] = true + end + if (raw_mode[4].to_i & 2) == 2 + result[:permissions][:owner][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:owner][:rename] = true + result[:permissions][:owner][:move] = true + result[:permissions][:owner][:destroy] = true + result[:permissions][:owner][:create] = true + end + end + if (raw_mode[4].to_i & 1) == 1 + result[:permissions][:owner][:execute] = true + end + # group permissions + if (raw_mode[5].to_i & 4) == 4 + result[:permissions][:owner][:read] = true + end + if (raw_mode[5].to_i & 2) == 2 + result[:permissions][:group][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:group][:rename] = true + result[:permissions][:group][:move] = true + result[:permissions][:group][:destroy] = true + result[:permissions][:group][:create] = true + end + end + if (raw_mode[5].to_i & 1) == 1 + result[:permissions][:group][:execute] = true + end + # other permissions + if (raw_mode[6].to_i & 4) == 4 + result[:permissions][:other][:read] = true + end + if (raw_mode[6].to_i & 2) == 2 + result[:permissions][:other][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:other][:rename] = true + result[:permissions][:other][:move] = true + result[:permissions][:other][:destroy] = true + result[:permissions][:other][:create] = true + end + end + if (raw_mode[6].to_i & 1) == 1 + result[:permissions][:other][:execute] = true + end + # effective (this user) permissions (while opened, supercedes normal effective file system permissions) + if result[:flags].index(:read) + result[:permissions][:effective][:read] = true + # + end + if result[:flags].index(:write) + result[:permissions][:effective][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:effective][:rename] = true + result[:permissions][:effective][:move] = true + result[:permissions][:effective][:destroy] = true + result[:permissions][:effective][:create] = true + end + end + if raw_stat.executable? + result[:permissions][:effective][:execute] = true + end + # + else + # in-memory object - no fs involvement + # ok ... why? Well, I want objects to be able to pass through my unwritten system stuff : experimental, w/o special handling. + result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + # + if result[:flags].index(:read) + result[:permissions][:effective][:read] = true + result[:permissions][:owner][:read] = true + result[:permissions][:group][:read] = true + result[:permissions][:other][:read] = true + # + end + # + if result[:flags].index(:write) + result[:permissions][:effective][:write] = true + result[:permissions][:owner][:write] = true + result[:permissions][:group][:write] = true + result[:permissions][:other][:write] = true + end + # + end + # + result[:mode] = {} + # + result + end + # +end +# +class StringIO + # + public + # + def binmode?() + if self.external_encoding == ::Encoding::ASCII_8BIT + true + else + false + end + end + # + def binmode() + # Question: ok, so the cosmetics are that it is in binmode ... but what will be its actual behavior?? + if self.binmode?() + self + else + self.set_encoding(::Encoding::ASCII_8BIT) + end + end + # + def write_latency() + # Just in StringIO for interface compatibility. + unless self.instance_variable_defined?(:@write_latency) + @write_latency= {:low => nil, :last => nil, :high => nil} + end + @write_latency.clone + end + # + def read_latency() + # Just in StringIO for interface compatibility. + unless self.instance_variable_defined?(:@read_latency) + @read_latency= {:low => nil, :last => nil, :high => nil} + end + @read_latency.clone + end + # + def latency_reading(old_reading=nil,&block) + # Just in StringIO for interface compatibility. + unless old_reading.is_a?(::Hash) + old_reading = {:low => nil, :last => nil, :high => nil} + end + new_reading = {:low => nil, :last => nil, :high => nil} + reading = millisecond_latency(&block) + if old_reading[:low] + if reading[:milliseconds] < old_reading[:low] + new_reading[:low] = reading[:milliseconds].dup + else + new_reading[:low] = old_reading[:low] + end + else + new_reading[:low] = reading[:milliseconds].dup + end + new_reading[:last] = reading[:milliseconds].dup + if old_reading[:high] + if reading[:milliseconds] > old_reading[:high] + new_reading[:high] = reading[:milliseconds].dup + else + new_reading[:high] = old_reading[:high] + end + else + new_reading[:high] = reading[:milliseconds].dup + end + # + {:result => reading[:result], :reading => new_reading} + end + # + def fcntl(*args) + if args[0] == ::Fcntl::F_GETFL + current = ::Fcntl::O_NONBLOCK + if self.closed_read?() + unless self.closed_write?() + current = (current | ::Fcntl::O_WRONLY) + end + else + if self.closed_write?() + current = (current | ::Fcntl::O_RDONLY) + else + current = (current | ::Fcntl::O_RDWR) + end + end + current + else + 0 + end + end + # + def flags() + result = [] + current = self.fcntl(::Fcntl::F_GETFL) + flags = {} + if self.binmode?() + result << :binary + if self.external_encoding == ::Encoding::ASCII_8BIT + result << :setenc_by_bom + end + else + result << :text + result << :wsplit + end + if self.tty? + result << :tty + end + # + #flag[:read] = 0x00000001 + #flag[:write] = 0x00000002 + #flag[:readwrite] = (flag[:read] | flag[:write]) + #flag[:binmode] = 0x00000004 + #flag[:readwrite] = (flag[:read] | flag[:write]) + flags[:read] = ::Fcntl::O_RDONLY + flags[:write] = ::Fcntl::O_WRONLY + flags[:readwrite] = ::Fcntl::O_RDWR + #flags[:binary] = 0x00000004 + flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk. + flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata. + flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC + #flags[:tty] = 0x00000010 + flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY + flags[:duplex] = 0x00000020 + flags[:append] = ::Fcntl::O_APPEND + flags[:create] = ::Fcntl::O_CREAT + flags[:exclusive] = ::Fcntl::O_EXCL + # flags[:wsplit] = 0x00000200 + flags[:wsplit_initialized] = 0x00000400 + flags[:trunc] = ::Fcntl::O_TRUNC + #flags[:text] = 0x00001000 + #flags[:setenc_by_bom] = 0x00100000 + flags[:seek_set] = ::File::SEEK_SET + flags[:seek_current] = ::File::SEEK_CUR + flags[:seek_end] = ::File::SEEK_END + # ### file region locking: + flags[:shared_lock] = ::File::LOCK_SH # for reading + flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking) + flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing. + flags[:unlock] = ::File::LOCK_UN + # + flags[:nonblocking] = ::Fcntl::O_NONBLOCK + flags[:ndelay] = ::Fcntl::O_NDELAY + # these do not apply to in-memory IO-like things at all: + # flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks. + # flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file. + # flags[:match_noescape] = ::File::FNM_NOESCAPE # + # flags[:match_pathname] = ::File::FNM_PATHNAME # + # flags[:match_dotmatch] = ::File::FNM_DOTMATCH # + # flags[:match_casefold] = ::File::FNM_CASEFOLD # + # flags[:match_systemcase] = ::File::FNM_SYSCASE # + # + flags.keys.to_enum.each do |the_flag_key| + if (current & flags[(the_flag_key)]) == flags[(the_flag_key)] + case the_flag_key + when :readwrite + if result.index(:write) + result << :read + else + if result.index(:read) + result << :write + end + end + when :wsplit_initialized + if result.index(:wsplit) + result[(result.index(:wsplit))] = :wsplit_initialized + else + result << :wsplit_initialized + end + else + result << the_flag_key + end + end + end + # + result + end + # + def stat(*args) + nil + end + alias :lstat :stat + # + def profile() + current = self.flags() + result = {:type => :memory, :flags => current, :permissions => {:effective=>{:execute=>false, :rename=>false, :move=>false, :destroy=>false, :create=>false, :write=>false, :read=>false}}} + # + if current.include?(:read) + result[:permissions][:effective][:read] = true + end + if current.include?(:write) + result[:permissions][:effective][:write] = true + result[:permissions][:effective][:destroy] = true + result[:permissions][:effective][:create] = true + end + # + result + end + # +end +# +class File + # [:SEPARATOR, :ALT_SEPARATOR, :PATH_SEPARATOR, :Stat,, :SEEK_SET, :SEEK_CUR, :SEEK_END, :LOCK_SH, :LOCK_EX, :LOCK_UN, :LOCK_NB, :RDONLY, :WRONLY, :RDWR, :APPEND, :CREAT, :EXCL, :NONBLOCK, :TRUNC, :NOCTTY, :BINARY, :SYNC, :DSYNC, :RSYNC, :NOFOLLOW, :NOATIME, :FNM_NOESCAPE, :FNM_PATHNAME, :FNM_DOTMATCH, :FNM_CASEFOLD, :FNM_SYSCASE] + unless defined?(TTY) + TTY = ::IO::TTY + end + unless defined?(DUPLEX) + DUPLEX = ::IO::DUPLEX + end + unless defined?(WSPLIT) + WSPLIT = ::IO::WSPLIT + end + unless defined?(WSPLIT_INITIALIZED) + WSPLIT_INITIALIZED = ::IO::WSPLIT_INITIALIZED + end + unless defined?(TEXT) + # What is the flag value under Win32? + TEXT = 0 + end + unless defined?(SETENC_BY_BOM) + # Note: need to verify ::IO::SETENC_BY_BOM value before using: + SETENC_BY_BOM = ::IO::SETENC_BY_BOM + end + # + def self.find_file_mimetype(the_file=nil) + if the_file.is_a?(::String) + if (/.*(?:\\|\/)(.+)$/.match(the_file.to_s) || the_file.to_s.split("/").size > 0 || the_file == "/") + if ::File.directory?(the_file) + raise ArgumentError, "You tried to look at a directory, not a File. (#{the_file.inspect})." + else + the_file = ::File.open(the_file, "r") + end + else + raise ArgumentError, "You MUST provide a valid File path." + end + end + if the_file.is_a?(::File) + the_file.file_type() + else + raise ArgumentError, "You MUST provide a valid File object." + end + end + # + def self.mode_permission_to_gxg(the_mode=0) + result = {} + if the_mode.is_a?(::Numeric) + raw_mode = the_mode.to_s(base=8) + set_user_id = false + set_group_id = false + set_sticky_bit = false + while raw_mode.size < 4 + raw_mode = ("0" << raw_mode) + end + if raw_mode.size > 4 + raw_mode = raw_mode[((raw_mode.size - 4)..-1)].to_s + end + # + if (raw_mode[0].to_i & 4) == 4 + set_user_id = true + end + if (raw_mode[0].to_i & 2) == 2 + set_group_id = true + end + if (raw_mode[0].to_i & 1) == 1 + set_sticky_bit = true + end + raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false } + result = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + # + # puts "using: #{raw_mode.inspect}" + # owner permissions + if (raw_mode[1].to_i & 4) == 4 + result[:owner][:read] = true + end + if (raw_mode[1].to_i & 2) == 2 + result[:owner][:write] = true + result[:owner][:rename] = true + result[:owner][:move] = true + result[:owner][:destroy] = true + result[:owner][:create] = true + end + if (raw_mode[1].to_i & 1) == 1 + result[:owner][:execute] = true + end + # group permissions + if (raw_mode[2].to_i & 4) == 4 + result[:group][:read] = true + end + if (raw_mode[2].to_i & 2) == 2 + result[:group][:write] = true + result[:group][:rename] = true + result[:group][:move] = true + result[:group][:destroy] = true + result[:group][:create] = true + end + if (raw_mode[2].to_i & 1) == 1 + result[:group][:execute] = true + end + # other permissions + if (raw_mode[3].to_i & 4) == 4 + result[:other][:read] = true + end + if (raw_mode[3].to_i & 2) == 2 + result[:other][:write] = true + result[:other][:rename] = true + result[:other][:move] = true + result[:other][:destroy] = true + result[:other][:create] = true + end + if (raw_mode[3].to_i & 1) == 1 + result[:other][:execute] = true + end + else + raise ArgumentError, "You MUST provide an Integer" + end + result + end + # + def self.mode_permission_to_unix(the_mode=0, type = :file) + result = "----------" + if the_mode.is_a?(::Numeric) + accumulator = "" + raw_mode = the_mode.to_s(base=8) + if raw_mode.size < 7 + raw_mode = ("0" << raw_mode) + end + set_user_id = false + set_group_id = false + set_sticky_bit = false + if (raw_mode[3].to_i & 4) == 4 + set_user_id = true + end + if (raw_mode[3].to_i & 2) == 2 + set_group_id = true + end + if (raw_mode[3].to_i & 1) == 1 + set_sticky_bit = true + end + # + case type.to_s.to_sym + when :directory + accumulator << "d" + when :symlink + accumulator << "l" + when :file + accumulator << "-" + else + raise ArgumentError, "You MUST provide a valid file system entity type: :file, :directory or :symlink." + end + # owner permissions + if (raw_mode[1].to_i & 4) == 4 + accumulator << "r" + else + accumulator << "-" + end + if (raw_mode[1].to_i & 2) == 2 + accumulator << "w" + else + accumulator << "-" + end + if (raw_mode[1].to_i & 1) == 1 + accumulator << "x" + else + accumulator << "-" + end + # group permissions + if (raw_mode[2].to_i & 4) == 4 + accumulator << "r" + else + accumulator << "-" + end + if (raw_mode[2].to_i & 2) == 2 + accumulator << "w" + else + accumulator << "-" + end + if (raw_mode[2].to_i & 1) == 1 + accumulator << "x" + else + accumulator << "-" + end + # other permissions + if (raw_mode[3].to_i & 4) == 4 + accumulator << "r" + else + accumulator << "-" + end + if (raw_mode[3].to_i & 2) == 2 + accumulator << "w" + else + accumulator << "-" + end + if (raw_mode[3].to_i & 1) == 1 + accumulator << "x" + else + accumulator << "-" + end + result = accumulator + else + raise ArgumentError, "You MUST provide an Integer" + end + result + end + # + def self.gxg_permissions_to_unix(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file) + result = "----------" + if permissions.is_a?(::Hash) + accumulator = "" + raw_data = "----------" + write_permissions = [:write, :create, :destroy, :rename, :move] + case type + when :directory + accumulator << "d" + when :symlink + accumulator << "l" + when :file + accumulator << "-" + else + raise ArgumentError, "You MUST provide a valid file system entity type: :file, :directory or :symlink." + end + raw_data.each_char_with_index do |character, index| + case index + when 1 + if permissions[:owner][:read] + accumulator << "r" + else + accumulator << character + end + when 2 + write_permissions.each do |the_perm| + if permissions[:owner][(the_perm)] + accumulator << "w" + break + end + end + if accumulator[-1] != "w" + accumulator << character + end + when 3 + if permissions[:owner][:execute] + accumulator << "x" + else + accumulator << character + end + when 4 + if permissions[:group][:read] + accumulator << "r" + else + accumulator << character + end + when 5 + write_permissions.each do |the_perm| + if permissions[:group][(the_perm)] + accumulator << "w" + break + end + end + if accumulator[-1] != "w" + accumulator << character + end + when 6 + if permissions[:group][:execute] + accumulator << "x" + else + accumulator << character + end + when 7 + if permissions[:other][:read] + accumulator << "r" + else + accumulator << character + end + when 8 + write_permissions.each do |the_perm| + if permissions[:other][(the_perm)] + accumulator << "w" + break + end + end + if accumulator[-1] != "w" + accumulator << character + end + when 9 + if permissions[:other][:execute] + accumulator << "x" + else + accumulator << character + end + end + end + result = accumulator + else + raise ArgumentError, "You MUST provide a Hash with :owner, :group, and :other permission sub-hashes of true/false flags" + end + result + end + # + def self.gxg_permissions_to_mode(permissions={:owner => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :group => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }, :other => { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false }}, type = :file) + result = 0 + if permissions.is_a?(::Hash) + octet = 0 + octet_string = "0" + unix_permissions = ::File.gxg_permissions_to_unix(permissions,type) + # + unix_permissions.each_char_with_index do |character, index| + # + if (1..3).include?(index) + case index + when 1 + unless character == "-" + octet += 4 + end + when 2 + unless character == "-" + octet += 2 + end + when 3 + unless character == "-" + octet += 1 + end + end + if index == 3 + octet_string << octet.to_s + octet = 0 + end + end + # + if (4..6).include?(index) + case index + when 4 + unless character == "-" + octet += 4 + end + when 5 + unless character == "-" + octet += 2 + end + when 6 + unless character == "-" + octet += 1 + end + end + if index == 6 + octet_string << octet.to_s + octet = 0 + end + end + # + if (7..9).include?(index) + case index + when 7 + unless character == "-" + octet += 4 + end + when 8 + unless character == "-" + octet += 2 + end + when 9 + unless character == "-" + octet += 1 + end + end + if index == 9 + octet_string << octet.to_s + octet = 0 + end + end + # + result = octet_string.to_i(base=8) + end + # + else + raise ArgumentError, "You MUST provide a Hash with :owner, :group, and :other permission sub-hashes of true/false flags" + end + result + end + # + def self.unix_permissions_to_gxg(permission_string="") + raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false } + blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + permission_record = blank_permissions.clone + # + if permission_string.is_a?(::String) + permission_string.each_char_with_index do |character, index| + # + case index + when 1 + if character == "r" + permission_record[:owner][:read] = true + end + when 2 + if character == "w" + permission_record[:owner][:write] = true + permission_record[:owner][:rename] = true + permission_record[:owner][:move] = true + permission_record[:owner][:destroy] = true + permission_record[:owner][:create] = true + end + when 3 + if character == "x" + permission_record[:owner][:execute] = true + end + when 4 + if character == "r" + permission_record[:group][:read] = true + end + when 5 + if character == "w" + permission_record[:group][:write] = true + permission_record[:group][:rename] = true + permission_record[:group][:move] = true + permission_record[:group][:destroy] = true + permission_record[:group][:create] = true + end + when 6 + if character == "x" + permission_record[:group][:execute] = true + end + when 7 + if character == "r" + permission_record[:other][:read] = true + end + when 8 + if character == "w" + permission_record[:other][:write] = true + permission_record[:other][:rename] = true + permission_record[:other][:move] = true + permission_record[:other][:destroy] = true + permission_record[:other][:create] = true + end + when 9 + if character == "x" + permission_record[:other][:execute] = true + end + end + # No way to calculate Effective Permissions - it will be nil consequently. + # + end + result = permission_record + else + result = nil + raise ArgumentError, "You MUST supply a valid unix permission String" + end + result + end + # + def file_type() + # TODO: return GxG-UFS value as well. + type = ::MimeMagic.by_path(self.path) + {:mime_type => type.type(), :subtype => type.subtype(), :mediatype => type.mediatype(), :extensions => type.extensions(), :comment => type.comment(), :ufs => nil} + end + # +end +# +module Fcntl + # [:F_DUPFD, :F_GETFD, :F_GETLK, :F_SETFD, :F_GETFL, :F_SETFL, :F_SETLK, :F_SETLKW, :FD_CLOEXEC, :F_RDLCK, :F_UNLCK, :F_WRLCK, :O_CREAT, :O_EXCL, :O_NOCTTY, :O_TRUNC, :O_APPEND, :O_NONBLOCK, :O_NDELAY, :O_RDONLY, :O_RDWR, :O_WRONLY, :O_ACCMODE] + unless const_defined?(:O_TEXT) + O_TEXT = ::File::TEXT + end + unless const_defined?(:O_BINARY) + O_BINARY = ::File::BINARY + end +end +# ### URI alterations +module URI + # + def self.parse(*args) + #::URI::Generic.new(*(::URI::split(*args))) + ::URI::Generic::parse(*args) + end + # + class Generic + # + def set_scheme(v) + if v.is_a?(String) + @scheme = v + end + v + end + + def scheme=(scheme="") + set_scheme(scheme) + scheme + end + + def scheme() + @scheme + end + + def set_userinfo(user, password = nil) + unless password + user, password = split_userinfo(user) + end + if user.is_a?(String) + @user = URI::escape(user) + else + @user = user + end + if password.is_a?(String) + @password = URI::escape(password) + else + @password = password + end + result = [] + if @user.is_a?(String) + result << URI::unescape(@user) + else + result << @user + end + if @password.is_a?(String) + result << URI::unescape(@password) + else + result << @password + end + result + end + + def userinfo + if @user.nil? + nil + elsif @password.nil? + URI::unescape(@user) + else + URI::unescape(@user) + ':' + URI::unescape(@password) + end + end + + def user=(user) + if user.is_a?(String) + user = (URI::escape(user)) + end + set_user(user) + self.user + # returns user + end + + def user + if @user.is_a?(String) + URI::unescape(@user) + else + @user + end + end + + def set_password(v) + if v.is_a?(String) + @password = URI::escape(v) + end + if @password.is_a?(String) + URI::unescape(@password) + else + @password + end + end + + def password=(password) + if password.is_a?(String) + password = (URI::escape(password)) + end + set_password(password) + # returns password + end + + def password + if @password.is_a?(String) + URI::unescape(@password) + else + @password + end + end + + def set_host(v) + if v.is_a?(String) + @host = URI::escape(v) + else + @host = v + end + end + # + def host=(v) + if v.is_a?(String) + v = (URI::escape(v)) + end + set_host(v) + self.host + end + # + def host() + if @host.is_a?(String) + URI::unescape(@host) + else + @host + end + end + # + def hostname() + if @host.to_s.size > 0 + self.host().to_s.dup.gsub("[","").gsub("]","") + else + nil + end + end + # + def hostname=(v) + if (v.to_s.include?(":") || (v.to_s.include?("[") && v.to_s.include?("]"))) + # ipv6 ?? + if v.to_s.include?("[") && v.to_s.include?("]") + self.host = v.to_s + else + self.host = "[" << v.to_s << "]" + end + else + self.host = v.to_s + end + self.host() + end + # + # def set_registry(v) + # if v.is_a?(String) + # @registry = URI::escape(v) + # else + # @registry = v + # end + # end + # # + # def registry=(v) + # if v.is_a?(String) + # v = (URI::escape(v)) + # end + # set_registry(v) + # self.registry + # end + + # def registry + # if @registry.is_a?(String) + # URI::unescape(@registry) + # else + # @registry + # end + # end + + # def set_path(v) + # if v.is_a?(String) + # @path = URI::escape(v) + # else + # @path = v + # end + # end + + # def path=(v) + # if v.is_a?(String) + # v = (URI::escape(v)) + # end + # set_path(v) + # self.path + # end + + # def path + # if @path.is_a?(String) + # URI::unescape(@path) + # else + # @path + # end + # end + + # def set_opaque(v) + # if v.is_a?(String) + # @opaque = URI::escape(v) + # else + # @opaque = v + # end + # end + + # def opaque=(v) + # if v.is_a?(String) + # v = (URI::escape(v)) + # end + # set_opaque(v) + # self.opaque + # end + + # def opaque + # if @opaque.is_a?(String) + # URI::unescape(@opaque) + # else + # @opaque + # end + # end + # Review - query modifications bugger MatrixClient logins. + # def set_query(v) + # if v.is_a?(String) + # @query = URI::escape(v) + # else + # @query = v + # end + # end + + # def query=(v) + # if v.is_a?(String) + # v = (URI::escape(v)) + # end + # set_query(v) + # self.query + # end + + # def query + # if @query.is_a?(String) + # URI::unescape(@query) + # else + # @query + # end + # end + + # def set_fragment(v) + # if v.is_a?(String) + # @fragment = URI::escape(v) + # else + # @fragment = v + # end + # end + + # def fragment=(v) + # if v.is_a?(String) + # v = (URI::escape(v)) + # end + # set_fragment(v) + # self.fragment + # end + + # def fragment + # if @fragment.is_a?(String) + # URI::unescape(@fragment) + # else + # @fragment + # end + # end + # + # def to_s + # str = '' + # if @scheme + # str << @scheme + # str << ':' + # end + # if @opaque + # str << @opaque + # else + # if @registry + # str << @registry + # else + # if @host + # str << '//' + # end + # if self.userinfo + # str << self.userinfo + # str << '@' + # end + # if @host + # str << @host + # end + # if @port && @port != self.default_port + # str << ':' + # str << @port.to_s + # end + # end + # str << @path + # if @query + # str << '?' + # str << @query + # end + # end + # if @fragment + # str << '#' + # str << @fragment + # end + # URI::escape(str) + # end + # + def from_hash(the_hash={}) + if the_hash[:scheme] + self.scheme = the_hash[:scheme] + else + @scheme = nil + end + if the_hash[:opaque] + self.opaque = the_hash[:opaque] + @registry = nil + @user = nil + @password = nil + @host = nil + @port = nil + @path = nil + @query = nil + else + @opaque = nil + if the_hash[:registry] + self.registry = the_hash[:registry] + @user = nil + @password = nil + @host = nil + @port = nil + else + @registry = nil + if the_hash[:user] + self.user = the_hash[:user] + else + @user = nil + end + if the_hash[:password] + self.password = the_hash[:password] + else + @password = nil + end + if the_hash[:host] + self.host = the_hash[:host] + else + @host = nil + end + if the_hash[:port] && the_hash[:port] != self.default_port + self.port = the_hash[:port].to_i + else + @port = nil + end + end + if the_hash[:path] + self.path = the_hash[:path] + else + @path = nil + end + if the_hash[:query] + self.query = the_hash[:query] + else + @query = nil + end + end + if the_hash[:fragment] + self.fragment = the_hash[:fragment] + else + @fragment = nil + end + self + end + # + def to_hash() + result = {} + if @scheme + result[:scheme] = @scheme + end + if @opaque + result[:opaque] = URI::unescape(@opaque) + else + if @registry + result[:registry] = URI::unescape(@registry) + else + if @user + result[:user] = URI::unescape(@user) + end + if @password + result[:password] = URI::unescape(@password) + end + if @host + result[:host] = URI::unescape(@host) + end + if @port && @port != self.default_port + result[:port] = URI::unescape(@port).to_i + end + end + if @path + result[:path] = URI::unescape(@path) + end + if @query + result[:query] = URI::unescape(@query) + end + end + if @fragment + result[:fragment] = URI::unescape(@fragment) + end + result + end + # + def address_info() + ::Addrinfo::parse(self) + end + # + def resolve_host() + case @scheme + when "inproc" + # no-op + when "ipc", "unix" + # no-op + when "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6" + self.hostname = ::TCPSocket.getaddress(self.hostname) + end + end + # + end + + def self.parse(uri) + # Alterations: + # leading/trailing whitespace removal - intended to reduce false exception conditions (missing regexp bits) + # I think first reported in 2007, but still not fixed. dunno why not. + while uri[0] == 32 + uri[0] = "" + end + if uri.size > 0 + while uri[-1] == 32 + uri[-1] = "" + end + end + # attempt to escape all strings going INTO the URI object? + uri = URI::escape(uri) + # End Alterations + scheme, userinfo, host, port, + registry, path, opaque, query, fragment = self.split(uri) + + if scheme && @@schemes.include?(scheme.upcase) + @@schemes[scheme.upcase].new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment) + else + Generic.new(scheme, userinfo, host, port, + registry, path, opaque, query, + fragment) + end + end +end +# +class Addrinfo + # + def self.parse(*args) + # Attempts to create an address object for the given URI/URL + address = nil + if args[0].is_a?(::URI::Generic) + raw_uri = args[0] + else + raw_uri = ::URI::parse(*args) + end + # + the_protocol = nil + the_port = nil + service_details = ::GxG::SYSTEM.service_ports_search({:scheme => raw_uri.scheme()}) + if service_details.keys().size > 0 + service_details = service_details[(service_details.keys()[0])] + if service_details[:usage_preference] + if service_details[:usage][(service_details[:usage_preference])].is_a?(::Array) + the_port = service_details[:usage][(service_details[:usage_preference])][0] + if the_port.is_a?(::Range) + the_protocol = (service_details[:usage_preference]) + the_port = the_port.first + end + end + else + if service_details[:usage][(service_details[:usage].keys()[0])].is_a?(::Array) + the_port = service_details[:usage][(service_details[:usage].keys()[0])][0] + if the_port.is_a?(::Range) + the_protocol = (service_details[:usage].keys()[0]) + the_port = the_port.first + end + end + end + end + unless the_protocol + the_protocol = raw_uri.scheme().to_sym + end + # + case the_protocol + when :inproc + # + when :ipc + # LATER: Anything to be done for IPC stuff here??? (Not available on winderz) + when :pgm + # + when :epgm + # + when :unix, :socket + if (raw_uri.hostname().to_s.size == 0) + # Unix socket + address = ::Addrinfo.unix(raw_uri.path()) + else + # tcp used by default for ipc + if raw_uri.hostname().to_s.size > 0 + address = ::Addrinfo.tcp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + else + address = ::Addrinfo.tcp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + end + end + when :tcp, :tcp4, :tcp6 + if raw_uri.hostname().to_s.size > 0 + address = ::Addrinfo.tcp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + else + address = ::Addrinfo.tcp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + end + when :udp, :udp4, :udp6 + if raw_uri.hostname().to_s.size > 0 + address = ::Addrinfo.udp(raw_uri.hostname(), (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + else + address = ::Addrinfo.udp("0.0.0.0", (raw_uri.port() || raw_uri.default_port() || the_port || "*")) + end + end + # + address + end + # + def uri_info() + result = nil + if self.ip?() + case self.protocol + when ::Socket::IPPROTO_TCP + result = ::URI::parse("tcp://#{self.ip_address()}:#{(self.ip_port() || '*')}") + when ::Socket::IPPROTO_UDP + result = ::URI::parse("udp://#{self.ip_address()}:#{(self.ip_port() || '*')}") + end + else + if self.unix?() + result = ::URI::parse("unix://#{self.unix_path()}") + else + # not sure what to do here + end + end + result + end +end +# +# Alteration to class +class String + # :macos text handling notes: http://hints.macworld.com/article.php?story=20060825071728278 + # Also, research UTF8_MAC <-> UTF8_HFS <-> UTF8 normalization conversions. + def self.transcode_options(destination_encoding, source_encoding, options={}) + # TODO: ::String::transcode_options : complete transcoding back-end supports. + # select a default encoding_option set given the two encodings, then merge options over that and return. Explore Hash deep-merge ideas. + # + new_options = {} + # fallback set/method or replace char + if options[:fallback] + # Note: I *confirmed* (thanks VGoff) that :invalid/:undef/:replace and :fallback are exclusive - So maybe this is really an optimized :fallback thing? + # Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method, + # or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder. + # Its value can be any encoding until it can be converted into the destination encoding of the transcoder. + if (options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || options[:fallback].respond_to?(:[])) + # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition. + new_options[:fallback] = options[:fallback].dup + end + else + # + if options[:invalid] == :replace + # If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to + # raise the Encoding::InvalidByteSequenceError exception + new_options[:invalid] = :replace + end + # + if options[:undef] == :replace + # If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character. + # The default is to raise the Encoding::UndefinedConversionError. + new_options[:undef] = :replace + end + # + if options[:replace] + # Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise. + new_options[:replace] = options[:replace].dup + end + # + end + # xml + if options[:xml] + # The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character + # references. ‘&’, ‘<’, and ‘>’ are converted to “&”, “<”, and “>”, respectively. If the value is :attr, encode also quotes the replacement + # result (using ‘“’), and replaces ‘”’ with “"”. + if [:text, :attr].include?(options[:xml]) + new_options[:xml] = options[:xml].dup + end + end + # + if options[:universal_newline] + # Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true. + unless (new_options[:cr_newline] || new_options[:crlf_newline]) + if options[:universal_newline] == true + new_options[:universal_newline] = true + end + end + else + if options[:crlf_newline] + # Replaces LF (“n”) with CRLF (“rn”) if value is true. + unless (new_options[:cr_newline] || new_options[:universal_newline]) + if options[:crlf_newline] == true + new_options[:crlf_newline] = true + end + end + else + if options[:cr_newline] + # Replaces LF (“n”) with CR (“r”) if value is true. + unless (new_options[:crlf_newline] || new_options[:universal_newline]) + if options[:cr_newline] == true + new_options[:cr_newline] = true + end + end + else + # default + end + end + end + # + default_options = {} + # find the default conversion options given the destination and source encodings + # then ... + new_options + # deep-merge new_options into a provided default_options + # then return default_options + # default_options + end + # + def transcode(*args) + destination_encoding = nil + conversion_options = nil + if args.size > 0 + if args[0].is_a?(::Hash) + conversion_options = args[0] + if args[1].is_a?(::Encoding) + destination_encoding = args[1] + else + destination_encoding = ::Encoding.default_external + end + else + # + if args[0].is_a?(::Encoding) + destination_encoding = args[0] + if args[1].is_a?(::Hash) + conversion_options = args[1] + else + conversion_options = {} + end + end + end + args = nil + end + # + unless destination_encoding.is_a?(::Encoding) + raise ArgumentError, "You must provide a valid Encoding, instead you provided #{destination_encoding.class}" + end + unless conversion_options.is_a?(::Hash) + raise ArgumentError, "You must provide a Hash for conversion options, instead you provided #{conversion_options.class}" + end + in_options = {} + out_options = {} + # + if (conversion_options[:invalid] || conversion_options[:undef] || conversion_options[:replace]) + # Note: I *confirmed* (thanks VGoff) that :invalid/:undef/:replace and :fallback are exclusive. + # + # If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to + # raise the Encoding::InvalidByteSequenceError exception + if conversion_options[:invalid] == :replace + in_options[:invalid] = :replace + out_options[:invalid] = :replace + end + # If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character. + # The default is to raise the Encoding::UndefinedConversionError. + if conversion_options[:undef] == :replace + in_options[:undef] = :replace + out_options[:undef] = :replace + end + # + # Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise. + if conversion_options[:replace].is_a?(::String) + in_options[:replace] = conversion_options[:replace] + out_options[:replace] = conversion_options[:replace] + end + # + else + # Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method, + # or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder. + # Its value can be any encoding until it can be converted into the destination encoding of the transcoder. + if (conversion_options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || conversion_options[:fallback].respond_to?(:[])) + # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition. + if conversion_options[:fallback].is_any?(::Proc, ::Method) + # TODO: study arguments and output expected, and construct one in and one out proc as wrapper. + # FIX: Odd Bug : MRI 1.9.3 will attempt to convert a Proc into a Hash directly and raise exception, instead of calling the proc. + # Also, when a method is supplied: it does call it, but passes no data and I don't think even collects a response. (wtf?) what is the point then?? + # So ... as a work around (FORNOW) I will simply construct a Hash and use that resultant Hash as fallback. + # LATER: I think I'd like to add the above comparison to simply check for a .respond_to?(:call) on action objects. + in_fallback = {} + out_fallback = {} + self.each_char do |the_char| + dst_defined_char = conversion_options[:fallback].call(the_char).to_s.chars.first + if (dst_defined_char.size > 0 && the_char != dst_defined_char) + utf_bridge = the_char.encode(::Encoding::UTF_8) + in_fallback[(the_char)] = utf_bridge + out_fallback[(utf_bridge)] = dst_defined_char + end + end + in_options[:fallback] = in_fallback + out_options[:fallback] = out_fallback + else + if conversion_options[:fallback].is_any?(::Hash, ::Struct) + # + in_fallback = {} + out_fallback = {} + # + conversion_options[:fallback].to_enum(:each_pair).each do |src_undefined_char,dst_defined_char| + utf_bridge = src_undefined_char.encode(::Encoding::UTF_8) + in_fallback[(src_undefined_char)] = utf_bridge + out_fallback[(utf_bridge)] = dst_defined_char + end + # + in_options[:fallback] = in_fallback + out_options[:fallback] = out_fallback + else + if (conversion_options[:fallback].respond_to?(:[]) && conversion_options[:fallback].respond_to?(:to_a)) + in_fallback = {} + out_fallback = {} + the_data = conversion_options[:fallback].to_a.flatten + the_size = the_data.size + if (the_size.even? && the_size > 0) + (0..(the_size)).step(2) do |index| + src_undefined_char = the_data[(index)] + dst_defined_char = the_data[(index + 1)] + utf_bridge = src_undefined_char.encode(::Encoding::UTF_8) + in_fallback[(src_undefined_char)] = utf_bridge + out_fallback[(utf_bridge)] = dst_defined_char + end + else + # raise error? this really requires matched pairs of things + end + in_options[:fallback] = in_fallback + out_options[:fallback] = out_fallback + end + end + end + end + end + # + # The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character + # references. ‘&’, ‘<’, and ‘>’ are converted to “&”, “<”, and “>”, respectively. If the value is :attr, encode also quotes the replacement + # result (using ‘“’), and replaces ‘”’ with “"”. + if [:text, :attr].include?(conversion_options[:xml]) + in_options[:xml] = conversion_options[:xml] + out_options[:xml] = conversion_options[:xml] + end + # + # Replaces LF (“n”) with CR (“r”) if value is true. + unless (conversion_options[:crlf_newline] || conversion_options[:universal_newline]) + if conversion_options[:cr_newline] == true + in_options[:cr_newline] = true + out_options[:cr_newline] = true + end + end + # + # Replaces LF (“n”) with CRLF (“rn”) if value is true. + unless (conversion_options[:cr_newline] || conversion_options[:universal_newline]) + if conversion_options[:crlf_newline] == true + in_options[:crlf_newline] = true + out_options[:crlf_newline] = true + end + end + # + # Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true. + unless (conversion_options[:cr_newline] || conversion_options[:crlf_newline]) + if conversion_options[:universal_newline] == true + in_options[:universal_newline] = true + out_options[:universal_newline] = true + end + end + # ### + # the_string = self.encode(::Encoding::UTF_8,self.external_encoding,in_options) + # the_string.encode!(destination_encoding,::Encoding::UTF_8,out_options) + # --or-- + if self.encoding == ::Encoding::UTF_8 + the_string = self.dup + else + the_string = self.encode(::Encoding::UTF_8,self.encoding,in_options) + end + if destination_encoding != ::Encoding::UTF_8 + the_string.encode!(destination_encoding,::Encoding::UTF_8,out_options) + end + # + the_string + end + # + def transcode!(*args) + data = self.transcode(*args) + if data.is_a?(::String) + self.force_encoding(data.encoding) + self.replace(data) + end + self + end + # + def to_d() + ::BigDecimal.new(self) + end + # + def numeric_values(categories=:any,delimiter="\t",locale=:en_US, numeric_base=10) + results = [] + prepared = [] + # SOMEDAY: .numeric_values: formulate regex patterns per locale and numeric_base + quanta_pattern = /[a-zA-Z]*/ + # proposed: /^-{0,1}\d*\.{0,1}\d+$/ + # /^[-+]{0,1}\d*\.{0,1}\d+$/ + # old: /[0-9.,]*/ + numerical_pattern = /[-+0-9.,]*/ + self.to_enum(:each_line).each do |row_text| + # ?? allow empty lines ?? I'm just not sure if I really *want* to attempt to preserve the structure that much. + row_prep = [] + row_text.split(delimiter).to_enum.each do |column_text| + if column_text.size > 0 + column_entry = {:text => column_text, :denomination => nil, :multiplier => nil} + column_text.scan(quanta_pattern).to_enum.each do |quanta| + if quanta.size > 0 + # + interpretation = ::GxG::Units::interpret_units({:text => quanta,:categories => categories,:locale => locale, :base => numeric_base}) + # + if interpretation[:error] + raise interpretation[:error] + else + # SOMEDAY: .numeric_values: a bit more discernment here than just the first interpretation. + interpretation = interpretation[:result] + if interpretation[0].is_a?(Hash) + if interpretation[0][:quantum].is_a?(Hash) + denomination = interpretation[0][:quantum].keys[0] + multiplier = interpretation[0][:quantum][(denomination)] + column_entry = {:text => column_text, :denomination => denomination, :multiplier => multiplier} + if interpretation[0][:attributes].is_a?(Hash) + column_entry[:attributes] = interpretation[0][:attributes] + end + break + end + end + end + # + end + # + end + # + row_prep << column_entry + # + else + row_prep << nil + end + end + prepared << row_prep + # + end + # process prepared array + prepared.to_enum.each do |row| + results << row.to_enum.map do |column| + result = nil + if column.is_a?(Hash) + column[:text].scan(numerical_pattern).to_enum.each do |text| + text.gsub!(",","") + if text.size > 0 + denomination = column[:denomination] + multiplier = column[:multiplier] + if text.include?(".") + # Note: defaults *assume* base-10 numbers + unless denomination + denomination = :float + end + unless multiplier + multiplier = 1.0 + end + result = {} + result[(denomination)] = text.to_f * multiplier.to_f + else + # Note: defaults *assume* base-10 numbers + unless denomination + denomination = :integer + end + unless multiplier + multiplier = 1 + end + result = {} + result[(denomination)] = text.to_i * multiplier.to_i + end + if column[:attributes].is_a?(Hash) + result.merge(column[:attributes]) + end + break + end + end + end + result + end + end + # unless it is a multi-line, or multi-column result, pass only the found denomination/value Hash. + if results.size == 1 + # single row + results = results[0] + if results.size == 1 + # single column + results = results[0] + end + end + # + results + end + # + def valid_time?() + # Parse for Time class + instance = nil + begin + instance = ::Chronic::parse(self) + rescue Exception + end + if instance.is_a?(::Time) + true + else + false + end + end + # + def valid_date?() + # Match for Date pattern + if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/.match(self)) + true + else + false + end + end + # + def valid_datetime?() + # Match for ISO 8601 pattern + if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9][+-][0-9][0-9]:[0-9][0-9]/.match(self)) + true + else + false + end + end + # + def valid_datetime_nolocale?() + # Match for not-so-much ISO 8601-ish pattern + if (/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/.match(self)) + true + else + false + end + end + # + def valid_path?() + if (/.*(?:\\|\/)(.+)$/.match(self) || (self.split(" ").size == 1 || self.split("/").size > 1 || self == "/")) + true + else + false + end + end + # + def valid_jid?() + # From Blather: /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/ + # From xmpp4r: /^(?:([^@\/<>\'\"]+)@)?([^@\/<>\'\"]+)(?:\/([^<>\'\"]*))?$/ + # Note: for now - using the one from Blather + if /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.match(self) + true + else + false + end + # if /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.match(self) + # true + # else + # false + # end + end + # + def valid_mxid?() + if /^[!@$#+][^:]+:.*$/.match(self) + true + else + false + end + end + # + def camel_case?() + # self.match(/([A-Z][a-z]+[A-Z][a-zA-Z]+)/) + if self.match(/[A-Z]([A-Z0-9]*[a-z][a-z0-9]*[A-Z]|[a-z0-9]*[A-Z][A-Z0-9]*[a-z])[A-Za-z0-9]*/) + true + else + false + end + end + # + def split_camelcase() + if self.camel_case?() + self.split(/(?=[A-Z])/) + else + self + end + end + # + def mime_type() + result = nil + raw = ::MimeMagic.by_magic(::StringIO.new(self.clone)) + if raw + result = {:type => (raw.type), :mediatype => (raw.mediatype), :subtype => (raw.subtype)} + end + result + end + # + def xml?() + declaration_test = self.include?(" the_error + end + end + (declaration_test && parse_test) + end + # + # def from_xml(options={}) + # # + # unless options[:no_test] == true + # unless self.xml?() + # raise Exception, "String is NOT formatted as XML." + # end + # end + # result = nil + # visit = Proc.new do |the_node=nil, accumulator=[]| + # node_stack = [] + # if the_node + # node_stack.push(the_node) + # while (node_stack.size > 0) do + # a_node = node_stack.pop() + # # process(a_node) + # if a_node.is_a?(::Oga::XML::Text) + # accumulator << a_node.text() + # end + # if a_node.is_any?(::Oga::XML::Element, ::Oga::XML::Document) + # record = {:name => "document_root", :attributes => {}, :parent => nil, :object => nil, :path => "/", :assembled => false} + # if a_node.respond_to?(:name) + # record[:name] = a_node.name() + # end + # if a_node.respond_to?(:attributes) + # a_node.attributes.each do |the_attr| + # record[:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value + # end + # end + # if a_node.respond_to?(:parent) + # record[:parent] = a_node.parent() + # else + # record[:parent] = a_node + # end + # record[:object] = a_node + # unless a_node.is_a?(::Oga::XML::Document) + # # set accumulator path + # # + # end + # accumulator << record + # end + # if a_node.children.size > 0 + # a_node.children.each do |entry| + # node_stack.push(entry) + # end + # end + # end + # end + # accumulator + # end + # # + # begin + # if options[:sax] == true + # database = visit.call(::Oga::sax_parse_xml(self.transcode({:replace => "."},::Encoding::UTF_8)),[]) + # else + # database = visit.call(::Oga::parse_xml(self.transcode({:replace => "."},::Encoding::UTF_8)),[]) + # end + # link_db = [] + # accumulator = [] + # # Build link_db + # database.each do |the_node| + # if the_node.is_a?(::Hash) + # node_record = {} + # as_key = the_node[:name].to_s.to_sym + # if the_node[:object].is_a?(::Oga::XML::Document) + # node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => "", :children => []} + # else + # node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => (the_node[:object].text), :children => []} + # end + # link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])} + # # + # database.each do |a_node| + # if a_node.is_a?(::Hash) + # if a_node[:parent].object_id == the_node[:object].object_id + # link_record[:desendents] << a_node[:object] + # end + # end + # end + # link_db << link_record + # if the_node[:parent].is_a?(::Oga::XML::Document) + # accumulator << node_record + # end + # else + # accumulator << the_node + # end + # end + # # + # while (link_db.size > 0) do + # entry = link_db.shift + # if entry.is_a?(::Hash) + # entry[:desendents].each do |node| + # unless node.is_a?(::Oga::XML::Document) + # if node.is_a?(::Oga::XML::Text) + # node_record = node.text + # else + # if node.is_a?(::Oga::XML::Comment) + # node_record = {:comment => node.text} + # else + # node_record = {} + # as_key = node.name.to_s.to_sym + # node_record[(as_key)] = {:attributes => {}, :text => (node.text), :children => []} + # node.attributes.each do |the_attr| + # node_record[(as_key)][:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value + # end + # link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])} + # node.children.each do |node_descendent| + # link_record[:desendents] << node_descendent + # end + # if link_record[:desendents].size > 0 + # link_db.push(link_record) + # end + # end + # end + # # + # entry[:accumulator] << node_record + # end + # end + # # + # end + # end + # # + # result = accumulator + # rescue Exception => the_error + # end + # # + # result + # end + # + # def from_xml_simple() + # if self.xml? + # the_hash = ::XmlSimple.xml_in(self) + # if the_hash.is_a?(::Hash) + # the_hash.symbolize_keys + # the_hash.process! do |value, selector, container| + # if value.is_a?(::String) + # item = value.numeric_values() + # if item.is_a?(::Hash) + # if item[:integer] + # item = item[:integer] + # container[(selector)] = item + # else + # if item[:float] + # item = item[:float] + # container[(selector)] = item + # end + # end + # else + # # Cast all time elements to ISO-8601 + # if (value.valid_time? || value.valid_date? || value.valid_datetime? || value.valid_datetime_nolocale?) + # container[(selector)] = ::DateTime::parse(value.to_s) + # end + # end + # end + # nil + # end + # end + # the_hash + # else + # nil + # end + # end + # + def html?() + declaration_test = (self.include?(' the_error + end + end + (declaration_test && parse_test) + end + # + # def from_html(options={}) + # # + # unless options[:no_test] == true + # unless self.html?() + # raise Exception, "String is NOT formatted as HTML." + # end + # end + # result = nil + # visit = Proc.new do |the_node=nil, accumulator=[]| + # node_stack = [] + # if the_node + # node_stack.push(the_node) + # while (node_stack.size > 0) do + # a_node = node_stack.pop() + # # process(a_node) + # if a_node.is_a?(::Oga::XML::Text) + # accumulator << a_node.text() + # end + # if a_node.is_any?(::Oga::XML::Element, ::Oga::XML::Document) + # record = {:name => "document_root", :attributes => {}, :parent => nil, :object => nil, :path => "/", :assembled => false} + # if a_node.respond_to?(:name) + # record[:name] = a_node.name() + # end + # if a_node.respond_to?(:attributes) + # a_node.attributes.each do |the_attr| + # record[:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value + # end + # end + # if a_node.respond_to?(:parent) + # record[:parent] = a_node.parent() + # else + # record[:parent] = a_node + # end + # record[:object] = a_node + # unless a_node.is_a?(::Oga::XML::Document) + # # set accumulator path + # # + # end + # accumulator << record + # end + # if a_node.children.size > 0 + # a_node.children.each do |entry| + # node_stack.push(entry) + # end + # end + # end + # end + # accumulator + # end + # # + # begin + # if options[:sax] == true + # database = visit.call(::Oga::sax_parse_html(self.transcode({:replace => "."},::Encoding::UTF_8)),[]) + # else + # database = visit.call(::Oga::parse_html(self.transcode({:replace => "."},::Encoding::UTF_8)),[]) + # end + # link_db = [] + # accumulator = [] + # # Build link_db + # database.each do |the_node| + # if the_node.is_a?(::Hash) + # node_record = {} + # as_key = the_node[:name].to_s.to_sym + # if the_node[:object].is_a?(::Oga::XML::Document) + # node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => "", :children => []} + # else + # node_record[(as_key)] = {:attributes => (the_node[:attributes]), :text => (the_node[:object].text), :children => []} + # end + # link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])} + # # + # database.each do |a_node| + # if a_node.is_a?(::Hash) + # if a_node[:parent].object_id == the_node[:object].object_id + # link_record[:desendents] << a_node[:object] + # end + # end + # end + # link_db << link_record + # if the_node[:parent].is_a?(::Oga::XML::Document) + # accumulator << node_record + # end + # else + # accumulator << the_node + # end + # end + # # + # while (link_db.size > 0) do + # entry = link_db.shift + # if entry.is_a?(::Hash) + # entry[:desendents].each do |node| + # unless node.is_a?(::Oga::XML::Document) + # if node.is_a?(::Oga::XML::Text) + # node_record = node.text + # else + # if node.is_a?(::Oga::XML::Comment) + # node_record = {:comment => node.text} + # else + # node_record = {} + # as_key = node.name.to_s.to_sym + # node_record[(as_key)] = {:attributes => {}, :text => (node.text), :children => []} + # node.attributes.each do |the_attr| + # node_record[(as_key)][:attributes][(the_attr.name.to_s.to_sym)] = the_attr.value + # end + # link_record = {:desendents => [], :accumulator => (node_record[(as_key)][:children])} + # node.children.each do |node_descendent| + # link_record[:desendents] << node_descendent + # end + # if link_record[:desendents].size > 0 + # link_db.push(link_record) + # end + # end + # end + # # + # entry[:accumulator] << node_record + # end + # end + # # + # end + # end + # # + # result = accumulator + # rescue Exception => the_error + # end + # # + # result + # end + # + def valid_uri?() + result = false + begin + if ::URI::parse(self).is_a?(::URI::Generic) + result = true + end + rescue Exception + end + result + end + # + def to_uri() + ::URI::parse(self) + end + # + def slice_bytes(*args) + # Return an ASCII_8BIT encoded sub-string + result = nil + the_range = nil + if args[0].is_a?(Numeric) + args[0] = args[0].to_i + if args[0] < 0 + args[0] = self.bytesize + args[0] + end + the_range = ((args[0])..(args[0])) + if args[1].is_a?(Numeric) + args[1] = args[1].to_i + if args[1] < 1 + raise ArgumentError.new("Second parameter needs to be a Numeric greater than 0, you provided #{args[1].inspect}") + else + the_range = ((args[0])..(args[0] + (args[1] - 1))) + end + end + else + if args[0].is_a?(Range) + if args[0].first < 0 + args[1] = self.bytesize + args[0].first + else + args[1] = args[0].first + end + if args[0].last < 0 + args[2] = self.bytesize + args[0].last + else + args[2] = args[0].last + end + if args[1] <= args[2] + the_range = ((args[1])..(args[2])) + else + the_range = ((args[2])..(args[1])) + end + else + raise ArgumentError.new("First parameter needs to be a Numeric or a Range, you provided #{args[0].inspect}") + end + end + # + if the_range + result = "" + result.force_encoding(::Encoding::ASCII_8BIT) + # + if self.encoding == ::Encoding::ASCII_8BIT + if self.bytesize > 0 + if the_range.min != the_range.max + result << self.slice(the_range) + else + result << self[(the_range.min)] + end + end + else + if self.bytesize > 0 + raw_text = self.clone + raw_text.force_encoding(::Encoding::ASCII_8BIT) + if the_range.min != the_range.max + result << raw_text.slice(the_range) + else + result << raw_text[(the_range.min)] + end + end + end + # + end + result + end + # + def byte_at(*args) + # Random-access indexed byte value retrieval. Returns a Integer or nil. + # why? This is to pluck a single byte value out of a string as if it were merely a string of bytes. 1.8.7 used to provide a [] method for this purpose. + if args[0].is_a?(Numeric) + args[0] = args[0].to_i + else + raise ArgumentError.new("The parameter needs to be a Numeric, you provided #{args[0].inspect}") + end + if self.bytesize > 0 + if self.encoding == ::Encoding::ASCII_8BIT + self[(args[0])].ord + else + self.slice_bytes(args[0])[0].ord + end + else + nil + end + end + # + def bytes_at(*args) + # Returns GxG::ByteArray + unless args.size > 0 + args = [(0..-1)] + end + GxG::ByteArray.new(1,self.slice_bytes(*args)) + end + # + def unpack_bytes(format_template_string="") + # See : http://ruby-doc.org/core-1.9.3/String.html#method-i-byteslice#This%20table%20summarizes + GxG::ByteArray.new(self.unpack(format_template_string)) + end + # + # Note: documentation: recommend use of these methods and NOT those which they call to ensure cooperative event processing. + # LATER: String: look into c code : is there a way to pluck a char off a string w/o using chars,bytes,codepoints,lines ? + # This would afford an opportunity to override chars,bytes,codepoints and lines so that cooperative event processing was truly and invisibly supported. + # This would be slower than the c code, but would let you load up gems w/o overriding another author's code to make it cooperative. (i.e. facets, etc) + def each_line(separator=$/,&block) + enumerator = self.lines(separator).to_enum + if block.respond_to?(:call) + enumerator.each do |item| + block.call(item) + end + self + else + enumerator + end + end + # + def each_line_with_index(separator=$/,offset=0,&block) + enumerator = self.lines(separator).to_enum.with_index(offset) + if block.respond_to?(:call) + enumerator.each do |item,index| + block.call(item,index) + end + self + else + enumerator + end + end + # + def each_char(&block) + enumerator = self.to_enum(:chars) + if block.respond_to?(:call) + enumerator.each do |item| + block.call(item) + end + self + else + enumerator + end + end + # + def each_char_with_index(offset=0,&block) + # Thanks to Hanmac from IRC :) + enumerator = self.to_enum(:chars).with_index(offset) + if block.respond_to?(:call) + enumerator.each do |item,index| + block.call(item,index) + end + self + else + enumerator + end + end + # + def each_byte(&block) + enumerator = self.to_enum(:bytes) + if block.respond_to?(:call) + enumerator.each do |item| + block.call(item) + end + self + else + enumerator + end + end + # + def each_byte_with_index(offset=0,&block) + # Thanks to Hanmac from IRC :) + enumerator = self.to_enum(:bytes).with_index(offset) + if block.respond_to?(:call) + enumerator.each do |item,index| + block.call(item,index) + end + self + else + enumerator + end + end + # + def each_codepoint(&block) + # String.codepoint will not return a GxG::Enumerator (non-blocking), so I'm providing these methods. + enumerator = self.to_enum(:codepoints) + if block.respond_to?(:call) + enumerator.each do |item| + block.call(item) + end + self + else + enumerator + end + end + # + def each_codepoint_with_index(offset=0,&block) + enumerator = self.to_enum(:codepoints).with_index(offset) + if block.respond_to?(:call) + enumerator.each do |item,index| + block.call(item,index) + end + self + else + enumerator + end + end + # Encoding format detection and handling: Serialized, JSON & binhex(base64) + def serialized?() + if ((self[0..7].to_s == "marshal:" || self[0..9].to_s == "structure:") && (self[8..-1].to_s.base64?() || self[10..-1].to_s.base64?())) + true + else + false + end + end + # + def unserialize() + if self.serialized?() + ::GxG::reconstitute(self) + else + self + end + end + # + def json_1?() + self.slice(0,1) == '{' + end + # + def json_2?() + (self.json_1?() && (self.match('"apiVersion"\s?:\s?"2.0"') || false)) + end + # + def json?() + self.json_1?() || self.json_2?() + end + # + def from_json(symbolize_names = true) + if self.json? + the_object = ::JSON::parse(self,{:symbolize_names => symbolize_names}) + if the_object.is_any?(::Hash, ::Array) + the_object.process! do |value, selector, container| + if value.is_a?(::String) + item = value.numeric_values() + if item.is_a?(::Hash) + if item[:integer] + item = item[:integer] + container[(selector)] = item + else + if item[:float] + item = item[:float] + container[(selector)] = item + end + end + else + if (value.valid_datetime? || value.valid_datetime_nolocale?) + container[(selector)] = ::DateTime::parse(value) + end + end + # + if value[0..6] == "binary:" && value[7..-1].base64? + container[(selector)] = GxG::ByteArray.new(value[7..-1].decode64) + end + end + nil + end + # + end + else + self + end + end + # Base64 Stuff: + def base64?() + # RFC 4648 + # SOMEDAY: use regex based detection, current is open to some bugs. + # See: http://ruby.about.com/od/advancedruby/ss/Base64-In-Ruby.htm + # See: http://www.perlmonks.org/?node_id=775820 + # See: http://mattfaus.com/blog/2007/02/14/base64-regular-expression/ + # See: http://stackoverflow.com/questions/475074/regex-to-parse-or-validate-base64-data + # Regex Testing: http://www.myregexp.com/signedJar.html + # For now: + begin + # Review : So, for a work-around - keep adding exclusion tests to this conditional set as you discover them. + if (self.size > 0) && (not ["html","form","page","head","body","show","hide","play"].include?(self.downcase)) + # Review : *almost perfect* - words like 'html' 'form' 'page' get mangled because this regex senses they are base64 when they are not! (consult regex expert for remedy) + if self.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) + true + else + false + end + else + false + end + rescue Exception => the_error + false + end + end + # + def encode64() + # RFC 4648 + ::Base64::strict_encode64(self) + end + # + def decode64() + # RFC 4648 + if self.base64?() + ::Base64::strict_decode64(self) + else + self + end + end + # + def encode64!() + data = self.encode64() + if data != self + self.replace(data) + end + self + end + # + def decode64!() + data = self.decode64() + if data != self + self.replace(data) + end + self + end + # + def encrypt(withkey="") + # + if withkey.to_s.size > 0 + the_encoding = self.encoding() + keybytes = ::GxG::ByteArray.new(withkey.to_s) + container = ::GxG::ByteArray.new(self) + # + container.each_index do |index| + the_value = container[(index)] + # + keybytes.each do |the_byte| + [127,63,31,15,7,3,1,0].each do |the_bit| + if the_byte > the_bit + the_value += 1 + if the_value > 255 + the_value = 0 + end + else + the_value -= 1 + if the_value < 0 + the_value = 255 + end + end + end + end + # + container[(index)] = the_value + end + # + result = container.to_s + result.force_encoding(the_encoding) + result + else + self.dup + end + end + # + def encrypt_aes(withkey="",iv=nil) + digest = Digest::SHA256.new + digest.update(withkey) + aes = OpenSSL::Cipher.new("AES-256-CBC") + iv = OpenSSL::Cipher.new("AES-256-CBC").random_iv + aes.encrypt + aes.key = digest.digest + aes.iv = iv + cipher = aes.update(self) + cipher << aes.final + {:vector => iv, :data => cipher} + end + # + def decrypt(withkey="") + # + if withkey.to_s.size > 0 + the_encoding = self.encoding() + keybytes = ::GxG::ByteArray.new(withkey.to_s) + container = ::GxG::ByteArray.new(self) + # + container.each_index do |index| + the_value = container[(index)] + # + keybytes.each do |the_byte| + [127,63,31,15,7,3,1,0].each do |the_bit| + if the_byte > the_bit + the_value -= 1 + if the_value < 0 + the_value = 255 + end + else + the_value += 1 + if the_value > 255 + the_value = 0 + end + end + end + end + # + container[(index)] = the_value + end + # + result = container.to_s + result.force_encoding(the_encoding) + result + else + self.dup + end + end + # + def decrypt_aes(withkey="", iv=nil) + digest = Digest::SHA256.new + digest.update(withkey) + decode_cipher = OpenSSL::Cipher.new("AES-256-CBC") + decode_cipher.decrypt + decode_cipher.key = digest.digest + decode_cipher.iv = iv + plain = decode_cipher.update(self) + plain << decode_cipher.final + plain + end +end +# Alteration to .times block call loop +class Integer + # + # def times(&block) + # if self > 0 + # if block + # (1..(self)).to_enum.each do + # block.call + # # *should* use GxG::Enumerator, so no pause command needed here. + # end + # self + # else + # (1..(self)).to_enum(:times) + # end + # else + # self + # end + # end + # # + # def upto(number=nil,&block) + # if block.respond_to?(:call) + # if number.is_a?(::Numeric) + # number = number.to_i + # if self <= number + # ((self)..(number)).to_enum.each do |the_number| + # block.call(the_number) + # end + # self + # else + # number + # end + # else + # raise ArgumentError, "comparison of Fixnum and #{number.class} failed" + # end + # else + # self.to_enum(:upto,number) + # end + # end + # # + # def step(number=nil,skip=nil,&block) + # if block.respond_to?(:call) + # if number.is_a?(::Numeric) + # number = number.to_i + # if skip.is_any?(::Numeric,::NilClass) + # if skip + # skip = skip.to_i + # else + # skip = 1 + # end + # # + # if self <= number + # current = self.dup + # while current <= number + # block.call(current) + # current += skip + # pause + # end + # self + # else + # number + # end + # # + # else + # raise ArgumentError, "comparison of #{number.class} and 0 failed" + # end + # else + # raise ArgumentError, "comparison of Fixnum and #{number.class} failed" + # end + # else + # if skip + # self.to_enum(:skip,number,skip) + # else + # self.to_enum(:skip,number) + # end + # end + # end + # + def to_d() + ::BigDecimal.new(("%#{::Float::DIG}f" % self.to_f)) + end +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 + # + def self.process(the_struct=::Struct.new(nil),&block) + new_struct = ::Struct.new(nil) + if block.respond_to?(:call) + if the_struct.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_struct = the_struct.process!(&block) + else + raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Set, or a Struct" + end + end + new_struct + end + # + def self.search(the_struct=::Struct.new(nil),&block) + new_struct = ::Struct.new(nil) + if block.respond_to?(:call) + if the_struct.is_any?(::Array, ::Hash, ::Set, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_struct = the_struct.search(&block) + else + raise ArgumentError, "you must pass a Hash, or an Array, or a ByteArray, or a Set, or a Struct" + end + end + new_struct + end + # + # def each_pair(&block) + # # ok, bizarre : this method is listed in instance_methods but is missing when called. hrm. + # collection = {} + # self.members.to_enum.each do |key| + # collection[(key)] = (self[(key)]) + # end + # if block.respond_to?(:call) + # collection.to_enum(:each_pair).each do |key,value| + # block.call(key,value) + # end + # else + # collection.to_enum(:each_pair) + # end + # end + # + def iterative(&block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash) + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + end + if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray) + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + end + # + accumulator << a_node + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + # + def process(&block) + result = self.clone + result.iterative(&block) + result + end + # + def process!(&block) + self.iterative(&block) + self + end + # + def search(&block) + results = [] + if block.respond_to?(:call) + results = self.iterative(&block) + end + results + end + # +end +# Alteration to Hash and Array for support of structural processing: +# additional methods to address elements of, and process, structural combinations of Hashes, Arrays, and ByteArrays +# SOMEDAY: @Hash extend these to the pain-in-the-ass Struct class as well. +class Hash + # + def self.process(the_hash={},&block) + new_hash = {} + if block.respond_to?(:call) + if the_hash.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_hash = the_hash.process!(&block) + else + raise ArgumentError, "you must pass a Hash or an Array, or a ByteArray" + end + end + new_hash + end + # + def self.search(the_hash={},&block) + new_hash = {} + if block.respond_to?(:call) + if the_hash.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_hash = the_hash.search(&block) + else + raise ArgumentError, "you must pass a Hash or an Array, or a ByteArray" + end + end + new_hash + end + # ### OpenStruct Integration + def as_structure() + ::OpenStruct.new(self) + end + # + def iterative(&block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash) + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + end + if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray) + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + end + # + accumulator << a_node + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + # + def process(&block) + result = self.clone + result.iterative(&block) + result + end + # + def process!(&block) + self.iterative(&block) + self + end + # + def search(&block) + results = [] + if block.respond_to?(:call) + results = self.iterative(&block) + end + results + end + # + def paths_to(the_object=nil,base_path="") + # new idea here: + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin, :prefix => "/"}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key = safe_key.gsub("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + def gxg_export() + result = {:type => "Hash", :content => {}} + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + export_record = Proc.new do |the_value| + if the_value.is_any?(Integer, Float, String) + {:type => (the_value.class.to_s), :content => the_value} + else + {:type => (the_value.class.to_s), :content => the_value.to_s} + end + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::Hash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Hash", :content => {}}} + else + if the_value.is_a?(::Array) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Array", :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => export_record.call(the_value)} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(Hash, Array) + link_db << the_child + end + end + end + # + result + end + # + def self.gxg_import(the_exported_record=nil) + result = nil + if the_exported_record.is_a?(Hash) + if the_exported_record[:type] == "Array" + result = ::Array::gxg_import(the_exported_record) + else + import_value = Proc.new do |type,value| + if value.is_a?(String) + begin + the_class = eval(type) + if the_class.respond_to?(:parse) + value = the_class.parse(value) + else + if the_class.respond_to?(:new) + value = the_class.new(value) + else + if the_class.respond_to?(:try_convert) + value = the_class.try_convert(value) + end + end + end + rescue Exception => the_error + end + end + value + end + if the_exported_record[:type] == "Hash" + result = {} + import_db = [{:parent => nil, :parent_selector => nil, :object => result, :record => the_exported_record}] + while import_db.size > 0 do + entry = import_db.shift + if entry[:record][:content].is_a?(Hash) + entry[:record][:content].each_pair do |selector, value| + if value[:type] == "Hash" + entry[:object][(selector)] = {} + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + if value[:type] == "Array" + entry[:object][(selector)] = [] + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + entry[:object][(selector)] = import_value.call(value[:type], value[:content]) + end + end + end + else + if entry[:record][:content].is_a?(Array) + entry[:record][:content].each_with_index do |value, selector| + if value[:type] == "Hash" + entry[:object][(selector)] = {} + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + if value[:type] == "Array" + entry[:object][(selector)] = [] + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + entry[:object][(selector)] = import_value.call(value[:type], value[:content]) + end + end + end + end + end + end + else + result = import_value.call(the_exported_record[:type], the_exported_record[:content]) + end + end + end + result + end + # + # def to_xml_simple(options={}) + # # keyattr keeproot contentkey noattr rootname + # # xmldeclaration outputfile noescape suppressempty + # # anonymoustag indent grouptags noindent attrprefix + # the_options = {:xmldeclaration => true, :rootname => :root, :keeproot => false} + # if options.is_a?(::Hash) + # if options[:XmlDeclaration].is_any?(::TrueClass, ::FalseClass) + # the_options[:xmldeclaration] = options[:XmlDeclaration] + # end + # if options[:xmldeclaration].is_any?(::TrueClass, ::FalseClass) + # the_options[:xmldeclaration] = options[:xmldeclaration] + # end + # if options[:RootName].is_any?(::String, ::Symbol) + # the_options[:rootname] = options[:RootName] + # end + # if options[:rootname].is_any?(::String, ::Symbol) + # the_options[:rootname] = options[:rootname] + # end + # if options[:KeepRoot].is_any?(::TrueClass, ::FalseClass) + # the_options[:keeproot] = options[:KeepRoot] + # end + # if options[:keeproot].is_any?(::TrueClass, ::FalseClass) + # the_options[:keeproot] = options[:keeproot] + # end + # else + # raise ArgumentError, "You MUST provide options in the form of a Hash." + # end + # ::XmlSimple.xml_out(self, the_options).gsub("\n", "") + # end + # + # def to_json() + # ::JSON.generate(self) + # end + # + def symbolize_keys() + self.process! do |value, selector, container| + if container.is_a?(::Hash) + unless selector.is_a?(::Symbol) + container[(selector.to_sym)] = container.delete(selector) + end + end + nil + end + self + end + # + # +end +# +class Array + # Interesting research on extending ruby classes '|' for instance: http://verboselogging.com/2011/05/06/simple-ruby-pipes + # Also: http://stackoverflow.com/questions/4234119/ruby-pipes-how-do-i-tie-the-output-of-two-subprocesses-together + # And a *really* nice one: http://jstorimer.com/2012/03/18/a-unix-shell-in-ruby-pipes.html#Shell%20Pipelines%20Demystified + # + def self.process(the_array=[],&block) + new_array = [] + if block.respond_to?(:call) + if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_array = the_array.process(&block) + else + raise ArgumentError, "you must pass a Array, a Hash, or a ByteArray" + end + end + new_array + end + # + def self.search(the_array=[],&block) + new_array = [] + if block.respond_to?(:call) + if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_array = the_array.search(&block) + else + raise ArgumentError, "you must pass a Array, a Hash, or a ByteArray" + end + end + new_array + end + # + def iterative(&block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_any?(::Hash, ::Struct, ::GxG::Database::PersistedHash) + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + end + if a_node[:object].is_any?(::Array, ::GxG::Database::PersistedArray) + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + end + # + accumulator << a_node + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + # + def process(&block) + result = self.clone + result.iterative(&block) + result + end + # + def process!(&block) + self.iterative(&block) + self + end + # + def search(&block) + results = [] + if block.respond_to?(:call) + results = self.iterative(&block) + end + results + end + # + # + def paths_to(the_object=nil,base_path="") + # new idea here: + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key.gsub!("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric + # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu + # + # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/")) + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + def gxg_export() + result = {:type => "Array", :content => []} + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + export_record = Proc.new do |the_value| + if the_value.is_any?(Integer, Float, String) + {:type => (the_value.class.to_s), :content => the_value} + else + {:type => (the_value.class.to_s), :content => the_value.to_s} + end + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::Hash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Hash", :content => {}}} + else + if the_value.is_a?(::Array) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => "Array", :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => export_record.call(the_value)} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(Hash, Array) + link_db << the_child + end + end + end + # + result + end + # + def self.gxg_import(the_exported_record=nil) + result = nil + if the_exported_record.is_a?(Hash) + if the_exported_record[:type] == "Hash" + result = ::Hash::gxg_import(the_exported_record) + else + import_value = Proc.new do |type,value| + if value.is_a?(String) + begin + the_class = eval(type) + if the_class.respond_to?(:parse) + value = the_class.parse(value) + else + if the_class.respond_to?(:new) + value = the_class.new(value) + else + if the_class.respond_to?(:try_convert) + value = the_class.try_convert(value) + end + end + end + rescue Exception => the_error + end + end + value + end + if the_exported_record[:type] == "Array" + result = [] + import_db = [{:parent => nil, :parent_selector => nil, :object => result, :record => the_exported_record}] + while import_db.size > 0 do + entry = import_db.shift + if entry[:record][:content].is_a?(Hash) + entry[:record][:content].each_pair do |selector, value| + if value[:type] == "Hash" + entry[:object][(selector)] = {} + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + if value[:type] == "Array" + entry[:object][(selector)] = [] + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + entry[:object][(selector)] = import_value.call(value[:type], value[:content]) + end + end + end + else + if entry[:record][:content].is_a?(Array) + entry[:record][:content].each_with_index do |value, selector| + if value[:type] == "Hash" + entry[:object][(selector)] = {} + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + if value[:type] == "Array" + entry[:object][(selector)] = [] + import_db << {:parent => entry[:object], :parent_selector => selector, :object => entry[:object][(selector)], :record => value} + else + entry[:object][(selector)] = import_value.call(value[:type], value[:content]) + end + end + end + end + end + end + else + result = import_value.call(the_exported_record[:type], the_exported_record[:content]) + end + end + end + result + end + # + def average() + # See: https://stackoverflow.com/questions/1341271/how-do-i-create-an-average-from-a-ruby-array + self.instance_eval { reduce(:+).to_f / size.to_f } + end + # +end +# OpenStruct support for Arrays. +module GxG + class ArrayWrapper + # Review : match all public methods of a conventional array. + def initialize(the_array=nil) + @data = the_array + end + # + def table() + @data + end + # + def [](*args) + result = @data[*args] + # + if result.is_any?(::Array, ::GxG::Database::PersistedArray, ::GxG::Database::DetachedArray) + result = ::GxG::ArrayWrapper.new(result) + else + if result.is_any?(::Hash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedHash) + result = ::OpenStruct.new(result) + end + end + # + result + end + # + def []=(the_index, the_value) + if the_value.is_any?(::OpenStruct, ::GxG::ArrayWrapper) + @data[(the_index)] = the_value.table + else + @data[(the_index)] = the_value + end + end + # + def shift() + result = @data.shift + # + if result.is_any?(::Array, ::GxG::Database::PersistedArray, ::GxG::Database::DetachedArray) + result = ::GxG::ArrayWrapper.new(result) + else + if result.is_any?(::Hash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedHash) + result = ::OpenStruct.new(result) + end + end + # + result + end + # + def unshift(the_value) + if the_value.is_any?(::OpenStruct, ::GxG::ArrayWrapper) + @data.unshift the_value.table + else + @data.unshift the_value + end + end + # + def pop() + result = @data.pop + # + if result.is_any?(::Array, ::GxG::Database::PersistedArray, ::GxG::Database::DetachedArray) + result = ::GxG::ArrayWrapper.new(result) + else + if result.is_any?(::Hash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedHash) + result = ::OpenStruct.new(result) + end + end + # + result + end + # + def push(the_value) + if the_value.is_any?(::OpenStruct, ::GxG::ArrayWrapper) + @data.push the_value.table + else + @data.push the_value + end + end + # + def each(&block) + if block.respond_to?(:call) + @data.each_index do |index| + block.call(self[(index)]) + end + self + else + self.to_enum(:each) + end + end + # + def each_index(&block) + if block.respond_to?(:call) + @data.each_index do |index| + block.call(index) + end + self + else + self.to_enum(:each_index) + end + end + # + def each_with_index(&block) + if block.respond_to?(:call) + @data.each_index do |index| + block.call(self[(index)], index) + end + self + else + self.to_enum(:each_with_index) + end + end + # + def first() + self[0] + end + # + def last() + self[-1] + end + # + def include?(the_value) + result = false + if the_value.is_any?(::OpenStruct, ::GxG::ArrayWrapper) + the_value = the_value.table + end + @data.each do |element| + if element == the_value + result = true + break + end + end + result + end + # + def find_index(the_value) + result = nil + if the_value.is_any?(::OpenStruct, ::GxG::ArrayWrapper) + the_value = the_value.table + end + @data.each_with_index do |element, indexer| + if element == the_value + result = indexer + break + end + end + result + end + # + def method_missing(the_method, *args) + unless the_method.to_sym == :"[]" || the_method == :"[]=" + if @data.respond_to?(the_method.to_sym) + @data.send(the_method.to_sym, *args) + else + begin + super + rescue NoMethodError => err + err.backtrace.shift + raise! + end + end + end + end + end + # +end +# +# OpenStruct Modifications to support nested structures. +class OpenStruct + def new_ostruct_member!(name) # :nodoc: + unless @table.key?(name) || is_method_protected!(name) + define_singleton_method!(name) { self[name] } + define_singleton_method!("#{name}=") {|x| self[name] = x} + end + end + private :new_ostruct_member! + # + private def method_missing(mid, *args) # :nodoc: + len = args.length + if mname = mid[/.*(?==\z)/m] + if len != 1 + raise! ArgumentError, "wrong number of arguments (given #{len}, expected 1)", caller(1) + end + if @table.respond_to?(mname.to_sym) + @table.send(mname.to_sym, args[0]) + else + set_ostruct_member_value!(mname, args[0]) + end + elsif len == 0 + self[(mid)] + else + begin + super + rescue NoMethodError => err + err.backtrace.shift + raise! + end + end + end + # + public + # + def [](name) + if @table[name.to_sym].is_any?(::Hash, ::GxG::Database::PersistedHash, ::GxG::Database::DetachedHash) + OpenStruct.new(@table[name.to_sym]) + else + if @table[name.to_sym].is_any?(::GxG::Database::PersistedArray, ::GxG::Database::DetachedArray) + @table[name.to_sym].as_structure(true) + end + @table[name.to_sym] + end + end + # + def []=(name, value) + name = name.to_sym + new_ostruct_member!(name) + if @table[name].is_a?(::GxG::Database::PersistedHash) + if ! value.is_a?(::GxG::Database::DetachedHash) + if value.is_a?(::OpenStruct) + if ! value.table.is_a?(::GxG::Database::DetachedHash) + @table[name] = value.table + else + # makes a copy of the detached data + @table[name] = value.table.unpersist() + end + else + @table[name] = value + end + else + # makes a copy of the detached data + @table[name] = value.unpersist() + end + else + if @table[name].is_a?(::GxG::Database::DetachedHash) + if ! value.is_a?(::GxG::Database::PersistedHash) + if value.is_a?(::OpenStruct) + if ! value.table.is_a?(::GxG::Database::PersistedHash) + @table[name] = value.table + else + # makes a copy of the persisted data + @table[name] = value.table.unpersist() + end + else + @table[name] = value + end + else + # makes a copy of the persisted data + @table[name] = value.unpersist() + end + else + @table[name] = value + end + end + end + alias_method :set_ostruct_member_value!, :[]= + private :set_ostruct_member_value! + # + def save() + if @table.respond_to?(:save) + @table.save() + else + false + end + end + # +end +# \ No newline at end of file diff --git a/lib/gxg/gxg_communications.rb b/lib/gxg/gxg_communications.rb new file mode 100644 index 0000000..b9fbc88 --- /dev/null +++ b/lib/gxg/gxg_communications.rb @@ -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: { => {:channel => , :entities => [, ...]}} + @channels = {} + # Keychain format: {:original => "", :current => ""} + @keychain = {} + # Messages format: { => [<::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 \ No newline at end of file diff --git a/lib/gxg/gxg_database.rb b/lib/gxg/gxg_database.rb new file mode 100644 index 0000000..6a2c043 --- /dev/null +++ b/lib/gxg/gxg_database.rb @@ -0,0 +1,5505 @@ +# require sequel here to ensure other mods are underneath it +require "sequel" +require "securerandom" +require "openssl" +require "base64" +require "net/ldap" +module GxG + DB_SAFETY = ::Mutex.new + DB = {:cache => {}, :formats => {}, :authority => nil, :administrator => nil, :roles => {}} + # + module Database + # Class Place-holder Definitions + class DetachedArray + # + end + class DetachedHash + # + end + class PersistedArray + # + end + class PersistedHash + # + end + # + def self.element_tables() + { + :unspecified => nil, + :element_boolean => [::TrueClass, ::FalseClass, ::NilClass], + :element_integer => [::Integer], + :element_float => [::Float], + :element_bigdecimal => [::BigDecimal], + :element_datetime => [::DateTime], + :element_text => [::String], + :element_binary => [::GxG::ByteArray], + :element_array => [::Array], + :element_hash => [::Hash] + } + end + # + def self.valid_field_classes() + [::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray] + end + def self.element_table_index(the_key=:unspecified) + (::GxG::Database::element_tables().keys.index(the_key.to_sym) || 0) + end + # + def self.element_table_by_index(the_index=0) + (::GxG::Database::element_tables().keys[(the_index)] || :unspecified) + end + # + def self.element_table_for_instance(the_instance=nil) + result = :unspecified + # + table = ::GxG::Database::element_tables() + table.keys.each do |the_key| + unless the_key == :unspecified + if the_instance.is_any?(table[(the_key)]) + result = the_key + break + end + end + end + # + result + end + # + def self.persistable?(the_object) + result = true + unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + if the_object.is_any?(::Hash, ::Array) + the_object.search do |item, selector, container| + if result + if ::GxG::Database::element_table_for_instance(item) == :unspecified + result = false + break + end + end + nil + end + else + if ::GxG::Database::element_table_for_instance(the_object) == :unspecified + result = false + end + end + end + result + end + # + def self.iterative_detached_persist(old_root=nil) + # New PersistedArray, or PersistedHash interface: + # + result = nil + begin + # db open? + # write permissions on db? + unless old_root.is_any?(::Array, ::Hash) + raise ArgumentError, "You MUST provide either an Array or a Hash." + end + unless GxG::Database::persistable?(old_root) + raise ArgumentError, "The object you are attempting to persist contains a non-persistable item." + end + # + if old_root.is_a?(::Array) + original_partner = ::GxG::Database::DetachedArray.new() + # + end + if old_root.is_a?(::Hash) + original_partner = ::GxG::Database::DetachedHash.new() + end + # + paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}] + children_of = Proc.new do |the_parent=nil| + list = [] + paring_data.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + parent_of = Proc.new do |the_parent=nil| + output = nil + paring_data.each do |entry| + if entry[:object].object_id == the_parent.object_id + output = entry + end + end + output + end + find_partner = Proc.new do |the_object| + found_partner = nil + paring_data.each do |the_record| + if the_record.is_a?(::Hash) + if the_record[:object].object_id == the_object.object_id + found_partner = the_record[:partner] + break + end + end + end + found_partner + end + # build paring data: + delegate_permission = nil + old_root.search do |the_value, the_selector, the_container| + if the_value.is_a?(::Array) + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedArray.new()} + else + if the_value.is_a?(::Hash) + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::DetachedHash.new()} + else + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value} + end + end + end + # + # Assign objects to structure in order by parent / parent_selector + link_db = [(paring_data[0])] + while link_db.size > 0 + entry = link_db.shift + if entry.is_a?(::Hash) + if entry[:object].is_any?(::Array, ::Hash) + # get children and assign + children = children_of.call(entry[:object]) + children.each do |child| + entry[:partner][(child[:parent_selector])] = child[:partner] + if child[:partner].is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) + link_db << child + end + end + end + end + end + result = original_partner + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:object => old_root}}) + end + result + end + # + def self.detached_format_find(the_criteria={}) + result = nil + tests = {} + [:uuid, :ufs, :title, :version].each do |the_key| + if the_criteria[(the_key)] + tests[(the_key)] = the_criteria[(the_key)] + end + end + GxG::DB[:formats].each_pair do |uuid,record| + score = 0 + tests.keys.each do |the_test| + if record[(the_test)] == tests[(the_test)] + score += 1 + end + end + if tests.keys.size == score + result = record + break + end + end + result + end + # + def self.detached_format_load(the_criteria={}, &block) + # If already loaded and block provided: return an Array of format records. + # Load any missing formats from server, and call block if provided. + # Note: results are in NO particular order (DO NOT INDEX into the result set from source criteria) + success = false + needed = [] + results = [] + if the_criteria.is_a?(::Hash) + loaded = ::GxG::Database::detached_format_find(the_criteria) + if loaded + results << loaded + if block.respond_to?(:call) + block.call(results) + end + success = true + else + needed << the_criteria + end + else + if the_criteria.is_a?(::Array) + the_criteria.each do |the_specifier| + loaded = ::GxG::Database::detached_format_find(the_specifier) + if loaded + results << loaded + else + needed << the_specifier + end + end + end + end + if needed.size > 0 + if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database) + needed.each do |the_specifier| + format_record = ::GxG::DB[:roles][:format].format_load({:uuid => the_specifier.to_s.to_sym}) + if format_record + GxG::DB[:formats][(format_record[:uuid].to_sym)] = format_record + results << format_record + end + end + if block.respond_to?(:call) + block.call(results) + end + success = true + end + else + if results.size > 0 + if block.respond_to?(:call) + block.call(results) + end + success = true + end + end + # + success + end + # + def self.process_detached_import(import_manifest=nil, options={}) + result = [] + if import_manifest.is_a?(::Hash) + if import_manifest[:formats].size > 0 + # import formats: GxG::DB[:formats] + import_manifest[:formats].keys.each do |the_key| + unless GxG::DB[:formats][(the_key)] + # Review do ::Hash.gxg_import on format content + format_record = import_manifest[:formats][(the_key)] + if format_record[:version].is_a?(::String) + format_record[:version] = BigDecimal.new(format_record[:version]) + end + format_record[:content] = ::Hash.gxg_import(format_record[:content]) + GxG::DB[:formats][(the_key)] = format_record + end + end + end + # + if import_manifest[:records].size > 1 + import_manifest[:records].each do |the_record| + result << GxG::Database::DetachedHash::import(the_record) + end + else + result << GxG::Database::DetachedHash::import(import_manifest[:records][0]) + end + end + result + end + # + def self.process_import(import_manifest=nil, credential=nil, options={}) + # sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={}) + result = false + if import_manifest.is_a?(::Hash) && ::GxG::valid_uuid?(credential) && options.is_a?(::Hash) + options = {:role => :data}.merge!(options) + database = ::GxG::DB[:roles][(options[:role])] + if database.is_a?(::GxG::Database::Database) + if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database) + unless database == ::GxG::DB[:roles][:format] + ::GxG::DB[:roles][:format].sync_import(credential, {:formats => import_manifest[:formats], :records => []}, options) + end + end + # + result = database.sync_import(credential, import_manifest, options) + else + log_warn("No suitable database of role #{options[:role].inspect} could be found for the import request : Ignoring Request") + end + else + log_warn("Invalid parameters to import request : Ignoring Request") + end + result + end + # + def self.as_import_record(records=[], options={}) + result = {:formats => {}, :records => []} + if records.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) + records = [(records)] + end + if records.is_any?(::Array, ::GxG::Database::DetachedArray, ::GxG::Database::PersistedArray) + records.each do |the_object| + if the_object.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) + object_record = the_object.export() + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + if ::GxG::DB[:roles][:format].is_a?(::GxG::Database::Database) + format_sample = ::GxG::DB[:roles][:format].format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + end + result[:records] << object_record + # + end + end + end + result + end + # + def self.connect(the_url, options={}) + # TODO: require :credential (access privs) + result = nil + begin + # check for URI ipv6 [format] compatibility + db_url = ::URI::parse(the_url.to_s) + the_path = db_url.path.to_s + if the_path == "" + the_path = db_url.hostname.to_s + end + if ::RUBY_ENGINE == "jruby" + # jdbc:sqlite::memory: + # jdbc:postgresql://localhost/database?user=username + # jdbc:mysql://localhost/test?user=root&password=root + # jdbc:h2:mem: + # jdbc:hsqldb:mem:mymemdb + # jdbc:derby:memory:myDb;create=true + # jdbc:sqlserver://localhost;database=sequel_test;integratedSecurity=true + # jdbc:jtds:sqlserver://localhost/sequel_test;user=sequel_test;password=sequel_test + # jdbc:oracle:thin:user/password@localhost:1521:database + # jdbc:db2://localhost:3700/database:user=user;password=password; + # jdbc:sqlanywhere://localhost?DBN=Test;UID=user;PWD=password + case db_url.scheme.to_sym + when :sqlite, :sqlite3 + if ["/memory", "memory"].include?(the_path) + result = Sequel.connect("jdbc:sqlite::memory:") + else + result = Sequel.connect("jdbc:sqlite:" + the_path) + end + when :mysql, :mysql2, :mariadb + result = Sequel.connect(("jdbc:mysql://" + db_url.hostname.to_s + "/" + db_url.path.to_s.split("/").last.to_s + "?user=" + db_url.user.to_s + "&password=" + db_url.password.to_s + "&serverTimezone=UTC")) + else + raise Exception, "As-yet unsupported adapter type --> Under Construction" + end + # + else + case db_url.scheme.to_sym + when :sqlite, :sqlite3 + if ["/memory", "memory"].include?(the_path) + result = Sequel.connect("sqlite::memory:") + else + result = Sequel.connect("sqlite://" + the_path) + end + else + # auto-forward to mysql2 gem if mysql:// scheme + if db_url.scheme.to_s == "mysql" + # + result = Sequel.connect({:adapter => "mysql2", :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)}) + # + else + # + result = Sequel.connect({:adapter => (db_url.scheme.to_s), :host => (db_url.hostname.to_s), :database => (db_url.path.to_s.split("/").last), :user => (db_url.user.to_s), :password => (db_url.password.to_s)}) + # + end + end + end + result = ::GxG::Database::Database.new(result, options.merge({:url => (db_url)})) + rescue => the_error + log_error({:error => the_error, :parameters => {:url => (the_url), :options => (options)}}) + end + result + end + # External Authority (Such as LDAP) + class ExternalAuthority + def alive?() + true + end + end + # + class LDAPAuthority < ::GxG::Database::ExternalAuthority + # + def initialize(settings={}) + # Settings: {:host => "localhost", :port => 389, :base_dn => "cn=admin, dc=my-domain, dc=com", :password => "secret-word"} + @active = false + @connector = ::Net::LDAP.new + @connector.host = ::TCPSocket.getaddress(settings[:host].to_s) + @connector.port = settings[:port].to_s.to_i + @connector.auth settings[:base_dn].to_s, settings[:password].to_s + if @connector.bind + @active = true + else + raise "Unable to bind to LDAP host." + end + self + end + # + def open?() + @active + end + # + end + # Database class proper + class Database + def self.connect(the_url, options={}) + ::GxG::Database::connect(the_url, options) + end + # + def self.element_tables() + { + :unspecified => nil, + :element_boolean => [::TrueClass, ::FalseClass, ::NilClass], + :element_integer => [::Integer], + :element_float => [::Float], + :element_bigdecimal => [::BigDecimal], + :element_datetime => [::DateTime], + :element_text => [::String], + :element_binary => [::GxG::ByteArray], + :element_array => [::Array], + :element_hash => [::Hash] + } + end + # + def self.valid_field_classes() + [::TrueClass, ::FalseClass, ::NilClass, ::Integer, ::Float, ::BigDecimal, ::DateTime, ::String, ::GxG::ByteArray] + end + # + def self.element_table_index(the_key=:unspecified) + (::GxG::Database::Database::element_tables().keys.index(the_key.to_sym) || 0) + end + # + def self.element_table_by_index(the_index=0) + (::GxG::Database::Database::element_tables().keys[(the_index)] || :unspecified) + end + # + def self.element_table_for_instance(the_instance=nil) + # Review : rewrite - in use + result = :unspecified + # + table = ::GxG::Database::Database::element_tables() + table.keys.each do |the_key| + unless the_key == :unspecified + if the_instance.is_any?(table[(the_key)]) + result = the_key + break + end + end + end + # + result + end + # + def db_migration() + # Review : Consider adding support for a ::Symbol class as well. (ease-of-use implications later) + # Internal mapping of the database format + # Datatypes - See: http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html + # Column Options - See: http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-add_column + # See also : http://ruby-doc.org/core-2.2.0/Array.html#method-i-pack + { + :element_text => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)} + }, + :text_page => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :content => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099} + }, + :element_binary => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)} + }, + :binary_page => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :content => {:type => :blob, :options => {:size => 65536}, :bytes => 65536} + }, + :element_array => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :constraint => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :array_elements => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259}, + :element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)}, + :element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)}, + :element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :element_datetime => {:type => :datetime, :options => {}, :bytes => 16}, + :time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40} + }, + :element_hash => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :format => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :title => {:type => :varchar, :options => {:size => 259, :null => false, :default => "Untitled"}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :hash_properties => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :parent_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :property => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :ordinal => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :element => {:type => :varchar, :options => {:size => 259, :default => "element_boolean", :null => false}, :bytes => 259}, + :element_boolean => {:type => :integer, :options => {:size => 1, :default => -1}, :bytes => ([ -1 ].pack('l!').length)}, + :element_integer => {:type => :integer, :options => {}, :bytes => ([ -1 ].pack('l!').length)}, + :element_float => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :element_bigdecimal => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :element_datetime => {:type => :datetime, :options => {}, :bytes => 16}, + :time_offset => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :time_prior => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :time_after => {:type => :float, :options => {:default => 0.0}, :bytes => ([ 0.1 ].pack('d').length)}, + :length => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :element_text => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :element_text_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_binary_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_array_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_hash_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40} + }, + :element_locks => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :type => {:type => :varchar, :options => {:size => 40,:default => "", :null => false}, :bytes => 40}, + :reservation => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)} + }, + :trash => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)} + }, + :users => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :user_id => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259}, + :password_hash => {:type => :varchar, :options => {:size => 259, :null => false, :default => ""}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :roles => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :seo => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :user_roles => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :user_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :role_uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :permissions => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :element_table => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :elementid => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :file_permissions => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :file_id => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :credential => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :execute => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :rename => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :move => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :destroy => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :create => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :write => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :read => {:type => :integer, :options => {:default => 0}, :bytes => ([ -1 ].pack('l!').length)}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + }, + :formats => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :type => {:type => :varchar, :options => {:size => 40, :null => false, :default => "structure"}, :bytes => 40}, + :ufs => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099}, + :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256}, + :mime_types => {:type => :varchar, :options => {:size => 4099, :default => "", :null => false}, :bytes => 4099} + }, + :settings => { + :dbid => {:type => :integer, :options => {:unique => true, :primary_key => true, :auto_increment => true}, :bytes => ([ -1 ].pack('l!').length)}, + :uuid => {:type => :varchar, :options => {:size => 40, :default => "", :null => false}, :bytes => 40}, + :title => {:type => :varchar, :options => {:size => 259, :default => "", :null => false}, :bytes => 259}, + :version => {:type => :numeric, :options => {:default => ::BigDecimal.new("0.0")}, :bytes => 256} + } + } + end + # + def db_format(format_options={}) + # MySql stuff: + # ALTER TABLE tbl_name MAX_ROWS=1000000000 AVG_ROW_LENGTH=nnn; + # See: https://dev.mysql.com/doc/refman/5.0/en/table-size-limit.html + result = true + # + begin + if @connector.is_a?(::Sequel::Database) + case @scheme + when :sqlite + @connector.run("PRAGMA page_size=65536") + @connector.run("PRAGMA max_page_count=281474976710656") + @connector.run("PRAGMA encoding='UTF-8'") + when :mysql, :mysql2 + @connector.run("SET GLOBAL max_allowed_packet = 17825792;") + @connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';") + @connector.run("SET collation_connection = 'utf8_general_ci';") + when :postgres + # place holder + end + migration = db_migration() + migration.keys.each do |the_table| + unless @connector.table_exists?(the_table) + @connector.create_table(the_table) do + primary_key :dbid + end + end + used_bytes = 0 + # + existing_columns = @connector[(the_table)].columns + migration[(the_table)].keys.each do |the_column| + used_bytes += (migration[(the_table)][(the_column)][:bytes]).to_i + if existing_columns.include?(the_column) + # TODO: figure out a way to inspect columns for attributes -> detailed verification + else + # collation for mysql + if @scheme == :mysql + # Note: might have to specify :collate first, use -> {:collate => ""}.merge(migration[(the_table)][(the_column)][:options]) + if migration[(the_table)][(the_column)][:type] == :varchar + # migration[(the_table)][(the_column)][:options] = {:collate => "utf8_general_ci"}.merge(migration[(the_table)][(the_column)][:options]) + migration[(the_table)][(the_column)][:options][:collate] = "utf8_general_ci" + end + if migration[(the_table)][(the_column)][:type] == :blob + # migration[(the_table)][(the_column)][:options] = {:collate => "binary"}.merge(migration[(the_table)][(the_column)][:options]) + # Note: MySQL 5 doesn't like putting collation on blobs! (blows up) + # migration[(the_table)][(the_column)][:options][:collate] = "binary" + end + else + end + # do the deed + @connector.add_column((the_table), (the_column), (migration[(the_table)][(the_column)][:type]), (migration[(the_table)][(the_column)][:options])) + # + end + end + # + if @scheme == :mysql + # @connector.run("ALTER TABLE #{the_table.to_s} MAX_ROWS=18446744073709551616 AVG_ROW_LENGTH=#{used_bytes.to_s};") + end + # + end + else + raise Exception, "Invalid db connection" + end + rescue Exception => the_error + result = false + log_error({:error => the_error, :parameters => format_options}) + end + # + result + end + # + def db_formatted?() + result = true + # + begin + if @connector.is_a?(::Sequel::Database) + migration = db_migration() + migration.keys.each do |the_table| + unless @connector.table_exists?(the_table) + result = false + break + end + #columns + existing_columns = @connector[(the_table)].columns + migration[(the_table)].keys.each do |the_column| + unless existing_columns.include?(the_column) + result = false + break + end + end + # + unless (result) + break + end + end + else + raise Exception, "Invalid db connection" + end + rescue Exception => the_error + result = false + log_error({:error => the_error, :parameters => nil}) + end + # + result + end + # + def open?() + begin + @connector.synchronize do |connection| + @active = @connector.valid_connection?(connection) + end + rescue => the_error + @active = false + log_error({:error => the_error, :parameters => nil}) + end + @active + end + # + def close() + # place holder for now. + if @authority + @authority.db_unregister(self) + end + end + # + def connector() + @connector + end + # + # + def to_uri() + ::URI.parse(@url.to_s) + end + # + def inspect() + "" + end + # ### Permission Handling methods + def db_permissions + @base_permissions.clone + end + # ### Authority support + def db_register(the_db) + if the_db.is_a?(::GxG::Database::Database) + unless @db_list.find_index(the_db) + @db_list << the_db + end + end + end + # + def db_unregister(the_db) + if the_db.is_a?(::GxG::Database::Database) + if @db_list.find_index(the_db) + @db_list.delete_at(@db_list.find_index(the_db)) + end + end + end + # + def db_list() + result = [] + @db_list.each do |the_db| + result << the_db + end + result + end + # ### User, Role, and Group methods + # + def user_manifest(include_password=false) + result = [] + if @authority + result = @authority.user_manifest(include_password) + else + if self.alive? + if self.open? + begin + if include_password == true + dataset = @connector[:users].select(:uuid, :user_id, :password_hash, :version).all + else + dataset = @connector[:users].select(:uuid, :user_id, :version).all + end + dataset.each do |entry| + result << entry + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + end + end + end + result + end + # + def user_id_available?(the_user_id) + result = false + if @authority + result = @authority.user_id_available?(the_user_id) + else + if self.alive? + if self.open? + begin + if the_user_id.to_s.size <= 256 + if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count == 0 + result = true + end + else + raise ArgumentError, "Oversized UserID attempted: #{the_user_id}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:user_id => the_user_id}}) + end + end + end + end + result + end + # + def user_exist?(the_user_id) + result = false + if @authority + result = @authority.user_exist?(the_user_id) + else + if self.alive? + if self.open? + begin + if ::GxG::valid_uuid?(the_user_id) + if @connector[:users].filter({:uuid => (the_user_id.to_s)}).count > 0 + result = true + end + else + if the_user_id.to_s.size <= 256 + if @connector[:users].filter({:user_id => (the_user_id.to_s)}).count > 0 + result = true + end + else + raise ArgumentError, "Oversized UserID attempted: #{the_user_id}" + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:user_id => the_user_id}}) + end + end + end + end + result + end + # + def user_credential(the_user_id="", the_password="") + result = nil + if @authority + result = @authority.user_credential(the_user_id, the_password) + else + if self.alive? + if self.open? + begin + if the_user_id.to_s.size <= 256 + if the_password.to_s.size > 0 + record = @connector[:users].select(:uuid, :user_id, :password_hash).where({:user_id => (the_user_id.to_s)}).first + if record + salt = record[:password_hash].split("\t")[0].to_s + pbkdf2 = ::Base64.decode64(record[:password_hash].split("\t")[1].to_s) + tester = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password.to_s, salt, 1000, pbkdf2.length) + if pbkdf2 == tester + result = record[:uuid].to_sym + else + log_warn("Password attempt invalid for #{the_user_id}") + end + else + raise Exception, "UserID not found: #{the_user_id}" + end + else + raise Exception, "Empty password not supported" + end + else + raise Exception, "Oversized UserID attempted: #{the_user_id}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:user_id => the_user_id}}) + end + end + end + end + result + end + # + def user_create(the_user_id="", the_password="", use_credential=nil) + result = false + if @authority + result = @authority.user_create(the_user_id, the_password) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if the_user_id.to_s.size <= 256 + if the_password.to_s.size > 0 + if self.user_id_available?(the_user_id) + if ::GxG::valid_uuid?(use_credential) + the_uuid = use_credential.to_s + if (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0) + raise Exception, "That credential UUID is already in use" + end + else + the_uuid = ::GxG::uuid_generate() + while (@connector[:users].filter({:uuid => (the_uuid.to_s)}).count > 0) + the_uuid = ::GxG::uuid_generate() + end + end + salt = ::SecureRandom.base64(24) + pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(the_password, salt, 1000, 24) + the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t") + @connector[:users].insert({:uuid => (the_uuid), :user_id => (the_user_id.to_s), :password_hash => (the_pass_hash)}) + result = true + else + raise Exception, "UserID not available: #{the_user_id}" + end + else + raise ArgumentError, "Empty password not supported" + end + else + raise ArgumentError, "Oversized UserID attempted: #{the_user_id}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:user_id => the_user_id}}) + end + end + end + end + end + result + end + # + def user_update(credential=nil, user_id=nil) + result = false + if @authority + result = @authority.user_update(credential, user_id) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(credential) + if self.user_id_available?(user_id.to_s) + record = @connector[:users].filter({:uuid => (credential.to_s)}).first + if record + @connector[:users].filter({:uuid => (credential.to_s)}).update({:user_id => (user_id), :version => (record[:version] + 0.0001)}) + result = true + else + raise Exception, "User record not found" + end + else + log_warn("UserID is not available: #{user_id.to_s}") + end + else + raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => credential, :user_id => user_id}}) + end + end + end + end + end + result + end + # + def user_set_password(credential=nil, old_password="", new_password="") + result = false + if @authority + result = @authority.user_set_password(credential, old_password, new_password) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(credential) + if new_password.to_s.size > 0 + if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0 + record = @connector[:users].filter({:uuid => (credential.to_s)}).first + if record + if self.user_credential(record[:user_id], old_password).to_s == credential.to_s + salt = ::SecureRandom.base64(24) + pbkdf2 = ::OpenSSL::PKCS5::pbkdf2_hmac_sha1(new_password, salt, 1000, 24) + the_pass_hash = [salt, ::Base64.strict_encode64(pbkdf2)].join("\t") + @connector[:users].filter({:uuid => (credential.to_s)}).update({:password_hash => (the_pass_hash), :version => (BigDecimal(record[:version].to_s) + 0.0001)}) + result = true + else + log_warn("Invalid password change attempted for #{credential}") + end + else + raise Exception, "Could not retrieve user record: #{credential}" + end + else + raise Exception, "No user with that credential exists" + end + else + raise ArgumentError, "Empty password not supported" + end + else + raise ArgumentError, "Invalid credential argument: #{credential}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => credential}}) + end + end + end + end + end + result + end + # + def user_destroy(credential=nil) + result = false + if @authority + result = @authority.user_destroy(credential) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(credential) + if @connector[:users].filter({:uuid => (credential.to_s)}).count > 0 + the_list = self.db_list() + the_list << (self) + the_list.each do |the_db| + the_db.connector()[:permissions].filter({:credential => (credential.to_s)}).delete + the_db.connector()[:user_roles].filter({:user_uuid => (credential.to_s)}).delete + end + @connector[:users].filter({:uuid => (credential.to_s)}).delete + result = true + end + else + raise ArgumentError, "Invalid credential argument: #{credential}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => credential}}) + end + end + end + end + end + result + end + # + def user_fetch(search_options={}) + result = nil + if @authority + result = @authority.user_fetch(search_options) + else + if self.alive? + if self.open? + begin + result = @connector[:users].filter(search_options).first + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential)}}) + end + end + end + end + result + end + # + def user_roles(credential=nil) + result = [] + if @authority + result = @authority.user_roles(credential) + else + if self.alive? + if self.open? + begin + if ::GxG::valid_uuid?(credential) + memberships = @connector[:user_roles].select(:role_uuid).where({:user_uuid => (credential.to_s)}) + pull_list = [] + memberships.each do |record| + unless pull_list.find_index(record[:role_uuid].to_sym) + pull_list << record[:role_uuid].to_sym + end + end + pull_list.each do |role_uuid| + record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}) + if record + result << {:credential => (role_uuid), :title => (record[:title]), :seo => (record[:seo])} + end + end + else + raise ArgumentError, "You must supply a valid user credential (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential)}}) + end + end + end + end + result + end + # + def role_manifest() + result = [] + # + if @authority + result = @authority.roles() + else + if self.alive? + if self.open? + @connector[:roles].all.each do |record| + result << {:uuid => record[:uuid].to_s.to_sym, :title => record[:title], :seo => record[:seo], :version => BigDecimal(record[:version].to_s)} + end + end + end + end + # + result + end + # + def role_exist?(credential=nil) + result = nil + if @authority + result = @authority.role_exist?(credential) + else + if self.alive? + if self.open? + if ::GxG::valid_uuid?(credential) + if @connector[:roles].filter({:uuid => (credential.to_s)}).count > 0 + result = true + end + else + if @connector[:roles].filter({:title => (credential.to_s)}).count > 0 + result = true + end + end + end + end + end + result + end + # + def role_create(title="", seo="") + result = nil + if @authority + result = @authority.role_create(title,seo) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + # + if title.to_s.size <= 256 + # + if title.to_s.size == 0 + raise ArgumentError, "You MUST supply a title for the role." + end + unless seo.to_s.size <= 256 + raise ArgumentError, "Oversized SEO attempted (256 Character Limit): #{seo}" + end + if seo.to_s.size == 0 + seo = title.downcase.gsub(" ","_")[0..255] + end + the_uuid = ::GxG::uuid_generate() + while (@connector[:roles].filter({:uuid => (the_uuid.to_s)}).count > 0) + the_uuid = ::GxG::uuid_generate() + end + @connector[:roles].insert({:uuid => (the_uuid.to_s), :title => (title.to_s), :seo => (seo.to_s)}) + result = the_uuid.to_sym + else + raise ArgumentError, "Oversized title attempted (256 Character Limit): #{title}" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:title => (title), :seo => (seo)}}) + end + end + end + end + end + result + end + # + def role_update(role_uuid=nil, data={}) + # data: :title, :seo + result = false + if @authority + result = @authority.role_update(role_uuid,data) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + unless data.is_a?(::Hash) + raise Exception, "Update data must be passed in a Hash" + end + if ::GxG::valid_uuid?(role_uuid) + record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first + if record + the_title = (data[:title] || record[:title].to_s) + the_seo = (data[:seo] || the_title.downcase.gsub(" ", "_")) + # + @connector[:roles].filter({:uuid => (role_uuid.to_s)}).update({:title => (the_title), :seo => (the_seo), :version => (BigDecimal(record[:version].to_s) + 0.0001)}) + result = true + else + raise Exception, "Attempted to update a non-existent role: #{role_uuid}" + end + else + raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :data => (data)}}) + end + end + end + end + end + result + end + # + def role_destroy(role_uuid=nil) + result = false + if @authority + result = @authority.role_destroy(role_uuid) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(role_uuid) + # cannot destroy if still linked as group's default role + record = @connector[:roles].filter({:uuid => (role_uuid.to_s)}).first + if record + the_list = self.db_list() + the_list << (self) + the_list.each do |the_db| + the_db.connector()[:permissions].filter({:credential => (role_uuid.to_s)}).delete + the_db.connector()[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).delete + end + @connector[:roles].filter({:uuid => (role_uuid.to_s)}).delete + result = true + end + else + raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid)}}) + end + end + end + end + end + result + end + # + def role_add_user(role_uuid=nil, credential=nil) + result = false + if @authority + result = @authority.role_add_user(role_uuid, credential) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(role_uuid) + if ::GxG::valid_uuid?(credential) + if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count == 0 + @connector[:user_roles].insert({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}) + end + result = true + else + raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)" + end + else + raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}}) + end + end + end + end + end + result + end + # + def role_remove_user(role_uuid=nil, credential=nil) + result = false + if @authority + result = @authority.role_remove_user(role_uuid, credential) + else + if self.alive? + if self.open? + if self.db_permissions()[:write] + begin + if ::GxG::valid_uuid?(role_uuid) + if ::GxG::valid_uuid?(credential) + if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0 + @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).delete + end + result = true + else + raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)" + end + else + raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}}) + end + end + end + end + end + result + end + # + def role_member?(role_uuid=nil, credential=nil) + result = false + if @authority + result = @authority.role_member?(role_uuid, credential) + else + if self.alive? + if self.open? + begin + if ::GxG::valid_uuid?(role_uuid) + if ::GxG::valid_uuid?(credential) + if @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s), :user_uuid => (credential.to_s)}).count > 0 + result = true + end + else + raise ArgumentError, "You must specify a valid user credential UUID (as a String or Symbol)" + end + else + raise ArgumentError, "You must specify a valid role UUID (as a String or Symbol)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:role_uuid => (role_uuid), :credential => (credential)}}) + end + end + end + end + result + end + # + def role_members(role_uuid=nil) + result = [] + if @authority + result = @authority.role_members(role_uuid) + else + if self.alive? + if self.open? + if GxG::valid_uuid?(role_uuid) + @connector[:user_roles].filter({:role_uuid => (role_uuid.to_s)}).each do |the_record| + unless result.include?(the_record[:user_uuid].to_s.to_sym) + result << the_record[:user_uuid].to_s.to_sym + end + end + end + end + end + end + result + end + # + def role_fetch(search_options={}) + result = nil + if @authority + result = @authority.role_fetch(search_options) + else + if self.alive? + if self.open? + result = @connector[:roles].filter(search_options).first + end + end + end + result + end + # + # ### Raw Permission Handling methods + def element_permissions(table=:unspecified, dbid=0, options={}) + result = [] + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "dbid must be specified with a Integer" + end + unless options.is_a?(::Hash) + raise ArgumentError, "options must be specified with a Hash" + end + credential = options[:credential] + if credential + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)" + end + end + if table.is_a?(::Integer) + table = ::GxG::Database::Database::element_table_by_index(table) + end + # Review : change in permission policy + # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + if [:element_array, :element_hash].include?(table) + if credential + list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}) + else + list = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)}) + end + list.each do |record| + # [:execute, :rename, :move, :destroy, :create, :write, :read] + entry = {:credential => (record[:credential].to_sym), :table => (record[:element_table].to_sym), :dbid => (record[:elementid]), :permissions => {}} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if record[(the_permission)] == 1 + entry[:permissions][(the_permission)] = true + else + entry[:permissions][(the_permission)] = false + end + end + result << entry + end + else + raise ArgumentError, "Invalid table specified" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}}) + end + result + end + # + def revoke_element_permissions(table=:unspecified, dbid=0, credential=nil) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "dbid must be specified with a Integer" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)" + end + if table.is_a?(::Integer) + table = ::GxG::Database::Database::element_table_by_index(table) + end + # Review : change in permission policy + # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + if [:element_array, :element_hash].include?(table) + @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete + result = true + else + raise ArgumentError, "Invalid table specified" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}}) + end + result + end + # + # + def element_permissions_manifest(table=:unspecified, dbid=0, options={}) + result = [] + self.element_permissions(table,dbid,options).each do |entry| + the_record = {:credential => nil, :permissions => {}, :details => {}} + # + the_record[:credential] = entry[:credential] + the_record[:permissions] = entry[:permissions] + if self.role_exist?(entry[:credential]) + role = self.role_fetch({:uuid => entry[:credential].to_s}) + group = self.group_fetch({:uuid => role[:group_uuid].to_s}) + the_record[:details][:role_title] = role[:title] + the_record[:details][:group] = group[:uuid].to_s.to_sym + the_record[:details][:group_title] = group[:title] + else + if self.user_exist?(entry[:credential]) + user = self.user_fetch({:uuid => entry[:credential].to_s}) + the_record[:details][:user_title] = user[:user_id] + else + log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}") + the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !" + end + end + result << the_record + end + result + end + # + def element_permissions_by_uuid(the_uuid=nil,options={}) + result = [] + begin + if self.alive? + if self.open? + found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result = self.element_permissions_manifest(:element_hash,(found[:dbid]),options) + else + found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result = self.element_permissions_manifest(:element_array,(found[:dbid]),options) + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}}) + end + result + end + # + def revoke_permissions_by_uuid(the_uuid=nil, credential=nil) + result = false + begin + if self.alive? + if self.open? + found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result = self.revoke_element_permissions(:element_hash,(found[:dbid]),credential) + else + found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result = self.revoke_element_permissions(:element_array,(found[:dbid]),credential) + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}}) + end + result + end + # + def assign_element_permission(table=:unspecified, dbid=0, credential=nil, permissions={}) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "dbid must be specified with a Integer" + end + unless permissions.is_a?(::Hash) + raise ArgumentError, "permissions must be specified with a Hash" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + if table.is_a?(::Integer) + table = ::GxG::Database::Database::element_table_by_index(table) + end + # Review : change in permission policy + # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + if [:element_array, :element_hash].include?(table) + record = @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).first + if record + # update + the_update = {:execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if record[(the_permission)] == 1 + the_update[(the_permission)] = 1 + end + end + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if permissions[(the_permission)].is_a?(::TrueClass) + the_update[(the_permission)] = 1 + end + if permissions[(the_permission)].is_a?(::FalseClass) + the_update[(the_permission)] = 0 + end + end + the_update[:version] = record[:version] + 0.0001 + @connector[:permissions].filter({:dbid => (record[:dbid])}).update(the_update) + else + # create + new_record = {:credential => (credential.to_s), :element_table => (table.to_s), :elementid => (dbid), :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if permissions[(the_permission)] + new_record[(the_permission)] = 1 + end + end + @connector[:permissions].insert(new_record) + end + result = true + else + raise ArgumentError, "Invalid table specified" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential), :permissions => (permissions)}}) + end + result + end + # + def extend_element_permissions(source_table=:unspecified, source_dbid=0, dest_table=:unspecified, dest_dbid=0) + permissions = self.element_permissions(source_table, source_dbid) + permissions.each do |entry| + if [:persisted_array, :persisted_hash].include?(dest_table) + self.assign_element_permission(dest_table, dest_dbid, entry[:credential], entry[:permissions]) + end + end + end + # + def remove_element_permissions(table=:unspecified, dbid=0, options={}) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "dbid must be specified with a Integer" + end + unless options.is_a?(::Hash) + raise ArgumentError, "permissions must be specified with a Hash" + end + credential = options[:credential] + if credential + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + end + if table.is_a?(::Integer) + table = ::GxG::Database::Database::element_table_by_index(table) + end + # Review : change in permission policy + # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + if [:element_array, :element_hash].include?(table) + if credential + @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid), :credential => (credential.to_s)}).delete + else + @connector[:permissions].filter({:element_table => (table.to_s), :elementid => (dbid)}).delete + end + result = true + else + raise ArgumentError, "Invalid table specified" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :options => (options)}}) + end + result + end + # ### Effective Permission Handling methods + # ### VFS File permission mappings + def vfs_permission_exist?(file_id=nil, credential=nil) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "file_id must be specified with a String" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + # + if @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).count > 0 + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # + def vfs_permission(file_id=nil, credential=nil) + result = [] + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "dbid must be specified with a String" + end + if credential + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "This option requires a valid credential UUID (as a String or Symbol)" + end + end + # + if credential + list = @connector[:file_permissions].filter({:file_id => (file_id.to_s), :credential => (credential.to_s)}) + else + list = @connector[:file_permissions].filter({:file_id => (file_id.to_s)}) + end + list.each do |record| + # [:execute, :rename, :move, :destroy, :create, :write, :read] + entry = {:credential => (record[:credential].to_sym), :permissions => {}} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if record[(the_permission)] == 1 + entry[:permissions][(the_permission)] = true + else + entry[:permissions][(the_permission)] = false + end + end + result << entry + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # + def vfs_permission_manifest(file_id=nil, credential=nil) + result = [] + self.vfs_permission(file_id, credential).each do |entry| + the_record = {:credential => nil, :permissions => {}, :details => {}} + # + the_record[:credential] = entry[:credential] + the_record[:permissions] = entry[:permissions] + if self.role_exist?(entry[:credential]) + role = self.role_fetch({:uuid => entry[:credential].to_s}) + group = self.group_fetch({:uuid => role[:group_uuid].to_s}) + the_record[:details][:role_title] = role[:title] + the_record[:details][:group] = group[:uuid].to_s.to_sym + the_record[:details][:group_title] = group[:title] + else + if self.user_exist?(entry[:credential]) + user = self.user_fetch({:uuid => entry[:credential].to_s}) + the_record[:details][:user_title] = user[:user_id] + else + log_warn("Invalid User Credential Detected: #{entry[:credential].to_s.to_sym.inspect}") + the_record[:details][:user_title] = "Invalid User Credential #{entry[:credential].to_s.to_sym.inspect} !" + end + end + result << the_record + end + result + end + # + def effective_vfs_permission(file_id=nil, credential=nil) + result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "file_id must be specified with a String" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + # ### VFS Permissions + raw_permissions = [] + credentials = ["00000000-0000-4000-0000-000000000000"] + if self.user_exist?(credential) + credentials << credential + role_list = self.user_roles(credential) + role_list.each do |entry| + credentials << entry[:credential] + end + # + else + if self.role_exist?(credential) + credentials << credential + else + raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID" + end + end + credentials.each do |entry| + manifest = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => (entry.to_s)}) + manifest.each do |record| + new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if record[(the_permission)] == 1 + new_permission[(the_permission)] = true + end + end + raw_permissions << new_permission + end + end + raw_permissions.each do |entry| + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if entry[(the_permission)] + result[(the_permission)] = true + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # + def create_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "file_id must be specified with a String" + end + unless permission.is_a?(::Hash) + raise ArgumentError, "permission must be specified with a Hash" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + # + record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first + if record + update = true + dbid = record.delete(:dbid) + else + update = false + dbid = nil + record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0} + end + permission.keys.each do |the_permission_key| + if permission[(the_permission_key)] == true + record[(the_permission_key)] = 1 + else + record[(the_permission_key)] = 0 + end + end + if update == true + @connector[:file_permissions].filter({:dbid => dbid}).update(record) + else + @connector[:file_permissions].insert(record) + end + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # + def update_vfs_permission(file_id=nil, credential=nil, permission={:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false}) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "file_id must be specified with a String" + end + unless permission.is_a?(::Hash) + raise ArgumentError, "permission must be specified with a Hash" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + # + record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first + if record + update = true + dbid = record.delete(:dbid) + else + update = false + dbid = nil + record = {:file_id => file_id.to_s, :credential => credential.to_s, :execute => 0, :rename => 0, :move => 0, :destroy => 0, :create => 0, :write => 0, :read => 0} + # Review : should we update the version ?? + end + permission.keys.each do |the_permission_key| + if permission[(the_permission_key)] == true + record[(the_permission_key)] = 1 + else + record[(the_permission_key)] = 0 + end + end + if update == true + @connector[:file_permissions].filter({:dbid => dbid}).update(record) + else + @connector[:file_permissions].insert(record) + end + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # + def destroy_vfs_permission(file_id=nil, credential=nil) + result = false + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless file_id.is_a?(::String) + raise ArgumentError, "file_id must be specified with a String" + end + # ### Review : make more efficient + if credential + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + record = @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).first + if record + @connector[:file_permissions].filter({:file_id => file_id.to_s, :credential => credential.to_s}).delete + end + else + record = @connector[:file_permissions].filter({:file_id => file_id.to_s}).first + if record + @connector[:file_permissions].filter({:file_id => file_id.to_s}).delete + end + end + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_id => (file_id), :credential => (credential)}}) + end + result + end + # ### DB Objects + def effective_element_permission(table=:unspecified, dbid=0, credential=nil) + result = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + begin + unless self.alive? + raise Exception, "Database inactive" + end + unless self.open? + raise Exception, "Database not available" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "dbid must be specified with a Integer" + end + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must specify a valid credential UUID (as a String or Symbol)" + end + if table.is_a?(::Integer) + table = ::GxG::Database::Database::element_table_by_index(table) + end + # Review : change in permission policy + # if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + if [:element_array, :element_hash].include?(table) + # is credential a user or role? + raw_permissions = [] + # Include PUBLIC credential + credentials = ["00000000-0000-4000-0000-000000000000"] + # + if self.user_exist?(credential) + credentials << credential + role_list = self.user_roles(credential) + role_list.each do |entry| + credentials << entry[:credential] + end + # + else + if self.role_exist?(credential) + credentials << credential + else + raise ArgumentError, "Invalid credential. It is neither a user nor a role UUID" + end + end + credentials.each do |entry| + manifest = @connector[:permissions].filter({:credential => (entry.to_s), :element_table => (table.to_s), :elementid => (dbid)}) + manifest.each do |record| + new_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if record[(the_permission)] == 1 + new_permission[(the_permission)] = true + end + end + raw_permissions << new_permission + end + end + permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + raw_permissions.each do |entry| + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if entry[(the_permission)] + permission[(the_permission)] = true + end + end + end + db_limits = self.db_permissions() + [:execute, :rename, :move, :destroy, :create, :write, :read].each do |the_permission| + if db_limits[(the_permission)] and permission[(the_permission)] + result[(the_permission)] = true + end + end + # + else + raise ArgumentError, "Invalid table specified" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid), :credential => (credential)}}) + end + result + end + # + def effective_uuid_permission(uuid=nil, credential=nil) + result = nil + begin + if self.alive? + if self.open? + address = nil + record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])} + else + record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => self, :table => :element_array, :dbid => (record[:dbid])} + end + end + if address + result = self.effective_element_permission(address[:table], address[:dbid], credential) + else + # ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures. + already_checked = [(self)] + ::GxG::DB[:roles].each_pair do |the_db_role, the_database| + unless already_checked.include?(the_database) + record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])} + result = the_database.effective_element_permission(address[:table], address[:dbid], credential) + break + else + record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])} + result = the_database.effective_element_permission(address[:table], address[:dbid], credential) + break + end + end + already_checked << the_database + end + end + # + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential)}}) + end + result + end + # ### Data Handling methods + # + def element_exists?(the_table=:unspecified, the_dbid=0) + result = false + if the_table.is_a?(::Integer) + the_table = ::GxG::Database::Database::element_table_by_index(the_table) + end + if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(the_table) + begin + record = @connector[(the_table)].select(:dbid).where({:dbid => (the_dbid)}).first + if record + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}}) + end + end + result + end + # + def element_retrieve(the_table=:unspecified, the_filter={}, options={}) + result = [] + begin + if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary,:binary_page, :element_array, :element_hash].include?(the_table) + # + if the_filter.is_a?(::Hash) + if options[:order] + raw_result = @connector[(the_table)].filter(the_filter).order(options[:order]) + else + raw_result = @connector[(the_table)].filter(the_filter) + end + # Review : UTF-8 Encoding Error Fix + if options[:single_record] == true + result = raw_result.first + # utf8 fix + if the_table == :text_page + if result[:content].is_a?(::String) + if result[:content].base64? + result[:content] = result[:content].decode64 + end + end + end + # + else + # utf8 fix + raw_result.each do |the_record| + if the_record[:content].is_a?(::String) + if the_record[:content].base64? + the_record[:content] = the_record[:content].decode64 + end + end + result << the_record + end + # result = raw_result + end + # + else + raise ArguementError, "You MUST provide a record selection filter as a Hash. " + end + else + raise ArgumentError, "The table specified is unknown - Check your code." + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :options => options}}) + end + result + end + # + def element_create(the_table=:unspecified, the_record=nil) + # Review : rewrite - in use + result = nil + begin + unless the_table.is_a?(::Symbol) + raise ArgumentError, "You MUST provide the table specifier as a Symbol." + end + unless the_record.is_a?(::Hash) + raise ArgumentError, "You MUST provide a record in the form of a Hash." + end + if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table) + # Review : UTF8 fix + begin + result = @connector[(the_table)].insert(the_record) + rescue Exception + if the_table == :text_page + the_record[:content] = the_record[:content].encode64 + end + result = @connector[(the_table)].insert(the_record) + end + else + raise ArgumentError, "The table specified is unknown - Check your code." + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (the_table), :record => (the_record)}}) + end + result + end + # + def element_update(the_table=:unspecified, the_filter={}, the_record=nil) + result = false + begin + unless the_table.is_a?(::Symbol) + raise ArgumentError, "You MUST provide the table specifier as a Symbol." + end + unless the_filter.is_a?(::Hash) + raise ArgumentError, "You MUST provide a selection filter in the form of a Hash." + end + unless the_record.is_a?(::Hash) + raise ArgumentError, "You MUST provide a record in the form of a Hash." + end + if [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :text_page, :element_binary, :binary_page, :element_array, :element_hash].include?(the_table) + # Review : shim to eliminate UTF-8 encoding errors + begin + @connector[(the_table)].filter(the_filter).update(the_record) + rescue Exception + if the_table == :text_page + the_record[:content] = the_record[:content].encode64 + end + @connector[(the_table)].filter(the_filter).update(the_record) + end + result = true + else + raise ArgumentError, "The table specified is unknown - Check your code." + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => the_table, :filter => the_filter, :record => the_record}}) + end + result + end + # + def structure_detach(the_uuid=nil) + if ::GxG::valid_uuid?(the_uuid) + record = self.structural_link(the_uuid) + if record + record[:database].connector()[(record[:table])].filter({:dbid => record[:dbid]}).update({:element => "element_boolean", :element_boolean => -1, :element_hash_uuid => "", :element_array_uuid => ""}) + true + else + false + end + else + false + end + end + # + def structural_link(the_uuid=nil) + # Review : rewrite - in use + result = nil + if ::GxG::valid_uuid?(the_uuid) + search_queue = [(self)] + ::GxG::DB[:roles].each_pair do |the_role, the_database| + unless search_queue.include?(the_database) + search_queue << the_database + end + end + search_queue.each do |the_database| + found = the_database.connector()[:hash_properties].filter({:element_hash_uuid => the_uuid.to_s}).first + if found + result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]} + break + end + found = the_database.connector()[:hash_properties].filter({:element_array_uuid => the_uuid.to_s}).first + if found + result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]} + break + end + found = the_database.connector()[:array_elements].filter({:element_hash_uuid => the_uuid.to_s}).first + if found + result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]} + break + end + found = the_database.connector()[:array_elements].filter({:element_array_uuid => the_uuid.to_s}).first + if found + result = {:database => the_database, :table => :hash_properties, :dbid => found[:dbid]} + break + end + end + end + result + end + # + def structure_attached?(the_uuid=nil) + # Review : rewrite - in use + result = false + record = self.structural_link(the_uuid) + if record + result = true + end + result + end + # + def element_in_use?(db_table=:unspecified, dbid = 0) + # Review : rewrite - in use for now. + # Review : rewrite - todo (alter to reflect the new architecture : perhaps limit to structures only) + unless db_table.is_any?(::Integer, ::Symbol) + raise ArgumentError, "Table needs to be specified as a Symbol, or a table code Integer" + end + unless dbid.is_a?(::Integer) + raise ArgumentError, "Record ID needs to be specified with an Integer" + end + result = false + begin + if db_table == :element_hash || db_table == :element_array + # Review : make more efficient in later revs of this. + the_record = @connector[(db_table)].filter({:dbid => (dbid)}).select(:uuid).first + if the_record + if @connector[:hash_properties].filter({:element_hash_uuid => the_record[:uuid]}).count > 0 || @connector[:array_elements].filter({:element_array_uuid => the_record[:uuid]}).count > 0 + result = true + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table_code => (db_table), :dbid => (dbid)}}) + end + result + end + # + def element_parent_address(the_table=:unspecified, the_dbid=0) + result = nil + if self.element_in_use?(the_table,the_dbid) + begin + link = @connector[:hash_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first + if link + record = @connector[:element_hash].select(:dbid).where({:uuid => link[:parent_uuid]}).first + if record + result = {:table => :element_hash, :dbid => (record[:dbid])} + end + else + link = @connector[:array_links].select(:parent_uuid).where({:element => (::GxG::Database::Database::element_table_index(the_table)), :elementid => (the_dbid)}).first + if link + record = @connector[:element_array].select(:dbid).where({:uuid => link[:parent_uuid]}).first + if record + result = {:table => :element_array, :dbid => (record[:dbid])} + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}}) + end + end + result + end + # + def element_manifest(the_table=:unspecified, the_dbid=0, everything=false) + # Review : rewrite - in use + # returns an Array of tables and dbid pairs and sub-item pairs if they exist + result = [] + # + begin + unless the_dbid > 0 + raise ArgumentError, "dbid specified must be greater than zero" + end + case the_table + when :element_text + the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first + if the_record + result << {:table => :element_text, :dbid => the_record[:dbid]} + if everything + the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal) + the_pages.each do |entry| + result << {:table => :text_page, :dbid => entry[:dbid]} + end + end + end + when :element_binary + the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => (the_dbid)}).first + if the_record + result << {:table => :element_binary, :dbid => the_record[:dbid]} + if everything + the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal) + the_pages.each do |entry| + result << {:table => :binary_page, :dbid => entry[:dbid]} + end + end + end + when :element_array, :element_hash + record_db = [{:table => the_table, :dbid => the_dbid}] + while record_db.size > 0 do + item = record_db.shift + case item[:table] + when :element_text + the_record = @connector[:element_text].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first + if the_record + result << {:table => :element_text, :dbid => the_record[:dbid]} + if everything + the_pages = @connector[:text_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal) + the_pages.each do |entry| + result << {:table => :text_page, :dbid => entry[:dbid]} + end + end + end + when :element_binary + the_record = @connector[:element_binary].select(:uuid, :dbid).where({:dbid => item[:dbid]}).first + if the_record + result << {:table => :element_binary, :dbid => the_record[:dbid]} + if everything + the_pages = @connector[:binary_page].select(:dbid, :ordinal).where({:parent_uuid => (the_record[:uuid])}).order(:ordinal) + the_pages.each do |entry| + result << {:table => :binary_page, :dbid => entry[:dbid]} + end + end + end + when :element_array + result << {:table => item[:table], :dbid => item[:dbid]} + array_record = @connector[:element_array].select(:uuid).where({:dbid => item[:dbid]}).first + if array_record + @connector[:array_elements].filter({:parent_uuid => (array_record[:uuid])}).order(:ordinal).each do |entry| + if entry[:element_text_uuid].size > 0 + the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first + if the_temp_record + record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_binary_uuid].size > 0 + the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first + if the_temp_record + record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_array_uuid].size > 0 + the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first + if the_temp_record + record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_hash_uuid].size > 0 + the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first + if the_temp_record + record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]} + end + end + result << {:table => :array_elements, :dbid => entry[:dbid]} + end + end + when :element_hash + result << {:table => item[:table], :dbid => item[:dbid]} + hash_record = @connector[:element_hash].select(:uuid).where({:dbid => item[:dbid]}).first + if hash_record + @connector[:hash_properties].filter({:parent_uuid => (hash_record[:uuid])}).order(:ordinal).each do |entry| + if entry[:element_text_uuid].size > 0 + the_temp_record = @connector[:element_text].select(:dbid).where({:uuid => entry[:element_text_uuid]}).first + if the_temp_record + record_db << {:table => :element_text, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_binary_uuid].size > 0 + the_temp_record = @connector[:element_binary].select(:dbid).where({:uuid => entry[:element_binary_uuid]}).first + if the_temp_record + record_db << {:table => :element_binary, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_array_uuid].size > 0 + the_temp_record = @connector[:element_array].select(:dbid).where({:uuid => entry[:element_array_uuid]}).first + if the_temp_record + record_db << {:table => :element_array, :dbid => the_temp_record[:dbid]} + end + end + if entry[:element_hash_uuid].size > 0 + the_temp_record = @connector[:element_hash].select(:dbid).where({:uuid => entry[:element_hash_uuid]}).first + if the_temp_record + record_db << {:table => :element_hash, :dbid => the_temp_record[:dbid]} + end + end + result << {:table => :hash_properties, :dbid => entry[:dbid]} + end + end + else + the_record = @connector[(item[:table])].select(:dbid).where({:dbid => (item[:dbid])}).first + if the_record + result << {:table => item[:table], :dbid => item[:dbid]} + end + end + end + # + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (the_table), :dbid => (the_dbid)}}) + end + result + end + # + def element_destroy(credential=nil,table=:unspecified,dbid=0, options={}) + # Review : rewrite for new arch. + # Review : rewrite - in use + result = false + begin + if self.alive? + if self.open? + if self.db_permissions()[:write] + # if credential has :destroy permission on this element? + if ::GxG::valid_uuid?(credential) + if [:persisted_array, :persisted_hash].include?(table.to_s.to_sym) + go_ahead = self.effective_element_permission(table, dbid, credential)[:destroy] + else + go_ahead = true + end + # xxx Quick fix : BETA: clean all this up! + if go_ahead + # Review : DB overhaul / rewrite - allow delete if object has write lock in place? I think not. (return false) + # Note: BETA - bring back read-locks? it was 9x slower with them ... not sure. + elements = self.element_manifest(table, dbid, false) + elements.each do |entry| + # + unless self.element_in_trash?(entry[:table], entry[:dbid]) + @connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + end + # + #if @connector[:element_locks].filter({:type => "read", :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0 + # put in trash + #unless self.element_in_trash?(entry[:table], entry[:dbid]) + #@connector[:trash].insert({:element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + #end + #else + # delete now + #manifest = self.element_manifest(entry[:table], entry[:dbid], true) + #manifest.each do |item| + #@connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete + #@connector[(item[:table])].filter({:dbid => item[:dbid]}).delete + #end + #end + end + result = true + else + raise Exception, "You do not have permission to destory this element" + end + else + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential), :table => (table), :dbid => (dbid)}}) + end + result + end + # + def destroy_by_uuid(credential=nil, the_uuid=nil) + # Review : rewrite - in use + # element_destroy(credential=nil,table=:unspecified,dbid=0, options={}) + # .effective_uuid_permission(data["location"].to_s.to_sym,@credential) + result = false + begin + if self.alive? + if self.open? + if self.db_permissions()[:destroy] == true + if ::GxG::valid_uuid?(credential) + found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + self.element_destroy(credential, :element_hash,(found[:dbid])) + result = true + else + found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + self.element_destroy(credential, :element_array,(found[:dbid])) + result = true + end + end + else + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential), :uuid => (the_uuid)}}) + end + result + end + # + def element_byte_size(table=:unspecified, dbid=0) + # + result = 0 + begin + if self.alive? + if self.open? + migration = self.db_migration() + elements = self.element_manifest(table, dbid, true) + elements.each do |entry| + table_definition = migration[(entry[:table])] + if table_definition + table_definition.keys.each do |the_field| + result += table_definition[(the_field)][:bytes].to_i + end + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}}) + end + result + end + # + def byte_size_by_uuid(the_uuid=nil) + result = 0 + begin + if self.alive? + if self.open? + found = @connector[:element_hash].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result += self.element_byte_size(:element_hash,(found[:dbid])) + else + found = @connector[:element_array].select(:dbid, :uuid).filter({:uuid => the_uuid.to_s}).first + if found + result += self.element_byte_size(:element_array,(found[:dbid])) + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uuid => (the_uuid)}}) + end + result + end + # + def persistable?(the_object) + result = true + unless the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + if the_object.is_any?(::Hash, ::Array) + the_object.search do |item, selector, container| + if result + if ::GxG::Database::Database::element_table_for_instance(item) == :unspecified + result = false + break + end + end + nil + end + else + if ::GxG::Database::Database::element_table_for_instance(the_object) == :unspecified + result = false + end + end + end + result + end + # + def iterative_persist(old_root=nil, credential=nil, options={}) + # New PersistedArray, or PersistedHash interface: + # mount element: {:database => , :uuid => , :credential => , :delegate => nil/, :parent => nil/} + # create element: {:database => , :credential => , :delegate => nil/, :parent => nil/} + # + result = nil + begin + # db open? + # write permissions on db? + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You MUST provide a valid UUID as a credential." + end + unless old_root.is_any?(::Array, ::Hash) + raise ArgumentError, "You MUST provide either an Array or a Hash." + end + unless self.persistable?(old_root) + raise ArgumentError, "The object you are attempting to persist contains a non-persistable item." + end + if old_root.is_a?(::Array) + original_partner = ::GxG::Database::PersistedArray.new({:database => self, :credential => credential}, options) + original_partner.get_reservation() + self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner}) + # + end + if old_root.is_a?(::Hash) + original_partner = ::GxG::Database::PersistedHash.new({:database => self, :credential => credential}, options) + original_partner.get_reservation() + self.enforce_permission_policy({:action => :extend, :credential => credential, :source => original_partner, :destination => original_partner}) + end + if old_root.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) + # TODO: support subsuming already persisted objects. + end + paring_data = [{:parent => nil, :parent_selector => nil, :object => old_root, :partner => original_partner}] + children_of = Proc.new do |the_parent=nil| + list = [] + paring_data.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + parent_of = Proc.new do |the_parent=nil| + output = nil + paring_data.each do |entry| + if entry[:object].object_id == the_parent.object_id + output = entry + end + end + output + end + find_partner = Proc.new do |the_object| + found_partner = nil + paring_data.each do |the_record| + if the_record.is_a?(::Hash) + if the_record[:object].object_id == the_object.object_id + found_partner = the_record[:partner] + break + end + end + end + found_partner + end + # build paring data: + delegate_permission = nil + old_root.search do |the_value, the_selector, the_container| + if the_value.is_a?(::Array) + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedArray.new({:database => self, :credential => credential})} + else + if the_value.is_a?(::Hash) + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => ::GxG::Database::PersistedHash.new({:database => self, :credential => credential})} + else + paring_data << {:parent => the_container, :parent_selector => the_selector, :object => the_value, :partner => the_value} + end + end + end + # Assign objects to structure in order by parent / parent_selector + link_db = [(paring_data[0])] + while link_db.size > 0 + entry = link_db.shift + if entry.is_a?(::Hash) + if entry[:object].is_any?(::Array, ::Hash) + # get children and assign + children = children_of.call(entry[:object]) + children.each do |child| + entry[:partner][(child[:parent_selector])] = child[:partner] + if child[:partner].is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) + link_db << child + end + end + end + end + end + result = original_partner + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:object => old_root, :credential => credential}}) + end + result + end + # + def try_persist(the_object=nil, credential=nil, options={}) + result = nil + if the_object.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + result = the_object + else + begin + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a credential (36 character limit) as a String or Symbol" + end + unless options.is_a?(::Hash) + raise ArgumentError, "You must supply persisting options as a Hash" + end + table = ::GxG::Database::Database::element_table_for_instance(the_object) + if table == :unspecified + raise ArgumentError, "You provided an object that cannot be persisted." + end + if [:element_hash, :element_array].include?(table) + result = self.iterative_persist(the_object, credential, options) + else + result = the_object + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:object => the_object, :credential => credential, :options => options}}) + end + end + result + end + # + def retrieve_by_address(the_address={}, credential=nil, delegate=nil) + result = nil + begin + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + if self.alive? + if self.open? + address = {} + if the_address[:database] + unless the_address[:database] == self + raise ArgumentError, "Database mismatch. Call this method on database #{the_address[:database].inspect}" + end + address[:database] = the_address[:database] + else + address[:database] = self + end + unless ((the_address[:table]) and (the_address[:dbid].is_a?(::Integer))) + raise ArgumentError, "Invalid database address: #{the_address.inspect}" + end + if the_address[:table].is_a?(::Integer) + address[:table] = ::GxG::Database::Database::element_table_by_index(the_address[:table]) + else + address[:table] = the_address[:table] + end + address[:dbid] = the_address[:dbid] + unless self.element_in_trash?(address[:table], address[:dbid]) + if [:persisted_array, :persisted_hash].include?(address[:table]) + the_permission = address[:database].effective_element_permission(address[:table], address[:dbid], credential) + else + the_permission = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => true} + end + # Review ???? + # puts "Effective Permission: #{the_permission.inspect} for #{address[:table].inspect},#{address[:dbid].inspect}" + if the_permission[:read] == true + case address[:table] + when :element_array + record = address[:database].connector()[(address[:table].to_s.to_sym)].select(:uuid).where({:dbid => (address[:dbid])}).first + if record + result = ::GxG::Database::PersistedArray.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)}) + else + raise Exception, "Unable to retrieve PersistedArray" + end + when :element_hash + record = address[:database].connector()[:element_hash].select(:uuid, :format).where({:dbid => (address[:dbid])}).first + if record + result = ::GxG::Database::PersistedHash.new({:database => (address[:database]), :uuid => (record[:uuid].to_sym), :credential => (credential), :delegate => (delegate)}) + if result + if record[:format].to_s.size > 0 + result.set_format(record[:format].to_sym) + end + end + else + raise Exception, "Unable to retrieve PersistedHash" + end + else + raise Exception, "Invalid table specifier: #{address[:table].inspect}" + end + else + # At this point the idea is that if you don't have permissions to read it, you never see it + # raise Exception, "You don't have permission to retrieve this element" + end + # + end + # + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:address => (the_address), :credential => (credential), :delegate => (delegate)}}) + end + result + end + # + def retrieve_by_uuid(uuid=nil, credential=nil, delegate=nil) + result = nil + begin + if self.alive? + if self.open? + address = nil + record = @connector[:element_hash].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => self, :table => :element_hash, :dbid => (record[:dbid])} + else + record = @connector[:element_array].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => self, :table => :element_array, :dbid => (record[:dbid])} + end + end + if address + result = self.retrieve_by_address(address, credential, delegate) + else + # ### Review : Experimental Code - attempt to seemlessly integrate dbs of different roles into in-memory structures. + already_checked = [(self)] + ::GxG::DB[:roles].each_pair do |the_db_role, the_database| + unless already_checked.include?(the_database) + record = the_database.connector[:element_hash].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => the_database, :table => :element_hash, :dbid => (record[:dbid])} + result = the_database.retrieve_by_address(address, credential, delegate) + break + else + record = the_database.connector[:element_array].filter({:uuid => (uuid.to_s)}).first + if record + address = {:database => the_database, :table => :element_array, :dbid => (record[:dbid])} + result = the_database.retrieve_by_address(address, credential, delegate) + break + end + end + already_checked << the_database + end + end + # + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uuid => (uuid), :credential => (credential), :delegate => (delegate)}}) + end + result + end + # Element Write-Lock Handling methods + def reserve_element_locks(credential=nil, manifest=[], type=:read) + result = nil + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a valid credential UUID to reserve locks" + end + unless manifest.is_a?(::Array) + raise ArguementError, "You must supply an Array of tables and record ids to reserve for writing" + end + unless [:write, :read].include?(type) + raise ArgumentError, "You must specify a type of reservation: either :write or :read are supported" + end + if manifest.size > 0 + reservation_uuid = ::GxG::uuid_generate() + while @connector[:element_locks].filter({:reservation => (reservation_uuid.to_s)}).count > 0 + reservation_uuid = ::GxG::uuid_generate() + end + if type == :write + if self.db_permissions()[:write] + # Write-lock must be unique to the elements + manifest.each do |entry| + if @connector[:element_locks].filter({:type => (type.to_s), :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0 + self.release_element_locks(reservation_uuid) + # raise Exception, "A reservation already exists for element #{entry[:table]}:#{entry[:dbid]}" + # Note - just silently fail and return Nil - no reservation for you. + break + else + @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + end + end + # if write-reservation was made + if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).count > 0 + result = reservation_uuid.to_sym + end + end + else + # Read-lock reservation - can share elements + if self.db_permissions()[:write] + manifest.each do |entry| + @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + # + end + end + # Note - on a read-only db it will act as though it has a read-reservation since no deletions can be made. + result = reservation_uuid.to_sym + end + end + # + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential), :manifest => (manifest), :type => (type)}}) + end + result + end + # + def release_element_locks(reservation_uuid=nil) + result = false + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(reservation_uuid) + raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to release locks" + end + if self.db_permissions()[:write] + @connector[:element_locks].filter({:reservation => reservation_uuid.to_s}).delete + end + # on read-only db, no reservation was made so mission accomplished + result = true + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}}) + end + result + end + # + def reservation_add_element(reservation_uuid=nil, table=:unspecified, dbid=0) + result = false + # + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(reservation_uuid) + raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to add an element to the reserve locks" + end + record = @connector[:element_locks].select(:type, :reservation, :credential).where({:reservation => reservation_uuid.to_s}).first + if record + type = record[:type].to_sym + credential = record[:credential].to_sym + manifest = self.element_manifest(table, dbid, false) + if type == :write + if self.db_permissions()[:write] + # + manifest.each do |entry| + if @connector[:element_locks].filter({:type => (type.to_s), :reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count == 0 + @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + end + end + result = true + end + else + # read + if self.db_permissions()[:write] + # + manifest.each do |entry| + @connector[:element_locks].insert({:type => (type.to_s), :reservation => reservation_uuid.to_s, :credential => credential.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}) + end + result = true + end + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}}) + end + # + result + end + # + def reservation_remove_element(reservation_uuid=nil, table=:unspecified, dbid=0) + result = false + # + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(reservation_uuid) + raise ArgumentError, "You must supply a valid reservation UUID (as a String or Symbol) to remove element from reserve locks" + end + record = @connector[:element_locks].select(:reservation).where({:reservation => reservation_uuid.to_s}).first + if record + manifest = self.element_manifest(table, dbid, false) + if self.db_permissions()[:write] + # + manifest.each do |entry| + if @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).count > 0 + @connector[:element_locks].filter({:reservation => reservation_uuid.to_s, :element_table => entry[:table].to_s, :elementid => entry[:dbid]}).delete + end + end + result = true + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Database not available (defunct)" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reservation => (reservation_uuid)}}) + end + # + result + end + # + def empty_trash() + result = false + begin + if self.alive? + if self.open? + if self.db_permissions()[:write] + scrub_list = [] + @connector[:trash].filter({}).each do |entry| + the_table = entry[:element_table].to_sym + the_dbid = entry[:elementid] + if @connector[:element_locks].filter({:type => "write", :element_table => the_table.to_s, :elementid => the_dbid}).count == 0 + # delete full manifest + manifest = self.element_manifest(the_table, the_dbid, true) + manifest.each do |item| + @connector[:permissions].filter({:element_table => (item[:table].to_s), :elementid => (item[:dbid])}).delete + @connector[(item[:table])].filter({:dbid => item[:dbid]}).delete + end + scrub_list << entry[:dbid] + end + end + scrub_list.each do |entry| + @connector[:trash].filter({:dbid => entry}).delete + end + if @connector[:trash].filter({}).count == 0 + result = true + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error}) + end + result + end + # + def element_in_trash?(table=:unspecified, dbid=0) + result = false + begin + if self.alive? + if self.open? + if @connector[:trash].filter({:element_table => table.to_s, :elementid => dbid}).count > 0 + result = true + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}}) + end + result + end + # + def element_recover(table=:unspecified, dbid=0) + # recover an element from the trash + result = false + begin + if self.alive? + if self.open? + if self.db_permissions()[:write] + unless [:element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime, :element_text, :element_binary, :element_array, :element_hash].include?(table) + raise ArgumentError, "Invalid table specified" + end + unless dbid > 0 + raise ArgumentError, "dbid must be greater than zero" + end + if self.element_in_trash?(table, dbid) + scrub_list = [] + manifest = self.element_manifest(table, dbid) + manifest.each do |entry| + if @connector[:trash].filter({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).count > 0 + record = @connector[:trash].select(:dbid).where({:element_table => (entry[:table].to_s), :elementid => (entry[:dbid])}).first + if record + scrub_list << record[:dbid] + end + end + end + scrub_list.each do |entry| + @connector[:trash].filter({:dbid => entry}).delete + end + result = true + else + # it was never in the trash, so mission accomplished + result = true + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:table => (table), :dbid => (dbid)}}) + end + result + end + # ### Format handing methods + def format_template(type=:structure) + result = nil + begin + if self.alive? + if self.open? + unless type.is_any?(::String, ::Symbol) + raise ArgumentError, "You must supply a type as a String or Symbol: :structure or :binary" + end + the_uuid = ::GxG::uuid_generate() + while (@connector[:formats].filter({:uuid => (the_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (the_uuid)}).count > 0) + the_uuid = ::GxG::uuid_generate() + end + result = {:uuid => (the_uuid.to_sym), :type => (type.to_s), :ufs => "unspecified", :title => "", :version => BigDecimal(0.0), :mime_types => [], :content => nil} + case type + when :structure + result[:content] = {} + when :binary + result[:content] = {:sample_size => 0, :recognition => "", :import => "", :export => ""} + else + raise Exception, "Invalid format type specified: #{type.inspect}. Use :structure or :binary" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + result = nil + log_error({:error => the_error, :parameters => {:type => (type)}}) + end + result + end + # + def format_list(search_options={}) + result = [] + begin + if self.alive? + if self.open? + unless search_options.is_a?(::Hash) + raise ArgumentError, "You must specify search options as a Hash" + end + the_filter = {} + if search_options[:uuid] + the_filter[:uuid] = search_options[:uuid].to_s + end + if search_options[:type] + the_filter[:type] = search_options[:type].to_s + end + if search_options[:ufs] + the_filter[:ufs] = search_options[:ufs].to_s[0..4096] + end + if search_options[:title] + the_filter[:title] = search_options[:title].to_s[0..256] + end + if search_options[:version].is_a?(::Numeric) + the_filter[:version] = ::BigDecimal.new(search_options[:version].to_s) + end + mime_type = search_options[:mime_type].to_s + if mime_type.size > 0 + @connector[:formats].filter(the_filter).where(::Sequel.like(:mime_types, ("%" + mime_type + "%"))).order(::Sequel[:version].desc).each do |entry| + result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]} + end + else + @connector[:formats].filter(the_filter).order(::Sequel[:version].desc).each do |entry| + result << {:uuid => entry[:uuid].to_sym, :type => entry[:type].to_sym, :ufs => entry[:ufs].to_sym, :title => entry[:title], :version => entry[:version]} + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:search_options => (search_options)}}) + end + result + end + # + def format_load(search_options={}) + result = nil + begin + if self.alive?() + if self.open? + format_stub = self.format_list(search_options).first + if format_stub + format_record = @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).first + if format_record + content = "" + @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).order(:ordinal).each do |entry| + content << entry[:content] + end + format_record.delete(:dbid) + format_record[:ufs] = format_stub[:ufs] + format_record[:type] = format_stub[:type] + if content.size > 0 + if content.base64? + content = content.decode64 + end + if content.json? + content = ::JSON::parse(content.to_s, {:symbolize_names => true}) + end + if content[:type] + content = ::Hash.gxg_import(content) + end + format_record[:content] = content + else + format_record[:content] = nil + end + # + if format_record[:mime_types].size > 0 + if format_record[:mime_types].include?(",") + format_record[:mime_types] = format_record[:mime_types].split(",") + else + format_record[:mime_types] = [(format_record[:mime_types])] + end + end + # + result = format_record + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:search_options => (search_options)}}) + end + result + end + # + def new_structure_from_format(credential=nil, search_options={}) + result = nil + begin + if self.alive?() + if self.open? + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + format = self.format_load(search_options) + if format + # TODO: add support for :binary formats. + unless format[:type] == :structure + raise Exception, "Only formats of type :structure are supported" + end + result = self.try_persist(format[:content], credential) + if result.is_a?(::GxG::Database::PersistedHash) + result.set_format(format[:uuid].to_sym) + @connector[:element_hash].filter({:uuid => result.uuid.to_s}).update({:format => format[:uuid].to_s}) + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:search_options => (search_options)}}) + end + result + end + # + def format_create(format_record=nil) + result = false + begin + if self.alive?() + if self.open? + unless self.db_permissions()[:write] + raise Exception, "Unable to write to database" + end + if format_record.is_a?(::Hash) + unless ::GxG::valid_uuid?(format_record[:uuid]) + raise Exception, "Invalid UUID passed for the format" + end + unless format_record[:type].is_any?([::String, ::Symbol]) + raise Exception, "You must pass the format_record[:type] as a String or Symbol" + end + unless format_record[:ufs].is_any?([::String, ::Symbol]) + raise Exception, "You must pass the format_record[:ufs] as a String or Symbol" + end + unless format_record[:title].is_a?(::String) + raise Exception, "You must pass the format_record[:title] as a String" + end + unless format_record[:version].is_a?(::Numeric) + raise Exception, "You must pass the format_record[:version] as a Numeric" + end + unless format_record[:mime_types].is_any?(::String, ::Array) + raise Exception, "You must pass the format_record[:mime_types] as a String" + end + unless format_record[:content].is_a?(::Hash) + raise Exception, "You must pass the format_record[:content] as a Hash" + end + unless self.persistable?(format_record[:content]) + raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements" + end + if (@connector[:settings].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).count == 0 and @connector[:element_text].filter({:uuid => format_record[:uuid].to_s}).count == 0) + new_record = {:uuid => (format_record[:uuid].to_s)} + new_record[:type] = format_record[:type].to_s + new_record[:ufs] = format_record[:ufs].to_s + new_record[:title] = format_record[:title].to_s[0..256] + new_record[:version] = ::BigDecimal.new(format_record[:version].to_s) + if format_record[:mime_types].is_a?(::Array) + the_mimes = format_record[:mime_types].join(",").to_s[0..4096] + else + the_mimes = format_record[:mime_types].to_s[0..4096] + end + new_record[:mime_types] = the_mimes + content = format_record[:content].gxg_export.to_json.encode64 + new_dbid = @connector[:formats].insert(new_record) + if content.size > 0 + ::GxG::apportioned_ranges(content.size, 4096).each_with_index do |portion, ordinal| + @connector[:text_page].insert({:parent_uuid => (new_record[:uuid].to_s), :ordinal => (ordinal), :length => (content[(portion)].size), :content => (content[(portion)])}) + end + end + if new_dbid + result = true + end + # + if result == true && ::GxG::DB[:roles][:formats] == self + already = [(self)] + GxG::DB[:roles].values.each do |the_db| + unless already.include?(the_db) + the_db.format_create(format_record) + already << the_db + end + end + end + # + else + raise Exception, "That format UUID is already in use, please generate another" + end + # + else + raise ArgumentError, "You must supply a valid format record as a Hash" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:format_record => (format_record)}}) + end + result + end + # + def format_update(format_record=nil) + format_record = format_record.clone + result = false + begin + if self.alive?() + if self.open? + unless self.db_permissions()[:write] + raise Exception, "Unable to write to database" + end + if format_record.is_a?(::Hash) + unless ::GxG::valid_uuid?(format_record[:uuid]) + raise Exception, "Invalid UUID passed for the format" + end + record = @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).first + if record + format_update = {} + record.keys.each do |the_key| + if [:type, :ufs, :title, :version, :mime_types].include?(the_key) + case the_key + when :type + unless format_record[:type].is_any?(::String, ::Symbol) + raise Exception, "You must pass the format_record[:type] as a String or Symbol" + end + when :ufs + unless format_record[:ufs].is_any?(::String, ::Symbol) + raise Exception, "You must pass the format_record[:ufs] as a String or Symbol" + end + when :title + unless format_record[:title].is_a?(::String) + raise Exception, "You must pass the format_record[:title] as a String" + end + when :version + unless format_record[:version].is_a?(::Numeric) + raise Exception, "You must pass the format_record[:version] as a Numeric" + end + when :mime_types + unless format_record[:mime_types].is_any?(::String, ::Array) + raise Exception, "You must pass the format_record[:mime_types] as a String" + end + end + # + if the_key == :mime_types + if format_record[:mime_types].is_a?(::Array) + the_mimes = format_record[:mime_types].join(",").to_s[0..4096] + else + the_mimes = format_record[:mime_types].to_s[0..4096] + end + format_record[:mime_types] = the_mimes + end + # + if format_record[(the_key)] != record[(the_key)] + format_update[(the_key)] = format_record[(the_key)] + end + end + end + if format_update.keys.size > 0 + # FIXME: :type and :ufs are being passed as a Symbols, but DB requires them to be a strings: + if format_update[:type].is_a?(::Symbol) + format_update[:type] = format_update[:type].to_s + end + if format_update[:ufs].is_a?(::Symbol) + format_update[:ufs] = format_update[:ufs].to_s + end + @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update(format_update) + end + if format_record[:content].is_a?(::Hash) + unless self.persistable?(format_record[:content]) + raise Exception, "You must pass the format_record[:content] as a Hash of persistable elements" + end + old_segments = [] + content = format_record[:content].gxg_export.to_json.encode64 + @connector[:text_page].select(:dbid).where({:parent_uuid => format_record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if content.size == 0 + if old_segments.size > 0 + @connector[:text_page].filter({:parent_uuid => format_record[:uuid].to_s}).delete + end + else + new_segments = ::GxG::apportioned_ranges(content.size, 4096) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + manifest.each do |action| + case action[:operation] + when :overwrite + @connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])}) + when :create + @connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])}) + when :delete + @connector[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + @connector[:formats].filter({:uuid => format_record[:uuid].to_s}).update({:version => (::BigDecimal.new(format_record[:version].to_s) + 0.0001)}) + # + end + end + # + result = true + # + if result == true && ::GxG::DB[:roles][:formats] == self + already = [(self)] + GxG::DB[:roles].values.each do |the_db| + unless already.include?(the_db) + the_db.format_update(format_record) + already << the_db + end + end + end + # + else + result = self.format_create(format_record) + end + else + raise ArgumentError, "You must supply a valid format record as a Hash" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:format_record => (format_record)}}) + end + result + end + # + def format_destroy(search_options={}) + result = false + begin + if self.alive?() + if self.open? + unless self.db_permissions()[:write] + raise Exception, "Unable to write to database" + end + format_stub = self.format_list(search_options).first + if format_stub + @connector[:formats].filter({:uuid => (format_stub[:uuid].to_s)}).delete + if @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).count > 0 + @connector[:text_page].filter({:parent_uuid => (format_stub[:uuid].to_s)}).delete + end + result = true + # + if result == true && ::GxG::DB[:roles][:formats] == self + already = [(self)] + GxG::DB[:roles].values.each do |the_db| + unless already.include?(the_db) + the_db.format_destroy(search_options) + already << the_db + end + end + end + # + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:search_options => (search_options)}}) + end + result + end + # Settings methods + def setting_keys() + result = [] + begin + if self.alive?() + if self.open?() + @connector[:settings].all.each do |the_entry| + result << the_entry[:title].to_sym + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:setting_key => setting_key}}) + end + result + end + # + def [](setting_key=nil) + result = nil + begin + if self.alive?() + if self.open?() + record = @connector[:settings].filter({:title => setting_key.to_s}).first + if record + content = "" + @connector[:text_page].select(:content).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry| + content << entry[:content] + end + if content.size > 0 + result = content.unserialize() + end + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:setting_key => setting_key}}) + end + result + end + # + def []=(setting_key=nil, object=nil) + result = object + begin + if self.alive?() + if self.open?() + unless self.db_permissions()[:write] + raise Exception, "Unable to write to database" + end + unless setting_key.is_a?(::Symbol) + raise ArgumentError, "You must supply a setting key as a Symbol" + end + unless setting_key.to_s.size > 0 + raise ArgumentError, "You must supply a setting key whose length is greater than zero" + end + record = @connector[:settings].filter({:title => setting_key.to_s}).first + unless record + new_uuid = ::GxG::uuid_generate().to_s + while (@connector[:settings].filter({:uuid => (new_uuid)}).count > 0 or @connector[:formats].filter({:uuid => (new_uuid)}).count > 0 or @connector[:element_text].filter({:uuid => (new_uuid)}).count > 0) + new_uuid = ::GxG::uuid_generate().to_s + end + @connector[:settings].insert({:title => setting_key.to_s, :uuid => new_uuid.to_s}) + record = @connector[:settings].filter({:uuid => (new_uuid)}).first + end + if record + @connector[:settings].filter({:dbid => (record[:dbid])}).update({:version => (record[:version] + 0.0001)}) + content = object.serialize() + old_segments = [] + @connector[:text_page].select(:dbid).where({:parent_uuid => record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if content.size == 0 + if old_segments.size > 0 + @connector[:text_page].filter({:parent_uuid => record[:uuid].to_s}).delete + end + else + new_segments = ::GxG::apportioned_ranges(content.size, 4096) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + manifest.each do |action| + case action[:operation] + when :overwrite + @connector[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])}) + when :create + @connector[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (content[(action[:portion])].size), :content => (content[(action[:portion])])}) + when :delete + @connector[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + # + end + else + raise Exception, "Internal Error - could not retrieve setting record: #{setting_key.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:setting => setting_key, :object => object}}) + end + result + end + # + # Permission policy support: + def permission_policy() + result = nil + begin + if self.alive?() + if self.open?() + if @authority + result = @authority.permission_policy() + else + result = @permission_policy + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def permission_policy_load() + result = false + begin + if self.alive?() + if self.open?() + if @authority + result = @authority.permission_policy_load() + else + @permission_policy = (self[:permission_policy] || []) + result = true + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def permission_policy_save() + result = false + begin + if self.alive?() + if self.open?() + if @authority + result = @authority.permission_policy_save() + else + self[:permission_policy] = (@permission_policy || []) + result = true + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def enforce_permission_policy(event={}) + # Event format: enforce_permission_policy({:action => :extend, :credential => nil, :source => nil, :destination => nil}) + # Rule format: {:event => :create, :credential => :any, :action => {:extend_permissions => nil}} + the_policy = self.permission_policy() + result = false + begin + if self.alive?() + if self.open?() + actions = [] + the_policy.each do |rule| + if event[:action] == rule[:event] + if rule[:credential] == event[:credential] || rule[:credential] == :any + actions << rule[:action] + end + end + end + actions.each do |action| + case action.keys[0] + when :extend_permissions + if action[:use_permission].is_a?(::Hash) + use_permission = action[:use_permission] + else + use_permission = nil + end + credential = event[:credential] + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You MUST provide a valid UUID as :credential." + end + new_credential = action[:extend_permissions] + source = event[:source] + unless source.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + raise ArgumentError, "You MUST provide a persisted object as :source." + end + unless source.db_address + raise Exception, "You provided a Deactivated object as :source." + end + destination = event[:destination] + unless destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + raise ArgumentError, "You MUST provide a persisted object as :source." + end + unless destination.db_address + raise Exception, "You provided a Deactivated object as :destination." + end + # + permissions = self.element_permissions(source.db_address()[:table],source.db_address()[:dbid]) + permission_db = [] + if [:persisted_array, :persisted_hash].include?(destination.db_address()[:table]) + search_db = [{:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]}] + permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]} + while search_db.size > 0 do + entry = search_db.shift + self.element_manifest(entry[:table],entry[:dbid],false).each do |item| + if [:persisted_array, :persisted_hash].include?(item[:table]) + search_db << item + permission_db << item + end + end + end + permission_db.each do |the_reference| + permissions.each do |record| + self.assign_element_permission(the_reference[:table],the_reference[:dbid], credential, record[:permissions]) + if use_permission + self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, use_permission) + else + self.assign_element_permission(the_reference[:table],the_reference[:dbid], new_credential, record[:permissions]) + end + end + end + # destination.search do |the_value, the_selector, the_container| + # if [:persisted_array, :persisted_hash].include?(the_value.db_address()[:table]) + # permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]} + # end + # end + # + end + # permission_db << {:table => destination.db_address()[:table], :dbid => destination.db_address()[:dbid]} + # if destination.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # destination.search do |the_value, the_selector, the_container| + # permission_db << {:table => the_value.db_address()[:table], :dbid => the_value.db_address()[:dbid]} + # end + # end + # permission_db.each do |item| + # self.element_manifest(item[:table],item[:dbid],false).each do |entry| + # permissions.each do |record| + # self.assign_element_permission(entry[:table],entry[:dbid], credential, record[:permissions]) + # if use_permission + # self.assign_element_permission(entry[:table],entry[:dbid], new_credential, use_permission) + # else + # self.assign_element_permission(entry[:table],entry[:dbid], new_credential, record[:permissions]) + # end + # end + # end + # end + # + end + end + result = true + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:event => event}}) + end + result + end + # ### Import methods (deprecated in favor of sychronize_records) + def import(credential=nil, the_record=nil) + # + result = nil + if self.alive? + if self.open? + if self.db_permissions()[:write] + cleanup_manifest = [] + begin + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + if the_record.is_a?(::Hash) + case the_record[:type] + when :element_boolean, :element_integer, :element_float, :element_bigdecimal, :element_datetime + new_dbid = @connector[(the_record[:type])].insert({:version => (the_record[:version]), :content => (the_record[:content])}) + self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + result = {:table => (the_record[:table]), :dbid => (new_dbid)} + when :element_text + the_uuid = ::GxG::uuid_generate() + while (@connector[:element_text].filter({:uuid => the_uuid}).count > 0) + the_uuid = ::GxG::uuid_generate() + end + new_dbid = @connector[:element_text].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)}) + cleanup_manifest << {:table => :element_text, :dbid => (new_dbid)} + ::GxG::apportioned_ranges(the_record[:content].size, 4096).each_with_index do |the_range,the_ordinal| + @connector[:text_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content][(the_range)].size), :content => (the_record[:content][(the_range)])}) + end + self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + result = {:table => :element_text, :dbid => (new_dbid)} + when :element_binary + the_uuid = ::GxG::uuid_generate() + while (@connector[:element_binary].filter({:uuid => the_uuid}).count > 0) + the_uuid = ::GxG::uuid_generate() + end + new_dbid = @connector[:element_binary].insert({:uuid => (the_uuid), :version => (the_record[:version]), :length => (the_record[:content].size)}) + cleanup_manifest << {:table => :element_binary, :dbid => (new_dbid)} + ::GxG::apportioned_ranges(the_record[:content].size, 65536).each_with_index do |the_range,the_ordinal| + @connector[:binary_page].insert({:parent_uuid => (the_uuid), :ordinal => (the_ordinal), :length => (the_record[:content].data()[(the_range)].size), :content => (the_record[:content].data()[(the_range)])}) + end + self.assign_element_permission((the_record[:type]), (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + result = {:table => :element_binary, :dbid => (new_dbid)} + when :element_array + # {:type => :element_array, :uuid => @uuid, :title => @title, :version => @version, :content => []} + existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first + if existing + if the_record[:version] > existing[:version] + unless self.element_destroy(credential, :element_array, existing[:dbid]) + raise Exception, "Error replacing array element - Aborting" + end + existing = nil + else + log_warn("Import record is older than existing one - Skipping") + new_dbid = existing[:dbid] + end + end + unless existing + new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version])}) + cleanup_manifest << {:table => :element_array, :dbid => (new_dbid)} + self.assign_element_permission(:element_array, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + the_record[:content].each_with_index do |element, indexer| + new_address = self.import(credential, (element)) + if new_address + cleanup_manifest << new_address + @connector[:array_links].insert({:parent_uuid => (the_record[:uuid].to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]}) + else + raise Exception, "Error processing the import record - Aborting" + end + end + end + result = {:table => :element_array, :dbid => (new_dbid)} + when :element_hash + # {:type => :element_hash, :uuid => @uuid, :title => @title, :version => @version, :format => @format, :content => {}} + existing = @connector[(the_record[:type])].select(:dbid, :version).where({:uuid => the_record[:uuid].to_s}).first + if existing + if the_record[:version] > existing[:version] + unless self.element_destroy(credential, :element_hash, existing[:dbid]) + raise Exception, "Error replacing hash element - Aborting" + end + existing = nil + else + log_warn("Import record is older than existing one - Skipping") + new_dbid = existing[:dbid] + end + end + unless existing + new_dbid = @connector[(the_record[:type])].insert({:uuid => (the_record[:uuid].to_s), :title => (the_record[:title]), :version => (the_record[:version]), :format => (the_record[:format].to_s)}) + cleanup_manifest << {:table => :element_hash, :dbid => (new_dbid)} + self.assign_element_permission(:element_hash, (new_dbid), credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + the_record[:content].keys.each_with_index do |the_key, indexer| + new_address = self.import(credential, (the_record[:content][(the_key)])) + if new_address + cleanup_manifest << new_address + @connector[:hash_links].insert({:parent_uuid => (the_record[:uuid].to_s), :property => (the_key.to_s), :ordinal => (indexer), :element => (::GxG::Database::Database::element_table_index(new_address[:table])), :elementid => new_address[:dbid]}) + else + raise Exception, "Error processing the import record - Aborting" + end + end + end + result = {:table => :element_hash, :dbid => (new_dbid)} + else + raise ArgumentError, "Invalid table specified" + end + else + raise ArgumentError, "You must supply an import record as a Hash" + end + rescue Exception => the_error + # clean up + cleanup_manifest.each do |entry| + self.element_destroy(credential, entry[:table], entry[:dbid]) + end + log_error({:error => the_error, :parameters => {:the_record => (the_record)}}) + end + else + log_warn("Cannot import records into a read-only Database - Aborting") + end + end + end + result + end + # ### Search function method + def uuid_list(credential=nil, criteria={}) + # will return an array of all structures the credential has (at least) read access to + result = [] + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You must supply a valid credential UUID (as a String or Symbol)" + end + unless criteria.is_a?(::Hash) + raise ArgumentError, "You must supply a search criteria as a Hash" + end + hash_criteria = {} + array_criteria = {} + # Limit, Offset, and Page selection + if criteria[:limit].is_a?(::Integer) + limit = criteria[:limit] + else + limit = nil + end + if criteria[:offset].is_a?(::Integer) + offset = criteria[:offset] + else + offset = 0 + end + if criteria[:page].is_a?(::Integer) + page = criteria[:page] + unless limit + limit = 100 + end + offset = ((page * limit) - 1) + else + page = nil + end + # UUID, Title, and Format searches + if ::GxG::valid_uuid?(criteria[:uuid]) + hash_criteria[:uuid] = criteria[:uuid].to_s + array_criteria[:uuid] = criteria[:uuid].to_s + end + if criteria[:title].is_a?(::String) + hash_criteria[:title] = criteria[:title].to_s[0..256] + array_criteria[:title] = criteria[:title].to_s[0..256] + end + if criteria[:format].is_any?([::String, ::Symbol]) + if ::GxG::valid_uuid?(criteria[:format]) + format_list = self.format_list({:uuid => criteria[:format].to_sym}) + unless format_list.size > 0 + raise ArgumentError, "Format not found: #{criteria[:format].inspect}" + end + hash_criteria[:format] = criteria[:format].to_s + end + end + if criteria[:ufs].is_any?(::String, ::Symbol) + if criteria[:ufs_version].is_a?(::Numeric) + format_list = self.format_list({:ufs => criteria[:ufs].to_sym, :version => ::BigDecimal.new(criteria[:ufs_version].to_s)}) + else + format_list = self.format_list({:ufs => criteria[:ufs].to_sym}) + end + unless format_list.size > 0 + if criteria[:ufs_version].is_a?(::Float) + raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}, Version: #{criteria[:ufs_version].inspect}" + else + raise ArgumentError, "Format not found: #{criteria[:ufs].inspect}" + end + end + hash_criteria[:format] = format_list[0][:uuid].to_s + end + # Output column selection: (very very basic at first) + selectables = [:uuid, :title, :version] + selected = [] + if criteria[:select].is_any?(::Symbol, ::Array) + if criteria[:select].is_a?(::Array) + criteria[:select].each do |the_column_symbol| + if selectables.include?(the_column_symbol) + selected << the_column_symbol + end + end + else + if selectables.include?(criteria[:select]) + selected << criteria[:select] + end + end + end + if selected.size == 0 + selected << :uuid + end + # + reduce_to_datetime = Proc.new do |the_value| + the_result = nil + db_content = nil + if the_value.is_a?(::String) + if the_value.include?("between") + the_times = the_value.split("between")[1].split("and") + the_times[0] = ::Chronic.parse(the_times[0]) + the_times[1] = ::Chronic.parse(the_times[1]) + if the_times[0].is_a?(::Time) && the_times[1].is_a?(::Time) + db_content = ((the_times[0].to_datetime)..(the_times[1].to_datetime)) + else + raise Exception, "Unable to understand your time specification text: #{the_value.inspect}" + end + else + if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime? + db_content = ::DateTime::parse(the_value) + else + the_times = ::Chronic.parse(the_value) + if the_times.is_a?(::Time) + db_content = the_times.to_datetime + else + raise Exception, "Unable to understand your time specification text: #{the_value.inspect}" + end + end + end + end + if the_value.is_a?(::Range) + the_times = [(the_value.first), (the_value.last)] + if the_times[0].is_a?(::Time) + the_times[0] = the_times[0].to_datetime + end + if the_times[0].is_a?(::Date) + the_times[0] = ::DateTime::parse(the_times[0].to_s) + end + if the_times[1].is_a?(::Time) + the_times[1] = the_times[1].to_datetime + end + if the_times[1].is_a?(::Date) + the_times[1] = ::DateTime::parse(the_times[1].to_s) + end + the_times = ((the_times[0])..(the_times[1])) + if the_times.first.is_a?(::DateTime) && the_value.last.is_a?(::DateTime) + db_content = the_times + else + raise Exception, "Invalid Range of dates, use DateTime objects in the Range: #{the_value.inspect}" + end + end + if the_value.is_any?(::Time, ::Date) + db_content = ::DateTime::parse(the_value.to_s) + end + if the_value.is_a?(::DateTime) + db_content = the_value + end + if db_content + result = db_content + else + raise Exception, "Unable to reduce to a DateTime with this input: #{the_value.inspect}" + end + the_result + end + # + db_content_value = Proc.new do |the_value| + the_result = nil + the_table = ::GxG::Database::Database::element_table_for_instance(the_value) + if the_table == :unspecified + raise Exception, "Invalid value passed as persistable: #{the_value.inspect}" + else + db_content = nil + case the_table + when :element_boolean + if the_value.is_a?(::TrueClass) + db_content = 1 + else + if the_value.is_a?(::FalseClass) + db_content = 0 + else + db_content = -1 + end + end + when :element_binary + db_content = "" + the_value.each do |the_byte| + db_content << the_byte.to_s(base=16) + end + when :element_text + if the_value.valid_time? || the_value.valid_date? || the_value.valid_datetime_nolocale? || the_value.valid_datetime? + db_content = reduce_to_datetime.call(the_value) + else + db_content = the_value + end + else + db_content = the_value + end + if db_content + the_result = db_content + else + raise Exception, "Could not cast the value to something useful: #{the_value.inspect}" + end + end + the_result + end + # + find_property_records = Proc.new do |a_selector, a_container| + a_result = [] + a_container.each do |item| + if item[(a_selector)] + a_result << item + end + end + a_result + end + find_element_records = Proc.new do |a_selector, a_container| + a_result = [] + a_container.each do |item| + if item[(a_selector)] + a_result << item + end + end + a_result + end + found_list = [] + # criteria[:property] : + # For all + # {:property_name => {:equals => value}} + # For :element_integer, :element_float, :element_bigdecimal + # {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}} + # For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it. + # {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => } + # For Text, Binary + # {:property_name => {:include => value}} + # + # criteria[:element] : + # similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed. + # + if criteria[:property] || criteria[:element] + # Expansion + # Text Search: @connector[:text_page].where(::Sequel.like(:content,'%#{search_text}%')) + # Binary Search: @connector[:binary_page].where(::Sequel.like(:content,'%#{byte_sequence}%')) + # self.element_parent_address(the_table=:unspecified, the_dbid=0) + if criteria[:property].is_a?(::Hash) + # Only ONE property key can be compared (TODO: expand to multiple keys) + property_matches = [] + last_key = nil + # + criteria[:property].keys.each do |property_key| + # compare each key's value to qualify the parent object: + # {:html => {:include => "\n"}} + comparison_op = criteria[:property][(property_key)].keys[0].to_sym + if criteria[:property][(property_key)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op) + comparison_value = reduce_to_datetime.call(criteria[:property][(property_key)][(comparison_op)]) + table_test = ::GxG::Database::Database::element_table_for_instance(comparison_value) + else + table_test = ::GxG::Database::Database::element_table_for_instance(criteria[:property][(property_key)][(comparison_op)]) + comparison_value = db_content_value.call(criteria[:property][(property_key)][(comparison_op)]) + end + # + if [:element_hash, :element_array].include?(table_test) + # Only properties with base elements can be compared - not structures (TODO: expand this) + log_warn("Attempted to compare a structure as a property value (not supported, ignoring).") + next + end + # Review : rewrite - adapt to new arch. + match_uuid_list = [] + # comparison_value + @connector[:hash_properties].filter({:property => property_key.to_s}).each do |entry| + element_value = nil + case entry[:element] + when "element_boolean" + case entry[:element_boolean] + when -1 + element_value = nil + when 0 + element_value = false + when 1 + element_value = nil + end + when "element_integer" + element_value = entry[:element_integer] + when "element_float" + element_value = entry[:element_float] + when "element_bigdecimal" + element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s) + when "element_datetime" + element_value = entry[:element_datetime] + when "element_text" + if entry[:element_text_uuid].to_s.size > 0 + # Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now. + element_value = "" + @connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + element_value << text_page[:content] + end + else + element_value = entry[:element_text] + end + when "element_binary" + element_value = ::GxG::ByteArray.new + @connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page| + element_value << binary_page[:content] + end + end + # + begin + case comparison_op + when :equals, :at + if element_value == comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :greater_than, :after + if element_value > comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :greater_or_equal, :at_or_after + if element_value >= comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :less_than, :before + if element_value < comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :less_or_equal, :at_or_before + if element_value <= comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :between + if comparison_value.include?(element_value) + match_uuid_list << entry[:parent_uuid].to_sym + end + when :include + if element_value.to_s.include?(comparison_value.to_s) + match_uuid_list << entry[:parent_uuid].to_sym + end + when :match + if element_value.to_s.match(comparison_value) + match_uuid_list << entry[:parent_uuid].to_sym + end + end + rescue Exception => cast_error + # Probably a type casting error (comparison of dissimilar types) + next + end + end + # + match_uuid_list.each do |the_matched_uuid| + # Add to main found list + parent_record = @connector[:element_hash].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first + if parent_record + new_record = {} + selected.each do |the_selected_key| + if parent_record.keys.include?(the_selected_key) + new_record[(the_selected_key)] = parent_record[(the_selected_key)] + if ::GxG::valid_uuid?(new_record[(the_selected_key)]) + new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym + end + end + end + unless found_list.include?(new_record) + found_list << new_record + end + end + end + # + if last_key != property_key + last_key = property_key + end + # + end + # + else + if criteria[:element].is_a?(::Array) + criteria[:element].each_with_index do |the_element, element_index| + comparison_op = criteria[:element][(element_index)].keys[0].to_sym + if criteria[:element][(element_index)][(comparison_op)].is_any?(::String, ::Range) && [:at, :before, :after, :at_or_before, :at_or_after, :between].include?(comparison_op) + comparison_value = reduce_to_datetime.call(criteria[:element][(element_index)][(comparison_op)]) + element_table = ::GxG::Database::Database::element_table_for_instance(comparison_value) + else + element_table = ::GxG::Database::Database::element_table_for_instance(criteria[:element][(element_index)][(comparison_op)]) + comparison_value = db_content_value.call(criteria[:element][(element_index)][(comparison_op)]) + end + # Structural comparison not supported. + if [:element_hash, :element_array].include?(element_table) + log_warn("Attempted to compare a structure as an element (not supported, ignoring).") + next + end + match_uuid_list = [] + # comparison_value + @connector[:array_elements].filter({:element => element_table.to_s}).each do |entry| + element_value = nil + case entry[:element] + when "element_boolean" + case entry[:element_boolean] + when -1 + element_value = nil + when 0 + element_value = false + when 1 + element_value = nil + end + when "element_integer" + element_value = entry[:element_integer] + when "element_float" + element_value = entry[:element_float] + when "element_bigdecimal" + element_value = ::BigDecimal.new(entry[:element_bigdecimal].to_s) + when "element_datetime" + element_value = entry[:element_datetime] + when "element_text" + if entry[:element_text_uuid].to_s.size > 0 + # Review: issue with Base64: decode64 mangles non-base64 text but does it w/o error now. + element_value = "" + @connector[:text_page].filter({:uuid => entry[:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + element_value << text_page[:content] + end + else + element_value = entry[:element_text] + end + when "element_binary" + element_value = ::GxG::ByteArray.new + @connector[:binary_page].filter({:uuid => entry[:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page| + element_value << binary_page[:content] + end + end + # + begin + case comparison_op + when :equals, :at + if element_value == comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :greater_than, :after + if element_value > comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :greater_or_equal, :at_or_after + if element_value >= comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :less_than, :before + if element_value < comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :less_or_equal, :at_or_before + if element_value <= comparison_value + match_uuid_list << entry[:parent_uuid].to_sym + end + when :between + if comparison_value.include?(element_value) + match_uuid_list << entry[:parent_uuid].to_sym + end + when :include + if element_value.to_s.include?(comparison_value.to_s) + match_uuid_list << entry[:parent_uuid].to_sym + end + when :match + if element_value.to_s.match(comparison_value) + match_uuid_list << entry[:parent_uuid].to_sym + end + end + rescue Exception => cast_error + # Probably a type casting error (comparison of dissimilar types) + next + end + # end - comparison loop + end + # + match_uuid_list.each do |the_matched_uuid| + # Add to main found list + parent_record = @connector[:element_array].select(:uuid, :version, :title).where({:uuid => the_matched_uuid.to_s}).first + if parent_record + new_record = {} + selected.each do |the_selected_key| + if parent_record.keys.include?(the_selected_key) + new_record[(the_selected_key)] = parent_record[(the_selected_key)] + if ::GxG::valid_uuid?(new_record[(the_selected_key)]) + new_record[(the_selected_key)] = new_record[(the_selected_key)].to_sym + end + end + end + unless found_list.include?(new_record) + found_list << new_record + end + end + end + # + # unless last_index == element_index + # last_index = element_index + # end + end + # + else + # Criteria Expansion Here + end + end + else + @connector[:element_hash].filter(hash_criteria).each do |entry| + new_record = {} + selected.each do |the_key| + if ::GxG::valid_uuid?(entry[(the_key)]) + new_record[(the_key)] = entry[(the_key)].to_sym + else + new_record[(the_key)] = entry[(the_key)] + end + end + found_list << new_record + end + # + unless hash_criteria[:format] + @connector[:element_array].filter(array_criteria).each do |entry| + new_record = {} + selected.each do |the_key| + if ::GxG::valid_uuid?(entry[(the_key)]) + new_record[(the_key)] = entry[(the_key)].to_sym + else + new_record[(the_key)] = entry[(the_key)] + end + end + found_list << new_record + end + end + end + # + # Sorting Options Here: (Expansion) + if criteria[:order].is_any?(::Symbol, ::Hash) + ascending = true + sort_key = nil + if criteria[:order].is_a?(::Hash) + if criteria[:order][:ascending].is_a?(::Symbol) + sort_key = criteria[:order][:ascending] + end + if criteria[:order][:descending].is_a?(::Symbol) + sort_key = criteria[:order][:descending] + ascending = false + end + else + sort_key = criteria[:order] + end + if selected.include?(sort_key) + found_list = found_list.sort do |entry, another| + entry[(sort_key)] <=> another[(sort_key)] + end + unless ascending + found_list.reverse! + end + end + end + # Impose result set limit and offset + if limit.is_a?(::Integer) + if found_list.size > 0 + if (offset + (limit - 1)) <= (found_list.size - 1) + found_list = found_list[((offset)..(offset + (limit - 1)))] + else + if offset <= (found_list.size - 1) + found_list = found_list[((offset)..(-1))] + end + end + end + end + # + found_list.size.times do + result << found_list.shift + end + # + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct Database" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:credential => (credential), :criteria => (criteria)}}) + end + result + end + # + def search_database(credential=nil, criteria={}) + # Usage: search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]}) + # + # criteria[:properties] : + # For all + # {:property_name => {:equals => value}} + # For :element_integer, :element_float, :element_bigdecimal + # {:property_name => {:greater_than => value, :greater_or_equal => value, :less_than => value, :less_or_equal => value}} + # For :element_datetime, Note: if you pass text as a date ::Chronic will be used to parse it. + # {:property_name => {:at => value, :before => value, :at_or_before => value, :after => value, :at_or_after => value, :between => } + # For Text, Binary + # {:property_name => {:include => value}} + # + # criteria[:element] : + # similar to criteria[:property] except that the entire table for the type is searched and no property dereferencing needed. + # + result = [] + criterium = [] + criteria_error = false + unless criteria.is_a?(::Hash) + criteria_error = true + end + unless criteria_error + common_criteria = criteria.clone + properties = (common_criteria.delete(:properties) || []) + unless properties.is_a?(::Array) + criteria_error = true + end + elements = (common_criteria.delete(:elements) || []) + unless elements.is_a?(::Array) + criteria_error = true + end + end + # + unless criteria_error + if properties.size > 0 && elements.size > 0 + criteria_error = true + log_warn("You MUST select EITHER :properites OR :elements, not both.") + end + end + # + unless criteria_error + # Prepare criterium + if common_criteria[:select].is_a?(::Array) + unless common_criteria[:select].include?(:uuid) + common_criteria[:select] << :uuid + end + else + common_criteria[:select] = [:uuid] + end + # + if common_criteria[:ufs] + criterium << {:ufs => common_criteria[:ufs]} + end + if common_criteria[:format] + criterium << {:format => common_criteria[:format]} + end + # + if properties.size > 0 + properties.each do |the_property_search| + criterium << ((common_criteria.clone).merge({:property => the_property_search})) + end + end + if elements.size > 0 + elements.each do |the_element_search| + criterium << ((common_criteria.clone).merge({:element => the_element_search})) + end + end + end + # + result_sets = [] + # if properties.size == 0 && elements.size == 0 + # result_sets << self.uuid_list(credential, criteria) + # else + criterium.each do |the_criteria| + result_sets << self.uuid_list(credential, the_criteria) + end + # end + scores = {} + result_sets.each do |the_result| + the_result.each do |the_answer| + if scores.keys.include?(the_answer[:uuid]) + scores[(the_answer[:uuid])] = (scores[(the_answer[:uuid])] + 1) + else + scores[(the_answer[:uuid])] = 1 + end + end + end + scores.keys.each do |the_uuid| + if scores[(the_uuid)] == result_sets.size + result_sets[0].each do |the_record| + if the_record[:uuid] == the_uuid + result << the_record + break + end + end + end + end + # + result + end + # Synchronize records: (and supports) + # + def synchronize_records(the_records=[], credential=nil, options={}) + # Review : rewrite for new architecture. + result = {:status => :completed} + # + total_records_processed = 0 + total_formats_processed = 0 + # List of format uuids needed from the OTHER database to complete the sync. + formats_needed = [] + # List of format records we are sharing. + formats_provided = [] + # records on THIS database that are higher version than submitted version. + updates_provided = [] + # In case something goes horribly wrong: + clean_up_list = [] + # + begin + if self.alive? + if self.open? + unless ::GxG::valid_uuid?(credential) + raise ArgumentError, "You MUST provide a valid UUID as credential." + end + # + if the_records.is_a?(::String) + if the_records.base64? + the_records = the_records.decode64 + end + if the_records.json? + the_records = ::JSON.parse(the_records,{:symbolize_names => true}) + end + end + if the_records.is_a?(::Hash) + the_records = [(the_records)] + end + unless the_records.is_a?(::Array) + raise ArgumentError, "You MUST provide: Base64, JSON, Hash, or Array of operation Hashes." + end + # Process each operation in the_records, data is inline under :data key. + the_records.each do |the_operation| + if the_operation.is_a?(::Hash) + if [:merge, :delete, :fetch, :merge_format, :delete_format, :formats_provided, :formats_needed].include?(the_operation[:operation]) + # + case the_operation[:operation] + when :merge + unless self.db_permissions()[:write] + raise Exception, "Attempted to alter a read-only database." + end + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if entry[:format] + unless self.format_list({:uuid => (entry[:format])}).size > 0 + raise Exception, "Attempted to merge with unknown :format #{entry[:format].inspect}. Synchronize formats first, then records." + end + end + # fetch existing: + # if existing - compare versions - if greater provide, if lesser update, else add to db. + queue = [(entry)] + while queue.size > 0 do + record = queue.shift + if ::GxG::valid_uuid?(record[:uuid]) + formatting = {:format => nil, :constraint => nil} + existing = self.retrieve_by_uuid(record[:uuid], credential) + unless existing + # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later. + unless @connector[(record[:type].to_s.downcase.to_sym)].filter({:uuid => record[:uuid].to_s}).count > 0 + # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation: + dbid = @connector[(record[:type].to_s.downcase.to_sym)].insert({:uuid => record[:uuid].to_s, :title => "Merged Persisted Object"}) + self.assign_element_permission(record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + # + end + existing = self.retrieve_by_uuid(record[:uuid], credential) + end + if existing + # ### Compare versions + if existing.version > record[:version] + action = :provide + else + if existing.version < record[:version] + action = :update + else + action = :ignore + end + end + # ### Take Action + case action + when :provide + updates_provided << {:operation => :merge, :data => [(existing.export())]} + existing.deactivate + total_records_processed += 1 + when :update + # ### Prelude + if record[:format].to_s.size > 0 + formatting[:format] = record[:format].to_s + end + if record[:constraint].to_s.size > 0 + formatting[:constraint] = record[:constraint].to_s + end + clean_up_list << existing + existing.get_reservation() + unless existing.write_permission?() && existing.write_reserved?() + raise Exception, "Failed to secure write permission and write reservation for this credential." + end + existing.title = (record[:title]) + # ### Update Elements + if record[:content].is_a?(::Hash) + # Review : What to do / how to handle formatted existing Hashes?? + unless existing.format + # Eliminate keys that the imported record does not have. (tracking structural changes) + existing.keys.each do |the_key| + unless record[:content].keys.include?(the_key) + temp_object = existing.delete(the_key) + if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) + temp_object.destroy() + end + end + end + end + record[:content].each_pair do |property, element_record| + if element_record[:uuid] + # ### Element is Hash or Array + element = self.retrieve_by_uuid(element_record[:uuid], credential) + unless element + # ### Create blank element record and retrieve + # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later. + unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0 + # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation: + dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"}) + self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + # + end + element = self.retrieve_by_uuid(element_record[:uuid], credential) + end + if element + # ### Ensure Element Linking + # Review : Warning - absence of a :write permission can bugger this up. + clean_up_list << element + element.get_reservation() + unless element.write_permission?() && element.write_reserved?() + raise Exception, "Failed to secure write permission and write reservation for this credential." + end + existing[(property)] = element + # element.deactivate ?? + end + # Note: return to processing queue to set its elements + queue << element_record + else + # ### Base Element + unless existing.version(property) > element_record[:version] + the_content = nil + case element_record[:type].to_s.downcase.to_sym + when :element_boolean, :element_integer, :element_float + the_content = element_record[:content] + when :element_bigdecimal + the_content = ::BigDecimal.new(element_record[:content].to_s) + when :element_datetime + the_content = ::DateTime::parse(element_record[:content].to_s) + when :element_text + the_content = element_record[:content].to_s + when :element_binary + the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64) + end + existing[(property)] = the_content + end + end + end + else + if record[:content].is_a?(::Array) + # Eliminate elements that the imported record does not have. (tracking structural changes) + if existing.size > record[:content].size + ((record[:content].size)..(existing.size - 1)).each do |the_indexer| + temp_object = existing.delete_at(the_indexer) + if temp_object.is_any?(::GxG::Database::PersistedArray, ::GxG::Database::PersistedHash) + temp_object.destroy() + end + end + end + record[:content].each_with_index do |element_record, indexer| + if element_record[:uuid] + # ### Element is Hash or Array + element = self.retrieve_by_uuid(element_record[:uuid], credential) + unless element + # ### Create blank element record and retrieve + # Note: since there is no way to create a Persisted Object with a given UUID, this method is used to create the object: object will be updated later. + unless @connector[(element_record[:type].to_s.downcase.to_sym)].filter({:uuid => element_record[:uuid].to_s}).count > 0 + # This prevents no-element-retrieved-due-to-permission issue causing an 'add duplicate uuid object' situation: + dbid = @connector[(element_record[:type].to_s.downcase.to_sym)].insert({:uuid => element_record[:uuid].to_s, :title => "Merged Persisted Object"}) + self.assign_element_permission(element_record[:type].to_s.downcase.to_sym,dbid,credential,{:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + # + end + element = self.retrieve_by_uuid(element_record[:uuid], credential) + end + if element + # ### Ensure Element Linking + # Review : Warning - absence of a :write permission can bugger this up. + clean_up_list << element + element.get_reservation() + unless element.write_permission?() && element.write_reserved?() + raise Exception, "Failed to secure write permission and write reservation for this credential." + end + existing[(indexer)] = element + # element.deactivate ?? + end + # Note: return to processing queue to set its elements + queue << element_record + else + # ### Base Element + unless existing.version(indexer) > element_record[:version] + the_content = nil + case element_record[:type].to_s.downcase.to_sym + when :element_boolean, :element_integer, :element_float + the_content = element_record[:content] + when :element_bigdecimal + the_content = ::BigDecimal.new(element_record[:content].to_s) + when :element_datetime + the_content = ::DateTime::parse(element_record[:content].to_s) + when :element_text + the_content = element_record[:content].to_s + when :element_binary + the_content = ::GxG::ByteArray.new(element_record[:content].to_s.decode64) + end + existing[(indexer)] = the_content + end + end + # + end + end + end + # ### Set Fromatting / Constraints + if existing.is_a?(::GxG::Database::PersistedHash) + if formatting[:format].to_s.size > 0 + existing.format = (formatting[:format]) + end + end + if existing.is_a?(::GxG::Database::PersistedArray) + if formatting[:constraint].to_s.size > 0 + existing.constraint = (formatting[:constraint]) + end + end + # ### Conclude Existing + existing.version = (record[:version]) + existing.save + existing.deactivate + total_records_processed += 1 + end + end + end + end + # + end + when :delete + unless self.db_permissions()[:write] + raise Exception, "Attempted to alter a read-only database." + end + # + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential) + if existing + the_address = existing.db_address() + existing.deactivate + if self.element_destroy(credential,the_address[:table],the_address[:dbid]) + total_records_processed += 1 + else + log_warn("Record #{entry[:uuid].inspect} was not deleted.") + end + else + log_warn("Record #{entry[:uuid].inspect} was not found.") + end + else + log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}") + end + end + # + when :fetch + # wrap export record in a :merge operation and add to updates_provided. + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + existing = self.retrieve_by_uuid(entry[:uuid].to_sym, credential) + if existing + updates_provided << {:operation => :merge, :data => [(existing.export())]} + existing.deactivate + total_records_processed += 1 + else + log_warn("Record #{entry[:uuid].inspect} was not found.") + end + else + log_warn("Invalid UUID specifier (ignoring): #{entry[:uuid].inspect}") + end + end + # + when :merge_format + unless self.db_permissions()[:write] + raise Exception, "Attempted to alter a read-only database." + end + # add format record if missing - check for existing, and provide if ours is newer. + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + existing = self.format_load({:uuid => entry[:uuid].to_sym}) + if existing.is_a?(::Hash) + if existing[:version] < entry[:version] + if self.format_update(entry) + total_formats_processed += 1 + else + log_warn("Could not update Format #{entry[:uuid].inspect}") + end + end + if existing[:version] > entry[:version] + unless formats_provided.include?(existing) + formats_provided << existing + total_formats_processed += 1 + end + end + else + if self.format_create(entry) + total_formats_processed += 1 + else + log_warn("Could not create Format #{entry[:uuid].inspect}") + end + end + else + log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}") + end + end + when :delete_format + unless self.db_permissions()[:write] + raise Exception, "Attempted to alter a read-only database." + end + # If the format is in use, log_warn that you're ignoring the directive. + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + if @connector[:element_hash].filter({:format => entry[:uuid].to_s}).count == 0 && @connector[:element_array].filter({:constraint => entry[:uuid].to_s}).count == 0 + # Not in use, safe to delete + if self.format_destroy({:uuid => entry[:uuid].to_sym}) + total_formats_processed += 1 + end + else + log_warn("Format #{entry[:uuid].inspect} in use (ignorning).") + end + else + log_warn("Invalid format specifier on :delete directive (ignorning): #{entry[:uuid].inspect}") + end + end + when :formats_provided + unless self.db_permissions()[:write] + raise Exception, "Attempted to alter a read-only database." + end + # compare uuids against db-installed formats and add to formats_needed if missing. + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + if self.format_list({:uuid => (entry[:uuid])}).size == 0 + record = {:uuid => (entry[:uuid].to_sym)} + unless formats_needed.include?(record) + formats_needed << record + total_formats_processed += 1 + end + end + else + log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}") + end + end + when :formats_needed + # Gather up existing format records to share. + manifest = [] + if the_operation[:data].is_a?(::Array) + manifest = the_operation[:data] + else + if the_operation[:data].is_a?(::Hash) + manifest = [(the_operation[:data])] + else + log_warn("Invalid record sepecifier or manifiest (ignoring): #{the_operation[:data].inspect}") + end + end + manifest.each do |entry| + if ::GxG::valid_uuid?(entry[:uuid]) + existing = self.format_load({:uuid => entry[:uuid].to_sym}) + if existing.is_a?(::Hash) + unless formats_provided.include?(existing) + formats_provided << existing + total_formats_processed += 1 + end + end + else + log_warn("Invalid Format specifier (ignoring): #{entry[:uuid].inspect}") + end + end + # + end + else + log_warn("Invalid operation (ignoring): #{the_operation[:operation].inspect}") + end + else + raise Exception, "Invalid formatted operation passed (MUST be a Hash): #{the_operation.inspect}" + end + end + # + else + raise Exception, "Database not available." + end + else + raise Exception, "Attempted to access a defunct database." + end + rescue Exception => the_error + clean_up_list.each do |the_object| + if the_object.alive? + the_object.release_reservation() + the_object.deactivate() + end + end + result[:status] = :failed + #log_error({:error => the_error, :parameters => {:records => (the_records), :options => (options)}}) + log_error({:error => the_error, :parameters => {}}) + end + result[:formats_needed] = formats_needed + result[:formats_provided] = formats_provided + result[:record_updates] = updates_provided + result[:records_processed] = total_records_processed + result[:formats_processed] = total_formats_processed + result + end + # + def sync_export(credential=nil, uuid_list=[], options={}) + result = {:formats => {}, :records => []} + if uuid_list.is_any?(::Array, ::GxG::Database::PersistedArray) + uuid_list.each do |the_reference| + the_uuid = the_reference + if ::GxG::valid_uuid?(the_uuid) + the_object = self.retrieve_by_uuid(the_uuid, credential) + if the_object + object_record = the_object.export() + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + format_sample = self.format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + result[:records] << object_record + # + end + end + end + end + result + end + # + def sync_import(credential=nil, the_records={:formats => {}, :records => []}, options={}) + result = false + # + if the_records.is_a?(::Hash) + if the_records[:formats].is_a?(::Hash) + op_frame = {:operation => :merge_format, :data => []} + the_records[:formats].each_pair do |the_uuid, the_format_record| + if the_format_record[:version].is_a?(::String) + the_format_record[:version] = ::BigDecimal.new(the_format_record[:version]) + end + # gxg_import format content prior to sync update op + the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content]) + op_frame[:data] << the_format_record + end + if op_frame[:data].size > 0 + self.synchronize_records([(op_frame)],credential) + end + end + # + if the_records[:records].is_a?(::Array) + if the_records[:records].size > 0 + the_records[:records].each do |the_record| + the_record.search do |value, selector, container| + if selector == :version && value.is_a?(::String) + container[(selector)] = ::BigDecimal.new(value) + end + end + end + self.synchronize_records([{:operation => :merge, :data => the_records[:records]}],credential) + result = true + end + end + end + # + result + end + # Class method for importing an export record as a detached structure. + def self.detached_import(import_record=nil) + result = nil + unless import_record.is_any?(::Hash, ::GxG::Database::PersistedHash) + raise Exception, "Malformed import record passed." + end + # {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}} + import_db = [{:target => ::GxG::Database::DetachedHash.new, :record => import_record}] + result = import_db[0][:target] + while import_db.size > 0 do + entry = import_db.shift + case entry[:record][:type].to_s.to_sym + when :element_hash + entry[:target].uuid = entry[:record][:uuid].to_s.to_sym + entry[:target].title = entry[:record][:title].to_s + entry[:record][:content].keys.each do |the_key| + case entry[:record][:content][(the_key)][:type].to_s.to_sym + when :element_hash + new_target = GxG::Database::DetachedHash.new + entry[:target][(the_key)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} + entry[:target][(the_key)].parent = (entry[:target]) + when :element_array + new_target = GxG::Database::DetachedArray.new + entry[:target][(the_key)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} + entry[:target][(the_key)].parent = (entry[:target]) + when :element_boolean, :element_integer, :element_float, :element_text + entry[:target][(the_key)] = entry[:record][:content][(the_key)][:content] + when :element_bigdecimal + entry[:target][(the_key)] = ::BigDecimal.new(entry[:record][:content][(the_key)][:content].to_s) + when :element_datetime + entry[:target][(the_key)] = ::Chronic::parse(entry[:record][:content][(the_key)][:content].to_s) + when :element_binary + entry[:target][(the_key)] = ::GxG::ByteArray.new(entry[:record][:content][(the_key)][:content].to_s.decode64) + end + end + entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) + entry[:target].format = entry[:record][:format].to_s.to_sym + when :element_array + entry[:target].uuid = entry[:record][:uuid].to_s.to_sym + entry[:target].title = entry[:record][:title].to_s + entry[:record][:content].each_index do |indexer| + case entry[:record][:content][(indexer)][:type].to_s.to_sym + when :element_hash + new_target = GxG::Database::DetachedHash.new + entry[:target][(indexer)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} + entry[:target][(indexer)].parent =(entry[:target]) + when :element_array + new_target = GxG::Database::DetachedArray.new + entry[:target][(indexer)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} + entry[:target][(indexer)].parent = (entry[:target]) + when :element_boolean, :element_integer, :element_float, :element_text + entry[:target][(indexer)] = entry[:record][:content][(indexer)][:content] + when :element_bigdecimal + entry[:target][(indexer)] = ::BigDecimal.new(entry[:record][:content][(indexer)][:content].to_s) + when :element_datetime + entry[:target][(indexer)] = ::Chronic::parse(entry[:record][:content][(indexer)][:content].to_s) + when :element_binary + entry[:target][(indexer)] = ::GxG::ByteArray.new(entry[:record][:content][(indexer)][:content].to_s.decode64) + end + end + entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) + entry[:target].constraint = entry[:record][:constraint].to_s.to_sym + end + end + # + result + end + # Class method to import an export package as a detached object. + def self.detached_package_import(the_records={:formats => {}, :records => []}, options={}) + result = [] + # + if the_records.is_a?(::Hash) + if the_records[:formats].is_a?(::Hash) + op_frame = {:operation => :merge_format, :data => []} + the_records[:formats].each_pair do |the_uuid, the_format_record| + if the_format_record[:version].is_a?(::String) + the_format_record[:version] = ::BigDecimal.new(the_format_record[:version]) + end + # gxg_import format content prior to sync update op + the_format_record[:content] = ::Hash.gxg_import(the_format_record[:content]) + # Add to ::GxG::DB[:formats] ?? + unless ::GxG::DB[:formats][(the_uuid.to_s.to_sym)] + # ?? Review : thread safety ?? + ::GxG::DB[:formats][(the_uuid.to_s.to_sym)] = the_format_record + end + op_frame[:data] << the_format_record + end + if op_frame[:data].size > 0 + ::GxG::DB[:roles][:formats].synchronize_records([(op_frame)],::GxG::DB[:administrator]) + end + end + # + if the_records[:records].is_a?(::Array) + if the_records[:records].size > 0 + the_records[:records].each do |the_record| + the_record.search do |value, selector, container| + if selector == :version && value.is_a?(::String) + container[(selector)] = ::BigDecimal.new(value) + end + end + # + result << ::GxG::Database::detached_import(the_record) + # + end + end + end + end + # + result + end + # + # ### Utility methods + def clear_all_element_locks() + # for when boo-boos happen + if self.open?() + if self.db_permissions()[:write] + @connector[:element_locks].filter({}).delete + end + end + end + # + # ### Various methods + # Comm objects cannot be serialized - return encoded nil + def serialize() + "marshal:BAgw" + end + # + # Comm objects cannot be cloned - returns nil + def initialize_clone + nil + end + alias :initialize_dup :initialize_clone + alias :dup :initialize_clone + def clone() + initialize_clone + end + # + def initialize(connection = nil, options={}) + # TODO: require :credential (access privs) + super() + @credential = nil + @url = options[:url] + @base_permissions = {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false} + # + if connection.is_a?(::Sequel::Database) + @connector = connection + # + @active = false + unless open?() + raise Exception, "Unable to connect with the database" + end + # + @scheme = @url.scheme.to_s.to_sym + case @scheme + when :sqlite, :sqlite3 + @connector.run("PRAGMA page_size=65536") + @connector.run("PRAGMA max_page_count=281474976710656") + @connector.run("PRAGMA encoding='UTF-8'") + when :mysql, :mysql2 + @connector.run("SET GLOBAL max_allowed_packet=17825792;") + @connector.run("SET NAMES 'utf8' COLLATE 'utf8_general_ci';") + @connector.run("SET collation_connection = 'utf8_general_ci';") + when :postgres + # place holder + end + else + raise Exception, "Invalid db connection" + end + # characterize basic effective permissions + begin + formatted = db_formatted?() + @base_permissions[:read] = true + @base_permissions[:execute] = true + rescue Exception => the_error + formatted = false + raise Exception, "Lack read permissions to the database" + end + # + unless options[:read_only] + if formatted + begin + dbid = @connector[:text_page].insert() + rescue Exception => the_error + dbid = nil + end + if dbid.is_a?(::Integer) + @connector[:text_page].filter({:dbid => (dbid)}).delete + @base_permissions[:write] = true + @base_permissions[:create] = true + @base_permissions[:destroy] = true + @base_permissions[:rename] = true + @base_permissions[:move] = true + end + else + begin + test_table_name = ::GxG::uuid_generate().gsub("-","") + while (@connector.table_exists?(test_table_name.to_sym)) do + test_table_name = ::GxG::uuid_generate().gsub("-","") + end + @connector.create_table(test_table_name.to_sym) do + primary_key :dbid + end + @connector.drop_table(test_table_name.to_sym) + @base_permissions[:write] = true + @base_permissions[:create] = true + @base_permissions[:destroy] = true + @base_permissions[:rename] = true + @base_permissions[:move] = true + rescue Exception => the_error + # Read-only Database + end + end + end + # + @base_permissions.freeze + # + unless formatted + if @base_permissions[:write] + db_format + unless db_formatted?() + @active = false + raise Exception, "Unable to format the database for use" + end + else + raise Exception, "Unable to format a read-only database" + end + end + # + @authority = nil + @db_list = [] + if options[:authority].is_any?(::GxG::Database::Database, ::GxG::Database::LDAPAuthority) + @authority = options[:authority] + @authority.db_register(self) + end + # + unless @authority + unless self.role_exist?("Administrators") + if @base_permissions[:write] + if self.user_id_available?("root") + self.user_create("root","password") + temp_cred = self.user_credential("root","password") + temp_group = self.role_create("Administrators") + self.role_add_user(temp_group,temp_cred) + # + dev_group = self.role_create("Developers") + dsn_group = self.role_create("Designers") + usr_group = self.role_create("Users") + # + self[:system_credentials] = {:administrator => temp_cred, :administrators => temp_group, :developers => dev_group, :designers => dsn_group, :users => usr_group} + self[:permission_policy] = [{:event => :extend, :credential => :any, :action => {:extend_permissions => temp_group}}] + # + end + if self.user_id_available?("public") + self.user_create("public","password", "00000000-0000-4000-0000-000000000000") + end + end + end + end + # + permission_policy_load + self + end + # + end + # + end + # +end +# +require File.expand_path("./gxg_database_persistedarray.rb",File.dirname(__FILE__)) +require File.expand_path("./gxg_database_persistedhash.rb",File.dirname(__FILE__)) +# diff --git a/lib/gxg/gxg_database_persistedarray.rb b/lib/gxg/gxg_database_persistedarray.rb new file mode 100644 index 0000000..e621794 --- /dev/null +++ b/lib/gxg/gxg_database_persistedarray.rb @@ -0,0 +1,3092 @@ +# PeristedArray Code: +module GxG + module Database + # + class DetachedArray + # + private + # + def element_destroy(indexer=nil) + # Review : develop on-server element destroy command path ?? + end + # Note: property_read handled by load_property_links + def element_write(indexer=nil) + # Review : not needed on webclient version ?? + end + # + def load_element_links + # Review : not needed on webclient version ?? + end + # + def refresh_ordinals() + new_links = [] + @element_links.each_with_index do |the_key, ordinal| + @element_links[(ordinal)][:record][:ordinal] = ordinal + new_links << @element_links[(ordinal)] + end + @element_links = new_links + end + # + public + # + def self.create() + result = ::GxG::Database::DetachedArray.new + result.uuid = ::GxG::uuid_generate.to_sym + result.title = "Untitled #{new_object.uuid.to_s}" + result + end + # + def self.create_constrained(the_format=nil) + result = ::GxG::Database::DetachedArray::create() + format_record = nil + if ::GxG::valid_uuid?(the_format) + format_record = ::GxG::DB[:roles][:formats].format_load({:uuid => the_format}) + else + format_record = ::GxG::DB[:roles][:formats].format_load({:ufs => the_format}) + end + if format_record + # + unless ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] } + ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] = format_record } + end + # + result.constraint = format_record[:uuid].to_s.to_sym + end + result + end + # + def assimilate_detached(detached_object=nil) + result = false + if detached_object.is_a?(::GxG::Database::DetachedArray) + # Review : ?? the ability to ingest a detached_object's data while retaining self's identity data. worth it ?? + object_db = [{:destination => self, :source => detached_object}] + while object_db.size > 0 do + pair = object_db.shift + if pair + pair[:source].search do |item, selector, container| + if item.is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) + if item.is_a?(::GxG::Database::DetachedHash) + unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedHash) + pair[:destination][(selector)] = @db_address[:database].try_persist({}, @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_format => item.format}) + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + else + if item.is_a?(::GxG::Database::DetachedArray) + unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedArray) + pair[:destination][(selector)] = @db_address[:database].try_persist([], @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_constraint => item.constraint}) + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + end + end + # + else + pair[:destination][(selector)] = item + end + end + end + end + # + result = true + end + result + end + # + def detach() + ::GxG::Database::detached_import(self.export()) + end + # + def emit_structures(preference=false) + @as_structures = preference + preference + end + # + def uuid() + @uuid.clone + end + # + def uuid=(the_uuid=nil) + if GxG::valid_uuid?(the_uuid) + @uuid = the_uuid.to_s.to_sym + end + end + # + def title() + @title.clone + end + # + def title=(the_title=nil) + if the_title + @title = the_title.to_s[0..255] + self.increment_version + end + end + # + def version() + @version.clone + end + # + def version=(the_version=nil) + if the_version.is_a?(::Numeric) + @version = the_version.to_s("F").to_d + end + end + # + def element_version(index=nil) + result = 0.0 + if index.is_a?(::Numeric) + # + if @element_links[(index)] + result = @element_links[(index)][:record][:version] + else + result = 0.0 + end + # + end + result + end + # + def set_element_version(element_index, the_version=nil) + result = false + if @element_links[(element_index)] + if the_version.is_a?(::Numeric) + @element_links[(element_index)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) + result = true + else + log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for index #{element_index.inspect} on Object #{@uuid.inspect}") + end + else + log_warning("Attempted to set version with an invalid index #{element_index.inspect} on Object #{@uuid.inspect}") + end + result + end + # + def constraint() + @constraint.clone + end + # + def constraint=(the_format=nil) + if GxG::valid_uuid?(the_format) + @constraint = the_format.to_s.to_sym + end + end + # + def parent() + @parent + end + # + def parent=(object=nil) + if object.is_any?([::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray]) + # Review : parent can only be set once -- is this best?? + unless @parent + @parent = object + end + end + end + # Permission Management: + def get_permissions() + # Review : not needed on webclient version ?? + end + # + def set_permissions(credential=nil, the_permission=nil) + # Review : not needed on webclient version ?? + end + # + def reservation() + # Review : not needed on webclient version ?? + end + # + def write_permission?() + true + end + # + def write_reserved?() + true + end + # + def release_reservation() + true + end + # + def get_reservation() + true + end + # + def wait_for_reservation(timeout=nil) + true + end + # + def get_delegate() + # Review : not needed on webclient version ?? + end + # + def set_delegate(the_delegate=nil) + # Review : not needed on webclient version ?? + end + # + def increment_version() + if self.alive? + if self.write_reserved?() + @version = (((@version + 0.0001) * 10000.0).to_i.to_f / 10000.0) + end + end + end + # + def save_version() + # Review : not needed on webclient version ?? + end + # + def clear_constraint() + if self.alive? + if @constraint + @constraint = nil + end + end + true + end + # + def constraint() + @constraint.clone + end + # + def ufs() + if @format + record = GxG::DB[:formats][(@format.to_s.to_sym)] + if record + record[:ufs].to_s.to_sym + else + "" + end + else + "" + end + end + # + def refresh_links() + # Review : not needed on webclient ?? + end + # + def initialize() + # Review : ! + @as_structures = false + @active = true + @uuid = nil + @title = nil + @version = 0.0 + @constraint = nil + @parent = nil + @data = [] + @element_links = [] + self + end + # + def inspect() + # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with DetachedArray. + # TODO: make interative instead of re-entrant. + result = "[" + @element_links.each_index do |element_index| + result = result + @element_links[(element_index)][:content].inspect + # if @element_links[(element_index)].is_a?(::Hash) + # result = result + @element_links[(element_index)][:content].inspect + # else + # result = result + "(Not Loaded)" + # end + unless element_index == (@element_links.size - 1) + result = result + ", " + end + end + result = result + "]" + result + end + # + def alive?() + @active + end + # + def db_address() + nil + end + # + def save() + # Review : create a save-to-server command path here. + end + # + def deactivate() + ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache].delete(@uuid) } + @active = false + true + end + # + def destroy() + result = false + if self.alive? + result = self.deactivate + end + result + end + # + def size() + @element_links.size + end + # + def structure_attached?() + # Review : not needed on webclient ?? + false + end + def structure_detach() + # Review : not needed on webclient ?? + true + end + # + def []=(indexer=nil, value=nil) + # Only works with Integer indexes. + unless self.alive?() + raise Exception, "Attempted to alter a defunct structure" + end + result = nil + if indexer.is_a?(::Integer) + if value.is_a?(::OpenStruct) + value = value.table + end + # Review : rewrite ???? + if value.is_any?(::GxG::Database::valid_field_classes()) || value.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray, ::Hash, ::Array) + unless self.write_reserved?() + self.get_reservation() + end + if self.write_reserved?() + # Further screen value provided: + # ### Check provided DetachedHashes and DetachedArrays + # ### Check provided Hashes and Arrays + if value.is_any?(::Hash, ::Array) + value.search do |the_value, the_selector, the_container| + unless the_value.is_any?(::GxG::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray, ::Hash, ::Array) + raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)." + end + end + end + # ### Property Exists? + if @element_links[(indexer)] + # set property to value + operation = :set_value + else + if @constraint + raise Exception, "Formatted - the structure cannot be altered" + else + # add property : value pair + operation = :add_value + end + end + # ### Prepare new value + new_value = { + :linkid => nil, + :content => nil, + :loaded => false, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :ordinal => 0, + :version => 0.0, + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::Time.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_binary => nil, + :element_array => nil, + :element_hash => nil + } + } + # + if value.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + # ### Assimilate DetachedHash or DetachedArray + new_value[:content] = value + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::GxG::Database::DetachedHash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash] = value + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array] = value + end + else + # ### Persist Hashes and Arrays + if value.is_any?(::Hash, ::Array) + new_value[:content] = ::GxG::Database::iterative_detached_persist(value) + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::Hash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash] = new_value[:content] + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array] = new_value[:content] + end + else + # ### Persist Base Element Values + new_value[:record][:element] = ::GxG::Database::element_table_for_instance(value).to_s + case new_value[:record][:element] + when "element_boolean" + case value.class + when ::NilClass + new_value[:record][:element_boolean] = -1 + new_value[:content] = nil + when ::FalseClass + new_value[:record][:element_boolean] = 0 + new_value[:content] = false + when ::TrueClass + new_value[:record][:element_boolean] = 1 + new_value[:content] = true + end + when "element_integer" + new_value[:record][:element_integer] = value + new_value[:content] = value + when "element_float" + new_value[:record][:element_float] = value + new_value[:content] = value + when "element_bigdecimal" + new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) + new_value[:content] = new_value[:record][:element_bigdecimal] + when "element_datetime" + new_value[:record][:element_datetime] = value + new_value[:content] = value + when "element_text" + new_value[:record][:element_text] = value + new_value[:record][:length] = value.size + new_value[:content] = value + when "element_binary" + # Note: be sure to keep version & length sync'd with linked binary element record + if operation == :set_value + new_value[:record][:version] = @element_links[(indexer)][:record][:version] + new_value[:record][:element_binary_uuid] = @element_links[(indexer)][:record][:element_binary_uuid] + end + new_value[:record][:length] = value.size + new_value[:content] = value + else + raise Exception, "Unable to map an element type for value: #{value.inspect}" + end + end + end + # ### Commit Changes + case operation + when :set_value + # Note: Set In-memory value only, don't save unless you have to. + if new_value[:content].is_a?(@element_links[(indexer)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@element_links[(indexer)][:content])) + # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). + new_value[:linkid] = @element_links[(indexer)][:linkid] + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (((@element_links[(indexer)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) + end + new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal] + new_value[:loaded] = true + new_value[:state] = @element_links[(indexer)][:state] + # + if new_value[:content].is_a?(::String) + @element_links[(indexer)] = new_value + end + # + else + # Other class value being substituted. + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (((@element_links[(indexer)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) + end + new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal] + new_value[:loaded] = true + # + @element_links[(indexer)] = new_value + end + # + when :add_value + # Note: set in-memory value and save. + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = 0.0 + end + new_value[:record][:ordinal] = @element_links.size + new_value[:loaded] = true + # + @element_links[(indexer)] = new_value + end + # ### Handle coordination between persisted objects: + if @element_links[(indexer)][:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + @element_links[(indexer)][:content].parent = (self) + end + # + self.increment_version() + result = @element_links[(indexer)][:content] + # + else + raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" + end + else + raise Exception, "The value is not persistable." + end + else + raise Exception, "You must provide a index in the form of an Integer." + end + result + end + # + # + def [](indexer=nil) + result = nil + # if exists + # if is not loaded, load element (if String or ByteArray) + if self.alive?() + if indexer.is_a?(::Integer) + if @element_links[(indexer)] + result = @element_links[(indexer)][:content] + if @as_structures == true + if result.is_a?(::GxG::Database::PersistedHash) + result = ::OpenStruct.new(result) + end + end + end + else + raise ArgumentError, "You must specify with an Integer, not a #{indexer.class.inspect}" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def delete_at(indexer=nil) + result = nil + # if exists + # if is not loaded, load element + if self.alive?() + if indexer.is_a?(::Integer) + if self.write_reserved?() + # + if @element_links[(indexer)] + # This will load the unloaded prior to separation. + result = self[(indexer)] + if @as_structures == true + if result.is_a?(::GxG::Database::PersistedHash) + result = ::OpenStruct.new(result) + end + end + the_link = @element_links.delete_at(indexer) + # Review : element-unlink on server side ?? + if the_link[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + the_link[:content].save + end + self.increment_version() + end + # + else + raise Exception, "You do not have sufficient privileges to make this change" + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def <<(*args) + if args.size > 0 + args.each do |item| + self[(@element_links.size)] = item + end + end + self + end + # + def push(*args) + if args.size > 0 + args.each do |item| + self << item + end + end + self + end + # + def pop() + self.delete_at((@element_links.size - 1)) + end + # + def shift() + self.delete_at(0) + end + # + def insert(the_index=nil, the_object=nil) + # resovle the_index : the_index < 0 : the_index = (size - the_index) + # if the_index == 0 : @element_links.unshift(nil-record); self[0] = the_object + # if the_index > size-1 : push(the_object) + # else: @element_links.insert(the_index, nil-record); self[(the_index)] = the_object + # vette the_object --> persistable? + if ::GxG::Database::persistable?(the_object) + new_value = { + :linkid => nil, + :content => nil, + :loaded => false, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :ordinal => 0, + :version => ::BigDecimal.new("0.0"), + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::DateTime.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_text_uuid => "", + :element_binary_uuid => "", + :element_array_uuid => "", + :element_hash_uuid => "" + } + } + the_index = the_index.to_i + if the_index < 0 + the_index = (self.size - the_index) + end + if the_index == 0 + new_value[:record][:ordinal] = the_index + @element_links.unshift(new_value) + refresh_ordinals + self[0] = the_object + else + if the_index > (self.size - 1) + self.push(the_object) + else + new_value[:record][:ordinal] = the_index + @element_links.insert(the_index, new_value) + refresh_ordinals + self[(the_index)] = the_object + end + end + end + self + end + # ???? def insert - ::GxG::Database::persistable?(the_object) + def unshift(the_object=nil) + self.insert(0,the_object) + end + # + def swap(first_index=nil, last_index=nil) + result = false + if first_index && last_index + if (0..(self.size - 1)).include?(first_index.to_i) && (0..(self.size - 1)).include?(last_index.to_i) + if first_index.to_i != last_index.to_i + record = @element_links[(first_index)] + @element_links[(first_index)] = @element_links[(last_index)] + @element_links[(last_index)] = record + result = true + end + end + end + result + end + # + def include?(the_value) + result = false + if the_value.is_a?(::OpenStruct) + the_value = the_value.table + end + @element_links.each do |element| + if element[:content] == the_value + result = true + break + end + end + result + end + # + def find_index(the_value) + result = nil + if the_value.is_a?(::OpenStruct) + the_value = the_value.table + end + @element_links.each_with_index do |element, indexer| + if element[:content] == the_value + result = indexer + break + end + end + result + end + # + def export(options={:exclude_file_segments=>false}) + if self.alive? + exclude_file_segments = (options[:exclude_file_segments] || false) + if options[:clone] == true + # Review : why are cloned objects unconstrained? sync issues?? + result = {:type => :element_array, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.clone, :content => []} + else + result = {:type => :element_array, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :constraint => @constraint.clone, :content => []} + end + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::DetachedHash) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :format => the_value.format().clone, :content => {}}} + end + end + if the_value.is_a?(::GxG::Database::DetachedArray) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().clone, :constraint => the_value.constraint().clone, :content => []}} + end + end + if the_value.is_any?(::GxG::Database::valid_field_classes()) + data_type = GxG::Database::element_table_for_instance(the_value) + case data_type + when :element_bigdecimal + data = the_value.to_s + when :element_datetime + data = the_value.iso8601.to_s + when :element_binary + data = the_value.to_s.encode64 + when :element_text + data = the_value.to_s + else + data = the_value + end + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (the_container.version(the_selector) || 0.0), :content => data}} + end + end + # Collect children export content: + link_db = [(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + link_db << the_child + end + end + end + else + result = nil + end + result + end + # + def export_package(options={:exclude_file_segments=>false}) + result = {:formats => {}, :records => []} + object_record = self.export(options) + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + format_sample = ::GxG::DB[:roles][:formats].format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + result[:records] << object_record + result + end + # + def first() + if @element_links.size > 0 + self[0] + else + nil + end + end + # + def last() + if @element_links.size > 0 + self[(@element_links.size - 1)] + else + nil + end + end + # + def each(&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.each_index do |index| + block.call(self[(index)]) + end + end + self + else + self.to_enum(:each) + end + end + # + def each_index(&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.each_index do |index| + block.call(index) + end + end + self + else + self.to_enum(:each_index) + end + end + # + def each_with_index(offset=0,&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.to_enum(:each).with_index(offset).each do |entry, index| + block.call(self[(index)], index) + end + end + self + else + self.to_enum(:each_with_index,offset) + end + end + # + def iterative(options={:include_inactive => true}, &block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_a?(::GxG::Database::DetachedHash) + if a_node[:object].alive? + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + if a_node[:object].is_a?(::GxG::Database::DetachedArray) + if a_node[:object].alive? + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + # + if a_node.alive? || options[:include_inactive] + accumulator << a_node + end + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + # + def process!(options={:include_inactive => true}, &block) + self.iterative(options, &block) + self + end + # + def search(options={:include_inactive => true}, &block) + results = [] + if block.respond_to?(:call) + results = self.iterative(options, &block) + end + results + end + # + def paths_to(the_object=nil,base_path="") + # new idea here: + if the_object.is_a?(::OpenStruct) + the_object = the_object.table + end + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key.gsub!("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric + # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu + # + # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/")) + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + def unpersist() + result = [] + if self.alive? + # + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::DetachedHash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} + else + if the_value.is_a?(::GxG::Database::DetachedArray) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + link_db << the_child + end + end + end + # + end + result + end + # + end + # + class PersistedArray + # + private + # + def element_destroy(indexer=nil) + # Destroy database property record, but leave in-memory property intact - for now. (preserves key-ordinal position) + result = false + if indexer + if @element_links[(indexer)] + element = @element_links[(indexer)] + # ### Delete sub-object linked to. + the_table = nil + the_uuid = nil + if element[:record][:element_text_uuid].size > 0 + the_table = :element_text + the_uuid = property[:record][:element_text_uuid] + end + if element[:record][:element_binary_uuid].size > 0 + the_table = :element_binary + the_uuid = element[:record][:element_binary_uuid] + end + if element[:record][:element_array_uuid].size > 0 + the_table = :element_array + the_uuid = element[:record][:element_array_uuid] + end + if element[:record][:element_hash_uuid].size > 0 + the_table = :element_hash + the_uuid = element[:record][:element_hash_uuid] + end + case the_table + when :element_text + the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_uuid}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) + end + when :element_binary + the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_uuid}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) + end + when :element_array, :element_hash + @db_address[:database].destroy_by_uuid(@credential, the_uuid) + end + # ### Delete property record. + @db_address[:database].connector()[:array_elements].filter({:dbid => element[:linkid]}).delete + result = true + end + end + result + end + # Note: property_read handled by load_property_links + def element_write(indexer=nil) + result = false + if indexer + if @element_links[(indexer)] + element = @element_links[(indexer)] + # Save out text and binary pages if needed. + if element[:content].is_a?(::String) + if element[:content].size > 256 + unless element[:record][:element_text_uuid].size > 0 + # create text header + the_uuid = ::GxG::uuid_generate + while @db_address[:database].connector()[:element_text].filter({:uuid => the_uuid.to_s}).count > 0 do + the_uuid = ::GxG::uuid_generate + end + element[:record][:length] = element[:content].size + the_dbid = @db_address[:database].connector()[:element_text].insert({:uuid => the_uuid.to_s, :version => element[:record][:version], :length => element[:record][:length]}) + # + element[:record][:element_text_uuid] = the_uuid + end + end + else + if element[:content].is_a?(::GxG::ByteArray) + unless element[:record][:element_binary_uuid].size > 0 + # create binary header + the_uuid = ::GxG::uuid_generate + while @db_address[:database].connector()[:element_binary].filter({:uuid => the_uuid.to_s}).count > 0 do + the_uuid = ::GxG::uuid_generate + end + element[:record][:length] = element[:content].size + # format field?? + the_dbid = @db_address[:database].connector()[:element_binary].insert({:uuid => the_uuid.to_s, :version => element[:record][:version], :length => element[:record][:length]}) + # + element[:record][:element_binary_uuid] = the_uuid + end + end + end + # Store text pages + if element[:record][:element_text_uuid].size > 0 + header_record = @db_address[:database].connector()[:element_text].filter({:uuid => element[:record][:element_text_uuid].to_s}).first + if header_record + old_segments = [] + @db_address[:database].connector()[:text_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if element[:content].size == 0 + # delete all text_pages if count > 0 + if old_segments.size > 0 + @db_address[:database].connector()[:text_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete + end + else + new_segments = ::GxG::apportioned_ranges(element[:content].size, 4096) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + # + manifest.each do |action| + case action[:operation] + when :overwrite + @db_address[:database].element_update(:text_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => element[:content][(action[:portion])].size, :content => element[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :create + new_dbid = @db_address[:database].element_create(:text_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => element[:content][(action[:portion])].size, :content => element[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :delete + @db_address[:database].element_destroy(@credential,:text_page,action[:dbid]) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + # + end + end + else + if element[:record][:element_binary_uuid].size > 0 + header_record = @db_address[:database].connector()[:element_binary].filter({:uuid => element[:record][:element_binary_uuid].to_s}).first + if header_record + old_segments = [] + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if element[:content].size == 0 + # delete all text_pages if count > 0 + if old_segments.size > 0 + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete + end + else + new_segments = ::GxG::apportioned_ranges(element[:content].size, 65536) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + # + manifest.each do |action| + case action[:operation] + when :overwrite + @db_address[:database].element_update(:binary_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => element[:content][(action[:portion])].size, :content => element[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :create + new_dbid = @db_address[:database].element_create(:binary_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => element[:content][(action[:portion])].size, :content => element[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :delete + @db_address[:database].element_destroy(@credential,:binary_page,action[:dbid]) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + # + end + end + end + end + # ### Create new property record if missing: otherwise just update it. + if element[:linkid] == nil || element[:linkid] == 0 + element[:linkid] = @db_address[:database].connector()[:array_elements].insert(element[:record]) + else + @db_address[:database].connector()[:array_elements].filter({:dbid => element[:linkid]}).update(element[:record]) + end + # + element[:state] = element[:content].hash + result = true + end + end + result + end + # + def load_element_links + # + result = true + begin + if @db_address + if @db_address[:database].is_a?(::GxG::Database::Database) + if @db_address[:database].open?() + new_links = [] + the_records = @db_address[:database].connector()[:array_elements].filter({:parent_uuid => @uuid.to_s}).order(:ordinal) + the_records.each_with_index do |entry, indexer| + # (element indexer) => {:linkid => 0, :content => nil, :loaded => false, :state => 0, :record => {}} + new_links[(indexer)] = {:linkid => entry.delete(:dbid), :content => nil, :loaded => false, :state => 0, :record => entry} + # Load indicated :content + case new_links[(indexer)][:record][:element] + when "element_boolean" + case new_links[(indexer)][:record][:element_boolean] + when -1 + new_links[(indexer)][:content] = nil + new_links[(indexer)][:loaded] = true + when 0 + new_links[(indexer)][:content] = false + new_links[(indexer)][:loaded] = true + when 1 + new_links[(indexer)][:content] = true + new_links[(indexer)][:loaded] = true + end + when "element_integer" + new_links[(indexer)][:content] = new_links[(indexer)][:record][:element_integer] + new_links[(indexer)][:loaded] = true + when "element_float" + new_links[(indexer)][:content] = new_links[(indexer)][:record][:element_float] + new_links[(indexer)][:loaded] = true + when "element_bigdecimal" + new_links[(indexer)][:content] = ::BigDecimal.new(new_links[(indexer)][:record][:element_bigdecimal].to_s) + new_links[(indexer)][:loaded] = true + when "element_datetime" + new_links[(indexer)][:content] = new_links[(indexer)][:record][:element_datetime] + if new_links[(indexer)][:content].is_a?(::Time) + new_links[(indexer)][:content] = new_links[(indexer)][:content].to_datetime() + end + new_links[(indexer)][:loaded] = true + when "element_text" + if new_links[(indexer)][:record][:element_text_uuid].size > 0 + # Load full text content from pages + new_links[(indexer)][:content] = "" + @db_address[:database].connector()[:text_page].filter({:parent_uuid => new_links[(indexer)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + new_links[(indexer)][:content] << text_page[:content] + end + else + new_links[(indexer)][:content] = new_links[(indexer)][:record][:element_text] + end + new_links[(indexer)][:loaded] = true + when "element_binary" + # Load binary data + new_links[(indexer)][:content] = ::GxG::ByteArray.new + new_links[(indexer)][:loaded] = false + # Review : support on-read segment loading and element unloading. (thinking of large binary files here) + when "element_array" + # Load PersistedArray + new_links[(indexer)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(indexer)][:record][:element_array_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) + if new_links[(indexer)][:content].is_a?(::GxG::Database::PersistedArray) + new_links[(indexer)][:loaded] = true + new_links[(indexer)][:state] = new_links[(indexer)][:content].hash + new_links[(indexer)][:content].set_parent(self) + else + new_links[(indexer)][:loaded] = false + end + when "element_hash" + # Load PersistedHash + new_links[(indexer)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(indexer)][:record][:element_hash_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) + if new_links[(indexer)][:content].is_a?(::GxG::Database::PersistedHash) + new_links[(indexer)][:loaded] = true + new_links[(indexer)][:state] = new_links[(indexer)][:content].hash + new_links[(indexer)][:content].set_parent(self) + else + new_links[(indexer)][:loaded] = false + end + end + end + @element_links = new_links + else + raise Exception, "Database not available" + end + else + raise Exception, "Invalid Database object" + end + else + # Deactivated + result = false + end + rescue Exception => the_error + result = false + log_error({:error => the_error}) + end + result + end + # + def refresh_ordinals() + if self.alive? + if @db_address[:database].open? + @element_links.each_with_index do |the_key, ordinal| + @element_links[(ordinal)][:record][:ordinal] = ordinal + @db_address[:database].connector()[:array_elements].filter({:dbid => @element_links[(ordinal)][:linkid]}).update({:ordinal => (ordinal)}) + end + end + end + end + # + public + # + def emit_structures(preference=false) + @as_structures = preference + preference + end + # Supplimental Supports + def element_byte_map(class_filter = ::GxG::ByteArray) + result = [] + manifest = [] + @element_links.each_with_index do |record, index| + element_type = nil + if class_filter + if record[:content].is_a?(class_filter) + element_type = @db_address[:database].element_table_for_instance(record[:content]).to_s + end + else + element_type = @db_address[:database].element_table_for_instance(record[:content]).to_s + end + if element_type + manifest << {:index => index, :type => element_type} + end + end + manifest.each do |reference| + record = @db_address[:database].connector()[:array_elements].filter({:parent_uuid => @uuid.to_s, :element => reference[:type], :ordinal => reference[:index]}).first + if record + result << {:index => record[:ordinal], :size => record[:length]} + end + end + result + end + # Permission Management: + def get_permissions() + @permission.clone + end + # + def set_permissions(credential=nil, the_permission=nil) + if self.alive? + if credential.is_a?(::Hash) && the_permission.is_a?(::Hash) + if credential[:user] + if credential[:user] == "public" || credential[:user] == "Public" + credential = "00000000-0000-4000-0000-000000000000" + else + credential = (@db_address[:database].user_fetch({:user_id => credential[:user].to_s}) || {})[:uuid] + end + if credential + credential = credential.to_s.to_sym + end + else + if credential[:group] + credential = @db_address[:database].group_credential(credential) + else + credential = nil + end + end + else + if credential.is_a?(::Hash) && the_permission == nil + the_permission = credential + credential = @credential + else + unless credential + credential = @credential + end + end + end + if the_permission.is_a?(::Hash) && ::GxG::valid_uuid?(credential) + manifest = [] + structures = [{:table => @db_address[:table], :dbid => @db_address[:dbid]}] + while structures.size > 0 do + entry = structures[0] + @db_address[:database].element_manifest(entry[:table],entry[:dbid]).each do |record| + if [:element_hash, :element_array].include?(record[:table]) + unless structures.include?(record) + structures << record + end + else + # Review - only set/get/check permissions on persisted arrays/hashes + # unless manifest.include?(record) + # manifest << record + # end + end + end + manifest << structures.shift + end + manifest.each do |entry| + @db_address[:database].assign_element_permission(entry[:table], entry[:dbid], credential, the_permission) + end + # + true + else + log_error({:error => Exception.new("You MUST provide a valid permissions Hash."), :parameters => {:credential => credential, :permission => the_permission}}) + false + end + end + end + # + def reservation() + if self.alive? + if @reservation.is_any?(::String, ::Symbol) + @reservation.clone + end + end + end + # + def write_permission?() + @permission[:write] + end + # + def write_reserved?() + if @reservation or @delegate + true + else + false + end + end + # + def release_reservation() + if self.alive?() + if @reservation + @db_address[:database].release_element_locks(@reservation) + @reservation = nil + end + if @delegate + @delegate = nil + end + # release write-locks of all sub-items + @element_links.each_index do |ordinal| + if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + @element_links[(ordinal)][:content].release_reservation + end + end + end + true + end + # + def get_reservation() + result = false + # Review : Experimental design change - deprecate @delegate use, require entire structure (and sub-structures) to get solid @reservation (all of them) or this fails. + # Note : keep your structures small as this opens the door for stack-depth exceptions if it is nested too deep. (say 256 or less layers deep??) + if @reservation + result = true + else + if self.alive? + if @permission[:write] + clean_up_list = [] + # Review : efficiency - write locks only apply to PersistedHashes and PersistedArrays so do we need a full manifest?? + the_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) + if the_reservation + @element_links.each_index do |ordinal| + if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + clean_up_list << @element_links[(ordinal)][:content] + unless @element_links[(ordinal)][:content].get_reservation() + @db_address[:database].release_element_locks(the_reservation) + the_reservation = nil + break + end + end + end + # + end + if the_reservation + @reservation = the_reservation + result = true + else + clean_up_list.each do |the_object| + the_object.release_reservation + end + end + end + end + end + # xxx keep old code for now + # if (@reservation || @delegate) + # result = true + # else + # if self.alive? + # if @permission[:write] + # @reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) + # if @reservation + # @element_links.each_index do |ordinal| + # if @element_links[(ordinal)][:content].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + # @element_links[(ordinal)][:content].set_delegate((@reservation || @delegate)) + # end + # end + # result = true + # end + # # + # end + # end + # end + # xxx end old code + result + end + # + def wait_for_reservation(timeout=nil) + result = true + the_timeout = Time.now.to_f + (timeout || 30.0).to_f + until self.get_reservation() == true do + sleep 1.0 + if Time.now.to_f >= the_timeout + result = false + break + end + end + result + end + # + def get_delegate() + if self.alive? + if @delegate.is_any?(::String, ::Symbol) + @delegate.clone + end + end + end + # + def set_delegate(the_delegate=nil) + if self.alive? + if the_delegate.is_any?([::String, ::Symbol]) + @delegate = the_delegate.to_sym + end + end + end + # + def uuid() + @uuid.clone + end + # + def version(input=nil) + @version.clone + end + # + def version=(the_version=nil) + if the_version.is_a?(::Numeric) + @version = ::BigDecimal.new(the_version.to_s) + end + end + # + def element_version(index=nil) + result = 0.0 + if index.is_a?(::Numeric) + if @element_links[(index)] + result = @element_links[(index)][:record][:version] + end + # + end + result + end + # + def set_element_version(element_index, the_version=nil) + result = false + if @element_links[(element_index)] + if the_version.is_a?(::Numeric) + @element_links[(element_index)][:record][:version] = the_version.to_s("F").to_d + result = true + else + log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for index #{element_index.inspect} on Object #{@uuid.inspect}") + end + else + log_warning("Attempted to set version with an invalid index #{element_index.inspect} on Object #{@uuid.inspect}") + end + result + end + # + def increment_version() + if self.alive? + if @db_address[:database].open? + if self.write_reserved?() + @version += 0.0001 + end + end + end + end + # + def save_version() + @db_address[:database].connector()[:element_array].filter({:dbid => @db_address[:dbid]}).update({:version => @version}) + end + # + def set_version(the_version=0.0) + if self.write_reserved?() + @version = the_version.to_s("F").to_d + self.save_version + end + end + # + def title() + @title.clone + end + # + def set_title(the_title=nil) + if self.alive? + if @db_address[:database].open? + if self.write_reserved?() + if the_title + @title = the_title.to_s[0..256] + @db_address[:database].connector()[:element_array].filter({:dbid => @db_address[:dbid]}).update({:title => @title}) + self.increment_version + end + end + end + end + end + alias :title= :set_title + # + def set_constraint(format_uuid=nil) + if self.alive? + unless @constraint + if ::GxG::valid_uuid?(format_uuid) + @constraint = format_uuid.to_sym + @db_address[:database].connector()[:element_array].filter({:dbid => (@db_address[:dbid])}).update({:constraint => (@constraint.to_s)}) + end + end + end + end + alias :constraint= :set_constraint + # + def clear_constraint() + if self.alive? + if @constraint + @constraint = nil + @db_address[:database].connector()[:element_array].filter({:dbid => (@db_address[:dbid])}).update({:constraint => ""}) + end + end + true + end + # + def constraint() + @constraint.clone + end + # + def ufs() + if @constraint + record = @db_address[:database].format_load({:uuid => @constraint.to_s}) + if record + record[:ufs] + else + "" + end + else + "" + end + end + # + def parent() + @parent + end + # + def set_parent(object=nil) + if self.alive? + if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + unless @parent + @parent = object + end + end + end + end + # + def refresh_links() + if self.alive?() + unless @db_address[:database].element_in_use?(@db_address[:table], @db_address[:dbid]) + @parent = nil + end + end + end + # + def initialize(settings = {}, options={}) + # mount element: {:database => , :uuid => , :credential => , :delegate => nil/, :parent => nil/} + # create element: {:database => , :credential => , :delegate => nil/, :parent => nil/} + # + if settings.is_a?(::Hash) + unless ::GxG::valid_uuid?(settings[:credential]) + raise ArgumentError, "You must supply a valid credential UUID (37 character limit) as a String or Symbol" + end + @as_structures = false + @quick = settings[:quick] + @credential = settings[:credential].to_sym + @reservation = nil + @delegate = settings[:delegate] + if settings[:parent].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + @parent = settings[:parent] + else + @parent = nil + end + @constraint = settings[:constraint] + unless @constraint + if options[:with_constraint] + @constraint = options[:with_constraint] + end + end + if settings[:database].is_a?(::GxG::Database::Database) + if settings[:database].open?() + the_uuid = settings[:uuid] + if the_uuid.is_a?(::Symbol) + the_uuid = the_uuid.to_s + end + # TODO: validate uuid pattern + if settings[:title].is_a?(::String) + the_title = settings[:title] + else + if options[:with_title] + the_title = options[:with_title] + else + the_title = ("Untitled PersistedArray " << (settings[:database].connector()[:element_array].count.to_s)) + end + end + unless ::GxG::valid_uuid?(the_uuid) + # without specifying a uuid, the implied intent is to create a new PersistedArray + if ::GxG::valid_uuid?(options[:with_uuid]) + the_uuid = options[:with_uuid].to_s + else + the_uuid = ::GxG::uuid_generate() + while ((settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0) or (settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0)) + the_uuid = ::GxG::uuid_generate() + end + end + # create blank array record + new_dbid = settings[:database].connector()[:element_array].insert({:uuid => (the_uuid), :title => (the_title), :constraint => (@constraint.to_s), :version => (options[:with_version] || BigDecimal.new("0.0"))}) + # + @db_address = {:database => (settings[:database]), :table => :element_array, :dbid => (new_dbid)} + if @parent + @permission = @parent.get_permissions() + # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => settings[:parent], :destination => self}) + # settings[:database].extend_element_permissions(settings[:parent].db_address()[:table], settings[:parent].db_address()[:dbid], :element_array, new_dbid) + else + # create raw permissions for this element + @permission = {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true} + # settings[:database].assign_element_permission(:element_array, new_dbid, @credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => self}) + end + settings[:database].assign_element_permission(:element_array, new_dbid, @credential, @permission) + end + # mount according to uuid + if the_uuid + # mount + record = settings[:database].connector()[:element_array].filter({:uuid => (the_uuid)}).first + if record + @uuid = the_uuid.to_sym + @title = record[:title].to_s + @version = record[:version] + @db_address = {:database => (settings[:database]), :table => :element_array, :dbid => (record[:dbid])} + @element_links = [] + if record[:constraint].to_s.size > 0 + @constraint = record[:constraint].to_sym + else + @constraint = nil + end + unless @permission + @permission = @db_address[:database].effective_element_permission(@db_address[:table], @db_address[:dbid], @credential) + end + # @read_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :read) + # + load_element_links + # + else + raise Exception, "Unable to mount the PersistedArray record" + end + else + raise Exception, "Unable to mount the PersistedArray record" + end + # + else + raise Exception, "Database not available for storage" + end + # + else + raise Exception, "Invalid Database object" + end + # + else + raise ArgumentError, "You must supply a hash of details" + end + # + end + # + def inspect() + # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with PersistedArray. + # TODO: make interative instead of re-entrant. + result = "(Not Loaded)" + if @db_address + if @db_address[:database].element_byte_size(@db_address[:table], @db_address[:dbid]) <= 10485760 + # smaller than 10MB + result = "[" + @element_links.each_index do |element_index| + if @element_links[(element_index)].is_a?(::Hash) + result << @element_links[(element_index)][:content].inspect + else + result << "(Not Loaded)" + end + unless element_index == (@element_links.size - 1) + result << ", " + end + end + result << "]" + else + result = "(Too Damned Big)" + end + # + end + result + end + # + def alive?() + if @db_address + if @db_address[:database].element_exists?(@db_address[:table],@db_address[:dbid]) + true + else + false + end + else + false + end + end + # + def db_address() + @db_address + end + # + def deactivate() + if self.alive?() + @element_links.each_index do |index| + element = @element_links[(index)][:content] + if element.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + containers = [(element)] + element.search({:include_inactive => false}) do |the_value, the_selector, the_container| + if the_value.alive? + the_value.deactivate + end + if the_container.alive? + unless containers.include?(the_container) + containers.unshift(the_container) + end + end + nil + end + containers.each do |the_container| + the_container.deactivate + end + end + end + if self.write_reserved?() + self.release_reservation() + end + @element_links = [] + @db_address = nil + end + true + end + # + def save() + result = false + if self.alive? + begin + if @db_address[:database].open?() + @element_links.each_index do |index| + # Note: only save property data and header if: a) data is still loaded and b) state has changed. + if @element_links[(index)][:loaded] == true + if @element_links[(index)][:state] != @element_links[(index)][:content].hash + @element_links[(index)][:record][:version] = (@element_links[(index)][:record][:version].to_s("F").to_d + 0.0001) + element_write(index) + end + end + # + end + else + raise Exception, "Database not available" + end + self.save_version + result = true + rescue Exception => the_error + log_error({:error => the_error}) + end + end + result + end + # + def destroy() + result = false + if self.alive? + begin + unless self.write_reserved?() + raise Exception, "You do not have a write-lock for this element" + end + unless @permission[:destroy] + raise Exception, "You do not have permission to destory this element" + end + if @db_address[:database].open?() + if @db_address[:database].element_in_use?(@db_address[:table], @db_address[:dbid]) + raise Exception, "Element is still linked to a structure. Use .delete().destroy or .delete_at().destroy" + else + if self.write_reserved?() + address = @db_address + self.deactivate + address[:database].element_destroy(@credential, address[:table],address[:dbid]) + # Review : consider either emptying the db trash now, or adding a flag to manifest records to prevent future loading. + result = true + else + raise Exception, "You do not have sufficient privileges to make this change" + end + end + else + raise Exception, "Database not available" + end + rescue Exception => the_error + log_error({:error => the_error}) + end + end + result + end + # + def size() + @element_links.size + end + # + def structure_attached?() + @db_address[:database].structure_attached?(@uuid.to_s) + end + def structure_detach() + @db_address[:database].structure_detach(@uuid.to_s) + end + # + def []=(indexer=nil, value=nil) + # Only works with Integer indexes. + unless self.alive?() + raise Exception, "Attempted to alter a defunct structure" + end + unless @db_address[:database].open?() + raise Exception, "Database not available" + end + result = nil + if value.is_a?(::OpenStruct) + value = value.table + end + if indexer.is_a?(::Integer) + # Review : rewrite ???? + if value.is_any?(::GxG::Database::Database::valid_field_classes()) || value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) + unless self.write_reserved?() + self.get_reservation() + end + if self.write_reserved?() + # Further screen value provided: + # ### Check provided PersistedHashes and PersistedArrays + if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless value.db_address() + raise Exception, "The structure you are attaching is Deactivated (not supported)." + end + if value.structure_attached?() + # Review : experimental - auto-detach from prior structural relationship and reassign here. + # raise Exception, "Cross linked structures not supported" + value.structure_detach + end + value.search do |the_value, the_selector, the_container| + if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless the_value.db_address() + raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." + end + end + end + end + # ### Check provided Hashes and Arrays + if value.is_any?(::Hash, ::Array) + value.search do |the_value, the_selector, the_container| + if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless the_value.db_address() + raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." + end + if @db_address[:database].structure_attached?(the_value.uuid.to_s) && the_container.is_any?(::Hash, ::Array) + raise Exception, "The structure you are attaching has a cross-linked structure within it (cross linked structures not supported)." + end + end + unless the_value.is_any?(::GxG::Database::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) + raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)." + end + end + end + # ### Property Exists? + if @element_links[(indexer)] + # set property to value + operation = :set_value + else + if @format + raise Exception, "Formatted - the structure cannot be altered" + else + # add property : value pair + operation = :add_value + end + end + # ### Prepare new value + new_value = { + :linkid => nil, + :content => nil, + :loaded => false, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :ordinal => 0, + :version => ::BigDecimal.new("0.0"), + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::DateTime.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_text_uuid => "", + :element_binary_uuid => "", + :element_array_uuid => "", + :element_hash_uuid => "" + } + } + # + if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # ### Assimilate PersistedHash or PersistedArray + new_value[:content] = value + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::GxG::Database::PersistedHash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash_uuid] = value.uuid.to_s + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array_uuid] = value.uuid.to_s + end + else + # ### Persist Hashes and Arrays + if value.is_any?(::Hash, ::Array) + new_value[:content] = @db_address[:database].iterative_persist(value,@credential) + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::Hash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash_uuid] = new_value[:content].uuid.to_s + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array_uuid] = new_value[:content].uuid.to_s + end + else + # ### Persist Base Element Values + new_value[:record][:element] = ::GxG::Database::Database::element_table_for_instance(value).to_s + case new_value[:record][:element] + when "element_boolean" + case value.class + when ::NilClass + new_value[:record][:element_boolean] = -1 + new_value[:content] = nil + when ::FalseClass + new_value[:record][:element_boolean] = 0 + new_value[:content] = false + when ::TrueClass + new_value[:record][:element_boolean] = 1 + new_value[:content] = true + end + when "element_integer" + new_value[:record][:element_integer] = value + new_value[:content] = value + when "element_float" + new_value[:record][:element_float] = value + new_value[:content] = value + when "element_bigdecimal" + new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) + new_value[:content] = new_value[:record][:element_bigdecimal] + when "element_datetime" + new_value[:record][:element_datetime] = value + new_value[:content] = value + when "element_text" + if value.size > 256 + # Note: be sure to keep text version & length sync'd with linked text element record + if operation == :set_value + new_value[:record][:version] = @element_links[(indexer)][:record][:version] + new_value[:record][:element_text_uuid] = @element_links[(indexer)][:record][:element_text_uuid] + end + new_value[:record][:length] = value.size + else + new_value[:record][:element_text] = value + new_value[:record][:length] = value.size + end + new_value[:content] = value + when "element_binary" + # Note: be sure to keep version & length sync'd with linked binary element record + if operation == :set_value + new_value[:record][:version] = @element_links[(indexer)][:record][:version] + new_value[:record][:element_binary_uuid] = @element_links[(indexer)][:record][:element_binary_uuid] + end + new_value[:record][:length] = value.size + new_value[:content] = value + else + raise Exception, "Unable to map an element type for value: #{value.inspect}" + end + end + end + # ### Commit Changes + case operation + when :set_value + # Note: Set In-memory value only, don't save unless you have to. + if new_value[:content].is_a?(@element_links[(indexer)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@element_links[(indexer)][:content])) + # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). + new_value[:linkid] = @element_links[(indexer)][:linkid] + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (@element_links[(indexer)][:record][:version] + 0.0001) + end + new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal] + new_value[:loaded] = true + new_value[:state] = @element_links[(indexer)][:state] + # ### Did string grow or shrink crossing the threshold?? + if new_value[:content].is_a?(::String) + if (@element_links[(indexer)][:content].size > 256 && new_value[:content].size <= 256) + # shrank - delete text object and pages as well as property record (slow) + element_destroy(indexer) + @element_links[(indexer)] = new_value + element_write(indexer) + else + if (@element_links[(indexer)][:content].size <= 256 && new_value[:content].size > 256) + # grew - create text object and pages (a little less slow) + @element_links[(indexer)] = new_value + element_write(indexer) + else + # N/A + @element_links[(indexer)] = new_value + end + end + else + # N/A + @element_links[(indexer)] = new_value + end + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # Note: this ensures attachment to this object + property_write(property_key) + end + # + else + # Other class value being substituted. + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (@element_links[(indexer)][:record][:version] + 0.0001) + end + new_value[:record][:ordinal] = @element_links[(indexer)][:record][:ordinal] + new_value[:loaded] = true + # Destroy old property record on db, and associated linked objects. + # Review : this is slow - look into optimizations here. + if @element_links[(indexer)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::GxG::ByteArray, ::String) + if @element_links[(indexer)][:content].is_a?(::String) + if (@element_links[(indexer)][:content].size > 256 && new_value[:content].size <= 256) || (@element_links[(indexer)][:content].size <= 256 && new_value[:content].size > 256) + element_destroy(indexer) + end + else + element_destroy(indexer) + end + end + # + @element_links[(indexer)] = new_value + element_write(indexer) + end + # + when :add_value + # Note: set in-memory value and save. + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = ::BigDecimal.new("0.0") + end + new_value[:record][:ordinal] = @element_links.size + new_value[:loaded] = true + # + @element_links[(indexer)] = new_value + # Review : is this wise to do this here?? + element_write(indexer) + end + # ### Handle coordination between persisted objects: + if @element_links[(indexer)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # ### Extend Reservation to persisted objects ( Review : does this slow it down too much?? ) + @element_links[(indexer)][:content].release_reservation + @db_address[:database].reservation_add_element((@reservation || @delegate), (@element_links[(indexer)][:content].db_address()[:table]), @element_links[(indexer)][:content].db_address()[:dbid]) + @element_links[(indexer)][:content].set_delegate((@reservation || @delegate)) + # ### Extend Permissions to persisted object + @db_address[:database].extend_element_permissions(@db_address[:table],@db_address[:dbid],@element_links[(indexer)][:content].db_address()[:table],@element_links[(indexer)][:content].db_address()[:dbid]) + @db_address[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => @element_links[(indexer)][:content]}) + # + @element_links[(indexer)][:content].set_parent(self) + end + # + self.increment_version() + result = @element_links[(indexer)][:content] + # + else + raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" + end + else + raise Exception, "The value is not persistable." + end + else + raise Exception, "You must provide a index in the form of an Integer." + end + result + end + # + def unload(indexer=nil) + result = false + # if key exists + # if key is loaded, unload element (if String or ByteArray) + if self.alive?() + if @db_address[:database].open?() + if indexer.is_a?(::Integer) + if @element_links[(indexer)] + if @element_links[(indexer)][:content].is_any?(::String, ::GxG::ByteArray) + if @element_links[(indexer)][:loaded] == true + # ### Unload Base Element contents from memory, leaving reference harness intact and active. + @element_links[(indexer)][:content].clear + @element_links[(indexer)][:loaded] = false + end + end + result = true + end + else + raise ArgumentError, "You must specify with an Integer, not a #{indexer.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def [](indexer=nil) + result = nil + # if exists + # if is not loaded, load element (if String or ByteArray) + if self.alive?() + if @db_address[:database].open?() + if indexer.is_a?(::Integer) + if @element_links[(indexer)] + if @element_links[(indexer)][:content].is_any?(::String, ::GxG::ByteArray) + unless @element_links[(indexer)][:loaded] == true + # ### Load Base Element contents from the database. + if @element_links[(indexer)][:content].is_a?(::String) + if @element_links[(indexer)][:record][:element_text_uuid].to_s.size > 0 + @db_address[:database].connector()[:text_page].filter({:parent_uuid => @element_links[(indexer)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + @element_links[(indexer)][:content] << text_page[:content] + end + else + @element_links[(indexer)][:content] = @element_links[(indexer)][:record][:element_text].to_s + end + else + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => @element_links[(indexer)][:record][:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page| + @element_links[(indexer)][:content] << binary_page[:content] + end + end + @element_links[(indexer)][:loaded] = true + end + end + result = @element_links[(indexer)][:content] + if @as_structures == true + if result.is_a?(::GxG::Database::PersistedHash) + result = ::OpenStruct.new(result) + end + end + end + else + raise ArgumentError, "You must specify with an Integer, not a #{indexer.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def delete_at(indexer=nil) + result = nil + # if exists + # if is not loaded, load element + if self.alive?() + if @db_address[:database].open?() + if indexer.is_a?(::Integer) + if self.write_reserved?() + property_key = key.to_s.to_sym + # + if @element_links[(indexer)] + # This will load the unloaded prior to separation. + result = self[(indexer)] + if @as_structures == true + if result.is_a?(::GxG::Database::PersistedHash) + result = ::OpenStruct.new(result) + end + end + the_link = @element_links.delete_at(indexer) + if the_link[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + the_link[:content].save + the_link[:content].release_reservation() + @db_address[:database].reservation_remove_element((@reservation || @delegate), the_link[:content].db_address()[:table], the_link[:content].db_address()[:dbid]) + else + # Eliminate orphaned text and binary objects in the database. They will exist only in memory. + if the_link[:content].is_any?(::String, ::GxG::ByteArray) + if the_link[:content].is_a?(::String) + if the_link[:content].size > 256 + # eliminate text object and pages. + the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_link[:record][:element_text_uuid]}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) + end + end + end + if the_link[:content].is_a?(::GxG::ByteArray) + # eliminate binary object and pages. + the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_link[:record][:element_binary_uuid]}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) + end + end + end + end + @db_address[:database].connector()[:array_elements].filter({:dbid => the_link[:linkid]}).delete + refresh_ordinals + self.increment_version() + end + # + else + raise Exception, "You do not have sufficient privileges to make this change" + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def <<(*args) + if args.size > 0 + args.each do |item| + self[(@element_links.size)] = item + end + end + self + end + # + def push(*args) + if args.size > 0 + args.each do |item| + self << item + end + end + self + end + # + def pop() + self.delete_at((@element_links.size - 1)) + end + # + def shift() + self.delete_at(0) + end + # + def insert(the_index=nil, the_object=nil) + # resovle the_index : the_index < 0 : the_index = (size - the_index) + # if the_index == 0 : @element_links.unshift(nil-record); self[0] = the_object + # if the_index > size-1 : push(the_object) + # else: @element_links.insert(the_index, nil-record); self[(the_index)] = the_object + # vette the_object --> persistable? + if @db_address + if @db_address[:database].persistable?(the_object) + new_value = { + :linkid => nil, + :content => nil, + :loaded => false, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :ordinal => 0, + :version => ::BigDecimal.new("0.0"), + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::DateTime.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_text_uuid => "", + :element_binary_uuid => "", + :element_array_uuid => "", + :element_hash_uuid => "" + } + } + the_index = the_index.to_i + if the_index < 0 + the_index = (self.size - the_index) + end + if the_index == 0 + new_value[:record][:ordinal] = the_index + @element_links.unshift(new_value) + element_write(the_index) + refresh_ordinals + self[0] = the_object + else + if the_index > (self.size - 1) + self.push(the_object) + else + new_value[:record][:ordinal] = the_index + @element_links.insert(the_index, new_value) + element_write(the_index) + refresh_ordinals + self[(the_index)] = the_object + end + end + else + log_warning("Ignoring attempt to insert a non-persistable object.") + end + end + self + end + # + def unshift(the_object=nil) + self.insert(0,the_object) + end + # + def swap(first_index=nil, last_index=nil) + result = false + if first_index && last_index + if (0..(self.size - 1)).include?(first_index.to_i) && (0..(self.size - 1)).include?(last_index.to_i) + if first_index.to_i != last_index.to_i + record = @element_links[(first_index)] + @element_links[(first_index)] = @element_links[(last_index)] + @element_links[(last_index)] = record + refresh_ordinals + result = true + end + end + end + result + end + # + def include?(the_value) + result = false + if the_value.is_a?(::OpenStruct) + the_value = the_value.table + end + @element_links.each do |element| + if element[:content] == the_value + result = true + break + end + end + result + end + # + def find_index(the_value) + result = nil + if the_value.is_a?(::OpenStruct) + the_value = the_value.table + end + @element_links.each_with_index do |element, indexer| + if element[:content] == the_value + result = indexer + break + end + end + result + end + # + def export(options={:exclude_file_segments=>false}) + if self.alive? + exclude_file_segments = (options[:exclude_file_segments] || false) + if options[:clone] == true + # Review : why are cloned objects unconstrained? sync issues?? + result = {:type => :element_array, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.to_s("F"), :content => []} + else + result = {:type => :element_array, :uuid => @uuid.clone, :title => @title.clone, :version => @version.to_s("F"), :constraint => @constraint.clone, :content => []} + end + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::PersistedHash) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} + end + end + if the_value.is_a?(::GxG::Database::PersistedArray) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + end + end + if the_value.is_any?(::GxG::Database::Database::valid_field_classes()) + data_type = GxG::Database::Database::element_table_for_instance(the_value) + case data_type + when :element_bigdecimal + data = the_value.to_s + when :element_datetime + data = the_value.to_s + when :element_binary + data = the_value.to_s.encode64 + when :element_text + data = the_value.to_s + else + data = the_value + end + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (::BigDecimal.new(the_container.version(the_selector).to_s) || ::BigDecimal.new("0.0")).to_s("F"), :content => data}} + end + end + # Collect children export content: + link_db = [(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + link_db << the_child + end + end + end + else + result = nil + end + result + end + # + def export_package(options={:exclude_file_segments=>false}) + self.save + result = {:formats => {}, :records => []} + object_record = self.export(options) + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + format_sample = @db_address[:database].format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + result[:records] << object_record + result + end + # + def first() + if @element_links.size > 0 + self[0] + else + nil + end + end + # + def last() + if @element_links.size > 0 + self[(@element_links.size - 1)] + else + nil + end + end + # + def each(&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.each_index do |index| + block.call(self[(index)]) + end + end + self + else + self.to_enum(:each) + end + end + # + def each_index(&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.each_index do |index| + block.call(index) + end + end + self + else + self.to_enum(:each_index) + end + end + # + def each_with_index(offset=0,&block) + if block.respond_to?(:call) + load_element_links + if @element_links.size > 0 + @element_links.to_enum(:each).with_index(offset).each do |entry, index| + block.call(self[(index)], index) + end + end + self + else + self.to_enum(:each_with_index,offset) + end + end + # + def iterative(options={:include_inactive => true}, &block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_a?(::GxG::Database::PersistedHash) + if a_node[:object].alive? + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + if a_node[:object].is_a?(::GxG::Database::PersistedArray) + if a_node[:object].alive? + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + # + if a_node.alive? || options[:include_inactive] + accumulator << a_node + end + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + alias :process :iterative + # + def process!(options={:include_inactive => true}, &block) + self.iterative(options, &block) + self + end + # + def search(options={:include_inactive => true}, &block) + results = [] + if block.respond_to?(:call) + results = self.iterative(options, &block) + end + results + end + # + def paths_to(the_object=nil,base_path="") + # new idea here: + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key.gsub!("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric + # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu + # + # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/")) + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + def unpersist() + result = [] + if self.alive? + # + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::PersistedHash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} + else + if the_value.is_a?(::GxG::Database::PersistedArray) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + link_db << the_child + end + end + end + # + end + result + end + # + end + end +end diff --git a/lib/gxg/gxg_database_persistedhash.rb b/lib/gxg/gxg_database_persistedhash.rb new file mode 100644 index 0000000..88df4d0 --- /dev/null +++ b/lib/gxg/gxg_database_persistedhash.rb @@ -0,0 +1,2859 @@ +# PersistedHash Code: +module GxG + module Database + # + class DetachedHash + # + public + # ### OpenStruct Integration + def as_structure() + ::OpenStruct.new(self) + end + # + def uuid() + @uuid.clone + end + # + def uuid=(the_uuid=nil) + if GxG::valid_uuid?(the_uuid) + @uuid = the_uuid.to_s.to_sym + end + end + # + def title() + @title.clone + end + # + def title=(the_title=nil) + if the_title + @title = the_title.to_s[0..255] + self.increment_version + end + end + # + def version() + @version.clone + end + # + def version=(the_version=nil) + if the_version.is_a?(::Numeric) + @version = the_version.to_s("F").to_d + end + end + # + def element_version(key=nil) + result = 0.0 + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + # + if @property_links[(key.to_s.to_sym)] + result = @property_links[(key.to_s.to_sym)][:record][:version] + else + result = 0.0 + end + # + end + result + end + # + def set_element_version(element_key, the_version=nil) + result = false + if @property_links[(element_key)] + if the_version.is_a?(::Numeric) + @property_links[(element_key)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) + result = true + else + log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for #{element_key.inspect} on Object #{@uuid.inspect}") + end + else + log_warning("Attempted to set version with an invalid key #{element_key.inspect} on Object #{@uuid.inspect}") + end + result + end + # + def format() + @format.clone + end + # + def format=(the_format=nil) + if GxG::valid_uuid?(the_format) + @format = the_format.to_s.to_sym + end + end + # + def parent() + @parent + end + # + def parent=(object=nil) + if object.is_any?([::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray]) + # Review : parent can only be set once -- is this best?? + unless @parent + @parent = object + end + end + end + # + def write_reserved?() + true + end + # + def release_reservation() + true + end + # + def get_reservation() + true + end + # + def wait_for_reservation(timeout=nil) + true + end + # ### Review: move to Database class as generic toolbox method at some point: + # + def self.import(import_record=nil) + result = nil + unless import_record.is_any?(::Hash, ::GxG::Database::DetachedHash) + raise Exception, "Malformed import record passed." + end + # {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}} + import_db = [{:target => ::GxG::Database::DetachedHash.new, :record => import_record}] + result = import_db[0][:target] + while import_db.size > 0 do + entry = import_db.shift + case entry[:record][:type].to_s.to_sym + when :element_hash + entry[:target].uuid = entry[:record][:uuid].to_s.to_sym + entry[:target].title = entry[:record][:title].to_s + entry[:record][:content].keys.each do |the_key| + case entry[:record][:content][(the_key)][:type].to_s.to_sym + when :element_hash + new_target = GxG::Database::DetachedHash.new + entry[:target][(the_key)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} + entry[:target][(the_key)].parent = (entry[:target]) + when :element_array + new_target = GxG::Database::DetachedArray.new + entry[:target][(the_key)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} + entry[:target][(the_key)].parent = (entry[:target]) + when :element_boolean, :element_integer, :element_float, :element_text + entry[:target][(the_key)] = entry[:record][:content][(the_key)][:content] + when :element_bigdecimal + entry[:target][(the_key)] = ::BigDecimal.new(entry[:record][:content][(the_key)][:content].to_s) + when :element_datetime + entry[:target][(the_key)] = ::Chronic::parse(entry[:record][:content][(the_key)][:content].to_s) + when :element_binary + entry[:target][(the_key)] = ::GxG::ByteArray.new(entry[:record][:content][(the_key)][:content].to_s.decode64) + end + end + entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) + entry[:target].format = entry[:record][:format].to_s.to_sym + when :element_array + entry[:target].uuid = entry[:record][:uuid].to_s.to_sym + entry[:target].title = entry[:record][:title].to_s + entry[:record][:content].each_index do |indexer| + case entry[:record][:content][(indexer)][:type].to_s.to_sym + when :element_hash + new_target = GxG::Database::DetachedHash.new + entry[:target][(indexer)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} + entry[:target][(indexer)].parent =(entry[:target]) + when :element_array + new_target = GxG::Database::DetachedArray.new + entry[:target][(indexer)] = new_target + import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} + entry[:target][(indexer)].parent = (entry[:target]) + when :element_boolean, :element_integer, :element_float, :element_text + entry[:target][(indexer)] = entry[:record][:content][(indexer)][:content] + when :element_bigdecimal + entry[:target][(indexer)] = ::BigDecimal.new(entry[:record][:content][(indexer)][:content].to_s) + when :element_datetime + entry[:target][(indexer)] = ::Chronic::parse(entry[:record][:content][(indexer)][:content].to_s) + when :element_binary + entry[:target][(indexer)] = ::GxG::ByteArray.new(entry[:record][:content][(indexer)][:content].to_s.decode64) + end + end + entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) + entry[:target].constraint = entry[:record][:constraint].to_s.to_sym + end + end + # + result + end + # + def self.create_from_format(the_format=nil) + result = nil + format_record = nil + if ::GxG::valid_uuid?(the_format) + format_record = ::GxG::DB[:roles][:formats].format_load({:uuid => the_format}) + else + format_record = ::GxG::DB[:roles][:formats].format_load({:ufs => the_format}) + end + if format_record + # Review : ?? the ability to ingest a detached_object's data while retaining self's identity data. worth it ?? + new_object = ::GxG::Database::DetachedHash.new + new_object.uuid = ::GxG::uuid_generate.to_sym + new_object.title = "Untitled #{new_object.uuid.to_s}" + object_db = [{:destination => new_object, :source => format_record[:content]}] + while object_db.size > 0 do + pair = object_db.shift + if pair + pair[:source].search do |item, selector, container| + if item.is_any?(::Array, ::Hash) + if item.is_a?(::Hash) + unless pair[:destination][(selector)].is_a?(::GxG::Database::DetachedHash) + new_item = ::GxG::Database::DetachedHash.new + new_item.uuid = ::GxG::uuid_generate.to_sym + new_item.title = "Untitled #{new_object.uuid.to_s}" + pair[:destination][(selector)] = new_item + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + else + if item.is_a?(::Array) + unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedArray) + new_item = ::GxG::Database::DetachedArray.new + new_item.uuid = ::GxG::uuid_generate.to_sym + new_item.title = "Untitled #{new_object.uuid.to_s}" + pair[:destination][(selector)] = new_item + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + end + end + # + else + pair[:destination][(selector)] = item + end + end + end + end + new_object.format = format_record[:uuid].to_s.to_sym + # + unless ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] } + ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] = format_record } + end + # + result = new_object + else + result = ::GxG::Database::DetachedHash.new + result.uuid = ::GxG::uuid_generate.to_sym + result.title = "Untitled #{new_object.uuid.to_s}" + end + # + result + end + # + def self.create() + ::GxG::Database::DetachedHash::create_from_format(nil) + end + # + def ufs() + if @format + record = GxG::DB[:formats][(@format.to_s.to_sym)] + if record + record[:ufs].to_s.to_sym + else + "" + end + else + "" + end + end + # + def increment_version() + @version += 0.0001 + end + # + def initialize() + @active = true + @uuid = nil + @title = nil + @version = ::BigDecimal.new("0.0") + @format = nil + @parent = nil + @property_links = {} + self + end + # + def inspect() + # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with DetachedHash. + # TODO: make interative instead of re-entrant. + last_key = @property_links.keys.last + result = "{" + @property_links.keys.each do |element_key| + result = result + (":#{element_key.to_s} => " + @property_links[(element_key)][:content].inspect) + # if @property_links[(element_key)][:loaded] == true + # result = result + (":#{element_key.to_s} => " + @property_links[(element_key)][:content].inspect) + # else + # result = result + (":#{element_key.to_s} => (Not Loaded)") + # end + unless last_key == element_key + result = result + ", " + end + end + result = result + "}" + result + end + # + def alive?() + @active + end + # + def save() + result = false + if self.alive? + begin + # Review : expand to save to server + result = true + rescue Exception => the_error + log_error({:error => the_error}) + end + end + result + end + # + def deactivate() + ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache].delete(@uuid) } + @active = false + true + end + # + def destroy() + result = false + if self.alive? + result = self.deactivate + end + result + end + # + def size() + @property_links.size + end + # + def keys() + @property_links.keys + end + # + def []=(key=nil, value=nil) + # Only works with Symbols. + unless self.alive?() + raise Exception, "Attempted to alter a defunct structure" + end + result = nil + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + property_key = key.to_s.to_sym + # Review : rewrite ???? + if value.is_any?(::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::BigDecimal, ::String, ::Time, ::GxG::ByteArray, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray, ::Hash, ::Array) + unless self.write_reserved?() + self.get_reservation() + end + if self.write_reserved?() + # Further screen value provided: + # ### Check provided DetachedHashes and DetachedArrays + # ### Check provided Hashes and Arrays + # ### Property Exists? + if @property_links[(key.to_s.to_sym)] + # set property to value + operation = :set_value + else + if @format + raise Exception, "Formatted - the structure cannot be altered" + else + # add property : value pair + operation = :add_value + end + end + # ### Prepare new value + new_value = { + :linkid => nil, + :content => nil, + :loaded => true, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :property => key.to_s, + :ordinal => 0, + :version => ::BigDecimal.new("0.0"), + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::Time.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_binary => nil, + :element_array => nil, + :element_hash => nil + } + } + # + if value.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + # ### Assimilate DetachedHash or DetachedArray + new_value[:content] = value + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::GxG::Database::DetachedHash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash] = value + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array] = value + end + else + # ### Persist Hashes and Arrays + if value.is_any?(::Hash, ::Array) + new_value[:content] = ::GxG::Database::iterative_detached_persist(value) + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::Hash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash] = new_value[:content] + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array] = new_value[:content] + end + else + # ### Persist Base Element Values + new_value[:record][:element] = ::GxG::Database::element_table_for_instance(value).to_s + case new_value[:record][:element] + when "element_boolean" + if value.class == ::NilClass + new_value[:record][:element_boolean] = -1 + new_value[:content] = nil + end + if value.class == ::FalseClass + new_value[:record][:element_boolean] = 0 + new_value[:content] = false + end + if value.class == ::TrueClass + new_value[:record][:element_boolean] = 1 + new_value[:content] = true + end + when "element_integer" + new_value[:record][:element_integer] = value + new_value[:content] = value + when "element_float" + new_value[:record][:element_float] = value + new_value[:content] = value + when "element_bigdecimal" + new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) + new_value[:content] = new_value[:record][:element_bigdecimal] + when "element_datetime" + new_value[:record][:element_datetime] = value + new_value[:content] = value + when "element_text" + new_value[:record][:element_text] = value + new_value[:record][:length] = value.size + new_value[:content] = value + when "element_binary" + # Note: be sure to keep version & length sync'd with linked binary element record + if operation == :set_value + new_value[:record][:version] = @property_links[(property_key)][:record][:version] + new_value[:record][:element_binary] = @property_links[(property_key)][:record][:element_binary] + end + new_value[:record][:length] = value.size + new_value[:content] = value + else + raise Exception, "Unable to map an element type for value: #{value.inspect}" + end + end + end + # ### Commit Changes + case operation + when :set_value + # Note: Set In-memory value only, don't save unless you have to. + if new_value[:content].is_a?(@property_links[(property_key)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@property_links[(property_key)][:content])) + # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). + new_value[:linkid] = @property_links[(property_key)][:linkid] + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) + end + new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] + new_value[:loaded] = true + new_value[:state] = @property_links[(property_key)][:state] + # + @property_links[(property_key)] = new_value + # + else + # Other class value being substituted. + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (((@property_links[(property_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) + end + new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] + new_value[:loaded] = true + # + @property_links[(property_key)] = new_value + end + result = @property_links[(property_key)][:content] + # + when :add_value + # Note: set in-memory value and save. + if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = ::BigDecimal.new("0.0") + end + new_value[:record][:ordinal] = @property_links.keys.size + new_value[:loaded] = true + # + @property_links[(property_key)] = new_value + # Review : is this wise to do this here?? + # property_write(property_key) + # refresh_ordinals + end + # ### Handle coordination between persisted objects: + if @property_links[(property_key)][:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + @property_links[(property_key)][:content].parent = (self) + end + # + self.increment_version() + result = @property_links[(property_key)][:content] + # + else + raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" + end + else + raise Exception, "The value is not persistable." + end + else + raise Exception, "You must provide a property key in the form of a Symbol." + end + result + end + # + def [](key=nil) + result = nil + # if key exists + if self.alive?() + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + property_key = key.to_s.to_sym + if @property_links[(property_key)] + result = @property_links[(property_key)][:content] + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def include?(the_key) + @property_links.keys.include?(the_key) + end + # + def delete(key=nil) + result = nil + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + if @format + raise Exception, "Formatted - the structure cannot be altered" + else + if @property_links[(key)] + result = @property_links[(key)][:content] + @property_links.delete(key) + # Review : send a 'delete_element' to server with the uuid of the DetachedHash and the key name. + # ### -OR- develop @ the server: an 'overwrite' push call that will trim missing elements from stored copy. + end + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + # + result + end + # + def unpersist() + result = {} + if self.alive? + # + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::DetachedHash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} + else + if the_value.is_a?(::GxG::Database::DetachedArray) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + link_db << the_child + end + end + end + # + end + result + end + # + def export(options={:exclude_file_segments=>false}) + exclude_file_segments = (options[:exclude_file_segments] || false) + if options[:clone] == true + # Review : why are cloned objects unformatted? sync issues?? + result = {:type => :element_hash, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.to_s("F"), :content => {}} + else + result = {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.to_s("F"), :format => @format.clone, :content => {}} + end + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::DetachedHash) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :format => the_value.format().clone, :content => {}}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :format => the_value.format().clone, :content => {}}} + end + end + if the_value.is_a?(::GxG::Database::DetachedArray) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + end + end + if the_value.is_any?(::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::BigDecimal, ::String, ::Time, ::GxG::ByteArray) + data_type = GxG::Database::element_table_for_instance(the_value) + # + case data_type + when :element_bigdecimal + data = the_value.to_s + when :element_datetime + data = the_value.iso8601.to_s + when :element_binary + data = the_value.to_s.encode64 + when :element_text + data = the_value.to_s + else + data = the_value + end + # + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (the_container.version(the_selector) || ::BigDecimal.new("0.0")).to_s("F"), :content => data}} + end + end + # Collect children export content: + link_db = [(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + link_db << the_child + end + end + end + # + result + end + # + def export_package(options={:exclude_file_segments=>false}) + result = {:formats => {}, :records => []} + object_record = self.export(options) + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + format_sample = ::GxG::DB[:roles][:formats].format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + result[:records] << object_record + result + end + # + # + def each_pair(&block) + collection = {} + @property_links.keys.each do |key| + collection[(key)] = (self[(key)]) + end + if block.respond_to?(:call) + collection.to_enum(:each_pair).each do |key,value| + block.call(key,value) + end + else + collection.to_enum(:each_pair) + end + end + # + def iterative(options={:include_inactive => true}, &block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_a?(::GxG::Database::DetachedHash) + if a_node[:object].alive? + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + if a_node[:object].is_a?(::GxG::Database::DetachedArray) + if a_node[:object].alive? + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + # + def process!(options={:include_inactive => true}, &block) + self.iterative(options, &block) + self + end + # + def search(options={:include_inactive => true}, &block) + results = [] + if block.respond_to?(:call) + results = self.iterative(options, &block) + end + results + end + # + def paths_to(the_object=nil,base_path="") + # new idea here: + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key.gsub!("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + end + # + class PersistedHash + # + private + # + def property_destroy(the_key=nil) + # Destroy database property record, but leave in-memory property intact - for now. (preserves key-ordinal position) + result = false + if the_key + if @property_links[(key.to_s.to_sym)] + property = @property_links[(key.to_s.to_sym)] + # ### Delete sub-object linked to. + the_table = nil + the_uuid = nil + if property[:record][:element_text_uuid].size > 0 + the_table = :element_text + the_uuid = property[:record][:element_text_uuid] + end + if property[:record][:element_binary_uuid].size > 0 + the_table = :element_binary + the_uuid = property[:record][:element_binary_uuid] + end + if property[:record][:element_array_uuid].size > 0 + the_table = :element_array + the_uuid = property[:record][:element_array_uuid] + end + if property[:record][:element_hash_uuid].size > 0 + the_table = :element_hash + the_uuid = property[:record][:element_hash_uuid] + end + case the_table + when :element_text + the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_uuid}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) + end + when :element_binary + the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_uuid}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) + end + when :element_array, :element_hash + @db_address[:database].destroy_by_uuid(@credential, the_uuid) + end + # ### Delete property record. + @db_address[:database].connector()[:hash_properties].filter({:dbid => property[:linkid]}).delete + result = true + end + end + result + end + # Note: property_read handled by load_property_links + def property_write(the_key=nil) + result = false + if the_key + if @property_links[(the_key.to_s.to_sym)] + the_property = @property_links[(the_key.to_s.to_sym)] + # Save out text and binary pages if needed. + if the_property[:content].is_a?(::String) + if the_property[:content].size > 256 + unless the_property[:record][:element_text_uuid].size > 0 + # create text header + the_uuid = ::GxG::uuid_generate + while @db_address[:database].connector()[:element_text].filter({:uuid => the_uuid.to_s}).count > 0 do + the_uuid = ::GxG::uuid_generate + end + the_property[:record][:length] = the_property[:content].size + the_dbid = @db_address[:database].connector()[:element_text].insert({:uuid => the_uuid.to_s, :version => the_property[:record][:version], :length => the_property[:record][:length]}) + # + the_property[:record][:element_text_uuid] = the_uuid + end + end + else + if the_property[:content].is_a?(::GxG::ByteArray) + unless the_property[:record][:element_binary_uuid].size > 0 + # create binary header + the_uuid = ::GxG::uuid_generate + while @db_address[:database].connector()[:element_binary].filter({:uuid => the_uuid.to_s}).count > 0 do + the_uuid = ::GxG::uuid_generate + end + the_property[:record][:length] = the_property[:content].size + # format field?? + the_dbid = @db_address[:database].connector()[:element_binary].insert({:uuid => the_uuid.to_s, :version => the_property[:record][:version], :length => the_property[:record][:length]}) + # + the_property[:record][:element_binary_uuid] = the_uuid + end + end + end + # Store text pages + if the_property[:record][:element_text_uuid].size > 0 + header_record = @db_address[:database].connector()[:element_text].filter({:uuid => the_property[:record][:element_text_uuid].to_s}).first + if header_record + old_segments = [] + @db_address[:database].connector()[:text_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if the_property[:content].size == 0 + # delete all text_pages if count > 0 + if old_segments.size > 0 + @db_address[:database].connector()[:text_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete + end + else + new_segments = ::GxG::apportioned_ranges(the_property[:content].size, 4096) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + # + manifest.each do |action| + case action[:operation] + when :overwrite + @db_address[:database].element_update(:text_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :create + new_dbid = @db_address[:database].element_create(:text_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :delete + @db_address[:database].element_destroy(@credential,:text_page,action[:dbid]) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + # + end + end + else + if the_property[:record][:element_binary_uuid].size > 0 + header_record = @db_address[:database].connector()[:element_binary].filter({:uuid => the_property[:record][:element_binary_uuid].to_s}).first + if header_record + old_segments = [] + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| + old_segments << entry[:dbid] + end + if the_property[:content].size == 0 + # delete all text_pages if count > 0 + if old_segments.size > 0 + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete + end + else + new_segments = ::GxG::apportioned_ranges(the_property[:content].size, 65536) + if old_segments.size > new_segments.size + the_count = old_segments.size + else + the_count = new_segments.size + end + manifest = [] + (0..(the_count - 1)).each do |the_ordinal| + if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] + #overwrite with new segment range + manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + if new_segments[(the_ordinal)] + # create new segment at this ordinal + manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} + else + # delete old segment + manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} + end + end + end + # + manifest.each do |action| + case action[:operation] + when :overwrite + @db_address[:database].element_update(:binary_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :create + new_dbid = @db_address[:database].element_create(:binary_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) + # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) + when :delete + @db_address[:database].element_destroy(@credential,:binary_page,action[:dbid]) + # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete + end + end + # + end + end + end + end + # ### Create new property record if missing: otherwise just update it. + if the_property[:linkid] == nil || the_property[:linkid] == 0 + the_property[:linkid] = @db_address[:database].connector()[:hash_properties].insert(the_property[:record]) + else + @db_address[:database].connector()[:hash_properties].filter({:dbid => the_property[:linkid]}).update(the_property[:record]) + end + # + the_property[:state] = the_property[:content].hash + result = true + end + end + result + end + # + def load_property_links + # + result = true + begin + if @db_address + if @db_address[:database].is_a?(::GxG::Database::Database) + if @db_address[:database].open?() + new_links = {} + the_records = @db_address[:database].connector()[:hash_properties].filter({:parent_uuid => @uuid.to_s}).order(:ordinal) + the_records.each do |entry| + # :property_name => {:linkid => 0, :content => nil, :loaded => false, :state => 0, :record => {}} + the_key = entry[:property].to_sym + new_links[(the_key)] = {:linkid => entry.delete(:dbid), :content => nil, :loaded => false, :state => 0, :record => entry} + # Load indicated :content + case new_links[(the_key)][:record][:element] + when "element_boolean" + case new_links[(the_key)][:record][:element_boolean] + when -1 + new_links[(the_key)][:content] = nil + new_links[(the_key)][:loaded] = true + when 0 + new_links[(the_key)][:content] = false + new_links[(the_key)][:loaded] = true + when 1 + new_links[(the_key)][:content] = true + new_links[(the_key)][:loaded] = true + end + when "element_integer" + new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_integer] + new_links[(the_key)][:loaded] = true + when "element_float" + new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_float] + new_links[(the_key)][:loaded] = true + when "element_bigdecimal" + new_links[(the_key)][:content] = ::BigDecimal.new(new_links[(the_key)][:record][:element_bigdecimal].to_s) + new_links[(the_key)][:loaded] = true + when "element_datetime" + new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_datetime] + if new_links[(the_key)][:content].is_a?(::Time) + new_links[(the_key)][:content] = new_links[(the_key)][:content].to_datetime() + end + new_links[(the_key)][:loaded] = true + when "element_text" + if new_links[(the_key)][:record][:element_text_uuid].size > 0 + # Load full text content from pages + new_links[(the_key)][:content] = "" + @db_address[:database].connector()[:text_page].filter({:parent_uuid => new_links[(the_key)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + new_links[(the_key)][:content] << text_page[:content] + end + else + new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_text] + end + new_links[(the_key)][:loaded] = true + when "element_binary" + # Load binary data + new_links[(the_key)][:content] = ::GxG::ByteArray.new + new_links[(the_key)][:loaded] = false + # Review : support on-read segment loading and property unloading. (thinking of large binary files here) + when "element_array" + # Load PersistedArray + new_links[(the_key)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(the_key)][:record][:element_array_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) + if new_links[(the_key)][:content].is_a?(::GxG::Database::PersistedArray) + new_links[(the_key)][:loaded] = true + new_links[(the_key)][:state] = new_links[(the_key)][:content].hash + new_links[(the_key)][:content].set_parent(self) + else + new_links[(the_key)][:loaded] = false + end + when "element_hash" + # Load PersistedHash + # Review : HOW to keep the version info updated when the sub-object changes happen?? objects have ZERO knowledge of thier parent properties. + # Maybe: @ object --> @parent.update_property_version(self,version) : it can match on :content and property_write(key) of found item. + new_links[(the_key)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(the_key)][:record][:element_hash_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) + if new_links[(the_key)][:content].is_a?(::GxG::Database::PersistedHash) + new_links[(the_key)][:loaded] = true + new_links[(the_key)][:state] = new_links[(the_key)][:content].hash + new_links[(the_key)][:content].set_parent(self) + else + new_links[(the_key)][:loaded] = false + end + end + end + # xxx + # :property_name => {:linkid => 0, :table => :unspecified, :dbid => 0, :ordinal => 0, :element => nil} + # link_record => {:dbid => 0, :parent_uuid => "", :property => "", :ordinal => 0, :element => 0, :elementid => 0} + # the_links = @db_address[:database].connector()[:hash_links].filter({:parent_uuid => @uuid.to_s}).order(:ordinal) + # the_links.each do |entry| + # # + # the_key = entry[:property].to_sym + # new_links[(the_key)] = {:linkid => (entry[:dbid])} + # new_links[(the_key)][:table] = ::GxG::Database::Database::element_table_by_index(entry[:element]) + # new_links[(the_key)][:dbid] = entry[:elementid] + # new_links[(the_key)][:ordinal] = entry[:ordinal] + # # TODO: load element based upon setting in Database: :immediate or :lazy (default) + # new_links[(the_key)][:element] = nil + # if @property_links[(the_key)] + # if @property_links[(the_key)][:element] + # new_links[(the_key)][:element] = @property_links[(the_key)][:element] + # end + # end + # end + # xxx + @property_links = new_links + else + raise Exception, "Database not available" + end + else + raise Exception, "Invalid Database object" + end + else + # Deactivated + result = false + end + rescue Exception => the_error + result = false + log_error({:error => the_error}) + end + result + end + # + def refresh_ordinals() + if self.alive? + if @db_address[:database].open? + @property_links.keys.each_with_index do |the_key, ordinal| + @property_links[(the_key)][:record][:ordinal] = ordinal + @db_address[:database].connector()[:hash_properties].filter({:dbid => @property_links[(the_key)][:linkid]}).update({:ordinal => (ordinal)}) + end + end + end + end + # + public + # + def assimilate_detached(detached_object=nil) + result = false + if detached_object.is_a?(::GxG::Database::DetachedHash) + # Review : ?? the ability to ingest a detached_object's data while retaining self's identity data. worth it ?? + object_db = [{:destination => self, :source => detached_object}] + while object_db.size > 0 do + pair = object_db.shift + if pair + pair[:source].search do |item, selector, container| + if item.is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) + if item.is_a?(::GxG::Database::DetachedHash) + unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedHash) + pair[:destination][(selector)] = @db_address[:database].try_persist({}, @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_format => item.format}) + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + else + if item.is_a?(::GxG::Database::DetachedArray) + unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedArray) + pair[:destination][(selector)] = @db_address[:database].try_persist([], @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_constraint => item.constraint}) + end + object_db << [{:destination => pair[:destination][(selector)], :source => item}] + end + end + # + else + pair[:destination][(selector)] = item + end + end + end + end + # + result = true + end + result + end + # + def detach() + ::GxG::Database::detached_import(self.export()) + end + # ### OpenStruct Integration + def as_structure() + ::OpenStruct.new(self) + end + # Review : favor the following more Ruby-like accessors over the set_xxx style methods, eliminate older duplicate methods. + def uuid() + @uuid.clone + end + # + def uuid=(the_uuid=nil) + if GxG::valid_uuid?(the_uuid) + @uuid = the_uuid.to_s.to_sym + end + end + # + def title() + @title.clone + end + # + def title=(the_title=nil) + if the_title + @title = the_title.to_s[0..255] + self.increment_version + end + end + # + def version() + @version.clone + end + # + def version=(the_version=nil) + if the_version.is_a?(::Numeric) + @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) + end + end + # + def element_version(key=nil) + result = 0.0 + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + # + if @property_links[(key.to_s.to_sym)] + result = @property_links[(key.to_s.to_sym)][:record][:version] + else + result = 0.0 + end + # + end + result + end + # + def set_element_version(element_key, the_version=nil) + result = false + if @property_links[(element_key)] + if the_version.is_a?(::Numeric) + @property_links[(element_key)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) + result = true + else + log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for #{element_key.inspect} on Object #{@uuid.inspect}") + end + else + log_warning("Attempted to set version with an invalid key #{element_key.inspect} on Object #{@uuid.inspect}") + end + result + end + # + def format() + @format.clone + end + # + def format=(the_format=nil) + if GxG::valid_uuid?(the_format) + @format = the_format.to_s.to_sym + end + end + # + def parent() + @parent + end + # + def parent=(object=nil) + if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + # Review : parent can only be set once -- is this best?? + unless @parent + @parent = object + end + end + end + # ### Review: move to Database class as generic toolbox method at some point: + # + def get_permissions() + @permission.clone + end + # + def set_permissions(credential=nil, the_permission=nil) + if self.alive? + if credential.is_a?(::Hash) && the_permission.is_a?(::Hash) + if credential[:user] + if credential[:user] == "public" || credential[:user] == "Public" + credential = "00000000-0000-4000-0000-000000000000" + else + credential = (@db_address[:database].user_fetch({:user_id => credential[:user].to_s}) || {})[:uuid] + end + if credential + credential = credential.to_s.to_sym + end + else + if credential[:group] + credential = @db_address[:database].group_credential(credential) + else + credential = nil + end + end + else + if credential.is_a?(::Hash) && the_permission == nil + the_permission = credential + credential = @credential + else + unless credential + credential = @credential + end + end + end + if the_permission.is_a?(::Hash) && ::GxG::valid_uuid?(credential) + manifest = [] + structures = [{:table => @db_address[:table], :dbid => @db_address[:dbid]}] + while structures.size > 0 do + entry = structures[0] + @db_address[:database].element_manifest(entry[:table],entry[:dbid]).each do |record| + if [:element_hash, :element_array].include?(record[:table]) + unless structures.include?(record) + structures << record + end + else + # Review - only set/get/check permissions on persisted arrays/hashes + # unless manifest.include?(record) + # manifest << record + # end + end + end + manifest << structures.shift + end + manifest.each do |record| + @db_address[:database].assign_element_permission(record[:table], record[:dbid], credential, the_permission) + end + # + true + else + log_error({:error => Exception.new("You MUST provide a valid permissions Hash."), :parameters => {:credential => credential, :permission => the_permission}}) + false + end + end + end + # + def reservation() + if self.alive? + if @reservation.is_any?(::String, ::Symbol) + @reservation.clone + end + end + end + # + def write_permission?() + @permission[:write] + end + # + def write_reserved?() + if @reservation or @delegate + true + else + false + end + end + # + def release_reservation() + if self.alive?() + if @reservation + @db_address[:database].release_element_locks(@reservation) + @reservation = nil + end + if @delegate + @delegate = nil + end + # release write-locks of all sub-items + @property_links.keys.each do |the_key| + if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + @property_links[(the_key)][:content].release_reservation + end + end + end + true + end + # + def get_reservation() + result = false + # Review : Experimental design change - deprecate @delegate use, require entire structure (and sub-structures) to get solid @reservation (all of them) or this fails. + # Note : keep your structures small as this opens the door for stack-depth exceptions if it is nested too deep. (say 256 or less layers deep??) + if @reservation + result = true + else + if self.alive? + if @permission[:write] + clean_up_list = [] + # Review : efficiency - write locks only apply to PersistedHashes and PersistedArrays so do we need a full manifest?? + the_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) + if the_reservation + @reservation = the_reservation + result = true + # + # @property_links.keys.each do |the_key| + # if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # clean_up_list << @property_links[(the_key)][:content] + # unless @property_links[(the_key)][:content].get_reservation() + # @db_address[:database].release_element_locks(the_reservation) + # the_reservation = nil + # break + # end + # end + # end + # + end + # if the_reservation + # result = true + # else + # clean_up_list.each do |the_object| + # the_object.release_reservation + # end + # end + end + end + end + # xxx + # if (@reservation || @delegate) + # result = true + # else + # if self.alive? + # if @permission[:write] + # @reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) + # if @reservation + # @property_links.keys.each do |the_key| + # if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # @property_links[(the_key)][:content].set_delegate((@reservation || @delegate)) + # end + # end + # result = true + # end + # # + # end + # end + # end + # xxx + result + end + # + def wait_for_reservation(timeout=nil) + result = true + the_timeout = Time.now.to_f + (timeout || 30.0).to_f + until self.get_reservation() == true do + sleep 1.0 + if Time.now.to_f >= the_timeout + result = false + break + end + end + result + end + # + def get_delegate() + if self.alive? + if @delegate.is_any?(::String, ::Symbol) + @delegate.clone + end + end + end + # + def set_delegate(the_delegate=nil) + if self.alive? + if the_delegate.is_any?([::String, ::Symbol]) + @delegate = the_delegate.to_sym + end + end + end + # + def set_format(format_uuid=nil) + if self.alive? + unless @format + if ::GxG::valid_uuid?(format_uuid) + @format = format_uuid.to_sym + @db_address[:database].connector()[:element_hash].filter({:dbid => (@db_address[:dbid])}).update({:format => (@format.to_s)}) + end + end + end + end + # + def format() + @format.clone + end + # + def ufs() + if @format + record = @db_address[:database].format_load({:uuid => @format.to_s}) + if record + record[:ufs] + else + "" + end + else + "" + end + end + # + def version(key=nil) + result = @version + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + # + if @property_links[(key.to_s.to_sym)] + result = @property_links[(key.to_s.to_sym)][:record][:version] + else + result = 0.0 + end + # + end + result + end + # + def version=(the_version=nil) + if the_version.is_a?(::Numeric) + @version = ::BigDecimal.new(the_version.to_s) + end + end + # + # + def parent() + @parent + end + # + def set_parent(object=nil) + if self.alive? + if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + unless @parent + @parent = object + end + end + end + end + # + def refresh_links() + if self.alive?() + unless @db_address[:database].element_in_use?(@db_address[:table], @db_address[:dbid]) + @parent = nil + end + end + end + # + def increment_version() + if self.alive? + if @db_address[:database].open? + if self.write_reserved?() + @version += 0.0001 + end + end + end + end + # + def save_version() + @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:version => @version}) + end + # + def set_version(the_version=0.0) + if self.write_reserved?() + @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) + self.save_version + end + end + # + def set_title(the_title=nil) + if self.alive? + if @db_address[:database].open? + if self.write_reserved?() + if the_title + @title = the_title.to_s[0..255] + @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:title => @title}) + self.increment_version + end + end + end + end + end + # + def initialize(settings = {}, options={}) + # mount element: {:database => , :uuid => , :credential => , :delegate => nil/, :parent => nil/} + # create element: {:database => , :credential => , :delegate => nil/, :parent => nil/} + # + if settings.is_a?(::Hash) + unless ::GxG::valid_uuid?(settings[:credential]) + raise ArgumentError, "You must supply a valid credential UUID (37 character limit) as a String or Symbol" + end + @quick = settings[:quick] + @credential = settings[:credential].to_sym + @reservation = nil + @delegate = settings[:delegate] + if settings[:parent].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) + @parent = settings[:parent] + else + @parent = nil + end + if settings[:database].is_a?(::GxG::Database::Database) + if settings[:database].open?() + # note - rewrite. if create then as subset, eventually will always mount if all checks out. + the_uuid = settings[:uuid] + if the_uuid.is_a?(::Symbol) + the_uuid = the_uuid.to_s + end + # TODO: validate uuid pattern + if settings[:title].is_a?(::String) + the_title = settings[:title] + else + if options[:with_title] + the_title = options[:with_title] + else + the_title = ("Untitled PersistedHash " << (settings[:database].connector()[:element_hash].count.to_s)) + end + end + @format = settings[:format] + unless @format + if options[:with_format] + @format = options[:with_format] + end + end + unless the_uuid + # without specifying a uuid, the implied intent is to create a new PersistedHash + if ::GxG::valid_uuid?(options[:with_uuid]) + the_uuid = options[:with_uuid].to_s + else + the_uuid = ::GxG::uuid_generate() + while ((settings[:database].connector()[:element_hash].filter({:uuid => the_uuid}).count > 0) or (settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0)) + the_uuid = ::GxG::uuid_generate() + end + end + # creat blank hash record + new_dbid = settings[:database].connector()[:element_hash].insert({:uuid => (the_uuid), :title => (the_title), :format => (@format.to_s), :version => (options[:with_version] || BigDecimal.new("0.0"))}) + # + @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (new_dbid)} + if @parent + @permission = @parent.get_permissions() + # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => settings[:parent], :destination => self}) + # settings[:database].extend_element_permissions(settings[:parent].db_address()[:table], settings[:parent].db_address()[:dbid], :element_hash, new_dbid) + else + # create raw permissions for this element + @permission = {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true} + # settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) + # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => self}) + end + settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, @permission) + end + # mount according to uuid + if the_uuid + # mount + record = settings[:database].connector()[:element_hash].filter({:uuid => (the_uuid)}).first + if record + @uuid = the_uuid.to_sym + @title = record[:title].to_s + @version = record[:version] + @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (record[:dbid])} + if record[:format].to_s.size > 0 + @format = record[:format].to_sym + end + @property_links = {} + # + unless @permission + @permission = @db_address[:database].effective_element_permission(@db_address[:table], @db_address[:dbid], @credential) + end + # @read_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :read) + # + load_property_links + # + else + raise Exception, "Unable to mount the PersistedHash record" + end + else + raise Exception, "Unable to mount the PersistedHash record" + end + # + else + raise Exception, "Database not available for storage" + end + # + else + raise Exception, "Invalid Database object" + end + # + else + raise ArgumentError, "You must supply a hash of details" + end + # + end + # + def inspect() + # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with PersistedHash. + # TODO: make interative instead of re-entrant. + result = "(Not Loaded)" + if @db_address + if @db_address[:database].element_byte_size(@db_address[:table], @db_address[:dbid]) <= 10485760 + # smaller than 10MB + inspection_db = [] + last_key = @property_links.keys.last + result = "{" + @property_links.keys.each do |element_key| + if @property_links[(element_key)][:loaded] == true + result << (":#{element_key.to_s} => " << @property_links[(element_key)][:content].inspect) + else + result << (":#{element_key.to_s} => (Not Loaded)") + end + unless last_key == element_key + result << ", " + end + end + result << "}" + else + result = "(Too Damned Big)" + end + # + # + # + end + result + end + # + def alive?() + if @db_address + true + else + false + end + end + # + def db_address() + @db_address + end + # + def deactivate() + if self.alive?() + @property_links.keys.each do |key| + element = @property_links[(key)][:content] + if element.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + containers = [(element)] + element.search({:include_inactive => false}) do |the_value, the_selector, the_container| + if the_value.alive? && the_value.respond_to?(:deactivate) + the_value.deactivate + end + if the_container.alive? + unless containers.include?(the_container) + containers.unshift(the_container) + end + end + nil + end + containers.each do |the_container| + the_container.deactivate + end + end + end + if self.write_reserved?() + self.release_reservation() + end + @property_links = {} + @db_address = nil + end + true + end + # + def save() + result = false + if self.alive? + begin + if @db_address[:database].open?() + @property_links.keys.each do |the_key| + # Note: only save property data and header if: a) data is still loaded and b) state has changed. + if @property_links[(the_key)][:loaded] == true + if @property_links[(the_key)][:state] != @property_links[(the_key)][:content].hash + @property_links[(the_key)][:record][:version] = (((@property_links[(the_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) + property_write(the_key) + end + end + # + end + else + raise Exception, "Database not available" + end + self.save_version + result = true + rescue Exception => the_error + log_error({:error => the_error}) + end + end + result + end + # + def destroy() + result = false + if self.alive? + begin + unless self.write_reserved?() + raise Exception, "You do not have a write-lock for this element" + end + unless @permission[:destroy] + raise Exception, "You do not have permission to destory this element" + end + if @db_address[:database].open?() + if @db_address[:database].structure_attached?(@uuid) + raise Exception, "Element is still linked to a structure. Use .delete().destroy or .delete_at().destroy" + else + if self.write_reserved?() + address = @db_address + self.deactivate + address[:database].element_destroy(@credential, address[:table],address[:dbid]) + # Review : consider either emptying the db trash now, or adding a flag to manifest records to prevent future loading. + result = true + else + raise Exception, "You do not have sufficient privileges to make this change" + end + end + else + raise Exception, "Database not available" + end + rescue Exception => the_error + log_error({:error => the_error}) + end + end + result + end + # + def size() + @property_links.size + end + # + def structure_attached?() + @db_address[:database].structure_attached?(@uuid.to_s) + end + def structure_detach() + @db_address[:database].structure_detach(@uuid.to_s) + end + # + def keys() + @property_links.keys + end + # + def []=(key=nil, value=nil) + # Only works with Symbols. + unless self.alive?() + raise Exception, "Attempted to alter a defunct structure" + end + unless @db_address[:database].open?() + raise Exception, "Database not available" + end + result = nil + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + property_key = key.to_s.to_sym + # Review : rewrite ???? + if value.is_any?(::GxG::Database::Database::valid_field_classes()) || value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) + unless self.write_reserved?() + self.get_reservation() + end + if self.write_reserved?() + # Further screen value provided: + # ### Check provided PersistedHashes and PersistedArrays + if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless value.db_address() + raise Exception, "The structure you are attaching is Deactivated (not supported)." + end + if value.structure_attached?() + # Review : experimental - auto-detach from prior structural relationship and reassign here. + # raise Exception, "Cross linked structures not supported" + unless value.write_reserved?() + value.get_reservation() + end + if value.write_reserved?() + value.structure_detach + else + raise Exception, "Unable to secure write-reservation for this structural change." + end + end + value.search do |the_value, the_selector, the_container| + if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless the_value.db_address() + raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." + end + end + end + end + # ### Check provided Hashes and Arrays + if value.is_any?(::Hash, ::Array) + value.search do |the_value, the_selector, the_container| + if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + unless the_value.db_address() + raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." + end + if @db_address[:database].structure_attached?(the_value.uuid.to_s) && the_container.is_any?(::Hash, ::Array) + raise Exception, "The structure you are attaching has a cross-linked structure within it (cross linked structures not supported)." + end + end + unless the_value.is_any?(::GxG::Database::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) + raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)." + end + end + end + # ### Property Exists? + if @property_links[(key.to_s.to_sym)] + # set property to value + operation = :set_value + else + if @format + raise Exception, "Formatted - the structure cannot be altered" + else + # add property : value pair + operation = :add_value + end + end + # ### Prepare new value + new_value = { + :linkid => nil, + :content => nil, + :loaded => false, + :state => 0, + :record => { + :parent_uuid => @uuid.to_s, + :property => key.to_s, + :ordinal => 0, + :version => ::BigDecimal.new("0.0"), + :element => "element_boolean", + :element_boolean => -1, + :element_integer => 0, + :element_float => 0.0, + :element_bigdecimal => ::BigDecimal.new("0.0"), + :element_datetime => ::DateTime.now, + :time_offset => 0.0, + :time_prior => 0.0, + :time_after => 0.0, + :length => 0, + :element_text => "", + :element_text_uuid => "", + :element_binary_uuid => "", + :element_array_uuid => "", + :element_hash_uuid => "" + } + } + # + if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # ### Assimilate PersistedHash or PersistedArray + new_value[:content] = value + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::GxG::Database::PersistedHash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash_uuid] = value.uuid.to_s + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array_uuid] = value.uuid.to_s + end + else + # ### Persist Hashes and Arrays + if value.is_any?(::Hash, ::Array) + new_value[:content] = @db_address[:database].iterative_persist(value,@credential) + new_value[:loaded] = true + new_value[:state] = new_value[:content].hash + # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. + new_value[:record][:version] = new_value[:content].version() + if value.is_a?(::Hash) + new_value[:record][:element] = "element_hash" + new_value[:record][:element_hash_uuid] = new_value[:content].uuid.to_s + else + new_value[:record][:element] = "element_array" + new_value[:record][:element_array_uuid] = new_value[:content].uuid.to_s + end + else + # ### Persist Base Element Values + new_value[:record][:element] = ::GxG::Database::Database::element_table_for_instance(value).to_s + case new_value[:record][:element] + when "element_boolean" + if value.class == ::NilClass + new_value[:record][:element_boolean] = -1 + new_value[:content] = nil + end + if value.class == ::FalseClass + new_value[:record][:element_boolean] = 0 + new_value[:content] = false + end + if value.class == ::TrueClass + new_value[:record][:element_boolean] = 1 + new_value[:content] = true + end + when "element_integer" + new_value[:record][:element_integer] = value + new_value[:content] = value + when "element_float" + new_value[:record][:element_float] = value + new_value[:content] = value + when "element_bigdecimal" + new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) + new_value[:content] = new_value[:record][:element_bigdecimal] + when "element_datetime" + new_value[:record][:element_datetime] = value + new_value[:content] = value + when "element_text" + if value.size > 256 + # Note: be sure to keep text version & length sync'd with linked text element record + if operation == :set_value + new_value[:record][:version] = @property_links[(property_key)][:record][:version] + new_value[:record][:element_text_uuid] = @property_links[(property_key)][:record][:element_text_uuid] + end + new_value[:record][:length] = value.size + else + new_value[:record][:element_text] = value + new_value[:record][:length] = value.size + end + new_value[:content] = value + when "element_binary" + # Note: be sure to keep version & length sync'd with linked binary element record + if operation == :set_value + new_value[:record][:version] = @property_links[(property_key)][:record][:version] + new_value[:record][:element_binary_uuid] = @property_links[(property_key)][:record][:element_binary_uuid] + end + new_value[:record][:length] = value.size + new_value[:content] = value + else + raise Exception, "Unable to map an element type for value: #{value.inspect}" + end + end + end + # ### Commit Changes + case operation + when :set_value + # Note: Set In-memory value only, don't save unless you have to. + if new_value[:content].is_a?(@property_links[(property_key)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@property_links[(property_key)][:content])) + # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). + new_value[:linkid] = @property_links[(property_key)][:linkid] + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) + end + new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] + new_value[:loaded] = true + new_value[:state] = @property_links[(property_key)][:state] + # ### Did string grow or shrink crossing the threshold?? + if new_value[:content].is_a?(::String) + if (@property_links[(property_key)][:content].size > 256 && new_value[:content].size <= 256) + # shrank - delete text object and pages as well as property record (slow) + property_destroy(property_key) + @property_links[(property_key)] = new_value + property_write(property_key) + else + if (@property_links[(property_key)][:content].size <= 256 && new_value[:content].size > 256) + # grew - create text object and pages (a little less slow) + @property_links[(property_key)] = new_value + property_write(property_key) + else + # N/A + @property_links[(property_key)] = new_value + end + end + else + # N/A + @property_links[(property_key)] = new_value + end + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # Note: this ensures attachment to this object + property_write(property_key) + end + # + else + # Other class value being substituted. + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) + end + new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] + new_value[:loaded] = true + # Destroy old property record on db, and associated linked objects. + # Review : this is slow - look into optimizations here. + if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::GxG::ByteArray, ::String) + if @property_links[(property_key)][:content].is_a?(::String) + if (@property_links[(property_key)][:content].size > 256 && new_value[:content].to_s.size <= 256) || (@property_links[(property_key)][:content].size <= 256 && new_value[:content].to_s.size > 256) + property_destroy(property_key) + end + else + property_destroy(property_key) + end + end + # + @property_links[(property_key)] = new_value + property_write(property_key) + end + # + when :add_value + # Note: set in-memory value and save. + if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + new_value[:record][:version] = new_value[:content].version() + else + new_value[:record][:version] = ::BigDecimal.new("0.0") + end + new_value[:record][:ordinal] = @property_links.keys.size + new_value[:loaded] = true + # + @property_links[(property_key)] = new_value + # Review : is this wise to do this here?? + property_write(property_key) + # self.refresh_ordinals ?? + end + # ### Handle coordination between persisted objects: + if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + # ### Extend Reservation to persisted objects ( Review : does this slow it down too much?? ) + @property_links[(property_key)][:content].release_reservation + @db_address[:database].reservation_add_element((@reservation || @delegate), (@property_links[(property_key)][:content].db_address()[:table]), @property_links[(property_key)][:content].db_address()[:dbid]) + @property_links[(property_key)][:content].set_delegate((@reservation || @delegate)) + # ### Extend Permissions to persisted object + @db_address[:database].extend_element_permissions(@db_address[:table],@db_address[:dbid],@property_links[(property_key)][:content].db_address()[:table],@property_links[(property_key)][:content].db_address()[:dbid]) + @db_address[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => @property_links[(property_key)][:content]}) + # + @property_links[(property_key)][:content].set_parent(self) + end + # + self.increment_version() + result = @property_links[(property_key)][:content] + # + else + raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" + end + else + raise Exception, "The value is not persistable." + end + else + raise Exception, "You must provide a property key in the form of a Symbol." + end + result + end + # + def unload(key=nil) + result = false + # if key exists + # if key is loaded, unload element (if String or ByteArray) + if self.alive?() + if @db_address[:database].open?() + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + property_key = key.to_s.to_sym + if @property_links[(property_key)] + if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray) + if @property_links[(property_key)][:loaded] == true + # ### Unload Base Element contents from memory, leaving reference harness intact and active. + @property_links[(property_key)][:content].clear + @property_links[(property_key)][:loaded] = false + end + end + result = true + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def [](key=nil) + result = nil + # if key exists + # if key is not loaded, load element (if String or ByteArray) + if self.alive?() + if @db_address[:database].open?() + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + property_key = key.to_s.to_sym + if @property_links[(property_key)] + if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray) + unless @property_links[(property_key)][:loaded] == true + # ### Load Base Element contents from the database. + if @property_links[(property_key)][:content].is_a?(::String) + if @property_links[(property_key)][:record][:element_text_uuid].to_s.size > 0 + @db_address[:database].connector()[:text_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| + @property_links[(property_key)][:content] << text_page[:content] + end + else + @property_links[(property_key)][:content] = @property_links[(property_key)][:record][:element_text].to_s + end + else + @db_address[:database].connector()[:binary_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page| + @property_links[(property_key)][:content] << binary_page[:content] + end + end + @property_links[(property_key)][:loaded] = true + end + end + result = @property_links[(property_key)][:content] + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def include?(the_key) + @property_links.include?(the_key) + end + # + def delete(key=nil) + result = nil + # if key exists + # if key is not loaded, load element + if self.alive?() + if @db_address[:database].open?() + if key.is_a?(::Symbol) + if key.to_s.size > 256 + log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) + key = key.to_s[(0..255)].to_sym + end + if @format + raise Exception, "Formatted - the structure cannot be altered" + else + if self.write_reserved?() + property_key = key.to_s.to_sym + # + if @property_links[(property_key)] + # This will load the unloaded prior to separation. + result = self[(property_key)] + the_link = @property_links.delete(property_key) + if the_link[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + the_link[:content].save + the_link[:content].release_reservation() + @db_address[:database].reservation_remove_element((@reservation || @delegate), the_link[:content].db_address()[:table], the_link[:content].db_address()[:dbid]) + else + # Eliminate orphaned text and binary objects in the database. They will exist only in memory. + if the_link[:content].is_any?(::String, ::GxG::ByteArray) + if the_link[:content].is_a?(::String) + if the_link[:content].size > 256 + # eliminate text object and pages. + the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_link[:record][:element_text_uuid]}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) + end + end + end + if the_link[:content].is_a?(::GxG::ByteArray) + # eliminate binary object and pages. + the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_link[:record][:element_binary_uuid]}).first + if the_temp_record + @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) + end + end + end + end + @db_address[:database].connector()[:hash_properties].filter({:dbid => the_link[:linkid]}).delete + refresh_ordinals + self.increment_version() + end + # + else + raise Exception, "You do not have sufficient privileges to make this change" + end + end + else + raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" + end + else + raise Exception, "Database not available" + end + else + raise Exception, "Attempted to access a defunct structure" + end + result + end + # + def unpersist() + result = {} + if self.alive? + # + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::PersistedHash) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} + else + if the_value.is_a?(::GxG::Database::PersistedArray) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} + end + end + end + # Collect children export content: + link_db =[(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + link_db << the_child + end + end + end + # + end + result + end + # + def import_update(the_record=nil) + if self.alive? + if the_record.is_a?(::Hash) + if @uuid == the_record[:uuid].to_s.to_sym + if @reservation + had_reservation = true + self.release_reservation + else + had_reservation = false + end + @db_address[:database].synchronize_records([{:operation => :merge, :data => the_record}],@credential) + @property_links = {} + load_property_links + if had_reservation + self.get_reservation + end + end + end + end + self + end + # + def export(options={:exclude_file_segments=>false}) + if self.alive? + exclude_file_segments = (options[:exclude_file_segments] || false) + if options[:clone] == true + # Review : why are cloned objects unformatted? sync issues?? + result = {:type => :element_hash, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => ::BigDecimal.new(@version.to_s).to_s("F"), :content => {}} + else + result = {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => ::BigDecimal.new(@version.to_s).to_s("F"), :format => @format.clone, :content => {}} + end + export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] + children_of = Proc.new do |the_parent=nil| + list = [] + export_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # Build up export_db: + self.search do |the_value, the_selector, the_container| + if the_value.is_a?(::GxG::Database::PersistedHash) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} + end + end + if the_value.is_a?(::GxG::Database::PersistedArray) + if options[:clone] == true + the_uuid = GxG::uuid_generate.to_s.to_sym + else + the_uuid = the_value.uuid().clone + end + if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + else + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} + end + end + if the_value.is_any?(::GxG::Database::Database::valid_field_classes()) + data_type = GxG::Database::Database::element_table_for_instance(the_value) + case data_type + when :element_bigdecimal + data = the_value.to_s("F") + when :element_datetime + data = the_value.to_s + when :element_binary + data = the_value.to_s.encode64 + when :element_text + data = the_value.to_s + else + data = the_value + end + export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (::BigDecimal.new(the_container.element_version(the_selector).to_s) || ::BigDecimal.new("0.0")).to_s("F"), :content => data}} + end + end + # Collect children export content: + link_db = [(export_db[0])] + while link_db.size > 0 do + entry = link_db.shift + children_of.call(entry[:object]).each do |the_child| + entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] + if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + link_db << the_child + end + end + end + else + result = nil + end + # + result + end + # + def export_package(options={:exclude_file_segments=>false}) + self.save + result = {:formats => {}, :records => []} + object_record = self.export(options) + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) + format_sample = @db_address[:database].format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + result[:formats][(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + result[:records] << object_record + result + end + # + def each_pair(&block) + collection = {} + @property_links.keys.each do |key| + collection[(key)] = (self[(key)]) + end + if block.respond_to?(:call) + collection.to_enum(:each_pair).each do |key,value| + block.call(key,value) + end + else + collection.to_enum(:each_pair) + end + end + # + def iterative(options={:include_inactive => true}, &block) + result = [] + visit = Proc.new do |the_node=nil, accumulator=[]| + node_stack = [] + if the_node + node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) + while (node_stack.size > 0) do + a_node = node_stack.shift + # + if a_node[:object].is_a?(::GxG::Database::PersistedHash) + if a_node[:object].alive? + a_node[:object].each_pair do |the_key, the_value| + node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + if a_node[:object].is_a?(::GxG::Database::PersistedArray) + if a_node[:object].alive? + a_node[:object].each_with_index do |the_value, the_index| + node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) + end + else + if options[:include_inactive] + accumulator << a_node + end + end + end + # Review : is this for a Field?? + if a_node.alive? || options[:include_inactive] + accumulator << a_node + end + end + end + accumulator + end + # + children_of = Proc.new do |the_db=[], the_parent=nil| + list = [] + the_db.each do |node| + if node[:parent].object_id == the_parent.object_id + list << node + end + end + list + end + # + begin + database = visit.call(self,[]) + link_db = children_of.call(database, self) + if block.respond_to?(:call) + while (link_db.size > 0) do + entry = link_db.shift + unless entry[:object].object_id == self.object_id + # calls with parameters: the_value, the_key/the_index (the_selector), the_container + raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) + if raw_result + result << raw_result + end + end + if entry[:object].object_id != nil.object_id + children = children_of.call(database, entry[:object]) + children.each do |child| + link_db << child + end + end + end + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + # + result + end + alias :process :iterative + # + def process!(options={:include_inactive => true}, &block) + self.iterative(options, &block) + self + end + # + def search(options={:include_inactive => true}, &block) + results = [] + if block.respond_to?(:call) + results = self.iterative(options, &block) + end + results + end + # + def paths_to(the_object=nil,base_path="") + # new idea here: + search_results = [] + unless base_path[0] == "/" + base_path = ("/" << base_path) + end + if base_path.size > 1 + path_stack = base_path.split("/")[1..-1].reverse + else + path_stack = [] + end + origin = self.get_at_path(base_path) + container_stack = [{:selector => nil, :container => origin}] + find_container = Proc.new do |the_container| + result = nil + container_stack.each_with_index do |entry, index| + if entry[:container] == the_container + result = entry + break + end + end + result + end + last_container = origin + found = false + # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} + if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + origin.process! do |the_value, selector, container| + if last_container.object_id != container.object_id + container_record = find_container.call(container) + if container_record + path_stack = container_record[:prefix].split("/").reverse + if path_stack.size == 0 + path_stack << "" + end + end + last_container = container + end + if selector.is_a?(Symbol) + safe_key = (":" + selector.to_s) + else + safe_key = selector.to_s + end + safe_key.gsub!("/","%2f") + path_stack.unshift(safe_key) + # compare the_value + found = false + if (the_value == the_object) + found = true + end + if found + search_results << ("/" << path_stack.reverse.join("/")) + end + # + if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) + container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) + end + path_stack.shift + # + nil + end + else + search_results << ("/" << path_stack.join("/")) + end + search_results + end + # + def get_at_path(the_path="/") + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + else + element = path_element + element.gsub!("%2f","/") + if element[0] == ":" + element = element[(1..-1)].to_sym + end + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self.get_at_path(::File::dirname(the_path)) + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + else + selector = raw_selector + selector.gsub!("%2f","/") + if selector[0] == ":" + selector = selector[(1..-1)].to_sym + end + end + end + if selector + container[(selector)] = the_value + result = container[(selector)] + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + # + end + end +end diff --git a/lib/gxg/gxg_dbfs.rb b/lib/gxg/gxg_dbfs.rb new file mode 100644 index 0000000..2e2fee4 --- /dev/null +++ b/lib/gxg/gxg_dbfs.rb @@ -0,0 +1,2820 @@ +require 'pathname' + +module GxG + module Storage + class BufferedSegments < ::StringIO + # + private + def refresh_map() + total = 0 + position = 0 + @map.each do |record| + if record[:size] > 0 + record[:offset_range] = ((position)..(record[:size] - 1)) + position += (record[:size] - 1) + total += record[:size] + else + record[:offset_range] = ((position)..(position)) + end + end + @total_length = total + end + # + public + def initialize(file_segments=nil) + # + unless file_segments.is_a?(::GxG::Database::PersistedArray) + raise ArgumentError, "You MUST provide a PersistedArray" + end + @read_buffer = ::GxG::ByteArray.new + @write_buffer = ::GxG::ByteArray.new + @data = file_segments + @map = @data.element_byte_map(::GxG::ByteArray) + @position = 0 + @total_length = 0 + refresh_map() + super("") + self + end + # + def read(length = nil, outbuf = nil) + eof = false + if outbuf.is_a?(::String) + result = outbuf + else + result = ::GxG::ByteArray.new + end + # + if length.is_a?(::Integer) + if length > 0 + if @total_length == 0 + eof = true + else + start_offset = @position + if length > @total_length + end_offset = (start_offset + (@total_length - 1)) + else + end_offset = (start_offset + (length - 1)) + end + @read_buffer.clear + # + @map.each do |record| + if start_offset < record[:offset_range].first && start_offset < record[:offset_range].last + segment_start = 0 + end + if start_offset >= record[:offset_range].first && start_offset <= record[:offset_range].last + segment_start = start_offset - record[:offset_range].first + end + if end_offset <= record[:offset_range].last && end_offset >= record[:offset_range].first + segment_end = (record[:size] - 1) - (record[:offset_range].last - end_offset) + end + if end_offset > record[:offset_range].last + segment_end = record[:size] - 1 + end + @read_buffer << @data[(record[:index])][((segment_start)..(segment_end))].to_s + end + # + @position += (@read_buffer.size) + result << @read_buffer.to_s + @read_buffer.clear + end + end + else + # return entire data length bytes + @read_buffer.clear + # + @map.each do |record| + @read_buffer << @data[(record[:index])].to_s + end + @position += (@read_buffer.size) + result << @read_buffer.to_s + @read_buffer.clear + end + # + if eof == true + nil + else + result.to_s + end + end + # + def seek(amount=nil, whence=::IO::SEEK_SET) + unless amount.is_a?(::Integer) + amount = 0 + end + unless whence + whence = ::IO::SEEK_SET + end + case whence + when ::IO::SEEK_CUR + @position += amount + when ::IO::SEEK_END + @position = @total_length + amount + when ::IO::SEEK_SET + @position = amount + end + 0 + end + # + # write(string, ...) → integer + # syswrite(string) → integer + # Appends the given string to the underlying buffer string. The stream must be opened for writing. If the argument is not a string, it will be converted to a string using to_s. + # Returns the number of bytes written. See IO#write. + def write(data=nil) + result = 0 + @write_buffer.clear + @write_buffer << data.to_s + start_offset = @position + end_offset = start_offset + @write_buffer.size - 1 + if start_offset > (@total_length - 1) + @data << @write_buffer.clone + @position += @write_buffer.size + result = @write_buffer.size + @write_buffer.clear + refresh_map() + else + manifest = [] + cursor = 0 + @map.each_with_index do |record| + if start_offset >= record[:offset_range].first && end_offset <= record[:offset_range].last + # AT or Within + src_start = cursor + src_end = cursor + (end_offset - start_offset) + dst_start = (start_offset - record[:offset_range].first) + dst_end = dst_start + (src_end - src_start) + manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))} + cursor += ((src_start)..(src_end)).size + end + if start_offset < record[:offset_range].first && end_offset >= record[:offset_range].first && end_offset <= record[:offset_range].last + # Stradles Start + src_start = cursor + src_end = cursor + (end_offset - record[:offset_range].first) + dst_start = 0 + dst_end = (end_offset - record[:offset_range].first) + manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))} + cursor += ((src_start)..(src_end)).size + end + if start_offset >= record[:offset_range].first && end_offset >= record[:offset_range].last + # Strandles End + src_start = cursor + src_end = cursor + (record[:offset_range].last - start_offset) + dst_start = (start_offset - record[:offset_range].first) + dst_end = dst_start + (src_end - src_start) + manifest << {:op => :overwrite, :index => record[:index], :source_range => ((src_start)..(src_end)), :destination_range => ((dst_start)..(dst_end))} + cursor += ((src_start)..(src_end)).size + end + end + if end_offset > (@total_length - 1) + src_start = cursor + src_end = ((@write_buffer.size - 1) - cursor) + @position += (end_offset - (@total_length - 1)) + manifest << {:op => :append, :source_range => ((src_start)..(src_end))} + else + @position += (end_offset + 1) + end + # Review : add truncate functionality to this method. (research) + manifest.each do |record| + if record[:op] == :overwrite + segment = @data[(record[:index])] + @write_buffer[(record[:source_range])].each_with_index do |the_byte, indexer| + segment[(indexer + record[:destination_range].first)] = the_byte + end + end + if record[:op] == :append + @data << @write_buffer[(record[:source_range])] + end + end + refresh_map() + # + end + result + end + alias :syswrite :write + # + def rewind() + @position = 0 + 0 + end + # + def size() + @total_length + end + # + end + # + class FileSpace + def initialize() + @thread_safety = ::Mutex.new + @mounted = [] + end + # + def subpath(src_path="", the_path="") + if src_path == "/" + the_path + else + src_path.each_char do |the_char| + if the_char == the_path[0] + the_path = the_path[(1..-1)] + end + end + the_path + end + end + # + def path_prefix(src_path="", the_path="") + result = "" + src_path.each_char do |the_char| + if the_char == the_path[0] + the_path = the_path[(1..-1)] + result << the_char + else + break + end + end + result + end + # + def volume_of_path(the_path="") + result = nil + @thread_safety.synchronize { + @mounted.reverse.each do |volume_entry| + if self.path_prefix(volume_entry[:path].to_s,the_path.to_s) == (volume_entry[:path].to_s) + result = {:volume => volume_entry[:volume], :subpath => self.subpath(volume_entry[:path].to_s, the_path.to_s) } + break + end + end + } + result + end + # + def valid_path?(the_path="") + result = false + if the_path.to_s == "/" + result = true + else + # VFS check + path_table = [] + @thread_safety.synchronize { + @mounted.reverse.each do |volume_entry| + path_entry = volume_entry[:path].to_s.split("/") + unless path_table.include?(path_entry) + path_table << path_entry + end + end + } + path_array = the_path.to_s.split("/") + vfs_check = false + path_table.each do |entry| + (0..(path_array.size - 1)).each do |indexer| + if entry == (path_array[(0..(indexer))]) + vfs_check = true + break + end + end + if vfs_check + break + end + end + # volume check + if vfs_check + volume_record = self.volume_of_path(the_path.to_s) + if volume_record + result = volume_record[:volume].exist?(volume_record[:subpath].to_s) + else + result = true + end + end + # + end + result + end + # + def exist?(the_path="") + self.valid_path?(the_path) + end + # + def get_permissions(the_path="", the_credential=nil) + result = [] + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].get_permissions(volume_record[:subpath].to_s, the_credential) + end + end + result + end + # + def revoke_permissions(the_path="", the_credential=nil) + result = false + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].revoke_permissions(volume_record[:subpath].to_s, the_credential) + end + end + result + end + # + # + def set_permissions(the_path="", the_credential=nil, the_permissions={}) + result = false + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].set_permissions(volume_record[:subpath].to_s, the_credential, the_permissions) + end + end + result + end + # + def profile(the_path="",params=nil) + result = nil + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + if volume_record[:subpath].to_s.size > 0 + if params + result = volume_record[:volume].profile(volume_record[:subpath].to_s,params) + else + result = volume_record[:volume].profile(volume_record[:subpath].to_s) + end + result[:title] = (the_path.split("/").last.to_s) + else + # VFS + if params + base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s,params) + else + base_profile = volume_record[:volume].profile(volume_record[:subpath].to_s) + end + if base_profile + result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil} + result[:title] = (the_path.split("/").last.to_s) + result[:accessed] = DateTime.now + if base_profile[:permissions][:effective][:write] + result[:permissions][:effective][:write] = true + result[:permissions][:effective][:create] = true + end + end + end + else + # VFS Other + result = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => nil, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil} + result[:title] = (the_path.split("/").last.to_s) + result[:accessed] = DateTime.now + end + else + begin + raise Exception, "Path not found: #{the_path}" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + result + end + # + def entries(the_path="", as_credential=nil) + result = [] + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + # + if volume_record + result = volume_record[:volume].entries(volume_record[:subpath].to_s, as_credential) + @mounted.each do |volume_entry| + if (volume_entry[:path].to_s).include?(the_path) && volume_entry[:path].to_s != the_path + result << self.profile(volume_entry[:path].to_s) + end + end + result = result.sort {|a,b| a[:title] <=> b[:title]} + else + path_table = [] + @thread_safety.synchronize { + @mounted.reverse.each do |volume_entry| + path_entry = volume_entry[:path].to_s.split("/") + unless path_table.include?(path_entry) + path_table << path_entry + end + end + } + if the_path == "/" + offset = 1 + else + offset = (the_path.to_s.split("/").size - 1) + end + # + already = [] + path_table.each do |subpath| + if self.path_prefix(the_path.to_s,subpath.join("/").to_s) == the_path.to_s + if subpath[(offset)].to_s.size > 0 + unless already.include?(subpath[(offset)].to_s) + the_profile = self.profile((the_path + "/" + subpath[(offset)].to_s).gsub("//","/"), {:with_credential => as_credential}) + if the_profile + the_profile[:title] = (subpath[(offset)].to_s) + the_profile[:accessed] = DateTime.now + result << the_profile + end + already << subpath[(offset)].to_s + end + end + end + end + result = result.sort {|a,b| a[:title] <=> b[:title]} + # + end + # + else + begin + raise Exception, "Path not found: #{the_path}" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + result + end + # + def rmdir(the_path="") + result = false + if self.valid_path?(the_path.to_s) + begin + if the_path.to_s == "/" + raise Exception, "You do not have destroy permissions here" + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + the_profile = self.profile(the_path.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type]) + result = volume_record[:volume].rmdir(volume_record[:subpath].to_s) + else + raise Exception, "Not a directory" + end + else + raise Exception, "You do not have destroy permissions here. See unmount method." + end + else + raise Exception, "Invalid path" + end + # + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + result + end + # + def rmfile(the_path="") + result = false + if self.valid_path?(the_path.to_s) + begin + if the_path.to_s == "/" + raise Exception, "You do not have destroy permissions here" + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + the_profile = self.profile(the_path.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type]) + raise Exception, "Not a valid :file or :persisted_hash" + else + result = volume_record[:volume].rmfile(volume_record[:subpath].to_s) + end + else + raise Exception, "You do not have destroy permissions here. See unmount method." + end + else + raise Exception, "Invalid path" + end + # + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + result + end + # + def mkdir(the_path="", permissions=nil) + result = false + if self.valid_path?(the_path.to_s) + result = true + else + if self.valid_path?(::File::dirname(the_path.to_s)) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].mkdir(volume_record[:subpath].to_s, permissions) + else + begin + raise Exception, "You do not have sufficient priviledges to create here. See the mount method." + rescue Exception => the_error + result = false + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + else + begin + raise Exception, "Invalid path to new directory" + rescue Exception => the_error + result = false + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + end + result + end + # + def mkpath(the_path="") + result = false + if the_path.to_s == "" + the_path = "/" + end + if self.valid_path?(the_path.to_s) + result = true + else + error_flag = false + path_array = the_path.to_s.split("/") + (0..(path_array.size - 1)).each do |indexer| + temp_path = path_array[(0..(indexer))].join("/") + if temp_path == "" + temp_path = "/" + end + unless self.valid_path?(temp_path) + unless self.mkdir(temp_path) + error_flag = true + break + end + end + end + unless error_flag + result = true + end + end + result + end + # + def open(the_path="", options={}) + result = nil + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].open(volume_record[:subpath].to_s, options) + else + begin + raise Exception, "You do not have sufficient priviledges to open here." + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + result + end + # + def rename(the_path="", new_name="", options={}) + result = false + if self.valid_path?(the_path.to_s) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].rename(volume_record[:subpath].to_s, new_name, options) + else + begin + raise Exception, "You do not have sufficient priviledges to rename here." + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + result + end + # + def mkfile(the_path="", options={}) + result = nil + if self.valid_path?(the_path.to_s) + result = self.open(the_path,options) + else + if self.valid_path?(::File::dirname(the_path.to_s)) + if the_path.to_s == "/" + volume_record = nil + else + volume_record = self.volume_of_path(the_path.to_s) + end + if volume_record + result = volume_record[:volume].open(volume_record[:subpath].to_s, options) + else + begin + raise Exception, "You do not have sufficient priviledges to create here." + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + else + begin + raise Exception, "Invalid path to new :file" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + end + result + end + # Copying support: + def copy(source_path=nil,destination_path=nil, options={}) + # Four scenarios: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS + # Objective: handle both file/object and directory/persisted_array in one method! + # Does OVERWRITE copy onto existing files/folders of same name. + result = false + error = false + if source_path == destination_path + error = true + end + # + source = {:enclosure => nil, :profile => nil, :record => nil, :permissions => nil} + if self.valid_path?(source_path.to_s) + if source_path.to_s == "/" + source[:record] = nil + else + source[:record] = self.volume_of_path(source_path.to_s) + end + if source[:record] + source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true})) + source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true})) + source[:permissions] = self.get_permissions(source_path) + else + error = true + log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + else + error = true + log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + # Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path + if File.basename(source_path) != File.basename(destination_path) + destination_path = File.expand_path(destination_path + "/" + File.basename(source_path)) + end + # + destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil, :permissions => source[:permissions].clone} + if self.valid_path?(destination_path.to_s) + if destination_path.to_s == "/" + destination[:record] = nil + else + destination[:record] = self.volume_of_path(destination_path.to_s) + end + if destination[:record] + destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true})) + else + error = true + log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + else + if destination[:enclosure].is_a?(::Hash) + unless destination[:enclosure][:permissions][:effective][:create] == true + error = true + log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + unless error == true + destination[:record] = self.volume_of_path(File.dirname(destination_path)) + if destination[:record].is_a?(::Hash) + # Note: this won't exist yet, but is here for future reference. + destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path)) + end + end + else + error = true + log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + end + unless error == true + if source[:profile] + # determine permissions + if source[:profile][:permissions][:effective][:read] == true + # Does destination already exist? --> overwrite permissions? + if destination[:profile] + unless destination[:profile][:permissions][:effective][:write] == true + error = true + log_error({:error => Exception.new("You do not have sufficient permissions at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + end + unless error == true + # determine copying scenario: FS_to_FS, DB_to_DB, FS_to_DB, DB_to_FS + if source[:record][:volume].file_system?() + if destination[:enclosure][:uuid].to_s.size > 0 + scenario = :FS_to_DB + else + scenario = :FS_to_FS + end + else + if destination[:enclosure][:uuid].to_s.size > 0 + scenario = :DB_to_DB + else + scenario = :DB_to_FS + end + end + # create source/destination path pairings: + file_list = [] + dir_list = [] + if ["persisted_array","directory","virtual_directory"].include?(source[:profile][:type].to_s) + # directory / collection + search_queue = [{:source => (source_path), :destination => (destination_path)}] + while search_queue.size > 0 do + entry = search_queue.shift + if entry + self.entries(entry[:source]).each do |the_profile| + new_record = {:source => (entry[:source] + "/" + the_profile[:title]), :destination => (entry[:destination] + "/" + the_profile[:title]), :mode => the_profile[:mode], :mime => the_profile[:mime]} + if ["persisted_array","directory","virtual_directory"].include?(the_profile[:type].to_s) + dir_list << new_record + search_queue << new_record + else + file_list << new_record + end + end + end + end + else + # single object + file_list << {:source => (source_path), :destination => (destination_path)} + end + # lay down dir structure + dir_list.each do |the_record| + self.mkpath(the_record[:destination]) + end + # + case scenario + when :FS_to_FS + # copy files one at a time. + file_list.each do |the_record| + if self.exist?(the_record[:destination]) + self.rmfile(the_record[:destination]) + end + # FIXME: find a way to preserve :mode (via REAL FS path) on new file. + begin + source_file = self.open(the_record[:source]) + destination_file = self.mkfile(the_record[:destination]) + if source_file && destination_file + GxG::apportioned_ranges(source_file.size,65536).each do |the_range| + source_file.seek(the_range.first) + destination_file.write(GxG::ByteArray.new(source_file.read(the_range.size)).to_s) + end + destination_file.close + source_file.close + else + raise Exception, "Error opening source or destination files." + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + error = true + break + end + end + unless error == true + destination[:permissions].each do |the_entry| + self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions]) + end + result = true + end + when :FS_to_DB + file_list.each do |the_record| + begin + # Question: should I support an object import format from a text file ?? + if File.extname(the_record[:source]) == ".gxg_export" && (the_record[:mime] == "application/octet-stream" || the_record[:mime] == "application/json") + source_file = self.open(the_record[:source]) + # + import_record = source_file.read() + source_file.close + if import_record.to_s.json? + import_record = ::JSON::parse(import_record,{:symbolize_names => true}) + end + if import_record.is_a?(::Hash) + database = nil + if destination[:record].is_a?(::Hash) + if destination[:record][:volume].is_a?(GxG::Storage::Volume) + database = destination[:record][:volume].database() + end + end + if database + enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym + destination_uuid = import_record[:record][:uuid].to_s.to_sym + # Import Formats first + if import_record[:formats].is_a?(::Hash) + op_frame = {:operation => :merge_format, :data => []} + import_record[:formats].keys.each do |the_format_key| + op_frame[:data] << import_record[:formats][(the_format_key)] + end + if op_frame[:data].size > 0 + database.synchronize_records([(op_frame)],destination[:record][:volume].credential()) + end + end + # + database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential()) + # + unless self.exist?(the_record[:destination]) + the_enclosure = database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential()) + the_enclosure.wait_for_reservation + destination_object = database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential()) + the_enclosure << destination_object + destination_object.deactivate + the_enclosure.release_reservation + the_enclosure.deactivate + end + # + else + raise Exception, "Error aquiring destination database." + end + else + raise Exception, "Error importing source file." + end + else + # Import the FS file into the DB as a org.gxg.file object + source_file = self.open(the_record[:source]) + destination_object = self.open(the_record[:destination],{:format => "org.gxg.file"}) + if source_file && destination_object + destination_object.wait_for_reservation + destination_object[:mime] = the_record[:mime] + if destination_object[:file_segments].size > 0 + (0..(destination_object[:file_segments].size - 1)).to_a.reverse.each do |the_segment_index| + destination_object[:file_segments].delete_at(the_segment_index).destroy + end + end + GxG::apportioned_ranges(source_file.size,65536).each do |the_range| + source_file.seek(the_range.first) + destination_object[:file_segments] << GxG::ByteArray.new(source_file.read(the_range.size)) + destination_object[:file_segments].save + destination_object[:file_segments].unload(destination_object[:file_segments].size - 1) + end + destination_object.release_reservation + destination_object.deactivate + source_file.close + else + if source_file + source_file.close + raise Exception, "Error opening destination object." + else + raise Exception, "Error opening source file & destination object." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + error = true + break + end + end + unless error == true + destination[:permissions].each do |the_entry| + self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions]) + end + result = true + end + when :DB_to_FS + file_list.each do |the_record| + begin + # Unless the object format is org.gxg.file --> export as .gxg_export object (formats + record) + source_object = self.open(the_record[:source]) + database = source_object.db_address[:database] + # check if it is an org.gxg.file for export + current_format = nil + if source_object.format().to_s.size > 0 + the_format = database.format_load({:uuid => source_object.format().to_s.to_sym}) + if the_format[:ufs].to_s == "org.gxg.file" + current_format = the_format[:ufs].to_s + end + end + if current_format.to_s == "org.gxg.file" + destination_file = self.open(the_record[:destination]) + source_object[:file_segments].each_with_index do |the_segment, segment_index| + destination_file.write(the_segment.to_s) + source_object[:file_segments].unload(segment_index) + # Field no longer exists: the_segment.unload + end + source_object.release_reservation + source_object.deactivate + destination_file.close + else + # Review: replace with .sync_export().to_json + format_records = {} + object_record = source_object.export + object_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless format_records[(item.to_s.to_sym)].is_a?(::Hash) + format_sample = database.format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + format_records[(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + buffer = GxG::ByteArray.new({:formats => format_records, :record => object_record}.to_json) + # + unless File.extname(the_record[:destination]) == ".gxg_export" + the_record[:destination] = (File.dirname(the_record[:destination]) + "/" + File.basename(the_record[:destination]) + ".gxg_export") + end + if self.exist?(the_record[:destination]) + self.rmfile(the_record[:destination]) + end + destination_file = self.open(the_record[:destination]) + destination_file.write(buffer.to_s) + destination_file.close + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + error = true + break + end + end + unless error == true + destination[:permissions].each do |the_entry| + self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions]) + end + result = true + end + when :DB_to_DB + source_database = source[:record][:volume].database() + destination_database = destination[:record][:volume].database() + if source_database && destination_database + file_list.each do |the_record| + source_object = self.open(the_record[:source]) + current_format = nil + if source_object.format().to_s.size > 0 + the_format = source_database.format_load({:uuid => source_object.format().to_s.to_sym}) + if the_format[:ufs].to_s == "org.gxg.file" + current_format = the_format[:ufs].to_s + end + end + # + if source_database == destination_database + if current_format.to_s == "org.gxg.file" + import_record = {:formats => {}, :record => source_object.export({:clone => true, :exclude_file_segments => true})} + else + import_record = {:formats => {}, :record => source_object.export({:clone => true})} + end + else + if current_format.to_s == "org.gxg.file" + import_record = {:formats => {}, :record => source_object.export({:exclude_file_segments => true})} + else + import_record = {:formats => {}, :record => source_object.export()} + end + end + # Gather format records for the object. + import_record.search do |item,selector,container| + if selector == :format || selector == :constraint + if item.to_s.size > 0 + format_uuid = item + unless format_records[(item.to_s.to_sym)].is_a?(::Hash) + format_sample = source_database.format_load({:uuid => format_uuid.to_s.to_sym}) + format_sample[:content] = format_sample[:content].gxg_export() + format_records[(format_uuid.to_s.to_sym)] = format_sample + end + end + end + end + # + enclosure_uuid = self.profile(File.dirname(the_record[:destination]))[:uuid].to_s.to_sym + destination_uuid = import_record[:record][:uuid].to_s.to_sym + if source_database != destination_database + # Import Formats first + if import_record[:formats].is_a?(::Hash) + op_frame = {:operation => :merge_format, :data => []} + import_record[:formats].keys.each do |the_format_key| + op_frame[:data] << import_record[:formats][(the_format_key)] + end + if op_frame[:data].size > 0 + destination_database.synchronize_records([(op_frame)],destination[:record][:volume].credential()) + end + end + # + end + # + if self.exist?(the_record[:destination]) + # Why? Because an object of the same NAME, but differing format/content may be present (unacceptable) + self.rmfile(the_record[:destination]) + # Risky, but avoids situation where: diff.db, same.uuid, gets deleted, then imported to deleted object (doh!) + destination_database.empty_trash + end + # + destination_database.synchronize_records([{:operation => :merge, :data => [(import_record[:record])]}],destination[:record][:volume].credential()) + # + unless self.exist?(the_record[:destination]) + the_enclosure = destination_database.retrieve_by_uuid(enclosure_uuid, destination[:record][:volume].credential()) + the_enclosure.wait_for_reservation + destination_object = destination_database.retrieve_by_uuid(destination_uuid, destination[:record][:volume].credential()) + if current_format.to_s == "org.gxg.file" + # copy file segments + destination_object.wait_for_reservation + source_object[:file_segments].each_with_index do |the_segment, segment_index| + destination_object[:file_segments] << ByteArray.new(the_segment.to_s) + destination_object[:file_segments].save + destination_object[:file_segments].unload(destination_object[:file_segments].size - 1) + # destination_object[:file_segments][(destination_object[:file_segments].size - 1)].save + # destination_object[:file_segments][(destination_object[:file_segments].size - 1)].unload + source_object[:file_segments].unload(segment_index) + # Field no longer exists : the_segment.unload + end + destination_object.release_reservation + end + the_enclosure << destination_object + destination_object.deactivate + the_enclosure.release_reservation + the_enclosure.deactivate + end + # + end + unless error == true + destination[:permissions].each do |the_entry| + self.set_permissions(destination_path, the_entry[:credential], the_entry[:permissions]) + end + result = true + end + else + log_error({:error => Exception.new("Error aquiring source or destination database."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + error = true + end + end + # + end + else + error = true + log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + end + end + result + end + # Moving support + def move(source_path=nil, destination_path=nil, options={}) + result = false + error = false + if source_path == destination_path + error = true + end + # + source = {:enclosure => nil, :profile => nil, :record => nil} + if self.valid_path?(source_path.to_s) + if source_path.to_s == "/" + source[:record] = nil + else + source[:record] = self.volume_of_path(source_path.to_s) + end + if source[:record] + source[:profile] = self.profile(source_path,options.merge({:follow_symlinks => true})) + source[:enclosure] = self.profile(File.dirname(source_path),options.merge({:follow_symlinks => true})) + else + error = true + log_error({:error => Exception.new("You do not have sufficient priviledges."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + else + error = true + log_error({:error => Exception.new("Invalid source path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + # Note: if destination is not full path, but path to its future enclosure, append the filename to fully qualify the destination_path + if File.basename(source_path) != File.basename(destination_path) + destination_path = File.expand_path(destination_path + "/" + File.basename(source_path)) + end + # + destination = {:enclosure => self.profile(File.dirname(destination_path),options.merge({:follow_symlinks => true})), :profile => nil, :record => nil} + if self.valid_path?(destination_path.to_s) + if destination_path.to_s == "/" + destination[:record] = nil + else + destination[:record] = self.volume_of_path(destination_path.to_s) + end + if destination[:record] + destination[:profile] = self.profile(destination_path,options.merge({:follow_symlinks => true})) + else + error = true + log_error({:error => Exception.new("You do not have sufficient permissions."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + else + if destination[:enclosure].is_a?(::Hash) + unless destination[:enclosure][:permissions][:effective][:create] == true + error = true + log_error({:error => Exception.new("You do not have sufficient permissions to create at destination."), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + unless error == true + destination[:record] = self.volume_of_path(File.dirname(destination_path)) + if destination[:record].is_a?(::Hash) + # Note: this won't exist yet, but is here for future reference. + destination[:record][:subpath] = (destination[:record][:subpath] + "/" + File.basename(destination_path)) + end + end + else + error = true + log_error({:error => Exception.new("Invalid destination path"), :parameters => {:source_path => source_path, :destination_path => destination_path, :options => options}}) + end + end + # + unless error == true + if source[:record][:volume].file_system?() + if destination[:enclosure][:uuid].to_s.size > 0 + scenario = :FS_to_DB + else + scenario = :FS_to_FS + end + else + if destination[:enclosure][:uuid].to_s.size > 0 + scenario = :DB_to_DB + else + scenario = :DB_to_FS + end + end + if scenario == :DB_to_DB && (source[:record][:volume].database() == destination[:record][:volume].database()) + database = source[:record][:volume].database() + source_enclosure = database.retrieve_by_uuid(source[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential()) + source_enclosure.wait_for_reservation + destination_enclosure = database.retrieve_by_uuid(destination[:enclosure][:uuid].to_s.to_sym, source[:record][:volume].credential()) + destination_enclosure.wait_for_reservation + # + source_enclosure.each_index do |the_index| + if source_enclosure[(the_index)].uuid().to_s.to_sym == source[:profile][:uuid].to_s.to_sym + destination_enclosure << source_enclosure.delete_at(the_index) + break + else + source_enclosure[(the_index)].deactivate + end + end + source_enclosure.release_reservation + destination_enclosure.release_reservation + source_enclosure.deactivate + destination_enclosure.deactivate + result = true + else + # copy to destination_path + if self.copy(source_path, destination_path, options) == true + # delete source_path object + if ["directory","persisted_array"].include?(source[:profile][:type].to_s) + result = self.rmdir(source_path) + else + result = self.rmfile(source_path) + end + end + end + end + # + result + end + # + def mount(the_volume = nil, the_path = "") + # Allow for a path to be mounted over the top of another - design choice + if the_volume.is_a?(::GxG::Storage::Volume) + @thread_safety.synchronize { @mounted << {:volume => the_volume, :path => ::Pathname.new(the_path.to_s)} } + true + else + @mounted + end + end + # + def mounted?(the_path="") + result = false + @thread_safety.synchronize { + @mounted.reverse.each do |entry| + if entry[:path] == the_path + result = true + break + end + end + } + result + end + # + def unmount(the_path="") + # Allow for a path to be mounted over the top of another - design choice + result = nil + @thread_safety.synchronize { + if @mounted.size > 0 + (0..(@mounted.size - 1)).to_a.reverse.each do |indexer| + if the_path.to_s == (@mounted[(indexer)][:path].to_s) + result = @mounted[(indexer)][:volume] + @mounted.delete_at(indexer) + break + end + end + end + } + # returns the volume object for proper closing + result + end + # + end + + # + class Volume + # + def inspect() + "" + end + # + def credential() + @credential + end + # + def database() + @database + end + # + def directory() + @directory + end + # + def file_system?() + if self.directory() + true + else + false + end + end + # + def db_path(subpath="") + result = nil + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + subpath_array = subpath.to_s.split("/") + if subpath_array.size == 0 + subpath_array << "Volume Root" + else + subpath_array[0] = "Volume Root" + end + path_array = [{:uuid => @root_uuid, :title => "Volume Root"}] + subpath_array.each_with_index do |the_entry, indexer| + parent_uuid = path_array.last[:uuid] + # + @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |item| + if [:element_array, :element_hash].include?(item[:element].to_s.to_sym) + if item[:element].to_s.to_sym == :element_array + record = @database.connector()[:element_array].filter({:uuid => item[:element_array_uuid].to_s}).first + else + record = @database.connector()[:element_hash].filter({:uuid => item[:element_hash_uuid].to_s}).first + end + if record + if record[:title] == subpath_array[(indexer + 1)].to_s + path_array << {:uuid => record[:uuid].to_sym, :title => record[:title]} + break + end + end + end + end + # + end + if path_array.size == subpath_array.size + result = path_array + end + result + end + # + def exist?(subpath="") + result = false + if @directory + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + result = ::File::exist?(the_path) + else + # DB object exist? + if self.db_path(subpath) + result = true + end + end + result + end + # + def profile(subpath="",params={:follow_symlinks => true}) + unless params.is_a?(::Hash) + params = {:follow_symlinks => true} + end + unless params[:follow_symlinks] + unless params.keys.include?(:follow_symlinks) + params[:follow_symlinks] = true + end + end + platform_details = GxG::SYSTEM.platform() + result = {:title => "", :type => :unspecified, :owner_type => :unspecified, :uuid => nil, :version => 0.0, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>false}}, :mode=>nil, :mime => nil} + if @directory + # return FS object profile + result[:title] = (subpath.split("/").last.to_s) + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + if params[:follow_symlinks] + raw_stat = ::File::stat(the_path.to_s) + else + raw_stat = ::File::lstat(the_path.to_s) + end + raw_mode = raw_stat.mode.to_s(base=8) + # puts "Got: #{raw_mode}" + if raw_mode.size < 6 + raw_mode = ("0" << raw_mode) + end + # puts "Then Got: #{raw_mode}" + set_user_id = false + set_group_id = false + set_sticky_bit = false + if (raw_mode[2].to_i & 4) == 4 + set_user_id = true + end + if (raw_mode[2].to_i & 2) == 2 + set_group_id = true + end + if (raw_mode[2].to_i & 1) == 1 + set_sticky_bit = true + end + raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false } + case raw_stat.ftype.to_s.downcase.to_sym + when :link + # are .lnk files handled properly on win32 for this? + result[:type] = :symlink + when :file + the_extension = ::File::basename(the_path.to_s).split(".")[-1].to_s.downcase + if the_extension == "lnk" + result[:type] = :symlink + else + if ["so","la","a","dylib","dll","class"].include?(the_extension) + result[:type] = :library + else + if (["out","exe","misc","java","jar", "sh", "rb", "py", "pyc"].include?(the_extension) || raw_stat.executable_real?) + result[:type] = :application + else + # TODO: find a way to externally sense if a tty + result[:type] = :file + end + end + end + when :fifo + result[:type] = :fifo + when :characterspecial + result[:type] = :character_device + when :blockspecial + result[:type] = :block_device + when :socket + result[:type] = :socket + when :directory + result[:type] = :directory + when :unknown + result[:type] = :unknown + if raw_stat.pipe? + result[:type] = :pipe + end + else + if raw_stat.pipe? + result[:type] = :pipe + end + end + # + result[:owner_type] = result[:type] + result[:on_device] = raw_stat.dev.to_s(base=10).to_i + if result[:on_device] + result[:on_device_major] = raw_stat.dev_major + result[:on_device_minor] = raw_stat.dev_minor + end + result[:is_device] = raw_stat.rdev + if result[:is_device] + result[:is_device_major] = raw_stat.rdev_major + result[:is_device_minor] = raw_stat.rdev_minor + end + result[:inode] = raw_stat.ino.to_i + # ### :file_id + if ::GxG::SYSTEM.platform()[:platform] == :windows + # FIX / Review : use Fiddle to call GetFileInformationByHandle from Kernel32.dll + # See : https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information?redirectedfrom=MSDN + # See : https://ruby-doc.org/stdlib-2.5.3/libdoc/fiddle/rdoc/Fiddle.html + # FOR NOW : if you rename the file it will mess this idea up, but it's all I got for now. + result[:file_id] = Digest::MD5.hexdigest(the_path).to_s + else + result[:file_id] = "#{(result[:on_device] || 0).to_s}-#{(result[:on_device_major] || 0).to_s}-#{(result[:on_device_minor] || 0).to_s}-#{(result[:inode] || 0).to_s}" + end + result[:flags] = [] + if ::File::writable_real?(the_path.to_s) + result[:flags] << :write + end + if ::File::readable_real?(the_path.to_s) + result[:flags] << :read + end + # + result[:hardlinks_to] = raw_stat.nlink.to_i + result[:user_id] = raw_stat.uid.to_i + result[:group_id] = raw_stat.gid.to_i + result[:size] = raw_stat.size + result[:block_size] = raw_stat.blksize + result[:blocks] = raw_stat.blocks + result[:version] = raw_stat.mtime.to_f.to_d + result[:accessed] = DateTime::parse(raw_stat.atime.to_s) + result[:modified] = DateTime::parse(raw_stat.mtime.to_s) + result[:status_modified] = DateTime::parse(raw_stat.ctime.to_s) + # File permissions work in an ACL style, even if based only upon Unix-style permissions. + # + result[:permissions] = {:effective => raw_permission.clone, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + if set_user_id + result[:flags] << :user_id_on_execute + end + if set_group_id + result[:flags] << :group_id_on_execute + end + if set_sticky_bit + result[:flags] << :sticky_bit + end + # + # owner permissions + if (raw_mode[3].to_i & 4) == 4 + result[:permissions][:owner][:read] = true + end + if (raw_mode[3].to_i & 2) == 2 + result[:permissions][:owner][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:owner][:rename] = true + result[:permissions][:owner][:move] = true + result[:permissions][:owner][:destroy] = true + result[:permissions][:owner][:create] = true + end + end + if (raw_mode[3].to_i & 1) == 1 + result[:permissions][:owner][:execute] = true + end + # group permissions + if (raw_mode[4].to_i & 4) == 4 + result[:permissions][:group][:read] = true + end + if (raw_mode[4].to_i & 2) == 2 + result[:permissions][:group][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:group][:rename] = true + result[:permissions][:group][:move] = true + result[:permissions][:group][:destroy] = true + result[:permissions][:group][:create] = true + end + end + if (raw_mode[4].to_i & 1) == 1 + result[:permissions][:group][:execute] = true + end + # other permissions + if (raw_mode[5].to_i & 4) == 4 + result[:permissions][:other][:read] = true + end + if (raw_mode[5].to_i & 2) == 2 + result[:permissions][:other][:write] = true + if platform_details[:platform] == :windows + # :windows way of reading :rename, :move, and :destroy permission for file/directory. + else + result[:permissions][:other][:rename] = true + result[:permissions][:other][:move] = true + result[:permissions][:other][:destroy] = true + result[:permissions][:other][:create] = true + end + end + if (raw_mode[5].to_i & 1) == 1 + result[:permissions][:other][:execute] = true + end + # effective (this user) permissions (while opened, supercedes normal effective file system permissions) + if result[:flags].index(:read) + result[:permissions][:effective][:read] = true + # + end + # ### db-based virtualized permission system keyed by :file_id + # ### on non-linux systems - make sure to support the :file_id format subsystem, and map to local-arch-platform equivilents. + # search_database(credential, {:ufs => "the.ufs.code", :properties => [{:the_property => {:equals => value}}]}) + if ::GxG::valid_uuid?(params[:with_credential] || @credential) + acts_as = :group + current_roles = GxG::DB[:authority].user_roles(params[:with_credential] || @credential) + current_roles.each do |role_record| + unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], role_record[:credential]) + if role_record[:credential] == GxG::DB[:authority][:system_credentials][:designers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:developers] || role_record[:credential] == GxG::DB[:authority][:system_credentials][:administrators] + GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:owner]) + acts_as = :owner + else + GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], role_record[:credential], result[:permissions][:group]) + end + # + end + end + # + unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], :"00000000-0000-4000-0000-000000000000") + GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], :"00000000-0000-4000-0000-000000000000", result[:permissions][:other]) + end + # ### Review : should I support individual permissions at all, or rather confine it to roles and the public id? + # unless GxG::DB[:roles][:vfs].vfs_permission_exist?(result[:file_id], (params[:with_credential] || @credential)) + # if acts_as == :owner + # GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:owner]) + # else + # GxG::DB[:roles][:vfs].create_vfs_permission(result[:file_id], (params[:with_credential] || @credential), result[:permissions][:group]) + # end + # end + # + result[:permissions][:effective] = GxG::DB[:roles][:vfs].effective_vfs_permission(result[:file_id], (params[:with_credential] || @credential)) + # + else + log_error({:error => Exception.new("Invalid credential: #{params[:with_credential] || @credential}")}) + end + result[:permissions].delete(:owner) + result[:permissions].delete(:group) + result[:permissions].delete(:other) + # + # + # if @act_as.is_a?(::Symbol) || params[:with_credential] + # if ::GxG::valid_uuid?(params[:with_credential]) + # if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], params[:with_credential].to_s.to_sym) + # now_act_as = :owner + # else + # if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, params[:with_credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, params[:with_credential].to_s.to_sym) + # now_act_as = :group + # else + # now_act_as = :other + # end + # end + # result[:permissions][:effective] = result[:permissions][(now_act_as)].clone + # else + # result[:permissions][:effective] = result[:permissions][(@act_as)].clone + # end + # else + # if result[:flags].index(:write) + # result[:permissions][:effective][:write] = true + # if platform_details[:platform] == :windows + # # :windows way of reading :rename, :move, and :destroy permission for file/directory. + # else + # result[:permissions][:effective][:rename] = true + # result[:permissions][:effective][:move] = true + # result[:permissions][:effective][:destroy] = true + # result[:permissions][:effective][:create] = true + # end + # end + # if raw_stat.executable? + # result[:permissions][:effective][:execute] = true + # end + # end + # + result[:mode] = raw_mode + # Mime type identification: + if File.directory?(the_path) + mime_type = "inode/directory" + else + mime_type = MimeMagic.by_extension(File.extname(result[:title])) + unless mime_type + mime_type = MimeMagic.by_path(the_path) + end + unless mime_type + handle = ::File.open(the_path,"rb") + handle.rewind + mime_type = ::MimeMagic.by_magic(handle) + handle.close + end + if mime_type + mime_type = mime_type.type() + else + mime_type = "application/octet-stream" + end + end + result[:mime] = mime_type + # + # + else + # return DB object profile + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + # + path_array = self.db_path(subpath.to_s) + if path_array + object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0} + header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first + if header + object_details[:table] = :element_hash + object_details[:dbid] = header[:dbid] + else + header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first + if header + object_details[:table] = :element_array + object_details[:dbid] = header[:dbid] + end + end + # + result = {:title => path_array.last[:title], :type => :virtual_directory, :owner_type => :virtual_directory, :uuid => path_array.last[:uuid], :version => ::BigDecimal.new(header[:version].to_s), :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :file_id => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => {:effective => {:execute => true, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read=>true}}, :mode=>nil, :mime => nil} + if object_details[:table] == :element_array + result[:type] = :persisted_array + result[:owner_type] = :persisted_array + else + result[:type] = :persisted_hash + result[:owner_type] = :persisted_hash + end + if ::GxG::valid_uuid?(params[:with_credential]) + result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], params[:with_credential]) + else + result[:permissions][:effective] = @database.effective_uuid_permission(object_details[:uuid], @credential) + end + # Object Size in DB: + if result[:type] == :persisted_hash + # Review : DB overhaul - ensure @database.byte_size_by_uuid supports new arch. + # result[:size] = @database.byte_size_by_uuid(object_details[:uuid]) + else + # result[:size] = 0 + end + # Pseudo-mime for objects: + mime_type = nil + if header[:format].to_s.size > 0 + the_format = @database.format_list({:uuid => header[:format]})[0] + if the_format + mime_type = "vnd.gxg/#{the_format[:ufs].to_s.gsub('_','-')}" + else + mime_type = "vnd.gxg/org.gxg.hash.persisted" + end + else + if object_details[:table] == :element_array + mime_type = "vnd.gxg/org.gxg.array.persisted" + end + end + result[:mime] = mime_type + else + # raise Exception, "Invalid path" + end + end + result + end + # + def initialize(options={}) + # options: :directory => "/path/to/dir", :credential => .to_s, :act_as => :owner? || :database => , :credential => .to_s, :root_uuid => .to_s + @thread_safety = ::Mutex.new + @credential = GxG::DB[:administrator].to_s + @database = nil + @root_uuid = nil + # Acts As: :owner, :group, :other for File System Volumes + if [:owner, :group, :other].include?(options[:act_as]) + @act_as = options[:act_as] + else + @act_as = nil + end + @directory = options[:directory] + if @directory.to_s.size > 0 + if ::GxG::valid_uuid?(options[:credential].to_s.to_sym) + unless @act_as + # set @act_as according to credential Group/Role membership: Administrators=:owner, Developers/Designers=:group, all others=:other + if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:administrators], options[:credential].to_s.to_sym) + @act_as = :owner + else + if GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:developers].to_s.to_sym, options[:credential].to_s.to_sym) || GxG::DB[:authority].role_member?(GxG::DB[:authority][:system_credentials][:designers].to_s.to_sym, options[:credential].to_s.to_sym) + @act_as = :group + else + @act_as = :other + end + end + end + end + if ::Dir.exist?(@directory.to_s) + if ::File.readable_real?(@directory.to_s) + @directory = ::Dir.new(@directory.to_s) + else + raise Exception, "You do not have read permissions to directory #{@directory.inspect}." + end + else + raise Exception, "Directory #{@directory.inspect} does not exist in the filesystem." + end + # + else + @credential = (options[:credential] || GxG::DB[:administrator]).to_s + if options[:database].is_a?(::GxG::Database::Database) and @credential.size > 0 + @database = options[:database] + if @database.setting_keys.include?(:volume_information) + vol_info = @database[:volume_information] + else + if @database.db_permissions()[:write] + new_root = @database.try_persist([], @credential) + if new_root + new_root.wait_for_reservation() + new_root.set_title("Volume Root") + new_root.set_permissions(@credential, {:destroy => false, :move => false, :rename => false, :create => true, :write => true}) + vol_info = {:volume_root => new_root.uuid()} + new_root.deactivate + @database[:volume_information] = vol_info + else + raise Exception, "Unable to initialize volume root" + end + else + raise Exception, "Cannot initialize Volume Information on read-only database." + end + end + # One can specify a sub-array to the volume as this volume's root array. + if GxG::valid_uuid?(options[:root_uuid].to_s) + # verify array's existence + the_persisted_array = @database.retrieve_by_uuid(options[:root_uuid], @credential) + if the_persisted_array.is_a?(::GxG::Database::PersistedArray) + the_persisted_array.deactivate + @root_uuid = options[:root_uuid].to_s.to_sym + else + raise Exception, "The root_uuid points to a non-existent or invalid object, or you do not have permissions to read it." + end + # + else + @root_uuid = vol_info[:volume_root].to_sym + end + else + raise ArgumentError, "You MUST supply a valid credential and a database object. :credential => #{@credential.inspect}, :database => #{options[:database].inspect}" + end + end + # + end + # + def entries(subpath="", as_credential=nil, options={:follow_symlinks => true}) + results = [] + if @directory + # FS + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + ::Dir::entries(the_path).each do |item| + unless [".", ".."].include?(item) + if as_credential + the_profile = self.profile((subpath + "/" + item), options.merge({:with_credential => as_credential})) + else + the_profile = self.profile((subpath + "/" + item), options) + end + if the_profile + the_profile[:title] = item + results << the_profile + end + end + end + # + else + # DB + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + path_array = self.db_path(subpath.to_s) + if path_array + base_profile = self.profile(subpath.to_s, {:with_credential => as_credential}) + if base_profile + if base_profile[:permissions][:effective][:read] + if base_profile[:type] == :persisted_array + object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0} + header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first + if header + object_details[:table] = :element_array + object_details[:dbid] = header[:dbid] + end + contained_titles = [] + @database.connector()[:array_elements].select(:element_hash_uuid, :element_array_uuid).where({:parent_uuid => path_array.last[:uuid].to_s}).each do |link_record| + if link_record[:element_hash_uuid].to_s.size > 0 + header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first + if header + contained_titles << header[:title].to_s + end + else + if link_record[:element_array_uuid].to_s.size > 0 + header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first + if header + contained_titles << header[:title].to_s + end + end + end + end + contained_titles.sort.each do |the_title| + if the_title.size > 0 + results << self.profile((subpath.to_s + "/" + the_title).gsub("//","/"), {:with_credential => as_credential}) + end + end + end + end + end + end + end + results + end + # + def rmdir(subpath="") + result = false + if @directory + # FS rmdir + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + # + if ::File::exist?(the_path) + begin + the_profile = self.profile(subpath.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + # Recursively delete any content files and subdirs first + file_list = [] + dir_list = [] + vfs_list = [ (the_profile[:file_id]) ] + # Gather up all VFS-DB :file_id entries: + search_queue = [(subpath)] + while search_queue.size > 0 do + entry = search_queue.shift + self.entries(entry).each do |the_subitem_profile| + if the_subitem_profile[:file_id].to_s.size > 0 + vfs_list << the_subitem_profile[:file_id] + end + if [:virtual_directory, :persisted_array, :directory].include?(the_subitem_profile[:type]) + search_queue << (entry + "/" + the_subitem_profile[:title]) + end + end + end + # Do the FS work: + search_queue = [(the_path)] + while search_queue.size > 0 do + entry = search_queue.shift + if entry + Dir.entries(entry).each do |the_item_name| + unless [".",".."].include?(the_item_name) + if File.directory?(entry + "/" + the_item_name) + dir_list << (entry + "/" + the_item_name) + search_queue << (entry + "/" + the_item_name) + else + file_list << (entry + "/" + the_item_name) + end + end + end + end + end + file_list.each do |the_file_path| + File.delete(the_file_path) + end + dir_list.each do |the_dir_path| + ::Dir::rmdir(the_dir_path) + end + if ::Dir::rmdir(the_path) == 0 + vfs_list.each do |the_file_id| + GxG::DB[:roles][:vfs].destroy_vfs_permission(the_file_id) + end + result = true + end + else + raise Exception, "You do not have destroy permissions to do this" + end + else + raise Exception, "Inavalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + else + begin + raise Exception, "Invaid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + # + else + # DB rmdir + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + path_array = self.db_path(subpath.to_s) + if path_array + if path_array.size > 1 + the_profile = self.profile(subpath.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + parent_uuid = (path_array[-2] || {})[:uuid] + link_uuids = [] + found_link = nil + if parent_uuid + @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record| + header = nil + if link_record[:element_array_uuid].to_s.size > 0 + header = @database.connector()[:element_array].filter({:uuid => link_record[:element_array_uuid].to_s}).first + end + if header + if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title] + found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]} + else + link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]} + end + end + end + if found_link + # Review : how to deal with lots of users deleting objects at once?? + if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true + # ### Delete array_elements link of id ... + @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete + # ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results) + link_uuids.each_with_index do |link_record, ordinal| + @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal}) + end + end + end + end + else + begin + raise Exception, "You do not have permissions to destroy this object" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath}}) + end + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath}}) + end + end + else + begin + raise Exception, "You do not have permissions to destroy this object" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath}}) + end + end + else + begin + raise Exception, "Invalid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath}}) + end + end + end + result + end + # + def rmfile(subpath="") + result = false + if @directory + # FS rmfile + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + # + if ::File::exist?(the_path) + begin + the_profile = self.profile(subpath.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + if ::File::delete(the_path) + GxG::DB[:roles][:vfs].destroy_vfs_permission(the_profile[:file_id]) + result = true + end + else + raise Exception, "You do not have destroy permissions to do this" + end + else + raise Exception, "Inavalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + else + begin + raise Exception, "Invaid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + end + # + else + # DB rmfile + path_array = self.db_path(subpath.to_s) + if path_array + the_profile = self.profile(subpath.to_s) + if the_profile + if the_profile[:permissions][:effective][:destroy] + parent_uuid = (path_array[-2] || {})[:uuid] + link_uuids = [] + found_link = nil + if parent_uuid + @database.connector()[:array_elements].filter({:parent_uuid => parent_uuid.to_s}).order(:ordinal).each do |link_record| + header = nil + if link_record[:element_hash_uuid].to_s.size > 0 + header = @database.connector()[:element_hash].filter({:uuid => link_record[:element_hash_uuid].to_s}).first + end + if header + if header[:uuid] == path_array.last[:uuid] && header[:title] == path_array.last[:title] + found_link = {:uuid => header[:uuid], :linkid => link_record[:dbid]} + else + link_uuids << {:uuid => header[:uuid], :linkid => link_record[:dbid]} + end + end + end + if found_link + # Review : how to deal with lots of users deleting objects at once?? + if @database.destroy_by_uuid(@credential, found_link[:uuid].to_sym) == true + # ### Delete array_elements link of id ... + @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).delete + # ### Reset ordinals of array_elements links. (Even though VFS.entries sorts results) + link_uuids.each_with_index do |link_record, ordinal| + @database.connector()[:array_elements].filter({:dbid => found_link[:linkid]}).update({:ordinal => ordinal}) + end + end + end + end + # + else + begin + raise Exception, "You do not have destroy permissions to do this" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath.to_s}}) + end + end + else + begin + raise Exception, "Invaid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath.to_s}}) + end + end + else + begin + raise Exception, "Invaid path" + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath.to_s}}) + end + end + end + result + end + # + def rename(subpath="", new_name="", options={}) + result = false + if @directory + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + # FS + begin + if options[:with_credential] + item_profile = self.profile(subpath.to_s,options.merge({:follow_symlinks => true})) + else + item_profile = self.profile(subpath.to_s) + end + if item_profile.is_a?(::Hash) + if item_profile[:permissions][:effective][:rename] == true + ::File.rename(the_path,(File.dirname(the_path) + "/" + new_name)) + # Review : check for file_id changes and update the VFS-DB permission records. + result = true + else + raise Exception, "You do not have rename permissions here" + end + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + # + end + else + # DB open/create :persisted_hash or open :persisted_array + begin + path_array = self.db_path(subpath.to_s) + if path_array + if options[:with_credential] + profile = self.profile(subpath.to_s, options) + else + profile = self.profile(subpath.to_s) + end + profile = self.profile(subpath.to_s) + if profile[:permissions][:effective][:rename] == true + if profile[:type] == :persisted_hash + @database.connector()[:element_hash].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name}) + result = true + else + if profile[:type] == :persisted_array + @database.connector()[:element_array].filter({:uuid => profile[:uuid].to_s}).update({:title => new_name}) + result = true + end + end + # + else + raise Exception, "You do not have rename permissions here" + end + # + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + end + end + result + end + # + def open(subpath="", options={}) + result = nil + if @directory + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + flags = 0 + flag_array = (options[:flags] || [:readwrite]) + flag_array.each do |the_flag| + case the_flag + when :append + flags += ::File::APPEND + when :binary + flags += ::File::BINARY + when :create + flags += ::File::CREAT + when :direct + flags += ::File::DIRECT + when :dsync + flags += ::File::DSYNC + when :duplex + flags += ::File::DUPLEX + when :exclusive + flags += ::File::EXCL + when :casefold + flags += ::File::FNM_CASEFOLD + when :dotmatch + flags += ::File::FNM_DOTMATCH + when :no_escape + flags += ::File::FNM_NOESCAPE + when :pathname + flags += ::File::FNM_PATHNAME + when :system_case + flags += ::File::FNM_SYSCASE + when :lock_ex + flags += ::File::LOCK_EX + when :lock_nb + flags += ::File::LOCK_NB + when :lock_sh + flags += ::File::LOCK_SH + when :lock_un + flags += ::File::LOCK_UN + when :no_atime + flags += ::File::NOATIME + when :no_ctty + flags += ::File::NOCTTY + when :no_follow + flags += ::File::NOFOLLOW + when :nonblock + flags += ::File::NONBLOCK + when :read + flags += ::File::RDONLY + when :rsync + flags += ::File::RSYNC + when :seek_cur + flags += ::File::SEEK_CUR + when :seek_end + flags += ::File::SEEK_END + when :seek_set + flags += ::File::SEEK_SET + when :setenc_by_bom + flags += ::File::SETENC_BY_BOM + when :sync + flags += ::File::SYNC + when :text + flags += ::File::TEXT + when :truncate + flags += ::File::TRUNC + when :tty + flags += ::File::TTY + when :write + flags += ::File::WRONLY + when :readwrite + flags += ::File::RDWR + when :wsplit + flags += ::File::WSPLIT + when :wsplit_initialized + flags += ::File::WSPLIT_INITIALIZED + end + end + perm = 16384 + permissions = options[:permissions] + if permissions.is_a?(::Hash) + # Best if you pass the entire :permissions hash from a profile query + "040000".each_char_with_index do |digit, indexer| + case indexer + when 0 + when 1 + # TODO: process other flags + when 2 + when 3 + if permissions[:owner].is_a?(::Hash) + if permissions[:owner][:execute] + perm += 256 + end + if permissions[:owner][:write] + perm += 128 + end + if permissions[:owner][:read] + perm += 64 + end + end + when 4 + if permissions[:group].is_a?(::Hash) + if permissions[:group][:execute] + perm += 32 + end + if permissions[:group][:write] + perm += 16 + end + if permissions[:group][:read] + perm += 8 + end + end + when 5 + if permissions[:other].is_a?(::Hash) + if permissions[:other][:execute] + perm += 4 + end + if permissions[:other][:write] + perm += 2 + end + if permissions[:other][:read] + perm += 1 + end + end + end + end + # + else + base_profile = self.profile("/") + if base_profile.is_a?(::Hash) + if base_profile[:mode].is_a?(::String) + perm = (base_profile[:mode][0..2].to_s + "644").to_i(base=8) + else + perm = 16804 + end + else + perm = 16804 + end + end + # FS + begin + parent_profile = self.profile(::File::dirname(subpath.to_s)) + if parent_profile + if ::File::exist?(the_path) + if ::File::directory?(the_path) + result = ::Dir::new(the_path) + else + if flag_array.include?(:read) + unless parent_profile[:permissions][:effective][:read] + raise Exception, "You do not have read permissions here." + end + end + if flag_array.include?(:write) + unless parent_profile[:permissions][:effective][:write] + raise Exception, "You do not have write permissions here." + end + end + if flag_array.include?(:readwrite) + unless parent_profile[:permissions][:effective][:read] + raise Exception, "You do not have read permissions here." + end + unless parent_profile[:permissions][:effective][:write] + raise Exception, "You do not have write permissions here." + end + end + if ::GxG::Engine::available_file_descriptors() > 0 + result = ::File::open(the_path, flags) + else + raise Exception, "Maximum File Descriptor count exceeded. Aborting" + end + end + else + if parent_profile[:permissions][:effective][:create] + if ::GxG::Engine::available_file_descriptors() > 0 + ::File::new(the_path, "w", perm).close + result = ::File::open(the_path, flags) + # Generate VFS-DB permissions, return result + self.profile(subpath) + else + raise Exception, "Maximum File Descriptor count exceeded. Aborting" + end + else + raise Exception, "You do not have create permissions here" + end + end + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :options => options}}) + # + end + else + # DB open/create :persisted_hash + begin + path_array = self.db_path(subpath.to_s) + if path_array + profile = self.profile(subpath.to_s) + if profile[:permissions][:effective][:read] + result = @database.retrieve_by_uuid(path_array.last[:uuid], @credential) + else + # Review : keep? this breaks the 'if you don't have privs you can't see it - no errors, security through obscurity' idea. + raise Exception, "You do not have read permissions on this object" + end + else + parent_profile = self.profile(::File::dirname(subpath.to_s)) + if parent_profile + if parent_profile[:permissions][:effective][:create] == true + if options[:format] + if GxG::valid_uuid?(options[:format]) + new_object = @database.new_structure_from_format(@credential,{:uuid => options[:format]}) + else + new_object = @database.new_structure_from_format(@credential,{:ufs => options[:format]}) + end + else + if options[:ufs] + new_object = @database.new_structure_from_format(@credential,{:ufs => options[:ufs]}) + else + new_object = @database.try_persist({}, @credential) + end + end + # ### allow to create the object with a specific UUID. + if ::GxG::valid_uuid?(options[:with_uuid]) + address = new_object.db_address() + new_object.deactivate + @database.connector()[(address[:table])].filter({:dbid => address[:dbid]}).first.update({:uuid => options[:with_uuid].to_s}) + new_object = @database.retrieve_by_uuid(options[:with_uuid].to_s.to_sym, @credential) + end + # + new_object.wait_for_reservation + new_object.set_title(::File::basename(subpath.to_s)) + # ### Fashion a link under the parent uuid. + new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count + @database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_hash", :element_hash_uuid => new_object.uuid.to_s}) + # ### Wrap up and return new object + new_object.release_reservation + result = new_object + else + raise Exception, "You do not have create permissions here" + end + # + else + raise Exception, "Invalid path" + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath, :options => options}}) + end + end + result + end + # + def mkdir(subpath="", permissions=nil) + result = false + if @directory + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + perm = 16384 + if permissions.is_a?(::Hash) + # Best if you pass the entire :permissions hash from a profile query + "040000".each_char_with_index do |digit, indexer| + case indexer + when 0 + when 1 + # TODO: process other flags + when 2 + when 3 + if permissions[:owner].is_a?(::Hash) + if permissions[:owner][:execute] + perm += 256 + end + if permissions[:owner][:write] + perm += 128 + end + if permissions[:owner][:read] + perm += 64 + end + end + when 4 + if permissions[:group].is_a?(::Hash) + if permissions[:group][:execute] + perm += 32 + end + if permissions[:group][:write] + perm += 16 + end + if permissions[:group][:read] + perm += 8 + end + end + when 5 + if permissions[:other].is_a?(::Hash) + if permissions[:other][:execute] + perm += 4 + end + if permissions[:other][:write] + perm += 2 + end + if permissions[:other][:read] + perm += 1 + end + end + end + end + # + else + base_profile = self.profile("/") + if base_profile.is_a?(::Hash) + if base_profile[:mode].is_a?(::String) + perm = base_profile[:mode].to_i(base=8) + else + perm = 16877 + end + else + perm = 16877 + end + end + # FS mkdir + begin + parent_profile = self.profile(::File::dirname(subpath.to_s)) + if parent_profile + if parent_profile[:permissions][:effective][:create] + if ::File::exist?(the_path) + result = true + else + if ::Dir::mkdir(the_path, perm) == 0 + # Generate VFS-DB permissions, return result + self.profile(subpath) + result = true + end + end + else + raise Exception, "You do not have create permissions here" + end + else + raise Exception, "Invalid path" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :permissions => permissions}}) + # + end + else + # DB dir create + begin + parent_profile = self.profile(::File::dirname(subpath.to_s)) + if parent_profile + if parent_profile[:permissions][:effective][:create] == true + new_object = @database.try_persist([], @credential) + new_object.wait_for_reservation + new_object.set_title(::File::basename(subpath.to_s)) + # ### Fashion a link under the parent uuid. + new_ordinal = @database.connector()[:array_elements].filter({:parent_uuid => parent_profile[:uuid].to_s}).count + @database.connector()[:array_elements].insert({:parent_uuid => parent_profile[:uuid].to_s, :version => new_object.version(), :ordinal => new_ordinal, :element => "element_array", :element_array_uuid => new_object.uuid.to_s}) + # ### Wrap up and return new object + new_object.release_reservation + result = true + else + raise Exception, "You do not have create permissions here" + end + else + raise Exception, "Invalid path" + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => subpath, :permissions => permissions}}) + # + end + # + end + result + end + # + def get_permissions(subpath="", the_credential=nil) + result = [] + if @directory + if self.exist?(subpath) + the_profile = self.profile(subpath) + if the_profile[:file_id] + result = GxG::DB[:roles][:vfs].vfs_permission_manifest(the_profile[:file_id], the_credential) + end + # ???? + # xxx + # group_roles = GxG::DB[:authority][:system_credentials] + # the_record = {:credential => group_roles[:administrators], :permissions => the_profile[:permissions][:owner], :details => {}} + # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s}) + # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s}) + # the_record[:details][:role_title] = role[:title] + # the_record[:details][:group] = group[:uuid].to_s.to_sym + # the_record[:details][:group_title] = group[:title] + # result << the_record + # the_record = {:credential => group_roles[:developers], :permissions => the_profile[:permissions][:group], :details => {}} + # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s}) + # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s}) + # the_record[:details][:role_title] = role[:title] + # the_record[:details][:group] = group[:uuid].to_s.to_sym + # the_record[:details][:group_title] = group[:title] + # result << the_record + # the_record = {:credential => group_roles[:designers], :permissions => the_profile[:permissions][:group], :details => {}} + # role = GxG::DB[:authority].role_fetch({:uuid => the_record[:credential].to_s}) + # group = GxG::DB[:authority].group_fetch({:uuid => role[:group_uuid].to_s}) + # the_record[:details][:role_title] = role[:title] + # the_record[:details][:group] = group[:uuid].to_s.to_sym + # the_record[:details][:group_title] = group[:title] + # result << the_record + # the_record = {:credential => :"00000000-0000-4000-0000-000000000000", :permissions => the_profile[:permissions][:other], :details => {}} + # user = GxG::DB[:authority].user_fetch({:uuid => the_record[:credential].to_s}) + # the_record[:details][:user_title] = user[:user_id] + # result << the_record + end + else + if self.exist?(subpath) + the_profile = self.profile(subpath) + result = @database.element_permissions_by_uuid(the_profile[:uuid]) + end + end + result + end + # + def revoke_permissions(subpath="", the_credential=nil) + # revoke_element_permissions(table=:unspecified, dbid=0, credential=nil) + result = false + if @directory + # Review - all I need is the :file_id : make more efficient + profile = self.profile(subpath) + if profile[:file_id] + result = GxG::DB[:roles][:vfs].destroy_vfs_permission(profile[:file_id], the_credential) + end + else + if self.exist?(subpath) + the_profile = self.profile(subpath) + result = @database.revoke_permissions_by_uuid(the_profile[:uuid], the_credential) + end + end + result + end + # + def set_permissions(subpath="", the_credential=nil, the_permissions={}) + result = false + if @directory + if subpath.to_s == "" or subpath.to_s == "/" + the_path = (@directory.path) + else + unless subpath.to_s[0] == "/" + subpath = ("/" << subpath.to_s) + end + the_path = (@directory.path + subpath.to_s).gsub("//", "/") + end + if ::File::exist?(the_path) + # Review - all I need is the :file_id : make more efficient + profile = self.profile(subpath, the_credential) + the_credentials = [] + owner_roles = [(GxG::DB[:authority][:system_credentials][:administrators]), (GxG::DB[:authority][:system_credentials][:developers]), (GxG::DB[:authority][:system_credentials][:designers])].flatten! + user_roles = GxG::DB[:authority].user_roles(the_credential).collect {|record| record[:credential]} + the_role = :other + user_roles.each do |the_role_credential| + if owner_roles.include?(the_role_credential) + the_role = :owner + the_credentials = owner_roles + break + end + end + unless the_role == :owner + if the_credential.to_s == "00000000-0000-4000-0000-000000000000" + the_role = :other + the_credentials = [ :"00000000-0000-4000-0000-000000000000" ] + else + the_role = :group + the_credentials = user_roles + end + end + # translate to unix permissions + old_permission = ::File.stat(the_path).mode.to_s(base=2) + x = nil + if ::File.directory?(the_path) + x = 1 + else + if the_permissions.keys.include?(:execute) + if the_permissions[:execute] == true + x = 1 + else + x = 0 + end + end + end + w = nil + if the_permissions.keys.include?(:write) + if the_permissions[:write] == true + w = 1 + else + w = 0 + end + else + [:rename, :move, :destroy, :create].each do |the_action| + if the_permissions.keys.include?(the_action) + if the_permissions[(the_action)] == true + w = 1 + break + end + end + end + end + if the_permissions.keys.include?(:read) + if the_permissions[:read] == true + r = 1 + else + r = 0 + end + else + r = nil + end + case the_role + when :owner + if x + old_permission[-7] = x.to_s + end + if w + old_permission[-8] = w.to_s + end + if r + old_permission[-9] = r.to_s + end + when :group + if x + old_permission[-4] = x.to_s + end + if w + old_permission[-5] = w.to_s + end + if r + old_permission[-6] = r.to_s + end + when :other + if x + old_permission[-1] = x.to_s + end + if w + old_permission[-2] = w.to_s + end + if r + old_permission[-3] = r.to_s + end + end + ::File.chmod(old_permission.to_i(2), the_path) + if profile[:file_id] + the_credentials.each do |credential_uuid| + GxG::DB[:roles][:vfs].update_vfs_permission(profile[:file_id], credential_uuid, the_permissions) + end + end + result = true + end + else + # DB object exist? + path_array = self.db_path(subpath.to_s) + if path_array + object_details = {:uuid => path_array.last[:uuid], :table => :unspecified, :dbid => 0} + header = @database.connector()[:element_hash].filter({:uuid => object_details[:uuid].to_s}).first + if header + object_details[:table] = :element_hash + object_details[:dbid] = header[:dbid] + else + header = @database.connector()[:element_array].filter({:uuid => object_details[:uuid].to_s}).first + if header + object_details[:table] = :element_array + object_details[:dbid] = header[:dbid] + end + end + if object_details[:uuid].to_s.size > 0 + if object_details[:table] = :element_array + manifest = [{:table => object_details[:table], :dbid => object_details[:dbid]}] + else + # Note: for an element_hash - ensure all sub-objects are extended this permission. + manifest = [] + structures = [{:table => object_details[:table], :dbid => object_details[:dbid]}] + while structures.size > 0 do + entry = structures[0] + @database.element_manifest(entry[:table],entry[:dbid]).each do |record| + if [:element_hash, :element_array].include?(record[:table]) + unless structures.include?(record) + structures << record + end + end + end + manifest << structures.shift + end + end + # + manifest.each do |record| + @database.assign_element_permission(record[:table], record[:dbid], credential, the_permissions) + end + result = true + end + end + end + result + end + # + def set_permissions_recursive(subpath="", the_credential=nil, the_permissions={}) + the_profile = self.profile((File.expand_path(subpath)).to_s) + if the_profile + if [:virtual_directory, :directory, :persisted_array].include?(the_profile[:type]) + paths = [(File.expand_path(subpath))] + while paths.size > 0 do + the_path = paths.shift + the_path_profile = self.profile(the_path.to_s) + if [:virtual_directory, :directory, :persisted_array].include?(the_path_profile[:type]) + self.entries(the_path, the_credential).each do |a_profile| + paths << File.expand_path(the_path + "/" + a_profile[:title].to_s) + end + self.set_permissions(the_path, the_credential, the_permissions) + else + self.set_permissions(the_path, the_credential, the_permissions) + end + end + else + self.set_permissions(subpath, the_credential, the_permissions) + end + end + true + end + # + end + end + VFS = ::GxG::Storage::FileSpace.new + # +end diff --git a/lib/gxg/gxg_elements.rb b/lib/gxg/gxg_elements.rb new file mode 100644 index 0000000..6ca5b40 --- /dev/null +++ b/lib/gxg/gxg_elements.rb @@ -0,0 +1,2263 @@ + +# --------------------------------------------------------------------------------------------------------------------- +# Various data element classes: +module GxG + # + class Version + def initialize(params={}) + unless params.is_a?(::Hash) + params = {:interpret => (params.to_s)} + end + @encoded_value = encode(params) + end + def value() + decode({:value => @encoded_value}) + end + def encoded_value() + @encoded_value + end + def <=>(other) + return @encoded_value <=> other.encoded_value() + end + def to_d() + ::BigDecimal.new(@encoded_value) + end + def to_s() + value.inspect + end + def inspect() + value.inspect + end + def increment(params={:revision => 1}) + current = decode({:value => @encoded_value}) + if current[:phase].is_a?(Hash) + numeric_phase = current[:phase][:candidate].to_i + 2 + else + case current[:phase] + when :alpha + numeric_phase = 0 + when :beta + numeric_phase = 1 + when :release + numeric_phase = 99 + end + end + if params[:phase].is_a?(Numeric) + phase_increment = params[:phase] + else + phase_increment = 0 + end + new_value = {} + # + if params[:revision].is_a?(Numeric) + if (params[:revision].to_i + current[:revision]) > 9999 + params[:patch_level] = params[:patch_level].to_i + 1 + new_value[:revision] = 0 + else + new_value[:revision] = (params[:revision].to_i + current[:revision]) + end + else + new_value[:revision] = current[:revision] + end + # + if params[:patch_level].is_a?(Numeric) + if (params[:patch_level].to_i + current[:patch_level]) > 9999 + phase_increment = phase_increment + 1 + new_value[:patch_level] = 0 + else + new_value[:patch_level] = (params[:patch_level].to_i + current[:patch_level]) + end + else + new_value[:patch_level] = current[:patch_level] + end + # + if phase_increment > 0 + if (phase_increment + numeric_phase) > 99 + params[:tiny] = params[:tiny].to_i + 1 + new_value[:phase] = 99 + else + new_value[:phase] = (phase_increment + numeric_phase) + end + else + new_value[:phase] = numeric_phase + end + # + if params[:tiny].is_a?(Numeric) + if (params[:tiny].to_i + current[:tiny]) > 999 + params[:minor] = params[:minor].to_i + 1 + new_value[:tiny] = 0 + else + new_value[:tiny] = (params[:tiny].to_i + current[:tiny]) + end + else + new_value[:tiny] = current[:tiny] + end + # + if params[:minor].is_a?(Numeric) + if (params[:minor].to_i + current[:minor]) > 999 + params[:major] = params[:major].to_i + 1 + new_value[:minor] = 0 + else + new_value[:minor] = (params[:minor].to_i + current[:minor]) + end + else + new_value[:minor] = current[:minor] + end + # + if params[:major].is_a?(Numeric) + new_value[:major] = current[:major] + params[:major].to_i + else + new_value[:major] = current[:major] + end + # convert phase numeric value to Symbol / Hash + if (2..98).include?(new_value[:phase]) + new_value[:phase] = {:candidate => (new_value[:phase] - 2)} + else + case new_value[:phase] + when 0 + new_value[:phase] = :alpha + when 1 + new_value[:phase] = :beta + when 99 + new_value[:phase] = :release + end + end + # + @encoded_value = encode(new_value) + self + end + def decode(params={:value => 0.0}) + # numeric value format: MmmmtttPPpppp.rrrrr + string_value = params[:value].to_s + major = string_value.split(".")[0][0..-13].to_i + minor = string_value.split(".")[0][-12..-10].to_i + teeny = string_value.split(".")[0][-9..-7].to_i + phase = string_value.split(".")[0][-6..-5].to_i + if (2..98).include?(phase) + phase = {:candidate => (phase - 2)} + else + case phase + when 0 + phase = :alpha + when 1 + phase = :beta + when 99 + phase = :release + end + end + patch_level = string_value.split(".")[0][-4..-1].to_i + revision = string_value.split(".")[1].to_i + # + {:major => major, :minor => minor, :teeny => teeny, :phase => phase, :patch_level => patch_level, :revision => revision} + end + def interpret_version(params={}) + result = {:phase => :release} + # params[:interpret] + sections = params[:interpret].split(".") + major_string = "" + minor_string = "" + teeny_string = "" + phase_string = "" + patch_level = "" + revision = "" + # *very* simple at this point + result[:major] = sections[0].to_i + result[:minor] = sections[1].to_i + result[:teeny] = sections[2].to_i + if sections.last.to_s.include?("alpha") + result[:phase] = :alpha + end + if sections.last.to_s.include?("beta") + result[:phase] = :beta + end + # TODO: Version.intereperet_version : add patch-level & revision detect (need a numeric string scrubber) + # + result + end + # + def encode(params={}) + # numeric value format: MmmmtttPPpppp.rrrrr + # + if params[:interpret].is_a?(String) + params = params.merge(interpret_version(params)) + end + # + major = 0 + minor = 0 + teeny = 0 + phase = :release + patch_level = 0 + revision = 0 + # + if params[:major].is_a?(Numeric) + major = params[:major].to_i + end + if params[:minor].is_a?(Numeric) + minor = params[:minor].to_i + end + if params[:teeny].is_a?(Numeric) + teeny = params[:teeny].to_i + end + if params[:phase].is_a?(Hash) + # :phase => :alpha + # :phase => :beta + # :phase => {:candidate => 12} (0 to 96) + # :phase => :release + if params[:phase][:candidate].is_a?(Numeric) + phase = params[:phase] + phase[:candidate] = phase[:candidate].to_i + unless (0..96).include?(phase[:candidate]) + raise ArgumentError, ":phase[:candidate] must be between 0 and 96, consider incrementing teeny version beyond that" + end + else + raise ArgumentError, ":phase[:candidate] must be an Numeric, you provided a #{params[:phase][:candidate].class}" + end + else + if params[:phase].is_a?(Symbol) + if [:alpha, :beta, :release].include?(params[:phase]) + phase = params[:phase] + else + # + end + end + end + if params[:patch_level].is_a?(Numeric) + patch_level = params[:patch_level].to_i + end + if params[:revision].is_a?(Numeric) + revision = params[:revision].to_i + end + # numeric value format: MmmmtttPPpppp.rrrrr + # + string_value = "#{major}" + (3 - minor.to_s.size).times do + string_value << "0" + end + string_value << minor.to_s + # + (3 - teeny.to_s.size).times do + string_value << "0" + end + string_value << teeny.to_s + # + if phase.is_a?(Hash) + if phase[:candidate] + phase[:candidate] += 2 + (2 - phase[:candidate].to_s.size).times do + string_value << "0" + end + string_value << "#{phase[:candidate]}" + end + else + case phase + when :alpha + string_value << "00" + when :beta + string_value << "01" + when :release + string_value << "99" + end + end + # + (4 - patch_level.to_s.size).times do + string_value << "0" + end + string_value << patch_level.to_s + string_value << "." + # + (4 - revision.to_s.size).times do + string_value << "0" + end + string_value << revision.to_s + # + string_value + end + private :decode, :encode, :interpret_version + end + # + class Enumerator < ::Enumerator + # this is a bit inefficient, but it gets the job done. It is essentially an each wrapper to do a cooperative pauses for non-blocking goodness. :) + # TODO: inspiration: what about just leveraging 'super' after a pause operation on all overrides. (thx) + # + def initialize(object=nil,method=:each,*args, &block) + # Without the call to super() it works, but will be uninitialized + # super(object,method,*args) + @collection = object.stock_to_enum(method,*args, &block) + # @collection = ::Enumerator.new(object,method,*args,&block) + self + end + # + def each(&block) + # reference = {:object => self, :method => __callee__, :fiber => Fiber.current, :thread => Thread.current} + if block.respond_to?(:call) + if ::GxG::EventManager::manager_running? + @collection.each do |*args| + block.call(*args) + pause + end + self + else + @collection.each(&block) + self + end + else + self + end + end + # + def next() + pause + @collection.next() + end + # + def next_values() + pause + @collection.next_values() + end + # + def peek() + pause + @collection.peek() + end + # + def peek_values() + pause + @collection.peek_values() + end + # + def rewind() + if @collection.respond_to?(:rewind) + @collection.rewind + end + self + end + # + def with_index(offset=0,&block) + enumerator = GxG::Enumerator.new(@collection.with_index(offset)) + if block.respond_to?(:call) + enumerator.each do |entry,index| + block.call(entry,index) + end + else + enumerator + end + end + alias :each_with_index :with_index + # + def with_object(an_object=nil,&block) + enumerator = GxG::Enumerator.new(@collection.with_object(an_object)) + if block.respond_to?(:call) + enumerator.each do |entry,the_object| + block.call(entry,the_object) + end + an_object + else + enumerator + end + end + # + end + # + class ReturnResponse + # this class allows for characterized muli-part return values for Procs and Methods + # this was created for a variety of reasons. Hope this isn't a dead end. + def initialize(response={}) + unless response.is_a?(::Hash) + raise ArgumentError.new("Response must be passed as a Hash") + end + @details = response + end + def new_selector() + @details[:new_selector] + end + def result() + @details[:result] + end + def error() + @details[:error] + end + def warning() + @details[:warning] + end + def info() + @details[:info] + end + def debug() + @details[:debug] + end + def inspect() + @details.inspect + end + end + # + class Tuple + # Inspired by Python's Tuple class, in this case: allows any object to be a key for any other object + # Monkey-patch notes: http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i + # (Array integration) + def initialize(key=nil,value=nil) + @thread_safety = ::Mutex.new + @key = key + @value = value + end + # + def key() + @thread_safety.synchronize { @key } + end + def key= (new_key=nil) + @thread_safety.synchronize { @key = new_key } + end + # + def value() + @thread_safety.synchronize { @value } + end + def value= (new_value=nil) + @thread_safety.synchronize { @value = new_value } + end + # + def <=>(other_value=nil) + if other_value.respone_to?(:<=>) + if other_value.is_a?(GxG::Tuple) + @thread_safety.synchronize { @value <=> other_value.value } + else + @thread_safety.synchronize { @value <=> other_value } + end + else + # taking liberties here, if other object does not support compare, then this obj is construed as greater than. (bias) + 1 + end + end + def to_s() + @thread_safety.synchronize { @value.to_s } + end + def inspect() + @thread_safety.synchronize { "#{@key.inspect} => #{@value.inspect}" } + end + # + end + # + class ByteArray + # make as close to array as possible with one exception: only works-with/accepts Integer between 0 and 255 as elements. + # Also: ByteArray is one-dimensional/flat --> cannot encapsulate other arrays, just Integers between 0 and 255. + # TODO: ByteArray : fill in missing methods compared to standard Array class. + # GxG::ByteArray::missing_methods(Array) + protected + # + def filter_data(the_data="") + # + error_message = ("ByteArray.new expects a String, a Integer or Float between 0 and 255, a nil ( counted as padvalue() ), a ByteArray, or an Array of any of those; you provided #{the_data.inspect}") + # + unless the_data.is_any?(::NilClass,::String,::Numeric, ::Array, ::GxG::ByteArray) + raise Exception.new(error_message) + end + if the_data.is_a?(::String) + the_data = the_data.slice_bytes((0..-1)) + end + if the_data.is_a?(::Numeric) + the_data = the_data.to_i + if (0..255).include?(the_data) + the_data = the_data.chr + the_data.force_encoding(Encoding::ASCII_8BIT) + else + raise Exception.new(error_message) + end + end + if the_data.is_a?(::GxG::ByteArray) + the_data = the_data.data().clone + end + if the_data.is_a?(::Array) + temp_string = "" + temp_string.force_encoding(Encoding::ASCII_8BIT) + the_data.flatten.to_enum.each do |element| + temp_string << self.filter_data(element) + end + if temp_string.size > 0 + the_data = temp_string + else + the_data = "" + the_data.force_encoding(Encoding::ASCII_8BIT) + end + end + unless the_data + the_data = @pad_value.to_i.chr + the_data.force_encoding(Encoding::ASCII_8BIT) + end + # + the_data + end + # + def filter_parameters(*args) + # puts "Got: #{args.inspect}" + the_range = nil + if args[0].is_any?(::Numeric, ::Integer, ::Float, ::BigDecimal) + args[0] = args[0].to_i + if args[0] < 0 + args[0] = @data.bytesize + args[0] + end + the_range = ((args[0])..(args[0])) + if args[1].is_a?(Numeric) + args[1] = args[1].to_i + if args[1] < 1 + raise ArgumentError.new("Second parameter needs to be a Numeric greater than 0, you provided #{args[1].inspect}") + else + the_range = ((args[0])..(args[0] + (args[1] - 1))) + end + end + else + if args[0].is_a?(Range) + if args[0].first < 0 + args[1] = @data.bytesize + args[0].first + else + args[1] = args[0].first + end + if args[0].last < 0 + args[2] = @data.bytesize + args[0].last + else + args[2] = args[0].last + end + if args[1] <= args[2] + the_range = ((args[1])..(args[2])) + else + the_range = ((args[2])..(args[1])) + end + else + unless the_range + raise ArgumentError.new("First parameter needs to be a Numeric or a Range, you provided #{args[0].inspect}") + end + end + end + the_range + end + # + public + # + def self.try_convert(*args) + begin + GxG::ByteArray.new(1,[(args)]) + rescue Exception + nil + end + end + # + def self.[](*args) + GxG::ByteArray.new(*args) + end + # + def self.process(the_array=GxG::ByteArray.new,&block) + new_array = GxG::ByteArray.new + if block.respond_to?(:call) + if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Set, ::Struct) + new_array = the_array.process(&block) + else + raise ArgumentError, "you must pass an Array, a ByteArray, or a Hash" + end + end + new_array + end + # + def self.search(the_array=GxG::ByteArray.new,&block) + new_array = GxG::ByteArray.new + if block.respond_to?(:call) + if the_array.is_any?(::Array, ::Hash, ::GxG::ByteArray, ::Set, ::Struct) + new_array = the_array.search(&block) + else + raise ArgumentError, "you must pass an Array, a ByteArray, or a Hash" + end + end + new_array + end + # + def data() + @data + end + # + # instance methods: + def initialize(*args,&block) + # Integer(how_many), , , , + # --OR-- + # Integer(how_many), , , &block + @data = "" + @data.force_encoding(Encoding::ASCII_8BIT) + # + memory_limits = GxG::SYSTEM.memory_limits() + @max_size = nil + @pad_value = 0 + @outer = nil + # *should* create a 'self' to call upon. + super() + # + if args.size > 0 + if block.respond_to?(:call) + if args[0].is_a?(Numeric) + args[0] = args[0].to_i + if args[0] >= 0 + if args[0] > memory_limits[:process].first + args[0] = memory_limits[:process].first + end + if args[1].is_a?(Numeric) + self.maxsize = args[1].to_i + end + if args[2].is_a?(Numeric) + self.padvalue = args[1].to_i + end + if args[3].is_a?(Numeric) + self.outervalue = args[1].to_i + end + # + (0..(args[0] - 1)).to_enum.each do |index| + raw_data_array = [(block.call(index))].flatten + raw_data_array.to_enum.each do |raw_data| + # + raw_data = (self.filter_data(raw_data) || "") + # + if raw_data.bytesize > 0 + if (args[0] * raw_data.bytesize) > memory_limits[:process].first + # Attempts to project whether or not maximum memory limits will be exceeded. + # LATER: ByteArray.new: Might get false positives on variable return data from block (re-think this) + raise NoMemoryError.new("This operation will exceed available per-process memory limits") + end + @data << raw_data + end + end + # + end + # + if @max_size + if @data.bytesize > @max_size + @max_size = @data.bytesize + end + end + # + end + end + else + # + if args[0].is_a?(Numeric) + how_many = args[0].to_i + if args[1] + raw_data_array = [(args[1])].flatten + else + raw_data_array = [(@pad_value.to_i.chr)] + end + if args[2].is_a?(Numeric) + self.maxsize = args[2].to_i + end + if args[3].is_a?(Numeric) + self.padvalue = args[3].to_i + end + if args[4].is_a?(Numeric) + self.outervalue = args[4].to_i + end + else + how_many = 1 + raw_data_array = [(args[0])].flatten + if args[1].is_a?(Numeric) + self.maxsize = args[1].to_i + end + if args[2].is_a?(Numeric) + self.padvalue = args[2].to_i + end + if args[3].is_a?(Numeric) + self.outervalue = args[3].to_i + end + end + # + new_data = "" + new_data.force_encoding(Encoding::ASCII_8BIT) + raw_data_array.to_enum.each do |raw_data| + new_data << (self.filter_data(raw_data) || "") + end + if new_data.bytesize > 0 + if (how_many * new_data.bytesize) > memory_limits[:process].first + raise NoMemoryError.new("This operation will exceed available per-process memory limits") + end + how_many.times do + @data << new_data + end + end + # + if @max_size + if @data.bytesize > @max_size + @max_size = @data.bytesize + end + end + # + end + # + end + self + end + # + def mime_type() + @data.mime_type() + end + # + def process(&block) + result = self.clone + if block.respond_to?(:call) + result.each_with_index do |entry,index| + result[(index)] = block.call(entry, index, self) + end + end + result + end + # + def process!(&block) + if block.respond_to?(:call) + self.replace(self.process(&block).data()) + end + self + end + # + def search(&block) + result = [] + if block.respond_to?(:call) + self.each_with_index do |entry,index| + raw_result = block.call(entry,index,self) + if raw_result + result << raw_result + end + end + end + result + end + # + def pattern_range(the_pattern="", initial_offset=0) + result = nil + if the_pattern.is_a?(::ByteArray) + the_pattern = the_pattern.to_s + end + if the_pattern.is_a?(::String) + if initial_offset.is_a?(::Integer) + if @max_size + if initial_offset > @max_size + raise ArgumentError, "Offset exceeds the maximum size." + end + end + if the_pattern.encoding != ::Encoding::ASCII_8BIT + the_pattern = the_pattern.clone + the_pattern.force_encoding(::Encoding::ASCII_8BIT) + end + offset = @data.index(the_pattern, initial_offset) + if offset.is_a?(::Integer) + result = ((offset)..(offset + (the_pattern.size - 1))) + end + else + raise ArgumentError, "You MUST provide the offset as an Integer." + end + else + raise ArgumentError, "You MUST provide a String to find." + end + result + end + # + def pattern_ranges(the_pattern="") + result = [] + offset = 0 + if the_pattern.is_a?(::ByteArray) + the_pattern = the_pattern.to_s + end + last_found = self.pattern_range(the_pattern,offset) + while last_found != nil do + if last_found.is_a?(::Range) + result << last_found + if (last_found.last() + 1) <= @data.bytesize + offset = (last_found.last() + 1) + else + break + end + end + last_found = self.pattern_range(the_pattern,offset) + end + result + end + # + def padvalue() + @pad_value.clone + end + # + def padvalue=(*args) + if args.size > 0 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if (0..255).include?(args[0]) + @pad_value = args[0] + end + else + raise TypeError.new("Value type: expected Float or Integer. You provided #{args[0].class}") + end + end + @pad_value.clone + end + # + def match(regular_expression) + @data.match(regular_expression) + end + # + def maxsize() + @max_size.clone + end + # + def maxsize=(*args) + # Set @maxsize: Warning: setting @maxsize lower than the @data.bytesize will truncate the data + if args.size > 0 + if args[0] + if args[0].is_a?(Numeric) + args[0] = args[0].to_i + end + # + if args[0].is_a?(Integer) + if args[0] > GxG::SYSTEM.memory_limits()[:process].first + raise NoMemoryError.new("Setting maxsize this high exceeds per-process memory limits") + end + if args[0] > @max_size.to_i + @max_size = args[0] + else + if args[0] < @max_size.to_i + # truncate data if @max_size lowered below @data.bytesize + @max_size = args[0] + if @data.bytesize > args[0] + @data.replace(@data[0..(args[0] - 1)]) + end + end + end + else + raise ArgumentError.new("Expected NilClass or Numeric class. You provided #{args[0].class}") + end + else + @max_size = nil + end + else + @max_size = nil + end + @max_size.clone + end + # + def outervalue() + @outer.clone + end + # + def outervalue=(*args) + if args.size > 0 + if args[0] + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if (0..255).include?(args[0]) + @outer = args[0] + end + else + raise TypeError.new("Expected a NilClass, a Float, or a Integer. You provided #{args[0].class}") + end + else + @outer = nil + end + else + @outer = nil + end + self.outervalue() + end + # + def dclone() + result = GxG::ByteArray.new(@data.clone) + result.padvalue = (self.padvalue()) + result.maxsize = (self.maxsize()) + result.outervalue = (self.outervalue()) + result + end + # + def initialize_clone() + result = self.dclone + if self.frozen? + result.freeze + end + result + end + # + def initialize_dup() + # ByteArry is one-dimensional anyhow + self.dclone + end + alias :dup :initialize_dup + alias :initialize_copy :initialize_dup + def clone() + initialize_dup() + end + # + def [](indexer=0, length=nil) + self.slice(indexer,length) + end + # + def []=(indexer=0, length=nil, value=nil) + # + if length + unless value + value = length + length = nil + end + end + value = self.filter_data(value) + error = nil + result = nil + # + if value + if value.bytesize > 0 + # + the_range = self.filter_parameters(indexer,length) + # + unless the_range + if length + error = ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer") + else + error = ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer") + end + end + if error + raise error + end + # check overall compliance with maxsize + if @max_size + if (the_range.count > @max_size || the_range.first > (@max_size - 1) || the_range.last > (@max_size - 1)) + error = IndexError.new("Index exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + if (the_range.first + (value.bytesize - 1)) > (@max_size - 1) + error = RangeError.new("Data size at specified Index exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + # + if indexer.is_a?(Integer) + if length + # replace operation + if (the_range.first + (the_range.count - 1)) > (@max_size - 1) + error = RangeError.new("Range exceeds #{@max_size} limit. You might need to adjust maxsize.") + else + if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1) + error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + end + else + # insert operation + if (@data.size - 1 + value.bytesize) > @max_size + error = RangeError.new("Operation exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + if (the_range.first + (value.bytesize - 1)) > (@max_size - 1) + error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + end + else + if the_range.count > @max_size + error = RangeError.new("Range exceeds #{@max_size} limit. You might need to adjust maxsize.") + else + if the_range.count < value.bytesize and (the_range.first + (the_range.count - 1) + ((value.bytesize - 1) - (the_range.count - 1))) > (@max_size - 1) + error = RangeError.new("Value size exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + end + end + if error + raise error + end + end + # + if the_range.first > (@data.bytesize - 1) + (the_range.first - (@data.bytesize - 1)).times do + @data << @pad_value.chr + end + end + # + if length + @data[(the_range.first),(the_range.count)] = value + else + if indexer.is_a?(Numeric) + @data[(the_range.first)] = value + else + @data[(the_range)] = value + end + end + # + end + result = GxG::ByteArray.new(value) + end + result + end + # + def |(*args) + scrap = GxG::ByteArray::try_convert(args) + result = self.dclone + if scrap + scrap.each do |byte| + unless result.include?(byte) + result << byte + end + end + end + result + end + # + def &(*args) + result = GxG::ByteArray.new + if args.size > 0 + other = GxG::ByteArray::try_convert(args) + if other + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + self.each do |the_byte| + if other.include?(the_byte) + result_scrap << the_byte.chr + end + end + result = GxG::ByteArray.new(result_scrap) + end + end + result + end + # + def *(*args) + result = GxG::ByteArray.new + if self.size > 0 + if args.size > 0 + if args[0].is_a?(::String) + result = self.join(args[0]) + else + if args[0].is_a?(Numeric) + scrap_size = ((args[0].to_f % args[0].to_i.to_f) * self.size.to_f).to_i + args[0] = args[0].to_i + if (args[0] * self.size) <= GxG::SYSTEM.memory_limits()[:process].first + args[0].times do + result << self.data().clone + end + if scrap_size > 0 + result << self.data()[0..(scrap_size)] + end + else + raise NoMemoryError.new("This operation will exceed per-process memory limits") + end + else + raise ArgumentError.new("a String or Numeric is required, you provied #{args[0].inspect}") + end + end + else + raise TypeError.new("Can't convert nil to ByteArray") + end + end + result + end + # + def +(*args) + result = GxG::ByteArray.new + if args.size > 0 + other = GxG::ByteArray::try_convert(args) + if other + if other.size > 0 + result = GxG::ByteArray.new(1,[(self.data().clone),(other.data())]) + end + end + else + raise TypeError.new("Can't convert nil to ByteArray") + end + result + end + # + def -(*args) + result = GxG::ByteArray.new + if args.size > 0 + scrap = GxG::ByteArray::try_convert(args) + if scrap + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + self.each do |the_byte| + unless scrap.include?(the_byte) + result_scrap << the_byte.chr + end + end + result = GxG::ByteArray.new(result_scrap) + end + end + result + end + # + def <<(*args) + if args.size > 0 + value = GxG::ByteArray::try_convert(args) + if value + if @max_size + if (@data.bytesize + value.size) > @max_size + raise RangeError.new("Operation exceeds #{@max_size} limit. You might need to adjust maxsize.") + end + end + @data << value.data() + end + end + self + end + # + def <=>(*args) + result = 1 + if args.size > 0 + if args[0].is_a?(::GxG::ByteArray) + result = (self.data() <=> args[0].data()) + else + # Allows sorting against std. Array or String that *would* sort normal if it were a ByteArray (byte comparison) + other = GxG::ByteArray::try_convert(args) + if other + result = (self.data() <=> other.data()) + end + end + end + result + end + # + def ==(*args) + result = false + if args.size > 0 + if args[0].is_a?(::GxG::ByteArray) + result = (self.data() == args[0].data()) + else + # kinda cool, but might be a bridge too far for strict equivalence. + # other = GxG::ByteArray::try_convert(args) + # if other + # result = (self.data() == other.data()) + #end + end + end + result + end + # + def at(*args) + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + self[(args[0])] + else + raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}") + end + end + # + def clear() + @data.clear + self + end + # + def collect(&block) + result = GxG::ByteArray.new + if block.respond_to?(:call) + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + self.each do |the_byte| + result_scrap << self.filter_data(block.call(the_byte)) + end + result = GxG::ByteArray.new(result_scrap) + else + result = self.to_enum(:each) + end + result + end + # + def collect!(&block) + if block.respond_to?(:call) + self.replace(self.collect(&block).data()) + self + else + self.to_enum(:each) + end + end + # + def combination(*args,&block) + # TODO: port c code to ruby (true-enumeration-as-bytearray support) + enumerator = @data.to_enum(:bytes).to_a.to_enum(:combination,*args) + if block.respond_to?(:call) + enumerator.each do |the_combination| + block.call(GxG::ByteArray.new(the_combination)) + end + self + else + # Note: this form does not put bytearrays of each combination into the block, just arrays (please fix) + GxG::Enumerator.new(enumerator) + end + end + # + def repeated_combination(*args,&block) + # TODO: port c code to ruby (true-enumeration-as-bytearray support) + enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_combination,*args) + if block.respond_to?(:call) + enumerator.each do |the_combination| + block.call(GxG::ByteArray.new(the_combination)) + end + self + else + GxG::Enumerator.new(enumerator) + end + end + # + def compact() + self + end + # + def concat(*args) + result = self + if args.size > 0 + other = GxG::ByteArray::try_convert(args) + if other + result = (self + other) + else + raise Exception.new("Could not convert #{args.inspect} to ByteArray") + end + end + result + end + # + def count(match_value=nil,&block) + result = 0 + if match_value.is_a?(Numeric) + self.each do |the_byte| + if the_byte == match_value.to_i + result += 1 + end + end + else + if block.respond_to?(:call) + self.each do |the_byte| + if block.call(the_byte) == true + result += 1 + end + end + else + result = @data.bytesize + end + end + result + end + # + def cycle(iterations=nil,&block) + if @data.bytesize > 0 + unless iterations.to_i < 0 + if block.respond_to?(:call) + if iterations + iterations.to_i.times do + self.each do |the_byte| + block.call(the_byte) + end + end + nil + else + while true do + self.each do |the_byte| + block.call(the_byte) + end + end + end + else + @data.to_enum(:bytes).to_a.to_enum(:cycle,iterations) + end + end + end + end + # + def delete(*args,&block) + result = nil + if args[0].is_a?(Numeric) + comparitor = self.filter_data(args[0].to_i) + if comparitor.bytesize > 1 + comparitor = comparitor[0] + end + scrap = @data.delete(comparitor) + if scrap == @data + if block.respond_to?(:call) + result = block.call() + end + else + @data.replace(scrap) + result = args[0] + end + end + result + end + # + def delete_at(*args) + result = nil + if args.size > 0 + the_range = self.filter_parameters(*args) + if the_range.last < self.size + result = @data.slice!(the_range) + end + end + result + end + # + def delete_if(&block) + self.reject!(&block) + end + # + def drop(length) + self.slice((length.to_i - 1)..-1) + end + # + def drop_while(&block) + accumulator = GxG::ByteArray.new + if block.respond_to?(:call) + self.each do |the_byte| + result = block.call(the_byte) + unless result + accumulator << the_byte + end + end + accumulator + else + # TODO: 5xMem efficiency fix for various engine run conditions + GxG::Enumeratior.new(@data.to_enum(:bytes).to_a.to_enum(:drop_while)) + end + end + # + # TODO: GxG::ByteArray.each_index/each_with_index: improve memory efficiency and speed significantly (!) + # + def each(&block) + if block.respond_to?(:call) + if @data.bytesize > 0 + @data.to_enum(:bytes).each {|the_byte| block.call(the_byte)} + end + self + else + self.to_enum(:each) + end + end + # + def each_index(&block) + if block.respond_to?(:call) + if @data.bytesize > 0 + (0..(@data.bytesize - 1)).to_enum.each {|index| block.call(index)} + end + self + else + self.to_enum(:each_index) + end + end + # + def each_with_index(offset=0,&block) + if block.respond_to?(:call) + if @data.bytesize > 0 + # bulky, but required to support self.reject!, etc : + @data.to_enum(:bytes).with_index(offset).each {|value,index| block.call(value,index)} + #old : (0..(@data.bytesize - 1)).to_enum.each {|index| block.call(@data[(index)].ord,index)} + end + self + else + self.to_enum(:each_with_index,offset) + end + end + # + def empty?() + @data.bytesize == 0 + end + # + def eql?(*args) + if args.size > 0 + self == args[0] + else + false + end + end + # + def fetch(*args,&block) + #fetch(index) → obj click to toggle source + #fetch(index, default ) → obj + #fetch(index) {|index| block } → obj + # + #Tries to return the element at position index. If the index lies outside the array, the first form throws an IndexError exception, + # the second form returns default, and the third form returns the value of invoking the block, passing in the index. + # Negative values of index count from the end of the array. + # LATER: ByteArray.fetch: check how to incorporate 'padded-edges' feature + if args.size > 0 + if args[0].is_a?(Numeric) + args[0] = self.filter_parameters(args[0].to_i).first + if args[0] >= self.size + if args[1] + if block.respond_to?(:call) + block.call(args[0]) + else + args[1] + end + else + raise IndexError.new("Index specified lies outside the ByteArray") + end + else + self.at(args[0]) + end + end + end + end + # + def fill(*args,&block) + #fill(obj) → ary click to toggle source + #fill(obj, start [, length]) → ary + #fill(obj, range ) → ary + #fill {|index| block } → ary + #fill(start [, length] ) {|index| block } → ary + #fill(range) {|index| block } → ary + # + #The first three forms set the selected elements of self (which may be the entire array) to obj. A start of nil is equivalent to zero. + #A length of nil is equivalent to self.length. The last three forms fill the array with the value of the block. + #The block is passed the absolute index of each element to be filled. Negative values of start count from the end of the array. + #a = [ "a", "b", "c", "d" ] + #a.fill("x") #=> ["x", "x", "x", "x"] + #a.fill("z", 2, 2) #=> ["x", "x", "z", "z"] + #a.fill("y", 0..1) #=> ["y", "y", "z", "z"] + #a.fill {|i| i*i} #=> [0, 1, 4, 9] + #a.fill(-2) {|i| i*i*i} #=> [0, 1, 8, 27] + # Note: if ByteArray is empty, it will be filled to @max_size, otherwise only the used portion or specified range. + result = nil + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + # + if block.respond_to?(:call) + if args.size > 0 + if args[0].is_a?(Numeric) + args[0] = args[0].to_i + if args[0] < 0 + if args[1].is_a?(Numeric) + if args[1].to_i > 0 + args = [((@data.bytesize + args[0])..(@data.bytesize + args[0] + (args[1].to_i - 1)))] + else + raise ArgumentError.new("length specified should be a positive Integer for Float") + end + else + args = [((@data.bytesize + args[0])..(@data.bytesize - 1))] + end + end + end + fillrange = self.filter_parameters(*args) + else + fillrange = (0..((@max_size || @data.bytesize) - 1)) + end + (0..(@data.bytesize - 1)).to_enum.each do |index| + if fillrange.include?(index) + raw_value = self.filter_data(block.call(index)) + if raw_value.size > 0 + # Allow only single byte results to merge into fill + # since the concept is 'per-item' which in this case is a single byte only. + result_scrap << raw_value[0] + else + result_scrap << @pad_value.chr + end + else + result_scrap << @data[(index)] + end + end + else + if args.size > 0 + value = nil + case args.size + when 1 + fillrange = (0..((@max_size || @data.bytesize) - 1)) + value = self.filter_data(args[0]) + when 2 + fillrange = self.filter_parameters(args[1]) + value = self.filter_data(args[0]) + when 3 + fillrange = self.filter_parameters(args[1],args[2]) + value = self.filter_data(args[0]) + else + fillrange = self.filter_parameters(args[1],args[2]) + value = self.filter_data(args[0]) + end + value = (value || @pad_value.chr) + # + (0..(@data.bytesize - 1)).to_enum.each do |index| + if fillrange.include?(index) + result_scrap << value + else + result_scrap << @data[(index)] + end + end + else + raise ArgumentError.new("wrong number of arguments (0 for 1..3)") + end + end + if result_scrap.size > 0 + self.replace(result_scrap) + end + self + end + # + def first(*args) + if args.size > 0 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if args[0] > 0 + if @max_size + unless args[0] <= @max_size + raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).") + end + end + self[(0..(args[0] - 1))] + end + else + raise TypeError.new("Expected Integer. You provided #{args[0].class}") + end + else + self[0] + end + end + # + def flatten() + # ByteArray is always flat anyhow + self.dclone + end + # + def flatten!() + # ByteArray is always flat anyhow + self + end + # + def freeze() + @data.freeze + @max_size.freeze + @pad_value.freeze + @outer.freeze + super() + end + # + def hash() + @data.hash + end + # + def include?(*args) + if args.size > 0 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if (0..255).include?(args[0]) + @data.include?(args[0].chr) + else + raise RangeError.new("Value out of range: range is 0 to 255. You provided #{args[0]}") + end + else + raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}") + end + else + raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1).") + end + end + # + def find_index(*args, &block) + result = nil + if block.respond_to?(:call) + found_at = 0 + @data.to_enum(:bytes).each do |the_byte| + if block.call(the_byte) == true + result = found_at + break + end + found_at += 1 + end + else + if args.size > 0 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if (0..255).include?(args[0]) + if args[1] + # offset specified (addtional option vs standard Array) + result = @data.index(args[0].chr,args[1]) + else + result = @data.index(args[0].chr) + end + end + else + raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}") + end + else + result = @data.to_enum(:bytes) + end + end + result + end + # + def insert(*args) + if args.size > 1 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + value = self.filter_data(args[(1..(args.size - 1))]) + if @max_size + unless (@data.bytesize + value.bytesize) <= @max_size + raise RangeError.new("Operation exceeds #{@max_size} limit. You need to adjust maxsize.") + end + end + begin + @data.insert(args[0],value) + rescue Exception => error + raise error.exception(error.message.gsub("string",(self.class.to_s))) + end + else + raise TypeError.new("Expected Integer or Float. You provided #{args[0].class}") + end + else + raise ArgumentError.new("Wrong number of arguments (#{args.size} for 2).") + end + self + end + # + def inspect() + result = "[" + @data.to_enum(:bytes).each do |the_byte| + if result.size > 1 + result << ", " + end + if the_byte > 10 + result << "0x#{the_byte.to_s(16).upcase}" + else + result << "0x0#{the_byte.to_s(16).upcase}" + end + end + result << "]" + result + end + # + def join(*args) + result = "" + if args[1].is_a?(Encoding) + # Extra optional encoding vs standard Array + result.force_encoding(args[1]) + else + result.force_encoding(Encoding::ASCII_8BIT) + end + @data.to_enum(:bytes).each do |the_byte| + if (args[0].is_a?(String) && result.size > 0) + result << (args[0] + the_byte.chr) + else + result << the_byte.chr + end + end + result + end + # + def last(*args) + if args.size > 0 + if args[0].is_a?(Float) + args[0] = args[0].to_i + end + if args[0].is_a?(Integer) + if args[0] > 0 + the_range = ((@data.bytesize - 1 - (args[0] - 1))..(@data.bytesize - 1)) + if @max_size + unless args[0] <= @max_size + raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).") + end + end + self[(the_range)] + else + raise RangeError.new("#{args[0]} is out of range (1 - #{@max_size}).") + end + else + raise TypeError.new("Expected Integer. You provided #{args[0].class}") + end + else + self[(@data.bytesize - 1)] + end + end + # + def pack(*args) + result = "" + if args.size >= 1 + args[0] = (args[0] || "") + if args[0].is_a?(String) + if args[1].is_a?(Encoding) + # Extra optional encoding vs standard Array + result.force_encoding(args[1]) + end + # LATER: ByteArray.pack: a more memory-efficient method is needed here long term. For now: leverage Array.pack. + # Each byte will take up 4 to 8 bytes depending just to get packed into a string ... gotta fix this. + result << self.to_a.pack(args[0]) + else + raise TypeError.new("Expected String. You provided #{args[0].class}") + end + else + raise ArgumentError.new("Wrong number of arguments (#{args.size} for 1+).") + end + result + end + # + def permutation(*args,&block) + # TODO: port c code to ruby (true-enumeration-as-bytearray support) + enumerator = @data.to_enum(:bytes).to_a.to_enum(:permutation,*args) + if block.respond_to?(:call) + enumerator.each do |the_permutation| + block.call(GxG::ByteArray.new(the_permutation)) + end + self + else + GxG::Enumerator.new(enumerator) + end + end + # + def repeated_permutation(*args,&block) + # TODO: port c code to ruby (true-enumeration-as-bytearray support) + enumerator = @data.to_enum(:bytes).to_a.to_enum(:repeated_permutation,*args) + if block.respond_to?(:call) + enumerator.each do |the_permutation| + block.call(GxG::ByteArray.new(the_permutation)) + end + self + else + GxG::Enumerator.new(enumerator) + end + end + # + def pop(how_many=nil) + # LIFO style stack operation + result = nil + if how_many.is_a?(Numeric) + unless how_many.to_i > 0 + raise ArgumentError.new("#{how_many} needs to be a positive Numeric") + end + result = ::GxG::ByteArray.new + how_many.times do + result << self.delete_at(-1) + end + else + result = self.delete_at(-1) + end + result + end + # + def product(*others,&block) + if @data.bytesize > 0 + others = GxG::ByteArray.new(others) + if block.respond_to?(:call) + self.each do |the_byte| + others.each do |other_byte| + block.call(GxG::ByteArray.new([(the_byte),(other_byte)])) + end + end + self + else + accumulator = GxG::ByteArray.new + self.each do |the_byte| + others.each do |other_byte| + accumulator << the_byte + accumulator << other_byte + end + end + accumulator + end + else + self + end + end + # + def push(*args) + # LIFO style stack operation + self << GxG::ByteArray.new(args) + end + # + def rassoc(key=nil) + nil + end + # + def reject(&block) + result = nil + if block.respond_to?(:call) + result = GxG::ByteArray.new + result.padvalue = (@pad_value) + if @max_size + result.maxsize = (@max_size) + end + self.to_enum.each do |the_byte| + result << the_byte + end + else + result = self.to_enum + end + result + end + # + def reject!(&block) + result = nil + if block.respond_to?(:call) + purgelist = [] + self.each_with_index do |value, index| + if block.call(value) == true + @data[(index)] = "" + purgelist << index + end + end + result = self + else + result = self.to_a.to_enum(:reject!) + end + result + end + # + def replace(*args) + # replace data payload while leaving max_size and pad_value untouched, fitting replacement data into in-place constraints if any. + if args.size > 0 + replacement = nil + if args[0].is_a?(::GxG::ByteArray) + replacement = args[0].data() + end + unless replacement.is_a?(::String) + replacement = GxG::ByteArray::try_convert(args) + if replacement + replacement = replacement.data() + end + end + if replacement + replacement.force_encoding(Encoding::ASCII_8BIT) + if @max_size + if replacement.bytesize <= @max_size + @data.replace(replacement) + else + @data.replace(replacement[(0..(@max_size - 1))]) + end + else + @data.replace(replacement) + end + else + raise Exception.new("Could not convert #{args[0].class} to ByteArray") + end + end + self + end + # + def reverse() + result = GxG::ByteArray.new(@data.reverse) + result.padvalue = (@pad_value.clone) + if @max_size + result.maxsize = (@max_size.clone) + end + result + end + # + def reverse!() + @data.reverse! + self + end + # + def reverse_each(&block) + result = nil + if block.respond_to?(:call) + # use duplicate of self, do not preserve frozen state. + result = self.dup + result.clear + @data.reverse.to_enum(:bytes).each {|the_byte| result << block.call(the_byte)} + else + # ballons mem footprint x2: *ouch* - don't know a better way yet. + result = self.clone.reverse!.to_enum + end + result + end + # + def rotate(vector=1) + unless vector.is_a?(Numeric) + raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}") + end + # from array.c : rotate_count + if vector < 0 + vector = (self.size - (~vector % self.size) - 1).to_i + else + vector = (vector % self.size).to_i + end + if GxG::Engine.memory_load_high? + # trade speed for (2x) space + scrap = [] + unless vector == 0 + the_range = self.filter_parameters(((vector)..-1)) + scrap << self[(the_range)] + if the_range.first > 0 + scrap << self[(0..(the_range.first - 1))] + end + end + if scrap.size > 0 + result = GxG::ByteArray.new(scrap) + else + result = self.dclone + end + else + # trade (5x) space for speed + result = GxG::ByteArray.new(self.to_a.rotate(vector)) + end + result + end + # + def rotate!(vector=1) + if vector.is_a?(Numeric) + if vector.to_i != 0 + new_data = self.rotate(vector).data() + unless @data.hash == new_data.hash + self.replace(new_data) + end + end + else + raise ArgumentError.new("Expected a Numeric, you provided #{vector.class}") + end + self + end + # + def sample(*args) + GxG::ByteArray.new(@data.to_enum(:bytes).to_a.sample(*args)) + end + # + def select(&block) + result = GxG::ByteArray.new + result.maxsize = (@max_size) + result.padvalue = (@pad_value) + result.outervalue = (@outer) + if block.respond_to?(:call) + self.each do |value| + unless block.call(value) == false + result << value + end + end + else + self.to_enum(:each) + end + result + end + # + def select!(&block) + if block.respond_to?(:call) + state = @data.hash + self.replace(self.select(&block).data()) + if state == @data.hash + nil + else + self + end + else + self.to_enum(:each) + end + end + # + def shift() + # FIFO style pop + self.delete_at(0) + end + # + def shuffle(*args) + GxG::ByteArray.new(@data.to_enum(:bytes).to_a.shuffle(*args)) + end + # + def shuffle!(*args) + self.replace(self.shuffle(*args)) + self + end + # + def size() + @data.bytesize + end + # + def slice(indexer=0, length=nil) + the_range = self.filter_parameters(indexer,length) + unless the_range + if length + raise ArgumentError.new("#{indexer.inspect},#{length.inspect} could not be fashioned into a valid Indexer") + else + raise ArgumentError.new("#{indexer.inspect} could not be fashioned into a valid Indexer") + end + end + # check overall compliance with maxsize + if @max_size + if ((the_range.count > @max_size) || (the_range.first > (@max_size - 1)) || (the_range.last > (@max_size - 1))) + raise IndexError.new("Index exceeds #{@max_size} limit. You need to adjust maxsize.") + end + end + # + result = nil + # + if the_range + if the_range.first == the_range.last + result = @data.byte_at(the_range.first) + else + result = @data.bytes_at(the_range) + end + end + # + result + end + # + def slice!(indexer=0, length=nil) + replacement = self.slice(indexer,length) + if replacement.is_a?(Numeric) + self.replace(replacement.chr) + else + self.replace(replacement) + end + if self.size == 1 + self.slice(0) + else + self + end + end + # + # BETA: ByteArray : see about self.to_a replacement (memory efficiency) in sort methods. + # + def sort(&block) + if block.respond_to?(:call) + GxG::ByteArray.new(self.to_a.sort {|a,b| pause;block.call(a,b)}) + else + GxG::ByteArray.new(self.to_a.sort) + end + end + # + def sort!(&block) + if block.respond_to?(:call) + self.replace(self.sort(&block).data()) + end + self + end + # + def sort_by!(&block) + if block.respond_to?(:call) + self.replace(GxG::ByteArray.new(self.to_a.sort_by! {|a,b| pause;block.call(a,b)}).data()) + self + else + self.to_enum(:sort_by!) + end + end + # + def take(length) + self.slice(0..(length.to_i - 1)) + end + # + def take_while(&block) + if block.respond_to?(:call) + accumulator = GxG::ByteArray.new + self.each do |the_byte| + result = block.call(the_byte) + if result + accumulator << the_byte + else + break + end + end + accumulator + else + self.to_enum(:take_while) + end + end + # + def to_a() + result = [] + if @data.bytesize > 0 + (0..(@data.bytesize - 1)).to_enum.each {|index| result << self[(index)]} + end + result + end + # + def to_s() + @data.dup + end + # + def to_json() + (("binary:" + self.to_s).encode64) + end + # + def transpose() + self.dclone + end + # + def uniq(&block) + if block.respond_to?(:call) + harness = Proc.new do |element| + pause + block.call(element) + end + GxG::ByteArray.new(self.to_a.uniq(&harness)) + else + self + end + end + # + def uniq!(&block) + scrap = self.uniq(&block) + if scrap.size == self.size + nil + else + self.replace(scrap) + self + end + end + # + def unshift(*args) + # FIFO style push + if args.size > 0 + self.insert(0,args) + end + self + end + # + def values_at(*args) + result = GxG::ByteArray.new + if args.size > 0 + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + args.to_enum.each do |selector| + if selector.is_a?(Numeric) or selector.is_a?(Range) + unless selector.is_a?(Range) + selector = selector.to_i + end + scrap = self[(selector)] + if scrap.is_a?(Integer) + result_scrap << scrap.chr + else + result_scrap << scrap.data() + end + else + raise TypeError.new("Expected Integer, Float or Range. You provided #{selector.class}") + end + end + result = GxG::ByteArray.new(result_scrap) + end + result + end + # + def zip(*args,&block) + # Converts any valid argument values to array, then merges elements of self with corresponding elements from each argument + # This generates a sequence of self.size n-element ByteArrays as step buffers, where n is one more that the count of arguments + # * Then flattened output as ByteArray * + # If the size of any argument is less than enumObj.size, @pad_value is supplied + # If a block given, it is invoked for each output array. + # If block returns an array or ByteArray, it replaces the provided buffer and is appended to output. + # + result = nil + if args.size > 0 + result_scrap = "" + result_scrap.force_encoding(Encoding::ASCII_8BIT) + args.to_enum(:each_index).each do |index| + unless args[(index)].is_a?(GxG::ByteArray) + args[(index)] = GxG::ByteArray.new(args[(index)]) + end + end + self.each_with_index do |byte,index| + # + buffer = "" + buffer.force_encoding(Encoding::ASCII_8BIT) + buffer << byte.chr + args.to_enum(:each_index).each do |arg_index| + if index >= args[(arg_index)].size + buffer << @pad_value.chr + else + buffer << args[(arg_index)].data()[(index)] + end + end + # + if block.respond_to?(:call) + # I want to differ from stock array.zip behavior: + # I want to have it possible to return the processed buffer back - + # to a result ByteArray if result of yield = array/ByteArray. + raw_new = block.call(GxG::ByteArray.new(buffer)) + if raw_new + buffer = GxG::ByteArray.new(raw_new).data() + end + end + result_scrap << buffer + end + result = GxG::ByteArray.new(result_scrap) + else + result = self.dclone + end + result + end + + # Method Aliases + alias :assoc :rassoc + alias :map :collect + alias :map! :collect! + alias :compact! :compact + alias :indices :values_at + alias :indexes :values_at + alias :rindex :find_index + alias :index :find_index + alias :length :size + alias :nitems :size + alias :to_ary :to_a + alias :keep_if :select! + + # Byte Operations + + # GxG Convenience Methods: + def paths_to(object=nil,base_path="") + # new idea here: + search_results = [] + # TODO: if paths can use ranges, expand to use string or byte or int byte or byte-pattern matching to define returned valid path for get/set. + if object.is_a?(Numeric) + self.to_enum.each_with_index do |entry,index| + if entry == object.to_i + search_results << (base_path + "/" + index.to_s) + end + end + end + search_results + end + # + def get_at_path(the_path="/") + # TODO: question: would it serve much to allow ranges as structure-path specifier? What would that involve? + # /^(?:[0-9])*[0-9](?:[0-9])*$/ = nil if an alpha present there, else 0 only numeric + # Attribution : http://stackoverflow.com/questions/1240674/regex-match-a-string-containing-numbers-and-letters-but-not-a-string-of-just-nu + # + # if ":" detected do: (str.gsub("%2f","/").to_sym) as key else (str.gsub("%2f","/")) + result = nil + if the_path == "/" + result = self + else + object_stack = [(self)] + path_stack = the_path.split("/") + path_stack.to_enum.each do |path_element| + element = nil + if path_element.size > 0 + if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + element = path_element.to_i + end + end + if element + result = object_stack.first[(element)] + if result.is_a?(NilClass) + break + else + object_stack.unshift(result) + end + else + # ignore double slashes? '//' + # break + end + end + end + result + end + # + def set_at_path(the_path="/",the_value=nil) + result = nil + if the_path != "/" + container = self + if container + raw_selector = ::File::basename(the_path) + selector = nil + if raw_selector.size > 0 + if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 + selector = raw_selector.to_i + end + end + if selector + container[(selector)] = the_value + if container[(selector)] == the_value + result = true + end + else + # ignore double slashes? '//' + # break + end + # + end + end + result + end + end + # +end +# diff --git a/lib/gxg/gxg_engine.rb b/lib/gxg/gxg_engine.rb new file mode 100644 index 0000000..ccd8f20 --- /dev/null +++ b/lib/gxg/gxg_engine.rb @@ -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 .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 => , :path => } + # if hash_path supplied, read the data; set the @details hash element, and return the formatted data + if specifier[:key] + # {:key => , :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 +# \ No newline at end of file diff --git a/lib/gxg/gxg_entities.rb b/lib/gxg/gxg_entities.rb new file mode 100644 index 0000000..8b41238 --- /dev/null +++ b/lib/gxg/gxg_entities.rb @@ -0,0 +1,2169 @@ +# +# --------------------------------------------------------------------------------------------------------------------- +# Preliminary Entity supports +module GxG + # + def find_local(the_key) + nil + end + # + module Entity + # SysInfo + # provide default filesystem object + class LocalSystem + # + def initialize(bootstrap_data={}) + # Rather than only using RUBY_PLATFORM, I want to define the *specific* environment on that platform to help further characterize what is at hand. + super() + # @thread_safety = ::Mutex.new + @thread_safety = ::Mutex.new + @details = {} + # + @details[:architecture] = bootstrap_data.delete(:architecture) + # + # ### Process bootstrap_data regarding platform and environment + @details[:platform] = bootstrap_data.delete(:platform) + @details[:platform_version] = GxG::Version.new(bootstrap_data.delete(:platform_version)) + @details[:platform_configuration] = {:process => {}, :flags => {}} + @details[:environment] = bootstrap_data.delete(:environment) + @details[:environment_variant] = bootstrap_data.delete(:environment_variant) + if bootstrap_data[:environment_version] + @details[:environment_version] = GxG::Version.new(bootstrap_data.delete(:environment_version)) + else + @details[:environment_version] = nil + end + @details[:environment_configuration] = {} + # + # ### Process CPU info + @details[:processors] = bootstrap_data.delete(:processors) + @details[:processors].process! do |item, selector, container| + if selector.is_any?(::String, ::Symbol) + # hash/struct processing + if selector.is_a?(Symbol) + key = selector + else + key = selector.to_s.to_sym + end + # massage data by key + # [:processor, :vendor_id, :cpu_family, :model, :model_name, :stepping, :cpu_mhz, :cache_size, :physical_id, :siblings, :core_id, :cpu_cores, :apicid, :initial_apicid, ::fpu, :fpu_exception, :cpuid_level, :wp, :flags, :bogomips, :clflush_size, :cache_alignment, :address_sizes, :power_management] + entry = nil + case key + when :processor, :cpu_family, :model, :stepping, :physical_id, :siblings, :core_id, :cpu_cores, :apicid, :initial_apicid, :cpuid_level, :clflush_size, :cache_alignment + entry = item.to_s.numeric_values() + if entry.is_a?(Hash) + item = entry[:integer] + end + when :cpu_mhz, :bogomips + entry = item.to_s.numeric_values() + if entry.is_a?(Hash) + item = entry[:float] + end + when :cache_size + entry = item.to_s.numeric_values([:byte]) + if entry.is_a?(Hash) + item = entry[:byte] + end + when :address_sizes + entry = item.to_s.numeric_values([:bit],",") + if entry.is_a?(Array) + item = {:physical_bits => (entry[0][:bit] || entry[0][:integer]), :virtual_bits => (entry[1][:bit] || entry[1][:integer])} + end + when :flags + item = item.to_s.split(" ").to_enum.map do |flag| + flag.to_sym + end + end + if item + if container.is_a?(::Hash) + container.delete(selector) + container[(key)] = item + end + end + end + nil + end + # TODO: Build up a list of flags that the cpu supports and are *actually* in use (i.e. PAE, ...) + @details[:platform_flags] = {} + # Gather platform kernel configuration data for processing. + case @details[:platform] + when :linux + release = (`uname -r`.split("\n")[0]) + likely_paths = ["/proc/config.gz",("/lib/modules/#{release}/build/.config"),("/usr/src/linux-#{release}/.config"),("/usr/src/linux-#{release}/configs/#{release}-#{@details[:architecture]}.config"),("/boot/config-#{release}")] + raw_data = "" + likely_paths.to_enum.each do |the_path| + if ::File.exist?(the_path) + # Note: at this point in everything it is ok for file io that blocks. + if the_path == "/proc/config.gz" + ::Zlib::GzipReader.open(the_path) { |gz| raw_data = gz.read } + else + ::File.open(the_path, "r") { |i| raw_data = i.read } + end + break + end + end + # process raw_data (each line) + raw_flags = {} + raw_data.to_enum(:each_line).each do |line_text| + flag = nil + setting = nil + if line_text.size > 0 + if line_text[0] == "#" + if line_text.include?("is not set") + # cull unset thing and make its value nil. + flag = line_text[1..-1].scan(/[A-Z_]*/)[0] + if flag.size > 0 + # exclude redundant 'CONFIG' portion + flag = flag.split("_")[1..-1].join("_") + else + flag = nil + end + end + else + flag, setting = line_text.split("=") + # exclude redundant 'CONFIG' portion + flag = flag.split("_")[1..-1].join("_") + if (setting == "y" || setting == "y\n") + setting = true + end + if (setting == "n" || setting == "n\n") + setting = false + end + end + end + if (flag) + raw_flags[(flag)] = setting + end + end + raw_data = nil + # Note: I am *loathe* to use strings instead of symbols in hashes generally, but I don't like what ruby does + # with symbols and memory for temporary data sets (looking at you matz and my slapping hand twitches). + universal_synonyms = {"X86_PAE" => "physical_address_extension"} + universal_synonyms["X86_32_SMP"] = "symetric_multiprocessing" + universal_synonyms["X86_64_SMP"] = "symetric_multiprocessing" + universal_synonyms["X86_HT"] = "hyperthreading" + universal_synonyms["SWAP"] = "virtual_memory" + # + raw_flags.each_pair do |selector,value| + # + if universal_synonyms[(selector)] + # include the flag ONLY if it is actually enabled (in use) on this build of the kernel + if value + @details[:platform_configuration][:flags][(universal_synonyms[(selector)].to_sym)] = value + end + end + end + # end linux area + end + # + # ### Memory crap: + # + # Set computational and storage/memory limits by platform and environment: + # See (with respect to Ruby) : http://www.rubynotes.net/uk/operating-systems/windows/item/82-physical-memory-limits.html + # Note: RVALUE size (consumption size) is determined in a completely separate manner than the limit size (according to cpu virtual addressing bits used). + # default for 32 bit OS used: + system_memory = self.memory() + per_process_max = 2147483648 + case @details[:platform] + when :linux + # for :linux : See http://linuxfollies.blogspot.com/2010/10/linux-memory-limits-rlimits.html + # Note: Process::getrlimit("AS") does not reflect the reality of total ram + total swap space as a limit. + if @details[:processors][0][:address_sizes].is_a?(::Hash) + if @details[:processors][0][:address_sizes][:virtual_bits] == 32 + if @details[:platform_configuration][:flags][:physical_address_extension] + # ??? what does that do to the memory limit under 32 bits + # 3.12GB rumored: 3350074490.88 (rounded-down) + per_process_max = 3350074490 + else + per_process_max = 3221225472 + end + end + end + if @details[:processors][0][:address_sizes].is_a?(::Hash) + if @details[:processors][0][:address_sizes][:virtual_bits] == 64 + # odd thing: they built 64bit computers that only have slots enough for 2GB max ram (imagine!) + # So... to deal with this dilemma, I'm going to set the per_process_max to system_memory[:actual][:total] (:actual=RAM) unless it is really a full 8TB+. + if system_memory[:actual][:total] >= 8796093022208 + per_process_max = 8796093022208 + else + per_process_max = system_memory[:actual][:total] + end + end + else + # virtual bits reading NOT supported: + if ([ -1 ].pack('l!').length * 8) == 32 + if @details[:platform_configuration][:flags][:physical_address_extension] + # ??? what does that do to the memory limit under 32 bits + # 3.12GB rumored: 3350074490.88 (rounded-down) + per_process_max = 3350074490 + else + per_process_max = 3221225472 + end + end + if ([ -1 ].pack('l!').length * 8) == 64 + if system_memory[:actual][:total] >= 8796093022208 + per_process_max = 8796093022208 + else + per_process_max = system_memory[:actual][:total] + end + end + end + when :windows + # for :windows : See http://msdn.microsoft.com/en-us/library/windows/desktop/aa366778%28v=vs.85%29.aspx + # And "4GB-tuning' at http://msdn.microsoft.com/en-us/library/windows/desktop/bb613473%28v=vs.85%29.aspx + # And "BCDEdit /set' at http://msdn.microsoft.com/en-us/library/ff542202.aspx + # TODO: :windows : find a way to test if IMAGE_FILE_LARGE_ADDRESS_AWARE is set, whether os runs in PAE mode, and + # what BCDEdit /set Increaseuserva (gigabytes-as-megabytes) was set to. (kernel_mode address space = 4GB-(2048MB-to-3072MB value of it)) + # on infamous /3GB switch in boot.ini : http://www.ditii.com/2007/04/14/memory-management-demystifying-3gb/ + # if @details[:processors][0][:address_sizes][:virtual_bits] == 32 + # # :windows 32-bit : 2GB per process memory limit (non-contiguous-only) + # per_process_max = 2147483648 + # end + # if @details[:processors][0][:address_sizes][:virtual_bits] == 64 + # # :if IMAGE_FILE_LARGE_ADDRESS_AWARE is set on process image, then 8TB max per 64-bit process, except on Intel Itanium-based systems (7TB) + # # otherwise (if unset, and since we have no F'n way to tell within ruby): + # per_process_max = 2147483648 + # end + # Review : Since I am only supporting 64-bit Windows (and don't know how to survey cpu info) -- skip prior tests + per_process_max = 2147483648 + end + # + if system_memory[:total][:total] >= per_process_max + @details[:memory_limits]={:process => ((per_process_max)..(system_memory[:total][:total]))} + else + @details[:memory_limits]={:process => ((system_memory[:total][:total])..(system_memory[:total][:total]))} + end + # stock setrlimit will ignore the 'hard-wired' limit reset to actual available memory (both kinds), but the overridden GxG::Engine::getrlimit(:AS) will respect it. + # GxG::Engine::memory_available() is built around these limits to ensure you do not encounter an issue without some warning (no Exabyte delusions). + # ::Process::setrlimit(:AS,@details[:memory_limits][:process].min,@details[:memory_limits][:process].max) + # + # ### Initial Buffer Sizes + # TODO: find a clever way to determine the platform's maximum buffer size (after linux 2.6.14 (I think) buffers are now 16x4KiB). + @details[:memory_limits][:buffers] = {:default => 4096, :terminal => 1024,:socket => {:ipc => {}}, :ipv4 => {:tcp => {}, :udp => {}}} + # Attribution: http://www.psc.edu/networking/projects/tcptune/#detailed and + # http://www.psc.edu/networking/projects/tcptune/#tutorial + # + case @details[:platform] + when :linux + # TODO: provide for better version comparisons: create >, <. etc methods that accept a string to interperet or a value instance. + # See: http://www.pixelbeat.org/programming/stdio_buffering/ + platform_version = @details[:platform_version].value + if platform_version[:major] > 2 + @details[:memory_limits][:buffers][:default] = 65536 + end + if platform_version[:major] == 2 + if platform_version[:minor] >= 6 + if platform_version[:teeny] >= 14 + @details[:memory_limits][:buffers][:default] = 65536 + end + end + end + end + # + # ### The Ruby runtime itself + # Now defined in GxG::Engine as a chance to correct some 'free-form-jazz-ensemble-a-la-SpinalTap' stuff from the guys at Ruby Core! :) + # + # ### Network stuff + # node (or host) name: + @details[:nodename] = ::Socket.gethostname + @details[:network] = {} + begin + address = ::TCPSocket.getaddress(@details[:nodename]) + rescue Exception + # raise Exception, "You need to edit your host file, and add an alias for this host." + # exit 1 + address = nil + end + # netstat --interfaces --all --verbose --extend + # + @details[:network][:hostname] = @details[:nodename] + # TODO: @gather localhost info: convert ip address info to std. ip_addr objects + @details[:network][:address] = address + @details[:network][:hosts] = [] + # raw_list = Sys::Host.info + # raw_list.to_enum.each do |entry| + # new_entry = {} + # entry.each_pair do |key,item| + # # TODO: @gather hosts info: convert ip address info to std. ip_addr objects + # new_entry[(key.to_sym)] = item + # end + # @details[:network][:hosts] << new_entry + # # + # end + # + # TODO: inventory each network interface and host address of the system + # then, attach active, kernel supported, protocols + # then, use self["a/setting/path"] to set the data for each setting of each protocol of each nic that is supported and active. + protocols = { + :ipv4=>{:settings=>{:tcp => {}, :udp => {}}, :aliases=>["IP"], :proto=>0}, + :icmp=>{:settings=>{}, :aliases=>["ICMP"], :proto=>1}, + :igmp=>{:settings=>{}, :aliases=>["IGMP"], :proto=>2}, + :ggp=>{:settings=>{}, :aliases=>["GGP"], :proto=>3}, + :ipencap=>{:settings=>{}, :aliases=>["IP-ENCAP"], :proto=>4}, + :st=>{:settings=>{}, :aliases=>["ST"], :proto=>5}, + :tcp=>{:settings=>{}, :aliases=>["TCP"], :proto=>6}, + :egp=>{:settings=>{}, :aliases=>["EGP"], :proto=>8}, + :igp=>{:settings=>{}, :aliases=>["IGP"], :proto=>9}, + :pup=>{:settings=>{}, :aliases=>["PUP"], :proto=>12}, + :udp=>{:settings=>{}, :aliases=>["UDP"], :proto=>17}, + :hmp=>{:settings=>{}, :aliases=>["HMP"], :proto=>20}, + :"xns-idp"=>{:settings=>{}, :aliases=>["XNS-IDP"], :proto=>22}, + :rdp=>{:settings=>{}, :aliases=>["RDP"], :proto=>27}, + :"iso-tp4"=>{:settings=>{}, :aliases=>["ISO-TP4"], :proto=>29}, + :dccp=>{:settings=>{}, :aliases=>["DCCP"], :proto=>33}, + :xtp=>{:settings=>{}, :aliases=>["XTP"], :proto=>36}, + :ddp=>{:settings=>{}, :aliases=>["DDP"], :proto=>37}, + :"idpr-cmtp"=>{:settings=>{}, :aliases=>["IDPR-CMTP"], :proto=>38}, + :ipv6=>{:settings=>{:tcp=>{}, :udp=>{}}, :aliases=>["IPv6"], :proto=>41}, + :"ipv6-route"=>{:settings=>{}, :aliases=>["IPv6-Route"], :proto=>43}, + :"ipv6-frag"=>{:settings=>{}, :aliases=>["IPv6-Frag"], :proto=>44}, + :idrp=>{:settings=>{}, :aliases=>["IDRP"], :proto=>45}, + :rsvp=>{:settings=>{}, :aliases=>["RSVP"], :proto=>46}, + :gre=>{:settings=>{}, :aliases=>["GRE"], :proto=>47}, + :esp=>{:settings=>{}, :aliases=>["IPSEC-ESP"], :proto=>50}, + :ah=>{:settings=>{}, :aliases=>["IPSEC-AH"], :proto=>51}, + :skip=>{:settings=>{}, :aliases=>["SKIP"], :proto=>57}, + :"ipv6-icmp"=>{:settings=>{}, :aliases=>["IPv6-ICMP"], :proto=>58}, + :"ipv6-nonxt"=>{:settings=>{}, :aliases=>["IPv6-NoNxt"], :proto=>59}, + :"ipv6-opts"=>{:settings=>{}, :aliases=>["IPv6-Opts"], :proto=>60}, + :rspf=>{:settings=>{}, :aliases=>["RSPF", "CPHB"], :proto=>73}, + :vmtp=>{:settings=>{}, :aliases=>["VMTP"], :proto=>81}, + :eigrp=>{:settings=>{}, :aliases=>["EIGRP"], :proto=>88}, + :ospf=>{:settings=>{}, :aliases=>["OSPFIGP"], :proto=>89}, + :"ax.25"=>{:settings=>{}, :aliases=>["AX.25"], :proto=>93}, + :ipip=>{:settings=>{}, :aliases=>["IPIP"], :proto=>94}, + :etherip=>{:settings=>{}, :aliases=>["ETHERIP"], :proto=>97}, + :encap=>{:settings=>{}, :aliases=>["ENCAP"], :proto=>98}, + :pim=>{:settings=>{}, :aliases=>["PIM"], :proto=>103}, + :ipcomp=>{:settings=>{}, :aliases=>["IPCOMP"], :proto=>108}, + :vrrp=>{:settings=>{}, :aliases=>["VRRP"], :proto=>112}, + :l2tp=>{:settings=>{}, :aliases=>["L2TP"], :proto=>115}, + :isis=>{:settings=>{}, :aliases=>["ISIS"], :proto=>124}, + :sctp=>{:settings=>{}, :aliases=>["SCTP"], :proto=>132}, + :fc=>{:settings=>{}, :aliases=>["FC"], :proto=>133}, + :"mobility-header"=>{:settings=>{}, :aliases=>["Mobility-Header"], :proto=>135}, + :udplite=>{:settings=>{}, :aliases=>["UDPLite"], :proto=>136}, + :"mpls-in-ip"=>{:settings=>{}, :aliases=>["MPLS-in-IP"], :proto=>137}, + :manet=>{:settings=>{}, :aliases=>[], :proto=>138}, + :hip=>{:settings=>{}, :aliases=>["HIP"], :proto=>139}, + :shim6=>{:settings=>{}, :aliases=>["Shim6"], :proto=>140}, + :wesp=>{:settings=>{}, :aliases=>["WESP"], :proto=>141}, + :rohc=>{:settings=>{}, :aliases=>["ROHC"], :proto=>142} + } + # + @details[:network][:protocols] = protocols + # if @details[:platform] == :windows + # # TODO: find suitable alternative to Net::Proto.getprotoent (breaks under rbx) + # else + # raw_list = Net::Proto.getprotoent + # raw_list.to_enum.each do |entry| + # new_entry = {:settings => {}} + # proto_name = :unknown + # entry.each_pair do |key,item| + # if key.to_s.to_sym == :name + # if item.to_s.to_sym == :ip + # proto_name = :ipv4 + # else + # proto_name = item.to_s.to_sym + # end + # else + # new_entry[(key)] = item + # end + # end + # if [:ipv4,:ipv6].include?(proto_name) + # new_entry[:settings][:tcp] = {} + # new_entry[:settings][:udp] = {} + # end + # @details[:network][:protocols][(proto_name)] = new_entry + # # + # end + # end + # Read in Sysctl-style keys and set @details data. + get_system_data(detail_mapping()) + # + # + # File descriptor reservation and release support, reserving $stdin, $stdout, and $stderr, etc. + # TODO: get a *complete* list of getconf variables *and their meanings* and incorporate them into sysctl-style path system. + # See: http://www.lainoox.com/system-wide-config-getconf/ + # self["configuration.process.maximum_file_descriptors"] + # LATER: add lsof to requirements list within installer. + case @details[:platform] + when :linux, :aix, :solaris, :bsd, :darwin + # TODO: research -F option of lsof + # See: http://geek00l.blogspot.com/2006/03/openbsd-fstat-vs-lsof.html + end + # + case @details[:platform] + when :bsd + # See: fstat vs lsof : http://geek00l.blogspot.com/2006/03/openbsd-fstat-vs-lsof.html + # + # if EventMachine::kqueue? + # ? + # @details[:maximum_descriptors][:events] = 1000000 + # end + when :linux + # if EventMachine::epoll? + # ? + # @details[:maximum_descriptors][:events] = 1000000 + # end + end + # ### + @valid_modes = {} + # Internal Services to Port Usage mapping + @details[:service_ports] = { + :tcpmux=>{:aliases=>[], :clients=>[], :description=>"TCP port service multiplexer", :uses=>{:tcp=>[1]}}, + :echo=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[7], :udp=>[7], :ddp=>[4]}}, + :discard=>{:aliases=>["sink", "null"], :clients=>[], :description=>"", :uses=>{:tcp=>[9], :udp=>[9]}}, + :systat=>{:aliases=>["users"], :clients=>[], :description=>"", :uses=>{:tcp=>[11]}}, + :daytime=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[13], :udp=>[13]}}, + :netstat=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[15]}}, + :qotd=>{:aliases=>["quote"], :clients=>[], :description=>"", :uses=>{:tcp=>[17]}}, + :msp=>{:aliases=>[], :clients=>[], :description=>"message send protocol", :uses=>{:tcp=>[18], :udp=>[18]}}, + :chargen=>{:aliases=>["ttytst", "source"], :clients=>[], :description=>"", :uses=>{:tcp=>[19], :udp=>[19]}}, + :"ftp-data"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[20]}}, + :ftp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[21]}}, + :fsp=>{:aliases=>["fspd"], :clients=>[], :description=>"", :uses=>{:udp=>[21]}}, + :ssh=>{:aliases=>[], :clients=>[], :description=>"SSH Remote Login Protocol", :uses=>{:tcp=>[22], :udp=>[22]}}, + :telnet=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[23]}}, + :smtp=>{:aliases=>["mail"], :clients=>[], :description=>"", :uses=>{:tcp=>[25]}}, + :time=>{:aliases=>["timserver"], :clients=>[], :description=>"", :uses=>{:tcp=>[37], :udp=>[37]}}, + :rlp=>{:aliases=>["resource"], :clients=>[], :description=>"resource location", :uses=>{:udp=>[39]}}, + :nameserver=>{:aliases=>["name"], :clients=>[], :description=>"IEN 116", :uses=>{:tcp=>[42]}}, + :whois=>{:aliases=>["nicname"], :clients=>[], :description=>"", :uses=>{:tcp=>[43]}}, + :tacacs=>{:aliases=>[], :clients=>[], :description=>"Login Host Protocol (TACACS)", :uses=>{:tcp=>[49], :udp=>[49]}}, + :"re-mail-ck"=>{:aliases=>[], :clients=>[], :description=>"Remote Mail Checking Protocol", :uses=>{:tcp=>[50], :udp=>[50]}}, + :domain=>{:aliases=>[], :clients=>[], :description=>"name-domain server", :uses=>{:tcp=>[53], :udp=>[53]}}, + :mtp=>{:aliases=>[], :clients=>[], :description=>"deprecated", :uses=>{:tcp=>[57]}}, + :"tacacs-ds"=>{:aliases=>[], :clients=>[], :description=>"TACACS-Database Service", :uses=>{:tcp=>[65], :udp=>[65]}}, + :bootps=>{:aliases=>[], :clients=>[], :description=>"BOOTP server", :uses=>{:tcp=>[67], :udp=>[67]}}, + :bootpc=>{:aliases=>[], :clients=>[], :description=>"BOOTP client", :uses=>{:tcp=>[68], :udp=>[68]}}, + :tftp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:udp=>[69]}}, + :gopher=>{:aliases=>[], :clients=>[], :description=>"Internet Gopher", :uses=>{:tcp=>[70], :udp=>[70]}}, + :rje=>{:aliases=>["netrjs"], :clients=>[], :description=>"", :uses=>{:tcp=>[77]}}, + :finger=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[79]}}, + :www=>{:aliases=>["http"], :clients=>[], :description=>"WorldWideWeb HTTP", :uses=>{:tcp=>[80], :udp=>[80]}}, + :link=>{:aliases=>["ttylink"], :clients=>[], :description=>"", :uses=>{:tcp=>[87]}}, + :kerberos=>{:aliases=>["kerberos5", "krb5", "kerberos-sec"], :clients=>[], :description=>"Kerberos v5", :uses=>{:tcp=>[88], :udp=>[88]}}, + :supdup=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[95]}}, + :hostnames=>{:aliases=>["hostname"], :clients=>[], :description=>"usually from sri-nic", :uses=>{:tcp=>[101]}}, + :"iso-tsap"=>{:aliases=>["tsap"], :clients=>[], :description=>"part of ISODE", :uses=>{:tcp=>[102]}}, + :"acr-nema"=>{:aliases=>["dicom"], :clients=>[], :description=>"Digital Imag. & Comm. 300", :uses=>{:tcp=>[104], :udp=>[104]}}, + :"csnet-ns"=>{:aliases=>["cso-ns"], :clients=>[], :description=>"also used by CSO name server", :uses=>{:tcp=>[105], :udp=>[105]}}, + :rtelnet=>{:aliases=>[], :clients=>[], :description=>"Remote Telnet", :uses=>{:tcp=>[107], :udp=>[107]}}, + :pop2=>{:aliases=>["postoffice", "pop-2"], :clients=>[], :description=>"POP version 2", :uses=>{:tcp=>[109], :udp=>[109]}}, + :pop3=>{:aliases=>["pop-3"], :clients=>[], :description=>"POP version 3", :uses=>{:tcp=>[110], :udp=>[110]}}, + :sunrpc=>{:aliases=>["portmapper"], :clients=>[], :description=>"RPC 4.0 portmapper", :uses=>{:tcp=>[111], :udp=>[111]}}, + :auth=>{:aliases=>["authentication", "tap", "ident"], :clients=>[], :description=>"", :uses=>{:tcp=>[113]}}, + :sftp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[115]}}, + :"uucp-path"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[117]}}, + :nntp=>{:aliases=>["readnews", "untp"], :clients=>[], :description=>"USENET News Transfer Protocol", :uses=>{:tcp=>[119]}}, + :ntp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[123], :udp=>[123]}}, + :pwdgen=>{:aliases=>[], :clients=>[], :description=>"PWDGEN service", :uses=>{:tcp=>[129], :udp=>[129]}}, + :"loc-srv"=>{:aliases=>["epmap"], :clients=>[], :description=>"Location Service", :uses=>{:tcp=>[135], :udp=>[135]}}, + :"netbios-ns"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS Name Service", :uses=>{:tcp=>[137], :udp=>[137]}}, + :"netbios-dgm"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS Datagram Service", :uses=>{:tcp=>[138], :udp=>[138]}}, + :"netbios-ssn"=>{:aliases=>[], :clients=>[], :description=>"NETBIOS session service", :uses=>{:tcp=>[139], :udp=>[139]}}, + :imap2=>{:aliases=>["imap"], :clients=>[], :description=>"Interim Mail Access P 2 and 4", :uses=>{:tcp=>[143], :udp=>[143]}}, + :snmp=>{:aliases=>[], :clients=>[], :description=>"Simple Net Mgmt Protocol", :uses=>{:tcp=>[161], :udp=>[161]}}, + :"snmp-trap"=>{:aliases=>["snmptrap"], :clients=>[], :description=>"Traps for SNMP", :uses=>{:tcp=>[162], :udp=>[162]}}, + :"cmip-man"=>{:aliases=>[], :clients=>[], :description=>"ISO mgmt over IP (CMOT)", :uses=>{:tcp=>[163], :udp=>[163]}}, + :"cmip-agent"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[164], :udp=>[164]}}, + :mailq=>{:aliases=>[], :clients=>[], :description=>"Mailer transport queue for Zmailer", :uses=>{:tcp=>[174], :udp=>[174]}}, + :xdmcp=>{:aliases=>[], :clients=>[], :description=>"X Display Mgr. Control Proto", :uses=>{:tcp=>[177], :udp=>[177]}}, + :nextstep=>{:aliases=>["NeXTStep", "NextStep"], :clients=>[], :description=>"NeXTStep window", :uses=>{:tcp=>[178], :udp=>[178]}}, + :bgp=>{:aliases=>[], :clients=>[], :description=>"Border Gateway Protocol", :uses=>{:tcp=>[179], :udp=>[179]}}, + :prospero=>{:aliases=>[], :clients=>[], :description=>"Cliff Neuman's Prospero", :uses=>{:tcp=>[191], :udp=>[191]}}, + :irc=>{:aliases=>[], :clients=>[], :description=>"Internet Relay Chat", :uses=>{:tcp=>[194], :udp=>[194]}}, + :smux=>{:aliases=>[], :clients=>[], :description=>"SNMP Unix Multiplexer", :uses=>{:tcp=>[199], :udp=>[199]}}, + :"at-rtmp"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk routing", :uses=>{:tcp=>[201], :udp=>[201]}}, + :"at-nbp"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk name binding", :uses=>{:tcp=>[202], :udp=>[202]}}, + :"at-echo"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk echo", :uses=>{:tcp=>[204], :udp=>[204]}}, + :"at-zis"=>{:aliases=>[], :clients=>[], :description=>"AppleTalk zone information", :uses=>{:tcp=>[206], :udp=>[206]}}, + :qmtp=>{:aliases=>[], :clients=>[], :description=>"Quick Mail Transfer Protocol", :uses=>{:tcp=>[209], :udp=>[209]}}, + :z3950=>{:aliases=>["wais"], :clients=>[], :description=>"NISO Z39.50 database", :uses=>{:tcp=>[210], :udp=>[210]}}, + :ipx=>{:aliases=>[], :clients=>[], :description=>"IPX", :uses=>{:tcp=>[213], :udp=>[213]}}, + :imap3=>{:aliases=>[], :clients=>[], :description=>"Interactive Mail Access", :uses=>{:tcp=>[220], :udp=>[220]}}, + :pawserv=>{:aliases=>[], :clients=>[], :description=>"Perf Analysis Workbench", :uses=>{:tcp=>[345], :udp=>[345]}}, + :zserv=>{:aliases=>[], :clients=>[], :description=>"Zebra server", :uses=>{:tcp=>[346], :udp=>[346]}}, + :fatserv=>{:aliases=>[], :clients=>[], :description=>"Fatmen Server", :uses=>{:tcp=>[347], :udp=>[347]}}, + :rpc2portmap=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[369], :udp=>[369]}}, + :codaauth2=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[370], :udp=>[370]}}, + :clearcase=>{:aliases=>["Clearcase"], :clients=>[], :description=>"", :uses=>{:tcp=>[371], :udp=>[371]}}, + :ulistserv=>{:aliases=>[], :clients=>[], :description=>"UNIX Listserv", :uses=>{:tcp=>[372], :udp=>[372]}}, + :ldap=>{:aliases=>[], :clients=>[], :description=>"Lightweight Directory Access Protocol", :uses=>{:tcp=>[389], :udp=>[389]}}, + :imsp=>{:aliases=>[], :clients=>[], :description=>"Interactive Mail Support Protocol", :uses=>{:tcp=>[406], :udp=>[406]}}, + :svrloc=>{:aliases=>[], :clients=>[], :description=>"Server Location", :uses=>{:tcp=>[427], :udp=>[427]}}, + :https=>{:aliases=>[], :clients=>[], :description=>"http protocol over TLS/SSL", :uses=>{:tcp=>[443], :udp=>[443]}}, + :snpp=>{:aliases=>[], :clients=>[], :description=>"Simple Network Paging Protocol", :uses=>{:tcp=>[444], :udp=>[444]}}, + :"microsoft-ds"=>{:aliases=>[], :clients=>[], :description=>"Microsoft Naked CIFS", :uses=>{:tcp=>[445], :udp=>[445]}}, + :kpasswd=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[464], :udp=>[464]}}, + :saft=>{:aliases=>[], :clients=>[], :description=>"Simple Asynchronous File Transfer", :uses=>{:tcp=>[487], :udp=>[487]}}, + :isakmp=>{:aliases=>[], :clients=>[], :description=>"IPsec - Internet Security Association", :uses=>{:tcp=>[500], :udp=>[500]}}, + :rtsp=>{:aliases=>[], :clients=>[], :description=>"Real Time Stream Control Protocol", :uses=>{:tcp=>[554], :udp=>[554]}}, + :nqs=>{:aliases=>[], :clients=>[], :description=>"Network Queuing system", :uses=>{:tcp=>[607], :udp=>[607]}}, + :"npmp-local"=>{:aliases=>["dqs313_qmaster"], :clients=>[], :description=>"npmp-local / DQS", :uses=>{:tcp=>[610], :udp=>[610]}}, + :"npmp-gui"=>{:aliases=>["dqs313_execd"], :clients=>[], :description=>"npmp-gui / DQS", :uses=>{:tcp=>[611], :udp=>[611]}}, + :"hmmp-ind"=>{:aliases=>["dqs313_intercell"], :clients=>[], :description=>"HMMP Indication / DQS", :uses=>{:tcp=>[612], :udp=>[612]}}, + :qmqp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[628], :udp=>[628]}}, + :ipp=>{:aliases=>[], :clients=>[], :description=>"Internet Printing Protocol", :uses=>{:tcp=>[631], :udp=>[631]}}, + :exec=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[512]}}, + :biff=>{:aliases=>["comsat"], :clients=>[], :description=>"", :uses=>{:udp=>[512]}}, + :login=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[513]}}, + :who=>{:aliases=>["whod"], :clients=>[], :description=>"", :uses=>{:udp=>[513]}}, + :shell=>{:aliases=>["cmd"], :clients=>[], :description=>"no passwords used", :uses=>{:tcp=>[514]}}, + :syslog=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:udp=>[514]}}, + :printer=>{:aliases=>["spooler"], :clients=>[], :description=>"line printer spooler", :uses=>{:tcp=>[515]}}, + :talk=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:udp=>[517]}}, + :ntalk=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:udp=>[518]}}, + :route=>{:aliases=>["router", "routed"], :clients=>[], :description=>"RIP", :uses=>{:udp=>[520]}}, + :timed=>{:aliases=>["timeserver"], :clients=>[], :description=>"", :uses=>{:udp=>[525]}}, + :tempo=>{:aliases=>["newdate"], :clients=>[], :description=>"", :uses=>{:tcp=>[526]}}, + :courier=>{:aliases=>["rpc"], :clients=>[], :description=>"", :uses=>{:tcp=>[530]}}, + :conference=>{:aliases=>["chat"], :clients=>[], :description=>"", :uses=>{:tcp=>[531]}}, + :netnews=>{:aliases=>["readnews"], :clients=>[], :description=>"", :uses=>{:tcp=>[532]}}, + :netwall=>{:aliases=>[], :clients=>[], :description=>"for emergency broadcasts", :uses=>{:udp=>[533]}}, + :gdomap=>{:aliases=>[], :clients=>[], :description=>"GNUstep distributed objects", :uses=>{:tcp=>[538], :udp=>[538]}}, + :uucp=>{:aliases=>["uucpd"], :clients=>[], :description=>"uucp daemon", :uses=>{:tcp=>[540]}}, + :klogin=>{:aliases=>[], :clients=>[], :description=>"Kerberized `rlogin' (v5)", :uses=>{:tcp=>[543]}}, + :kshell=>{:aliases=>["krcmd"], :clients=>[], :description=>"Kerberized `rsh' (v5)", :uses=>{:tcp=>[544]}}, + :"dhcpv6-client"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[546], :udp=>[546]}}, + :"dhcpv6-server"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[547], :udp=>[547]}}, + :afpovertcp=>{:aliases=>[], :clients=>[], :description=>"AFP over TCP", :uses=>{:tcp=>[548], :udp=>[548]}}, + :idfp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[549], :udp=>[549]}}, + :remotefs=>{:aliases=>["rfs_server", "rfs"], :clients=>[], :description=>"Brunhoff remote filesystem", :uses=>{:tcp=>[556]}}, + :nntps=>{:aliases=>["snntp"], :clients=>[], :description=>"NNTP over SSL", :uses=>{:tcp=>[563], :udp=>[563]}}, + :submission=>{:aliases=>[], :clients=>[], :description=>"Submission [RFC4409]", :uses=>{:tcp=>[587], :udp=>[587]}}, + :ldaps=>{:aliases=>[], :clients=>[], :description=>"LDAP over SSL", :uses=>{:tcp=>[636], :udp=>[636]}}, + :tinc=>{:aliases=>[], :clients=>[], :description=>"tinc control port", :uses=>{:tcp=>[655], :udp=>[655]}}, + :silc=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[706], :udp=>[706]}}, + :"kerberos-adm"=>{:aliases=>[], :clients=>[], :description=>"Kerberos `kadmin' (v5)", :uses=>{:tcp=>[749]}}, + :webster=>{:aliases=>[], :clients=>[], :description=>"Network dictionary", :uses=>{:tcp=>[765], :udp=>[765]}}, + :rsync=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[873], :udp=>[873]}}, + :"ftps-data"=>{:aliases=>[], :clients=>[], :description=>"FTP over SSL (data)", :uses=>{:tcp=>[989]}}, + :ftps=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[990]}}, + :telnets=>{:aliases=>[], :clients=>[], :description=>"Telnet over SSL", :uses=>{:tcp=>[992], :udp=>[992]}}, + :imaps=>{:aliases=>[], :clients=>[], :description=>"IMAP over SSL", :uses=>{:tcp=>[993], :udp=>[993]}}, + :ircs=>{:aliases=>[], :clients=>[], :description=>"IRC over SSL", :uses=>{:tcp=>[994], :udp=>[994]}}, + :pop3s=>{:aliases=>[], :clients=>[], :description=>"POP-3 over SSL", :uses=>{:tcp=>[995], :udp=>[995]}}, + :socks=>{:aliases=>[], :clients=>[], :description=>"socks proxy server", :uses=>{:tcp=>[1080], :udp=>[1080]}}, + :proofd=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1093], :udp=>[1093]}}, + :rootd=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1094], :udp=>[1094]}}, + :openvpn=>{:aliases=>["vpn"], :clients=>[], :description=>"", :uses=>{:tcp=>[1194], :udp=>[1194]}, :uses_preference => :udp}, + :rmiregistry=>{:aliases=>[], :clients=>[], :description=>"Java RMI Registry", :uses=>{:tcp=>[1099], :udp=>[1099]}}, + :kazaa=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1214], :udp=>[1214]}}, + :nessus=>{:aliases=>[], :clients=>[], :description=>"Nessus vulnerability", :uses=>{:tcp=>[1241], :udp=>[1241]}}, + :lotusnote=>{:aliases=>[], :clients=>[], :description=>"Lotus Note", :uses=>{:tcp=>[1352], :udp=>[1352]}}, + :"ms-sql-s"=>{:aliases=>[], :clients=>[], :description=>"Microsoft SQL Server", :uses=>{:tcp=>[1433], :udp=>[1433]}}, + :"ms-sql-m"=>{:aliases=>[], :clients=>[], :description=>"Microsoft SQL Monitor", :uses=>{:tcp=>[1434], :udp=>[1434]}}, + :ingreslock=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1524], :udp=>[1524]}}, + :"prospero-np"=>{:aliases=>[], :clients=>[], :description=>"Prospero non-privileged", :uses=>{:tcp=>[1525], :udp=>[1525]}}, + :datametrics=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1645], :udp=>[1645]}}, + :"sa-msg-port"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1646], :udp=>[1646]}}, + :kermit=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1649], :udp=>[1649]}}, + :l2f=>{:aliases=>["l2tp"], :clients=>[], :description=>"", :uses=>{:tcp=>[1701], :udp=>[1701]}}, + :radius=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[1812], :udp=>[1812]}}, + :"radius-acct"=>{:aliases=>[], :clients=>[], :description=>"Radius Accounting", :uses=>{:tcp=>[1813], :udp=>[1813]}}, + :msnp=>{:aliases=>[], :clients=>[], :description=>"MSN Messenger", :uses=>{:tcp=>[1863], :udp=>[1863]}}, + :"unix-status"=>{:aliases=>[], :clients=>[], :description=>"remstats unix-status server", :uses=>{:tcp=>[1957]}}, + :"log-server"=>{:aliases=>[], :clients=>[], :description=>"remstats log server", :uses=>{:tcp=>[1958]}}, + :remoteping=>{:aliases=>[], :clients=>[], :description=>"remstats remoteping server", :uses=>{:tcp=>[1959]}}, + :"cisco-sccp"=>{:aliases=>[], :clients=>[], :description=>"Cisco SCCP", :uses=>{:tcp=>[2000], :udp=>[2000]}}, + :search=>{:aliases=>["ndtp"], :clients=>[], :description=>"", :uses=>{:tcp=>[2010]}}, + :pipe_server=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[2010]}}, + :nfs=>{:aliases=>[], :clients=>[], :description=>"Network File System", :uses=>{:tcp=>[2049], :udp=>[2049]}}, + :gnunet=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[2086], :udp=>[2086]}}, + :"rtcm-sc104"=>{:aliases=>[], :clients=>[], :description=>"RTCM SC-104 IANA 1/29/99", :uses=>{:tcp=>[2101], :udp=>[2101]}}, + :gsigatekeeper=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[2119], :udp=>[2119]}}, + :gris=>{:aliases=>[], :clients=>[], :description=>"Grid Resource Information Server", :uses=>{:tcp=>[2135], :udp=>[2135]}}, + :cvspserver=>{:aliases=>[], :clients=>[], :description=>"CVS client/server operations", :uses=>{:tcp=>[2401], :udp=>[2401]}}, + :venus=>{:aliases=>[], :clients=>[], :description=>"codacon port", :uses=>{:tcp=>[2430], :udp=>[2430]}}, + :"venus-se"=>{:aliases=>[], :clients=>[], :description=>"tcp side effects", :uses=>{:tcp=>[2431], :udp=>[2431]}}, + :codasrv=>{:aliases=>[], :clients=>[], :description=>"not used", :uses=>{:tcp=>[2432], :udp=>[2432]}}, + :"codasrv-se"=>{:aliases=>[], :clients=>[], :description=>"tcp side effects", :uses=>{:tcp=>[2433], :udp=>[2433]}}, + :mon=>{:aliases=>[], :clients=>[], :description=>"MON traps", :uses=>{:tcp=>[2583], :udp=>[2583]}}, + :dict=>{:aliases=>[], :clients=>[], :description=>"Dictionary server", :uses=>{:tcp=>[2628], :udp=>[2628]}}, + :gsiftp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[2811], :udp=>[2811]}}, + :gpsd=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[2947], :udp=>[2947]}}, + :gds_db=>{:aliases=>[], :clients=>[], :description=>"InterBase server", :uses=>{:tcp=>[3050], :udp=>[3050]}}, + :icpv2=>{:aliases=>["icp"], :clients=>[], :description=>"Internet Cache Protocol", :uses=>{:tcp=>[3130], :udp=>[3130]}}, + :mysql=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[3306], :udp=>[3306]}}, + :nut=>{:aliases=>[], :clients=>[], :description=>"Network UPS Tools", :uses=>{:tcp=>[3493], :udp=>[3493]}}, + :distcc=>{:aliases=>[], :clients=>[], :description=>"distributed compiler", :uses=>{:tcp=>[3632], :udp=>[3632]}}, + :daap=>{:aliases=>[], :clients=>[], :description=>"Digital Audio Access Protocol", :uses=>{:tcp=>[3689], :udp=>[3689]}}, + :svn=>{:aliases=>["subversion"], :clients=>[], :description=>"Subversion protocol", :uses=>{:tcp=>[3690], :udp=>[3690]}}, + :suucp=>{:aliases=>[], :clients=>[], :description=>"UUCP over SSL", :uses=>{:tcp=>[4031], :udp=>[4031]}}, + :sysrqd=>{:aliases=>[], :clients=>[], :description=>"sysrq daemon", :uses=>{:tcp=>[4094], :udp=>[4094]}}, + :sieve=>{:aliases=>[], :clients=>[], :description=>"ManageSieve Protocol", :uses=>{:tcp=>[4190]}}, + :epmd=>{:aliases=>[], :clients=>[], :description=>"Erlang Port Mapper Daemon", :uses=>{:tcp=>[4369], :udp=>[4369]}}, + :remctl=>{:aliases=>[], :clients=>[], :description=>"Remote Authenticated Command Service", :uses=>{:tcp=>[4373], :udp=>[4373]}}, + :iax=>{:aliases=>[], :clients=>[], :description=>"Inter-Asterisk eXchange", :uses=>{:tcp=>[4569], :udp=>[4569]}}, + :mtn=>{:aliases=>[], :clients=>[], :description=>"monotone Netsync Protocol", :uses=>{:tcp=>[4691], :udp=>[4691]}}, + :"radmin-port"=>{:aliases=>[], :clients=>[], :description=>"RAdmin Port", :uses=>{:tcp=>[4899], :udp=>[4899]}}, + :rfe=>{:aliases=>[], :clients=>[], :description=>"Radio Free Ethernet", :uses=>{:udp=>[5002], :tcp=>[5002]}}, + :mmcc=>{:aliases=>[], :clients=>[], :description=>"multimedia conference control tool (Yahoo IM)", :uses=>{:tcp=>[5050], :udp=>[5050]}}, + :sip=>{:aliases=>[], :clients=>[], :description=>"Session Initiation Protocol", :uses=>{:tcp=>[5060], :udp=>[5060]}}, + :"sip-tls"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[5061], :udp=>[5061]}}, + :aol=>{:aliases=>[], :clients=>[], :description=>"AIM", :uses=>{:tcp=>[5190], :udp=>[5190]}}, + :"xmpp-client"=>{:aliases=>[], :clients=>[], :description=>"Jabber Client Connection", :uses=>{:tcp=>[5222], :udp=>[5222]}}, + :"xmpp-server"=>{:aliases=>[], :clients=>[], :description=>"Jabber Server Connection", :uses=>{:tcp=>[5269], :udp=>[5269]}}, + :cfengine=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[5308], :udp=>[5308]}}, + :mdns=>{:aliases=>[], :clients=>[], :description=>"Multicast DNS", :uses=>{:tcp=>[5353], :udp=>[5353]}}, + :postgresql=>{:aliases=>[], :clients=>[], :description=>"PostgreSQL Database", :uses=>{:tcp=>[5432], :udp=>[5432]}}, + :freeciv=>{:aliases=>["rptp"], :clients=>[], :description=>"Freeciv gameplay", :uses=>{:tcp=>[5556], :udp=>[5556]}}, + :amqp=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[5672], :udp=>[5672], :sctp=>[5672]}}, + :ggz=>{:aliases=>[], :clients=>[], :description=>"GGZ Gaming Zone", :uses=>{:tcp=>[5688], :udp=>[5688]}}, + :x11=>{:aliases=>["x11-0"], :clients=>[], :description=>"X Window System", :uses=>{:tcp=>[6000], :udp=>[6000]}}, + :"x11-1"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6001], :udp=>[6001]}}, + :"x11-2"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6002], :udp=>[6002]}}, + :"x11-3"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6003], :udp=>[6003]}}, + :"x11-4"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6004], :udp=>[6004]}}, + :"x11-5"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6005], :udp=>[6005]}}, + :"x11-6"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6006], :udp=>[6006]}}, + :"x11-7"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[6007], :udp=>[6007]}}, + :"gnutella-svc"=>{:aliases=>[], :clients=>[], :description=>"gnutella", :uses=>{:tcp=>[6346], :udp=>[6346]}}, + :"gnutella-rtr"=>{:aliases=>[], :clients=>[], :description=>"gnutella", :uses=>{:tcp=>[6347], :udp=>[6347]}}, + :sge_qmaster=>{:aliases=>[], :clients=>[], :description=>"Grid Engine Qmaster Service", :uses=>{:tcp=>[6444], :udp=>[6444]}}, + :sge_execd=>{:aliases=>[], :clients=>[], :description=>"Grid Engine Execution Service", :uses=>{:tcp=>[6445], :udp=>[6445]}}, + :"mysql-proxy"=>{:aliases=>[], :clients=>[], :description=>"MySQL Proxy", :uses=>{:tcp=>[6446], :udp=>[6446]}}, + :"afs3-fileserver" => {:aliases => ["bbs"], :clients => [], :description => "file server itself", :uses => {:tcp => [7000], :udp => [7000]}}, + :"afs3-callback"=>{:aliases=>[], :clients=>[], :description=>"callbacks to cache managers", :uses=>{:tcp=>[7001], :udp=>[7001]}}, + :"afs3-prserver"=>{:aliases=>[], :clients=>[], :description=>"users & groups database", :uses=>{:tcp=>[7002], :udp=>[7002]}}, + :"afs3-vlserver"=>{:aliases=>[], :clients=>[], :description=>"volume location database", :uses=>{:tcp=>[7003], :udp=>[7003]}}, + :"afs3-kaserver"=>{:aliases=>[], :clients=>[], :description=>"AFS/Kerberos authentication", :uses=>{:tcp=>[7004], :udp=>[7004]}}, + :"afs3-volser"=>{:aliases=>[], :clients=>[], :description=>"volume managment server", :uses=>{:tcp=>[7005], :udp=>[7005]}}, + :"afs3-errors"=>{:aliases=>[], :clients=>[], :description=>"error interpretation service", :uses=>{:tcp=>[7006], :udp=>[7006]}}, + :"afs3-bos"=>{:aliases=>[], :clients=>[], :description=>"basic overseer process", :uses=>{:tcp=>[7007], :udp=>[7007]}}, + :"afs3-update"=>{:aliases=>[], :clients=>[], :description=>"server-to-server updater", :uses=>{:tcp=>[7008], :udp=>[7008]}}, + :"afs3-rmtsys"=>{:aliases=>[], :clients=>[], :description=>"remote cache manager service", :uses=>{:tcp=>[7009], :udp=>[7009]}}, + :"font-service"=>{:aliases=>[], :clients=>[], :description=>"X Font Service", :uses=>{:tcp=>[7100], :udp=>[7100]}}, + :"http-alt"=>{:aliases=>[], :clients=>[], :description=>"WWW caching service", :uses=>{:tcp=>[8080], :udp=>[8080]}}, + :"bacula-dir"=>{:aliases=>[], :clients=>[], :description=>"Bacula Director", :uses=>{:tcp=>[9101], :udp=>[9101]}}, + :"bacula-fd"=>{:aliases=>[], :clients=>[], :description=>"Bacula File Daemon", :uses=>{:tcp=>[9102], :udp=>[9102]}}, + :"bacula-sd"=>{:aliases=>[], :clients=>[], :description=>"Bacula Storage Daemon", :uses=>{:tcp=>[9103], :udp=>[9103]}}, + :xmms2=>{:aliases=>[], :clients=>[], :description=>"Cross-platform Music Multiplexing System", :uses=>{:tcp=>[9667], :udp=>[9667]}}, + :nbd=>{:aliases=>[], :clients=>[], :description=>"Linux Network Block Device", :uses=>{:tcp=>[10809]}}, + :"zabbix-agent"=>{:aliases=>[], :clients=>[], :description=>"Zabbix Agent", :uses=>{:tcp=>[10050], :udp=>[10050]}}, + :"zabbix-trapper"=>{:aliases=>[], :clients=>[], :description=>"Zabbix Trapper", :uses=>{:tcp=>[10051], :udp=>[10051]}}, + :amanda=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :uses=>{:tcp=>[10080], :udp=>[10080]}}, + :hkp=>{:aliases=>[], :clients=>[], :description=>"OpenPGP HTTP Keyserver", :uses=>{:tcp=>[11371], :udp=>[11371]}}, + :bprd=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :uses=>{:tcp=>[13720], :udp=>[13720]}}, + :bpdbm=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :uses=>{:tcp=>[13721], :udp=>[13721]}}, + :"bpjava-msvc"=>{:aliases=>[], :clients=>[], :description=>"BP Java MSVC Protocol", :uses=>{:tcp=>[13722], :udp=>[13722]}}, + :vnetd=>{:aliases=>[], :clients=>[], :description=>"Veritas Network Utility", :uses=>{:tcp=>[13724], :udp=>[13724]}}, + :bpcd=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :uses=>{:tcp=>[13782], :udp=>[13782]}}, + :vopied=>{:aliases=>[], :clients=>[], :description=>"VERITAS NetBackup", :uses=>{:tcp=>[13783], :udp=>[13783]}}, + :dcap=>{:aliases=>[], :clients=>[], :description=>"dCache Access Protocol", :uses=>{:tcp=>[22125]}}, + :gsidcap=>{:aliases=>[], :clients=>[], :description=>"GSI dCache Access Protocol", :uses=>{:tcp=>[22128]}}, + :wnn6=>{:aliases=>[], :clients=>[], :description=>"wnn6", :uses=>{:tcp=>[22273], :udp=>[22273]}}, + :rtmp=>{:aliases=>[], :clients=>[], :description=>"Routing Table Maintenance Protocol", :uses=>{:ddp=>[1]}}, + :nbp=>{:aliases=>[], :clients=>[], :description=>"Name Binding Protocol", :uses=>{:ddp=>[2]}}, + :zip=>{:aliases=>[], :clients=>[], :description=>"Zone Information Protocol", :uses=>{:ddp=>[6]}}, + :kerberos4=>{:aliases=>["kerberos-iv", "kdc"], :clients=>[], :description=>"Kerberos (server)", :uses=>{:udp=>[750], :tcp=>[750]}}, + :kerberos_master=>{:aliases=>[], :clients=>[], :description=>"Kerberos authentication", :uses=>{:udp=>[751], :tcp=>[751]}}, + :passwd_server=>{:aliases=>[], :clients=>[], :description=>"Kerberos passwd server", :uses=>{:udp=>[752]}}, + :krb_prop=>{:aliases=>["krb5_prop", "hprop"], :clients=>[], :description=>"Kerberos slave propagation", :uses=>{:tcp=>[754]}}, + :krbupdate=>{:aliases=>["kreg"], :clients=>[], :description=>"Kerberos registration", :uses=>{:tcp=>[760]}}, + :swat=>{:aliases=>[], :clients=>[], :description=>"swat", :uses=>{:tcp=>[901]}}, + :kpop=>{:aliases=>[], :clients=>[], :description=>"Pop with Kerberos", :uses=>{:tcp=>[1109]}}, + :knetd=>{:aliases=>[], :clients=>[], :description=>"Kerberos de-multiplexor", :uses=>{:tcp=>[2053]}}, + :"zephyr-srv"=>{:aliases=>[], :clients=>[], :description=>"Zephyr server", :uses=>{:udp=>[2102]}}, + :"zephyr-clt"=>{:aliases=>[], :clients=>[], :description=>"Zephyr serv-hm connection", :uses=>{:udp=>[2103]}}, + :"zephyr-hm"=>{:aliases=>[], :clients=>[], :description=>"Zephyr hostmanager", :uses=>{:udp=>[2104]}}, + :eklogin=>{:aliases=>[], :clients=>[], :description=>"Kerberos encrypted rlogin", :uses=>{:tcp=>[2105]}}, + :kx=>{:aliases=>[], :clients=>[], :description=>"X over Kerberos", :uses=>{:tcp=>[2111]}}, + :iprop=>{:aliases=>[], :clients=>[], :description=>"incremental propagation", :uses=>{:tcp=>[2121]}}, + :supfilesrv=>{:aliases=>[], :clients=>[], :description=>"SUP server", :uses=>{:tcp=>[871]}}, + :supfiledbg=>{:aliases=>[], :clients=>[], :description=>"SUP debugging", :uses=>{:tcp=>[1127]}}, + :linuxconf=>{:aliases=>[], :clients=>[], :description=>"LinuxConf", :uses=>{:tcp=>[98]}}, + :poppassd=>{:aliases=>[], :clients=>[], :description=>"Eudora", :uses=>{:tcp=>[106], :udp=>[106]}}, + :ssmtp=>{:aliases=>["smtps"], :clients=>[], :description=>"SMTP over SSL", :uses=>{:tcp=>[465]}}, + :moira_db=>{:aliases=>[], :clients=>[], :description=>"Moira database", :uses=>{:tcp=>[775]}}, + :moira_update=>{:aliases=>[], :clients=>[], :description=>"Moira update protocol", :uses=>{:tcp=>[777]}}, + :moira_ureg=>{:aliases=>[], :clients=>[], :description=>"Moira user registration", :uses=>{:udp=>[779]}}, + :spamd=>{:aliases=>[], :clients=>[], :description=>"spamassassin daemon", :uses=>{:tcp=>[783]}}, + :omirr=>{:aliases=>["omirrd"], :clients=>[], :description=>"online mirror", :uses=>{:tcp=>[808], :udp=>[808]}}, + :customs=>{:aliases=>[], :clients=>[], :description=>"pmake customs server", :uses=>{:tcp=>[1001], :udp=>[1001]}}, + :skkserv=>{:aliases=>[], :clients=>[], :description=>"skk jisho server port", :uses=>{:tcp=>[1178]}}, + :predict=>{:aliases=>[], :clients=>[], :description=>"predict -- satellite tracking", :uses=>{:udp=>[1210]}}, + :rmtcfg=>{:aliases=>[], :clients=>[], :description=>"Gracilis Packeten remote config server", :uses=>{:tcp=>[1236]}}, + :wipld=>{:aliases=>[], :clients=>[], :description=>"Wipl network monitor", :uses=>{:tcp=>[1300]}}, + :xtel=>{:aliases=>[], :clients=>[], :description=>"french minitel", :uses=>{:tcp=>[1313]}}, + :xtelw=>{:aliases=>[], :clients=>[], :description=>"french minitel", :uses=>{:tcp=>[1314]}}, + :support=>{:aliases=>[], :clients=>[], :description=>"GNATS", :uses=>{:tcp=>[1529]}}, + :cfinger=>{:aliases=>[], :clients=>[], :description=>"GNU Finger", :uses=>{:tcp=>[2003]}}, + :frox=>{:aliases=>[], :clients=>[], :description=>"frox: caching ftp proxy", :uses=>{:tcp=>[2121]}}, + :ninstall=>{:aliases=>[], :clients=>[], :description=>"ninstall service", :uses=>{:tcp=>[2150], :udp=>[2150]}}, + :zebrasrv=>{:aliases=>[], :clients=>[], :description=>"zebra service", :uses=>{:tcp=>[2600]}}, + :zebra=>{:aliases=>[], :clients=>[], :description=>"zebra vty", :uses=>{:tcp=>[2601]}}, + :ripd=>{:aliases=>[], :clients=>[], :description=>"ripd vty (zebra)", :uses=>{:tcp=>[2602]}}, + :ripngd=>{:aliases=>[], :clients=>[], :description=>"ripngd vty (zebra)", :uses=>{:tcp=>[2603]}}, + :ospfd=>{:aliases=>[], :clients=>[], :description=>"ospfd vty (zebra)", :uses=>{:tcp=>[2604]}}, + :bgpd=>{:aliases=>[], :clients=>[], :description=>"bgpd vty (zebra)", :uses=>{:tcp=>[2605]}}, + :ospf6d=>{:aliases=>[], :clients=>[], :description=>"ospf6d vty (zebra)", :uses=>{:tcp=>[2606]}}, + :ospfapi=>{:aliases=>[], :clients=>[], :description=>"OSPF-API", :uses=>{:tcp=>[2607]}}, + :isisd=>{:aliases=>[], :clients=>[], :description=>"ISISd vty (zebra)", :uses=>{:tcp=>[2608]}}, + :afbackup=>{:aliases=>[], :clients=>[], :description=>"Afbackup system", :uses=>{:tcp=>[2988], :udp=>[2988]}}, + :afmbackup=>{:aliases=>[], :clients=>[], :description=>"Afmbackup system", :uses=>{:tcp=>[2989], :udp=>[2989]}}, + :xtell=>{:aliases=>[], :clients=>[], :description=>"xtell server", :uses=>{:tcp=>[4224]}}, + :fax=>{:aliases=>[], :clients=>[], :description=>"FAX transmission service (old)", :uses=>{:tcp=>[4557]}}, + :hylafax=>{:aliases=>[], :clients=>[], :description=>"HylaFAX client-server protocol (new)", :uses=>{:tcp=>[4559]}}, + :distmp3=>{:aliases=>[], :clients=>[], :description=>"distmp3host daemon", :uses=>{:tcp=>[4600]}}, + :munin=>{:aliases=>["lrrd"], :clients=>[], :description=>"Munin", :uses=>{:tcp=>[4949]}}, + :"enbd-cstatd"=>{:aliases=>[], :clients=>[], :description=>"ENBD client statd", :uses=>{:tcp=>[5051]}}, + :"enbd-sstatd"=>{:aliases=>[], :clients=>[], :description=>"ENBD server statd", :uses=>{:tcp=>[5052]}}, + :pcrd=>{:aliases=>[], :clients=>[], :description=>"PCR-1000 Daemon", :uses=>{:tcp=>[5151]}}, + :noclog=>{:aliases=>[], :clients=>[], :description=>"noclogd with TCP (nocol)", :uses=>{:tcp=>[5354], :udp=>[5354]}}, + :hostmon=>{:aliases=>[], :clients=>[], :description=>"hostmon uses TCP (nocol)", :uses=>{:tcp=>[5355], :udp=>[5355]}}, + :rplay=>{:aliases=>[], :clients=>[], :description=>"RPlay audio service", :uses=>{:udp=>[5555]}}, + :nrpe=>{:aliases=>[], :clients=>[], :description=>"Nagios Remote Plugin Executor", :uses=>{:tcp=>[5666]}}, + :nsca=>{:aliases=>[], :clients=>[], :description=>"Nagios Agent - NSCA", :uses=>{:tcp=>[5667]}}, + :mrtd=>{:aliases=>[], :clients=>[], :description=>"MRT Routing Daemon", :uses=>{:tcp=>[5674]}}, + :bgpsim=>{:aliases=>[], :clients=>[], :description=>"MRT Routing Simulator", :uses=>{:tcp=>[5675]}}, + :canna=>{:aliases=>[], :clients=>[], :description=>"cannaserver", :uses=>{:tcp=>[5680]}}, + :"sane-port"=>{:aliases=>[], :clients=>[], :description=>"SANE network scanner daemon", :uses=>{:tcp=>[6566]}}, + :ircd=>{:aliases=>[], :clients=>[], :description=>"Internet Relay Chat", :uses=>{:tcp=>[6667]}}, + :"zope-ftp"=>{:aliases=>[], :clients=>[], :description=>"zope management by ftp", :uses=>{:tcp=>[8021]}}, + :tproxy=>{:aliases=>[], :clients=>[], :description=>"Transparent Proxy", :uses=>{:tcp=>[8081]}}, + :omniorb=>{:aliases=>[], :clients=>[], :description=>"OmniORB", :uses=>{:tcp=>[8088], :udp=>[8088]}}, + :xinetd=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[9098]}}, + :mandelspawn=>{:aliases=>[], :clients=>[], :description=>"network mandelbrot", :uses=>{:udp=>[9359]}}, + :git=>{:aliases=>[], :clients=>[], :description=>"Git Version Control System", :uses=>{:tcp=>[9418]}}, + :zope=>{:aliases=>[], :clients=>[], :description=>"zope server", :uses=>{:tcp=>[9673]}}, + :webmin=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:tcp=>[10000]}}, + :kamanda=>{:aliases=>[], :clients=>[], :description=>"amanda backup services (Kerberos)", :uses=>{:tcp=>[10081], :udp=>[10081]}}, + :amandaidx=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :uses=>{:tcp=>[10082]}}, + :amidxtape=>{:aliases=>[], :clients=>[], :description=>"amanda backup services", :uses=>{:tcp=>[10083]}}, + :smsqp=>{:aliases=>[], :clients=>[], :description=>"Alamin SMS gateway", :uses=>{:tcp=>[11201], :udp=>[11201]}}, + :xpilot=>{:aliases=>[], :clients=>[], :description=>"XPilot Contact Port", :uses=>{:tcp=>[15345], :udp=>[15345]}}, + :"sgi-cmsd"=>{:aliases=>[], :clients=>[], :description=>"Cluster membership services daemon", :uses=>{:udp=>[17001]}}, + :"sgi-crsd"=>{:aliases=>[], :clients=>[], :description=>"", :uses=>{:udp=>[17002]}}, + :"sgi-gcd"=>{:aliases=>[], :clients=>[], :description=>"SGI Group membership daemon", :uses=>{:udp=>[17003]}}, + :"sgi-cad"=>{:aliases=>[], :clients=>[], :description=>"Cluster Admin daemon", :uses=>{:tcp=>[17004]}}, + :isdnlog=>{:aliases=>[], :clients=>[], :description=>"isdn logging system", :uses=>{:tcp=>[20011], :udp=>[20011]}}, + :vboxd=>{:aliases=>[], :clients=>[], :description=>"voice box system", :uses=>{:tcp=>[20012], :udp=>[20012]}}, + :binkp=>{:aliases=>[], :clients=>[], :description=>"binkp fidonet protocol", :uses=>{:tcp=>[24554]}}, + :asp=>{:aliases=>[], :clients=>[], :description=>"Address Search Protocol", :uses=>{:tcp=>[27374], :udp=>[27374]}}, + :csync2=>{:aliases=>[], :clients=>[], :description=>"cluster synchronization tool", :uses=>{:tcp=>[30865]}}, + :dircproxy=>{:aliases=>[], :clients=>[], :description=>"Detachable IRC Proxy", :uses=>{:tcp=>[57000]}}, + :tfido=>{:aliases=>[], :clients=>[], :description=>"fidonet EMSI over telnet", :uses=>{:tcp=>[60177]}}, + :fido=>{:aliases=>[], :clients=>[], :description=>"fidonet EMSI over TCP", :uses=>{:tcp=>[60179]}} + } + # + # + # ### end of SYSTEM detail initialization + end + # + def gxg_root() + @thread_safety.synchronize { @details[:gxg_root].to_s } + end + # + def gxg_root=(the_path=nil) + if the_path.is_a?(::String) + @thread_safety.synchronize { @details[:gxg_root] = the_path } + end + end + # + def server_paths() + result = {} + # Define Server directories + gxg_root_dir = ::GxG::SYSTEM.gxg_root() + public_dir = File.expand_path("./Public",gxg_root_dir) + services_dir = File.expand_path("./Services",gxg_root_dir) + app_dir = File.expand_path("./Applications",gxg_root_dir) + system_dir = File.expand_path("./System",gxg_root_dir) + sys_config_dir = "#{system_dir}/Configuration" + sys_db_dir = "#{system_dir}/Databases" + sys_ext_dir = "#{system_dir}/Extensions" + sys_gem_dir = "#{system_dir}/Gems" + sys_lib_dir = "#{system_dir}/Libraries" + tmp_dir = "#{system_dir}/Temporary" + log_dir = "#{system_dir}/Logs" + result = {:root => gxg_root_dir, :system => system_dir, :services => services_dir, :temporary => tmp_dir, :logs => log_dir, :applications => app_dir, :users => nil, :public => public_dir, :configuration => sys_config_dir, :databases => sys_db_dir, :extensions => sys_ext_dir, :gems => sys_gem_dir, :libraries => sys_lib_dir} + result + end + # + def application_paths(application_name=nil) + result = {} + if application_name.is_a?(::String) + result = {:file_system => "/", :user => ::ENV['HOME']} + case GxG::SYSTEM.platform[:platform] + when :windows + result[:file_system] = (ENV["HOMEDRIVE"] + "/") + result[:user] = File.join(ENV["HOME"].split("\\")) + result[:shared] = File.join(ENV["PUBLIC"].split("\\")) + result[:temporary] = File.join(ENV["TEMP"].split("\\")) + result[:application_data] = (File.join(ENV["LOCALAPPDATA"].split("\\")) + "/GxG/" + application_name) + result[:preferences] = (result[:application_data] + "/Preferences") + result[:logs] = (result[:application_data] + "/Logs") + result[:projects] = (result[:user] + "/GxG") + result[:fonts] = (result[:projects] + "/Fonts") + result[:desktop] = (result[:user] + "/Desktop") + result[:documents] = (result[:user] + "/Documents") + result[:downloads] = (result[:user] + "/Downloads") + result[:audio] = (result[:user] + "/Music") + result[:images] = (result[:user] + "/Pictures") + result[:video] = (result[:user] + "/Videos") + when :linux + case GxG::SYSTEM.environment[:environment] + when :ubuntu + result[:user] = ENV['HOME'] + result[:shared] = "/tmp" + result[:temporary] = "/tmp" + result[:application_data] = (result[:user] + "/.local/share/GxG/" + application_name) + result[:preferences] = (result[:user] + "/.config/GxG/" + application_name) + result[:logs] = (result[:application_data] + "/Logs") + result[:projects] = (result[:user] + "/GxG") + result[:fonts] = (result[:user] + "/.fonts") + result[:desktop] = (result[:user] + "/Desktop") + result[:documents] = (result[:user] + "/Documents") + result[:downloads] = (result[:user] + "/Downloads") + result[:audio] = (result[:user] + "/Music") + result[:images] = (result[:user] + "/Pictures") + result[:video] = (result[:user] + "/Videos") + when :redhat + end + when :darwin + case GxG::SYSTEM.environment[:environment] + when :darwin + when :macos + result[:user] = ENV['HOME'] + result[:shared] = "/Users/Shared" + result[:temporary] = "/tmp" + result[:application_data] = (result[:user] + "/Library/Application Support/GxG/" + application_name) + result[:preferences] = (result[:user] + "/Library/Preferences/GxG/" + application_name) + result[:logs] = (result[:user] + "/Library/Logs/GxG/" + application_name) + result[:projects] = (result[:user] + "/GxG") + result[:fonts] = (result[:user] + "/Library/Fonts") + result[:desktop] = (result[:user] + "/Desktop") + result[:documents] = (result[:user] + "/Documents") + result[:downloads] = (result[:user] + "/Downloads") + result[:audio] = (result[:user] + "/Music") + result[:images] = (result[:user] + "/Pictures") + result[:video] = (result[:user] + "/Movies") + end + end + end + result + end + # + def gxg_cpu_add(cpu_record=nil) + # Review : part of a monkey-patch solution to extremely long data-fetch times for using wmic for cpu data + if cpu_record.is_a?(::Hash) + @thread_safety.synchronize { @details[:processors] << cpu_record } + end + end + # + def detail_mapping() + # Maps universal system data keys to @details hash paths (cached data from system) + # TCP tuning: http://www.psc.edu/networking/projects/tcptune/#tutorial + # + result = [] + # + @thread_safety.synchronize { + if @details[:network][:protocols][:ipv4] + result << {:key => "net.ipv4.tcp.autotune", :path => "/:network/:protocols/:ipv4/:settings/:tcp/:autotune"} + result << {:key => "net.ipv4.tcp.timestamps", :path => "/:network/:protocols/:ipv4/:settings/:tcp/:timestamps"} + result << {:key => "net.ipv4.tcp.window_scaling", :path => "/:network/:protocols/:ipv4/:settings/:tcp/:window_scaling"} + result << {:key => "net.ipv4.tcp.selective_ack", :path => "/:network/:protocols/:ipv4/:settings/:tcp/:selective_ack"} + end + } + result << {:key => "net.ipv4.tcp.read", :path => "/:memory_limits/:buffers/:ipv4/:tcp/:read"} + result << {:key => "net.ipv4.tcp.write", :path => "/:memory_limits/:buffers/:ipv4/:tcp/:write"} + result << {:key => "net.socket.ipc.read", :path => "/:memory_limits/:buffers/:socket/:ipc/:read"} + result << {:key => "net.socket.ipc.write", :path => "/:memory_limits/:buffers/:socket/:ipc/:write"} + result << {:key => "net.ipv4.udp.read", :path => "/:memory_limits/:buffers/:ipv4/:udp/:read"} + result << {:key => "net.ipv4.udp.write", :path => "/:memory_limits/:buffers/:ipv4/:udp/:write"} + result << {:key => "configuration.process.maximum_file_descriptors", :path => "/:platform_configuration/:process/:maximum_file_descriptors"} + # + result + end + private :detail_mapping + # + def system_data_keys(other_platform=nil) + # Lists known techniques per-universal-key for gathering system data (not necessarily cached) + # Linux getconf : http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Frzahz%2Frzahzgetconf.htm + # Linux configuration files : http://www.ibm.com/developerworks/linux/library/l-config/index.html + # + result = [] + data = [] + # + data << {:key => "net.ipv4.tcp.autotune", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_moderate_rcvbuf", :format => :zerofalse}} + # RFC1323 extensions support + data << {:key => "net.ipv4.tcp.timestamps", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_timestamps", :format => :zerofalse}} + data << {:key => "net.ipv4.tcp.window_scaling", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_window_scaling", :format => :zerofalse}} + # RFC2108 extension support + data << {:key => "net.ipv4.tcp.selective_ack", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_sack", :format => :zerofalse}} + data << {:key => "net.ipv4.tcp.read", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_rmem", :format => :valid_with_initial}} + data << {:key => "net.ipv4.tcp.write", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_wmem", :format => :valid_with_initial}} + # net.ipv4.tcp.(rmem/wmem) sets the buffer sizing for unix domains and non-tcp sockets + data << {:key => "net.socket.ipc.read", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_rmem", :format => :valid_with_initial}} + data << {:key => "net.socket.ipc.write", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.tcp_wmem", :format => :valid_with_initial}} + # LATER: confirm udp read and write mem is symmetric. + data << {:key => "net.ipv4.udp.read", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.udp_mem", :format => :valid_with_initial}} + data << {:key => "net.ipv4.udp.write", :platforms => [:linux], :operation => {:sysctl => "net.ipv4.udp_mem", :format => :valid_with_initial}} + # TODO: Research sysctl keys for Path MTU Discovery (RFC1191, RFC1981, RFC4821) + # System-wide maximum threads available on this system: (per-process on Windows) + data << {:key => "kernel.threads_max", :platforms => [:windows], :operation => {:literal => "2000", :format => :to_int}} + data << {:key => "kernel.threads_max", :platforms => [:linux], :operation => {:sysctl => "kernel.threads-max", :format => :to_int}} + # Review : FORNOW using kern.maxproc but that's not right --> research actual method. + data << {:key => "kernel.threads_max", :platforms => [:bsd, :darwin], :operation => {:sysctl => "kern.maxproc", :format => :to_int}} + # # + # getconf variable meanings: + # Attribution: http://www.lainoox.com/system-wide-config-getconf/ + #ARG_MAX Maximum length, in bytes, of the arguments for one of the exec subroutines, including environment data. + #CHAR_BIT Number of bits in a type character. + #CHAR_MAX Maximum value of a type character. + #CHAR_MIN Minimum value of a type character. + #CHILD_MAX Maximum number of simultaneous processes for each real user ID. + #CLK_TCK Number of clock ticks per second returned by the time subroutine. + #INT_MAX Maximum value of a type int. + #INT_MIN Minimum value of a type int. + #LONG_BIT Number of bits in a type long int. + #LONG_MAX Maximum value of a type long int. + #LONG_MIN Minimum value of a type long int. + #MB_LEN_MAX Maximum number of bytes in a character for any supported locale. + #NGROUPS_MAX Maximum number of simultaneous supplementary group IDs for each process. + #NL_ARGMAX Maximum value of digit in calls to the printf and scanf subroutines. + #NL_LANGMAX Maximum number of bytes in a LANG name. + #NL_MSGMAX Maximum message number. + #NL_NMAX Maximum number of bytes in an N-to-1 collation mapping. + #NL_SETMAX Maximum set number. + #NL_TEXTMAX Maximum number of bytes in a message string. + #NZERO Default process priority. + #OPEN_MAX Maximum number of files that one process can have open at one time. + #SCHAR_MAX Maximum value of a type signed char. + #SCHAR_MIN Minimum value of a type signed char. + #SHRT_MAX Maximum value of a type short. + #SHRT_MIN Minimum value of a type short. + #SSIZE_MAX Maximum value of an object of type ssize_t. + #TZNAME_MAX Maximum number of bytes supported for the name of a time zone (not the length of the TZ environment variable). + #UCHAR_MAX Maximum value of a type unsigned char. + #UINT_MAX Maximum value of a type unsigned int. + #ULONG_MAX Maximum value of a type unsigned long int. + #USHRT_MAX Maximum value of a type unsigned short int. + #WORD_BIT Number of bits in a word or type int. + data << {:key => "configuration.process.maximum_file_descriptors", :platforms => [:linux], :operation => {:getconf => "OPEN_MAX", :format => :to_int}} + # TODO: Research maximum limit under epoll/kqueue: 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. + # 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 || @details[:platform])) + result << entry + end + end + # + result + end + private :system_data_keys + # + def get_system_data(keys=[]) + # Support for .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_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 + # + reference = system_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 :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 + end + begin + 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][:literal] + data = the_formatter.call(entry[:operation][:literal]) + end + end + end + rescue Exception + # + 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 => , :path => } + # if hash_path supplied, read the data; set the @details hash element, and return the formatted data + if specifier[:key] + # {:key => , :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 + private :get_system_data + # + def set_system_data(keys=[]) + # LATER: 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 + get_system_data(nil,nil) + # compare hash value to the_value; return true if successful + end + result + end + private :set_system_data + # + def keys() + result = [] + reference = detail_mapping() + reference.to_enum.each do |mapping| + result << mapping[:key] + end + result + end + # + def [](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 = 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 architecture() + @thread_safety.synchronize { + {:architecture => @details[:architecture], :processors => @details[:processors]}.clone + } + end + # + def platform() + @thread_safety.synchronize { + {:platform => @details[:platform], :version => @details[:platform_version], :configuration => @details[:platform_configuration]}.clone + } + end + # + def environment() + @thread_safety.synchronize { + {:environment => @details[:environment], :version => @details[:environment_version], :variant => @details[:environment_variant]}.clone + } + end + # + def engine() + # ??? just info ??? GxG::Engine::profile() + ::GxG::Engine + end + # + def network() + @thread_safety.synchronize { + @details[:network].clone + } + end + # + def load_average() + # this or cpuload ?? + this_platform = @details[:platform] + case this_platform + when :linux + result = (`uptime`.split(":")[-1].chomp.split(",")) + result.to_enum(:each_index).each do |indexer| + result[(indexer)] = result[(indexer)].to_f + end + else + result = [] + end + result + end + # + def cpuload + # this or load_average ?? + average = Sys::CPU.load_avg + { :minute1 => average[0], :minute5 => average[1], :minute15 => average[2] } + end + # + def valid_modes() + result = {} + result.merge(GxG::IO::IO::valid_modes()) + @thread_safety.synchronize { + result.merge(@valid_modes) + } + result + end + def memory_limits() + # See: http://linux-mm.org/LinuxMM + # http://linux.die.net/man/2/mlock + # https://answers.launchpad.net/graphite/+question/191807 + # + @thread_safety.synchronize { + @details[:memory_limits].clone + } + end + def maximum_buffer_size() + self.memory_limits()[:buffers] + end + # + def memory() + result = {} + this_platform = nil + @thread_safety.synchronize { this_platform = @details[:platform] } + case this_platform + # TODO: GxG::SYSTEM.memory : research free mem discovery for Winderz and BSD + when :windows + # No known way to get this info. MEM is dosbox only style memory counting. :P + # Might have to bind to a C or C++ lib for this kind of data (PITA) + # FORNOW: just pretend upper theoretical limit on a process is all you have (sucks) + # mem_limit = ::GxG::SYSTEM.memory_limits()[:process].max + # Total Physical Memory: 8,192 MB + # Available Physical Memory: 5,053 MB + # Virtual Memory: Max Size: 9,472 MB + # Virtual Memory: Available: 4,932 MB + # Virtual Memory: In Use: 4,540 MB + raw_system_info = `systeminfo /FO LIST` + data = {:mem_limit => nil, :mem_free => nil, :vmem_limit => nil, :vmem_used => nil, :vmem_free => nil} + raw_system_info.each_line do |the_line| + if the_line.include?("Total Physical Memory") + data[:mem_limit] = the_line.gsub(",","").numeric_values()[:byte] + end + if the_line.include?("Available Physical Memory") + data[:mem_free] = the_line.gsub(",","").numeric_values()[:byte] + end + if the_line.include?("Virtual Memory: Max Size") + data[:vmem_limit] = the_line.gsub(",","").numeric_values()[:byte] + end + if the_line.include?("Virtual Memory: In Use") + data[:vmem_used] = the_line.gsub(",","").numeric_values()[:byte] + end + if the_line.include?("Virtual Memory: Available") + data[:vmem_free] = the_line.gsub(",","").numeric_values()[:byte] + end + end + result[:actual] = {:total => data[:mem_limit].to_i, :used => (data[:mem_limit].to_i - data[:mem_free].to_i), :free => data[:mem_free].to_i, :shared => 0, :buffers => 0, :cached => 0} + result[:buffers] = {:used => 0, :free => 0} + result[:virtual] = {:total => data[:vmem_limit].to_i, :used => data[:vmem_used].to_i, :free => data[:vmem_free].to_i} + when :bsd, :darwin, :macos + # Inspired by: http://www.cyberciti.biz/faq/freebsd-command-to-get-ram-information/ by Ralf S. Engelschall + # ported to Ruby: + env = self.environment() + case env[:environment] + when :freebsd, :dragonfly, :pcbsd + # TODO: GxG::SYSTEM.memory() : test under freebsd, dragonfly and pcbsd + page_size = (`sysctl hw.pagesize`.to_s.split(":")[1].numeric_values()[:integer] || 0) + mem_phys = (`sysctl hw.physmem`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_all = (`sysctl vm.stats.vm.v_page_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_wire = (`sysctl vm.stats.vm.v_wire_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_active = (`sysctl vm.stats.vm.v_active_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_inactive = (`sysctl vm.stats.vm.v_inactive_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_cache = (`sysctl vm.stats.vm.v_cache_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_free = (`sysctl vm.stats.vm.v_free_count`.to_s.split(":")[1].numeric_values()[:integer] || 0) * page_size + mem_vm_total = (`sysctl vm.swap_total`.to_s.split(":")[1].numeric_values()[:integer] || 0) + mem_vm_used = (`pstat -s`.to_s.split(" ")[2].numeric_values()[:integer] || 0) + when :netbsd + # TODO: GxG::SYSTEM.memory() : test under netbsd + page_size = 0 + mem_phys = 0 + mem_all = 0 + mem_wire = 0 + mem_active = 0 + mem_inactive = 0 + mem_cache = 0 + mem_free = 0 + mem_vm_total = 0 + mem_vm_used = 0 + # + raw_data = `vmstat -s` + raw_data.to_enum(:each_line).each do |the_line| + if the_line.include?("bytes per page") + page_size = (the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) + break + end + end + if page_size > 0 + raw_data.to_enum(:each_line).each do |the_line| + if the_line.include?("pages managed") + mem_phys = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + mem_all = mem_phys + end + if the_line.include?("pages wired") + mem_wire = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + if the_line.include?("pages active") + mem_active = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + if the_line.include?("pages inactive") + mem_inactive = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + if the_line.include?("pages free") + mem_free = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + if the_line.include?("swap pages in use") + mem_vm_used = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + if the_line.split(" ")[(1..-1)].join(" ") == "swap pages" + mem_vm_total = ((the_line.split(" ")[0].to_s.numeric_values()[:integer] || 0) * page_size) + end + end + end + when :darwin, :macos + # TODO: GxG::SYSTEM.memory() : test under darwin + raw_data = `top -l 1` + page_size = (`sysctl hw.pagesize`.to_s.split(":")[1].numeric_values()[:integer] || 0) + # gives a byte-count, not a page count : hw.physmem + mem_phys = (`sysctl hw.physmem`.to_s.split(":")[1].numeric_values()[:integer] || 0) + mem_all = mem_phys + # virtual memory usage: + raw_data = (`sysctl vm.swapusage`.to_s.split(":")[1].split(" ")) + mem_vm_total = ((raw_data[5].to_s.numeric_values()[:float] || 0.0) * (1024**2)).to_i + mem_vm_used = ((raw_data[2].to_s.numeric_values()[:float] || 0.0) * (1024**2)).to_i + #SharedLibs: num = 146, resident = 22.9M code, 3.40M data, 8.36M LinkEdit + #MemRegions: num = 7213, resident = 344M + 4.92M private, 22.5M shared + #PhysMem: 59.1M wired, 285M active, 138M inactive, 483M used, 28.3M free + raw_data.to_enum(:each_line).each do |the_line| + if the_line.include?("PhysMem:") + data = the_line.split(":")[1].to_s.split(",") + data.to_enum(:each).each do |the_param| + numeric_string = the_param.split(" ")[0] + if numeric_string.include?(".") + numeric_string.gsub!("M","") + else + numeric_string.gsub!("M",".0") + end + case the_param.split(" ")[1] + when /wired/ + mem_wire = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i + when /active/ + mem_active = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i + when /inactive/ + mem_inactive = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i + when /free/ + mem_free = ((numeric_string.numeric_values()[:float] || 0.0) * (1024**2)).to_i + end + end + break + end + end + else + # default BSD behavior ??? Seems like everyone does their own special thing. + end + # + chip_size = 1 + chip_guess = ((mem_phys / 8) - 1) + while (chip_guess != 0) + chip_guess >>= 1 + chip_size <<= 1 + end + mem_hw = (((mem_phys / chip_size).to_i + 1) * chip_size) + # + mem_gap_vm = (mem_all - (mem_wire + mem_active + mem_inactive + mem_cache + mem_free)) + mem_gap_sys = (mem_phys - mem_all) + mem_gap_hw = (mem_hw - mem_phys) + # + mem_available = (mem_inactive + mem_cache + mem_free) + mem_used = (mem_hw - mem_available) + # format result: + result[:actual] = {:total => mem_hw, :used => mem_used, :free => mem_available, :shared => 0, :buffers => 0, :cached => 0} + result[:buffers] = {:used => 0, :free => 0} + result[:virtual] = {:total => mem_vm_total, :used => mem_vm_used, :free => (mem_vm_total - mem_vm_used)} + # + when :linux, :solaris + # total used free shared buffers cached + raw_text = `free -b` + raw_text.to_enum(:each_line).each do |text_line| + if text_line.include?("Mem:") + stats = text_line.split(" ") + result[:actual] = {:total => stats[1].to_i, :used => stats[2].to_i, :free => stats[3].to_i, :shared => stats[4].to_i, :buffers => stats[5].to_i, :cached => stats[6].to_i} + end + if text_line.include?("-/+ buffers/cache:") + stats = text_line.split(" ") + result[:buffers] = {:used => stats[2].to_i, :free => stats[3].to_i} + end + if text_line.include?("Swap:") + stats = text_line.split(" ") + result[:virtual] = {:total => stats[1].to_i, :used => stats[2].to_i, :free => stats[3].to_i} + end + end + end + result.process! do |entry,selector,container| + if entry.is_a?(::String) + item = entry.numeric_values() + if item.is_a?(::Hash) + if item[:integer] + container[(selector)] = item[:integer].to_i + end + if item[:float] + container[(selector)] = item[:float].to_f + end + end + if item.is_a?(::Array) + item.each do |the_data| + if the_data.is_a?(::Hash) + if the_data[:integer] + container[(selector)] = the_data[:integer].to_i + end + if the_data[:float] + container[(selector)] = the_data[:float].to_f + end + end + if the_data.is_a?(::Array) + data = the_data[0] + if data.is_a?(::Hash) + if data[:integer] + container[(selector)] = data[:integer].to_i + end + if data[:float] + container[(selector)] = data[:float].to_f + end + break + end + end + end + end + end + nil + end + result[:total] = {:total => (result[:actual][:total].to_i + result[:virtual][:total].to_i), :used => (result[:actual][:used].to_i + result[:virtual][:used].to_i), :free => (result[:actual][:free].to_i + result[:virtual][:free].to_i)} + result + end + # + def processes(params={}) + results = [] + columns = [:cmdline, :cwd, :environ, :exe, :fd, :root, :pid, :comm, :state, :ppid, :pgrp, :session, :tty_nr, :tpgid, + :flags, :minflt, :cminflt, :majflt, :cmajflt, :utime, :stime, :cutime, :cstime, :priority, :nice, :itrealvalue, :starttime, + :vsize, :rss, :rlim, :startcode, :endcode, :startstack, :kstkesp, :kstkeip, :signal, :blocked, :sigignore, :sigcatch, :wchan, + :nswap, :cnswap, :exit_signal, :processor, :rt_priority, :policy, :name, :uid, :euid, :gid, :egid, :pctcpu, :pctmem] + include_columns = [] + if params[:columns].respond_to?(:each) + params[:columns].to_enum.each do |entry| + if columns.include?(entry.to_s.to_sym) + include_columns << entry.to_s.to_sym + end + end + else + include_columns = columns + end + params.delete(:columns) + # + search_for = {} + if params.respond_to?(:each_pair) + params.each_pair do |key,entry| + if columns.include?(key.to_s.to_sym) + search_for[(key.to_s.to_sym)] = entry + end + end + end + # + if (params[:pid]) + raw_data = (Sys::ProcTable.ps(params[:pid]) || []) + else + raw_data = (Sys::ProcTable.ps() || []) + end + raw_data.to_enum.each do |entry| + new_entry = {} + process = true + if search_for.keys.size > 0 + search_for.each_pair do |search_key,search_value| + unless (entry[(search_key)] == search_value) + process = false + end + end + end + if process + entry.each_pair do |key,item| + if include_columns.include?(key.to_s.to_sym) + new_entry[(key.to_sym)] = item + end + end + end + if new_entry.keys.size > 0 + results << new_entry + end + end + # + results + end + # + def current_user() + ::ENV["USER"] + end + # + def network_status() + # LATER: find out how to get the PID for AF_UNIX output of bsd-netstat -f unix + # goal: lsof given a path, find the PID associated + # lsof -Rt -t path (have to have read permissions) + # OK, I give up (F-U bsd guys for not having a PID field in netstat unix output :P ) + # no unix socket info until I can figure a hooley-hoop set to jump through on this -- F'n Dorks! + data = [] + local_data = [] + case ::GxG::SYSTEM.platform()[:platform] + when :bsd + predata = [] + raw_data = `netstat -an` + raw_data.to_enum(:each_line).each do |the_line| + ["tcp4 ", "tcp6 ", "udp4 ", "udp6 "].to_enum(:each).each do |entry| + if the_line.include?(entry) + predata << the_line.split(" ") + break + end + end + # + end + predata.to_enum(:each).each do |the_entry| + new_entry = {} + addressing = nil + case the_entry[0].downcase + when "tcp4", "tcp" + addressing = {:family => ::Socket::AF_INET, :protocol => :tcp} + when "tcp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :tcp6} + when "udp4", "udp" + addressing = {:family => ::Socket::AF_INET, :protocol => :udp} + when "udp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :udp6} + end + if addressing + new_entry[:protocol] = addressing[:protocol] + if addressing[:family] == ::Socket::AF_INET6 + chunks = the_entry[3].split(".") + unless chunks[1] == "*" + chunks[1] = chunks[1].to_i + end + else + if the_entry[3].include?("*") + chunks = the_entry[3].split(".") + if chunks[0] == "*" + chunks = ["0.0.0.0",(the_entry[3].split(".").last)] + else + chunks = [(the_entry[3].split(".")[0..3].join(".")),(the_entry[3].split(".")[4])] + end + unless chunks[1] == "*" + chunks[1] = chunks[1].to_i + end + else + chunks = [(the_entry[3].split(".")[0..3].join(".")),(the_entry[3].split(".")[4].to_i)] + end + end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # ## + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:local_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:local_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + # + if addressing[:family] == ::Socket::AF_INET6 + chunks = the_entry[4].split(".") + unless chunks[1] == "*" + chunks[1] = chunks[1].to_i + end + else + if the_entry[4].include?("*") + chunks = the_entry[4].split(".") + if chunks[0] == "*" + chunks = ["0.0.0.0",(the_entry[4].split(".").last)] + else + chunks = [(the_entry[4].split(".")[0..3].join(".")),(the_entry[4].split(".")[4])] + end + unless chunks[1] == "*" + chunks[1] = chunks[1].to_i + end + else + chunks = [(the_entry[4].split(".")[0..3].join(".")),(the_entry[4].split(".")[4].to_i)] + end + end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # ## + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:foreign_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:foreign_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + # + if the_entry[5].to_s.size > 0 + new_entry[:status] = the_entry[5].to_sym + else + new_entry[:status] = nil + end + # + data << new_entry + end + # + end + when :linux + predata = [] + raw_data = `netstat -an` + raw_data.to_enum(:each_line).each do |the_line| + ["tcp ", "tcp6 ", "udp ", "udp6 "].to_enum(:each).each do |entry| + if the_line.include?(entry) + predata << the_line.split(" ") + break + end + end + # + end + predata.to_enum(:each).each do |the_entry| + the_error = nil + new_entry = {} + addressing = nil + case the_entry[0].downcase + when "tcp" + addressing = {:family => ::Socket::AF_INET, :protocol => :tcp} + when "tcp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :tcp6} + when "udp" + addressing = {:family => ::Socket::AF_INET, :protocol => :udp} + when "udp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :udp6} + end + if addressing + new_entry[:protocol] = addressing[:protocol] + if addressing[:family] == ::Socket::AF_INET6 + chunks = the_entry[3].split(":") + unless chunks[-1] == "*" + chunks[-1] = chunks[-1].to_i + end + chunks = [(chunks[0..-2]).join(":"),(chunks.last)] + else + # Odd bug work-around + addr = the_entry[3].split(":")[0] + port = the_entry[3].split(":")[1] + unless port == "*" + port = port.to_i + end + chunks = [(addr.split(":")[0]),(port)] + end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # + begin + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:local_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:local_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + rescue Exception => the_error + end + # ## + if addressing[:family] == ::Socket::AF_INET6 + # IPV6 + chunks = the_entry[4].split(":") + unless chunks[-1] == "*" + chunks[-1] = chunks[-1].to_i + end + chunks = [(chunks[0..-2]).join(":"),(chunks.last)] + else + # IPV4 + # Odd bug work-around + addr = the_entry[4].split(":")[0] + port = the_entry[4].split(":")[1] + unless port == "*" + port = port.to_i + end + chunks = [(addr.split(":")[0]),(port)] + # puts "Got: #{chunks.inspect}" + end + # if chunks[1].is_a?(::String) + # if chunks[1].numeric_values(:integer) + # chunks[1] = chunks[1].numeric_values(:integer)[:integer] + # end + # end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # + begin + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:foreign_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:foreign_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + rescue Exception => the_error + end + # ## + if the_entry[5].to_s.size > 0 + new_entry[:status] = the_entry[5].to_sym + else + new_entry[:status] = nil + end + # + unless the_error + data << new_entry + end + # + end + end + # + when :windows + predata = [] + raw_data = `netstat /a` + raw_data.to_enum(:each_line).each do |the_line| + ["TCP ", "UDP "].to_enum(:each).each do |entry| + if the_line.include?(entry) + item = the_line.split(" ") + if item[0] == "TCP" + # ["tcp6", "0", "0", "::1:631", ":::*", "LISTEN"] + if item[1].include?("[") && item[1].include?("]") + item = ["tcp6", "0", "0", item[1], item[2], (item[3] || "")] + else + item = ["tcp", "0", "0", item[1], item[2], (item[3] || "")] + end + end + if item[0] == "UDP" + # ["udp", "0", "0", "224.0.0.251:5353", "0.0.0.0:*"] + if item[1].include?("[") && item[1].include?("]") + item = ["udp6", "0", "0", item[1], item[2]] + else + item = ["udp", "0", "0", item[1], item[2]] + end + end + predata << item + break + end + end + # + end + predata.to_enum(:each).each do |the_entry| + the_error = nil + new_entry = {} + addressing = nil + case the_entry[0].downcase + when "tcp" + addressing = {:family => ::Socket::AF_INET, :protocol => :tcp} + when "tcp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :tcp6} + when "udp" + addressing = {:family => ::Socket::AF_INET, :protocol => :udp} + when "udp6" + addressing = {:family => ::Socket::AF_INET6, :protocol => :udp6} + end + if addressing + new_entry[:protocol] = addressing[:protocol] + if addressing[:family] == ::Socket::AF_INET6 + chunks = the_entry[3].split(":") + unless chunks[-1] == "*" + chunks[-1] = chunks[-1].to_i + end + chunks = [(chunks[0..-2]).join(":"),(chunks.last)] + else + # Odd bug work-around + addr = the_entry[3].split(":")[0] + port = the_entry[3].split(":")[1] + unless port == "*" + port = port.to_i + end + chunks = [(addr.split(":")[0]),(port)] + end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # + begin + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:local_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:local_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + rescue Exception => the_error + end + # ## + if addressing[:family] == ::Socket::AF_INET6 + # IPV6 + chunks = the_entry[4].split(":") + unless chunks[-1] == "*" + chunks[-1] = chunks[-1].to_i + end + chunks = [(chunks[0..-2]).join(":"),(chunks.last)] + else + # IPV4 + # Odd bug work-around + addr = the_entry[4].split(":")[0] + port = the_entry[4].split(":")[1] + unless port == "*" + port = port.to_i + end + chunks = [(addr.split(":")[0]),(port)] + # puts "Got: #{chunks.inspect}" + end + # if chunks[1].is_a?(::String) + # if chunks[1].numeric_values(:integer) + # chunks[1] = chunks[1].numeric_values(:integer)[:integer] + # end + # end + # bug fix: + if chunks[1] == "*" + chunks[1] = 0 + end + # + begin + if addressing[:protocol] == :udp || addressing[:protocol] == :udp6 + new_entry[:foreign_address] = Addrinfo.udp(chunks[0],chunks[1]) + else + new_entry[:foreign_address] = Addrinfo.tcp(chunks[0],chunks[1]) + end + rescue Exception => the_error + end + # ## + if the_entry[5].to_s.size > 0 + new_entry[:status] = the_entry[5].to_sym + else + new_entry[:status] = nil + end + # + unless the_error + data << new_entry + end + # + end + end + end + {:internet => data, :local => local_data} + end + # + def network_port_used?(the_port=0, the_address="0.0.0.0") + if the_port.is_a?(::Addrinfo) + the_address = the_port.ip_address() + the_port = the_port.ip_port() + end + if the_address == "*" || the_address == nil + the_address = "0.0.0.0" + end + unless the_address.is_a?(::String) + raise ArgumentError, "address needs to be specified as a String, you provided #{the_address.class}" + end + if the_port == "*" || the_port == nil + the_port = 0 + else + if the_port.is_any?(::String, ::Numeric) + the_port = the_port.to_i + else + raise ArgumentError, "port needs to be specified by an Integer or a String of an Integer or asterisk character, you provided #{the_port.class}" + end + end + current = {:internet => [], :local => []} + if the_port == 0 + found = true + else + found = false + current = self.network_status() + end + # + unless found + current[:internet].to_enum(:each).each do |entry| + if ((entry[:local_address].ip_address() == the_address && entry[:local_address].ip_port() == the_port) || (entry[:foreign_address].ip_address() == the_address && entry[:foreign_address].ip_port() == the_port)) + found = true + break + end + end + end + unless found + current[:local].to_enum(:each).each do |entry| + # pending further Research and hefty work-arounds within self.network_status(). + end + end + # + found + end + # + def network_next_port(the_address="0.0.0.0", honor_conventions=true) + # finds next available port on given interface, by default respecting common port usage conventions. + if the_address.is_a?(::Addrinfo) + the_address = the_address.ip_address() + end + if the_address == "*" || the_address == nil + the_address = "0.0.0.0" + end + unless the_address.is_a?(::String) + raise ArgumentError, "address needs to be specified as a String, you provided #{the_address.class}" + end + # + if honor_conventions + commonly_available = (49152..65535) + else + commonly_available = (1..65535) + end + # + found = nil + currently_used_ports = [] + current = self.network_status() + current[:internet].to_enum(:each).each do |entry| + if entry[:local_address].ip_address() == the_address + currently_used_ports << entry[:local_address].ip_port() + end + if entry[:foreign_address].ip_address() == the_address + currently_used_ports << entry[:foreign_address].ip_port() + end + end + # + commonly_available.to_enum(:each).each do |the_port| + unless currently_used_ports.include?(the_port) + found = the_port + break + end + end + # + found + end + # ## Services general port usage reference data + def service_ports() + @details[:service_ports] + end + # + def service_ports_register_alias(service,alias_name) + result = false + if @details[:service_ports][(service.to_sym)] + if alias_name.to_s.size > 0 + unless @details[:service_ports][(service.to_sym)][:aliases].include?(alias_name.to_s) + @details[:service_ports][(service.to_sym)][:aliases] << alias_name.to_s + result = true + end + end + end + result + end + # + def service_ports_remove_alias(service,alias_name) + result = false + if @details[:service_ports][(service.to_sym)] + if @details[:service_ports][(service.to_sym)][:aliases].include?(alias_name.to_s) + position = @details[:service_ports][(service.to_sym)][:aliases].index(alias_name.to_s) + if position + @details[:service_ports][(service.to_sym)][:aliases].delete_at(position) + result = true + end + end + end + result + end + # + def service_ports_register_client(service,client_scheme) + result = false + if @details[:service_ports][(service.to_sym)] + if client_scheme.to_s.size > 0 + unless @details[:service_ports][(service.to_sym)][:clients].include?(client_scheme) + @details[:service_ports][(service.to_sym)][:clients] << client_scheme.to_s + result = true + end + end + end + result + end + # + def service_ports_remove_client(service,client_scheme) + result = false + if @details[:service_ports][(service.to_sym)] + if @details[:service_ports][(service.to_sym)][:clients].include?(client_scheme.to_s) + position = @details[:service_ports][(service.to_sym)][:clients].index(client_scheme.to_s) + if position + @details[:service_ports][(service.to_sym)][:clients].delete_at(position) + result = true + end + end + end + result + end + # + def service_ports_usage_preference(service,preference=nil) + result = false + if @details[:service_ports][(service.to_sym)] + if preference + if [:tcp, :udp, :icmp, :snmp].include?(preference) + if @details[:service_ports][(service.to_sym)][:uses][(preference)] + @details[:service_ports][(service.to_sym)][:uses_preference] = preference + result = true + end + end + else + @details[:service_ports][(service.to_sym)].delete(:uses_preference) + result = true + end + end + result + end + # + def service_ports_register_usage(service,the_protocol,the_entry_port) + # the_entry_port can be an Integer or a Range + result = false + if @details[:service_ports][(service.to_sym)] + if @details[:service_ports][(service)][:uses].keys.include?(the_protocol) + included = false + @details[:service_ports][(service)][:uses][(the_protocol)].to_enum(:each).each do |the_result_port| + if the_entry_port.is_a?(::Range) + if the_result_port.is_a?(::Range) + if (the_result_port.include?(the_entry_port.first) && the_result_port.include?(the_entry_port.last)) + included = true + break + end + else + if the_entry_port.include?(the_result_port) + included = true + break + end + end + else + if the_result_port.is_a?(::Range) + if the_result_port.include?(the_entry_port) + included = true + break + end + else + if the_result_port == the_entry_port + included = true + break + end + end + end + end + unless included + @details[:service_ports][(service)][:uses][(the_protocol)] << the_entry_port + end + else + @details[:service_ports][(service)][:uses][(the_protocol)] = the_entry_port + end + result = true + # + end + result + end + # + def service_ports_register(service, entry={}) + unless service.to_s.size > 0 + raise ArgumentError, "you must specify a service symbol (i.e. :www ...), you provided #{service.inspect}" + end + unless service.is_a?(::Symbol) + service = service.to_s.to_sym + end + unless entry.is_a?(::Hash) + entry = {} + end + unless entry[:aliases].is_a?(::Array) + entry[:aliases] = [] + end + unless entry[:clients].is_a?(::Array) + entry[:clients] = [] + end + unless entry[:description].is_a?(::String) + entry[:description] = "" + end + unless entry[:uses].is_a?(::Hash) + entry[:uses] = {} + end + result = false + # + if @details[:service_ports][(service)] + entry[:aliases].to_enum(:each).each do |the_alias| + unless @details[:service_ports][(service)][:aliases].include?(the_alias) + @details[:service_ports][(service)][:aliases] << the_alias.to_s + end + end + entry[:clients].to_enum(:each).each do |the_client| + unless @details[:service_ports][(service)][:clients].include?(the_client.to_s) + @details[:service_ports][(service)][:clients] << the_client.to_s + end + end + entry[:uses].keys.to_enum(:each).each do |the_protocol| + if @details[:service_ports][(service)][:uses].keys.include?(the_protocol) + entry[:uses][(the_protocol)].to_enum(:each).each do |the_entry_port| + included = false + @details[:service_ports][(service)][:uses][(the_protocol)].each do |the_result_port| + if the_entry_port.is_a?(::Range) + if the_result_port.is_a?(::Range) + if (the_result_port.include?(the_entry_port.first) && the_result_port.include?(the_entry_port.last)) + included = true + break + end + else + if the_entry_port.include?(the_result_port) + included = true + break + end + end + else + if the_result_port.is_a?(::Range) + if the_result_port.include?(the_entry_port) + included = true + break + end + else + if the_result_port == the_entry_port + included = true + break + end + end + end + end + unless included + @details[:service_ports][(service)][:uses][(the_protocol)] << the_entry_port + end + end + else + @details[:service_ports][(service)][:uses][(the_protocol)] = entry[:uses][(the_protocol)] + end + # + end + # + else + @details[:service_ports][(service.to_sym)] = entry + result = true + end + # + result + end + # + def service_ports_search(criteria={}) + # search by client-scheme, and/or protocol and port-number/range. + result = {} + # + @details[:service_ports].keys.to_enum(:each).each do |service| + if (criteria[:scheme].to_s.to_sym == service || (@details[:service_ports][(service)][:aliases].include?(criteria[:scheme].to_s)) || (@details[:service_ports][(service)][:clients].include?(criteria[:scheme].to_s))) + result[(service)] = @details[:service_ports][(service)].clone + end + unless result[(service)] + the_protocol = criteria[:protocol] + the_entry_port = criteria[:port] + if (the_entry_port.is_any?(::Numeric, ::Range) && [:tcp, :udp, :icmp, :snmp].include?(the_protocol)) + if @details[:service_ports][(service)][:uses][(the_protocol)].is_a?(::Array) + included = false + @details[:service_ports][(service)][:uses][(the_protocol)].to_enum(:each).each do |the_result_port| + if the_entry_port.is_a?(::Range) + if the_result_port.is_a?(::Range) + if (the_result_port.include?(the_entry_port.first) && the_result_port.include?(the_entry_port.last)) + included = true + break + end + else + if the_entry_port.include?(the_result_port) + included = true + break + end + end + else + if the_result_port.is_a?(::Range) + if the_result_port.include?(the_entry_port) + included = true + break + end + else + if the_result_port == the_entry_port + included = true + break + end + end + end + end + if included + result[(service)] = @details[:service_ports][(service)].clone + end + # + end + end + end + end + # + result + end + # + end + # + class RemoteSystem + # + end + # + end # end-GxG::Entity +end +module GxG + # setup constants, dispose of BOOTSTRAP data. + if const_defined?(:BOOTSTRAP) + SYSTEM = GxG::Entity::LocalSystem.new(BOOTSTRAP) + SYSTEM.freeze + remove_const(:BOOTSTRAP) + end +end +# \ No newline at end of file diff --git a/lib/gxg/gxg_events.rb b/lib/gxg/gxg_events.rb new file mode 100644 index 0000000..69cf575 --- /dev/null +++ b/lib/gxg/gxg_events.rb @@ -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 => , :body => , :on_success => , :on_fail => } + 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 +# \ No newline at end of file diff --git a/lib/gxg/gxg_io.rb b/lib/gxg/gxg_io.rb new file mode 100644 index 0000000..6ec95f0 --- /dev/null +++ b/lib/gxg/gxg_io.rb @@ -0,0 +1,2717 @@ +# IO Support Library Module & IO Classes ############################################ +module GxG + module Support + module Library + # + module SocketIO + # + def sendmsg(data="", flags=0, destination_socket_address=nil, *controls) + unless self.instance_variable_defined?(:@remote_closed) + @remote_closed = nil + end + unless self.instance_variable_defined?(:@write_latency) + @write_latency = {:low => nil, :last => nil, :high => nil} + end + unless self.instance_variable_defined?(:@before_send_data) + @before_send_data = nil + end + unless self.instance_variable_defined?(:@after_send_data) + @after_send_data = nil + end + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + # on Winderz, just to a buffer sized spoon full until done ?? + if self.closed?() + raise IOError, "attempting to sendmsg on a closed stream" + else + if @remote_closed + raise Errno::EPIPE + else + data = data.to_s + written_bytes = data.bytesize() + # process controls: + if controls.size > 0 + if controls[0].is_a?(::Socket::AncillaryData) + controls = controls[0] + else + if controls[0].is_a?(::Hash) + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno)) + else + controls.flatten! + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno)) + end + end + else + raise ArgumentError, "you MUST supply ancillary data as a control" + end + # + 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 + buffer_size = self.buffer_limits(:out,whatfor)[:initial] + if written_bytes >= buffer_size + passes = self.passes_needed(written_bytes, buffer_size) + else + passes = 1 + end + # + total = (written_bytes - 1) + starting = 0 + ending = ([written_bytes,buffer_size].min - 1) + # + passes.times do + # + begin + # if remote end closes during write, raise error by setting @remote_closed and breaking loop. + reading = self.latency_reading(self.write_latency()) do + self.sendmsg_nonblock(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) + end + @write_latency = reading[:reading] + # + rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN + pause + begin + selected = ::IO::select(nil,[self],nil,0.010) + until selected + pause + selected = ::IO::select(nil,[self],nil,0.010) + end + rescue Exception + pause + retry + end + retry + rescue ::Errno::EPIPE + @remote_closed=true + break + end + # + starting = ending + 1 + ending = (starting + (buffer_size - 1)) + if ending > total + ending = ending - (ending - total) + end + end + if @remote_closed + raise Errno::EPIPE + end + written_bytes + end + end + else + if self.closed?() + raise IOError, "attempting to sendmsg on a closed stream" + else + if @remote_closed + raise Errno::EPIPE + else + data = self.transcode_to_external(data) + written_bytes = data.bytesize() + # process controls: + if controls.size > 0 + if controls[0].is_a?(::Socket::AncillaryData) + controls = controls[0] + else + if controls[0].is_a?(::Hash) + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno)) + else + controls.flatten! + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno)) + end + end + else + raise ArgumentError, "you MUST supply ancillary data as a control" + end + # + 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 + buffer_size = self.buffer_limits(:out,whatfor)[:initial] + if written_bytes >= buffer_size + passes = self.passes_needed(written_bytes, buffer_size) + else + passes = 1 + end + # + total = (written_bytes - 1) + starting = 0 + ending = ([written_bytes,buffer_size].min - 1) + # + passes.times do + # if remote end closes during write, raise error. + reading = self.latency_reading(self.write_latency()) do + self.original_sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true + end + if @remote_closed + break + else + @write_latency = reading[:reading] + end + starting = ending + 1 + ending = (starting + (buffer_size - 1)) + if ending > total + ending = ending - (ending - total) + end + end + if @remote_closed + raise Errno::EPIPE + end + # + end + written_bytes + end + end + end + # + def send(data="", flags=0, destination_socket_address=nil, *controls) + # for now, flags ignored. LATER: study Socket.send message flags in detail + unless self.instance_variable_defined?(:@remote_closed) + @remote_closed = nil + end + unless self.instance_variable_defined?(:@write_latency) + @write_latency = {:low => nil, :last => nil, :high => nil} + end + unless self.instance_variable_defined?(:@before_send_data) + @before_send_data = nil + end + unless self.instance_variable_defined?(:@after_send_data) + @after_send_data = nil + end + if self.closed?() + raise IOError, "attempting to send on a closed stream" + else + if @remote_closed + raise Errno::EPIPE + else + data = data.to_s + length = 0 + if data.bytesize() > 0 + length = data.bytesize() + # process controls: + if controls.size > 0 + if controls[0].is_a?(::Socket::AncillaryData) + controls = controls[0] + else + if controls[0].is_a?(::Hash) + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[:level] || :SOCKET), (controls[:type] || ::Socket::SCM_RIGHTS), (controls[:data] || self.fileno)) + else + controls.flatten! + controls = ::Socket::AncillaryData.new(::Socket::AF_UNIX, (controls[0] || :SOCKET), (controls[1] || ::Socket::SCM_RIGHTS), (controls[2] || self.fileno)) + end + end + else + controls = nil + end + # + 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 + buffer_size = self.buffer_limits(:out,whatfor)[:initial] + if length >= buffer_size + passes = self.passes_needed(length, buffer_size) + else + passes = 1 + end + # + total = (length - 1) + starting = 0 + ending = ([length,buffer_size].min - 1) + # + passes.times do + # if remote end closes during write, raise error. + if controls + reading = self.latency_reading(self.write_latency()) do + self.sendmsg(data.slice_bytes(starting..ending),flags,destination_socket_address,controls) or @remote_closed=true + end + else + if destination_socket_address + reading = self.latency_reading(self.write_latency()) do + self.original_send(data.slice_bytes(starting..ending),flags,destination_socket_address) or @remote_closed=true + end + else + reading = self.latency_reading(self.write_latency()) do + self.original_send(data.slice_bytes(starting..ending),flags) or @remote_closed=true + end + end + end + if @remote_closed + break + else + @write_latency = reading[:reading] + end + starting = ending + 1 + ending = (starting + (buffer_size - 1)) + if ending > total + ending = ending - (ending - total) + end + end + # + if @remote_closed + raise Errno::EPIPE + end + end + length + end + end + end + # + def recvmsg(max_mesg_length=nil, flags=0, max_control_length=nil, options={}) + # for now, flags ignored. LATER: study Socket.recv message flags in detail + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + # on Winderz, just to a buffer sized spoon full until done ?? + if self.closed?() + raise IOError, "attempting to recvmsg on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + begin + result = self.recvmsg_nonblock(max_mesg_length, flags, max_control_length, options) + # + rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable + pause + begin + # about 10 ms + selected = GxG::IO::IO::select([self],nil,nil,0.010) + until selected + pause + selected = GxG::IO::IO::select([self],nil,nil,0.010) + end + rescue Exception + pause + retry + end + retry + end + # + if result.is_a?(::Array) + if result[0].is_a?(::String) + result[0].force_encoding(::Encoding::ASCII_8BIT) + end + end + if @after_receive_data.respond_to?(:call) + @after_receive_data.call(result) + end + end + else + if self.closed?() + raise IOError, "attempting to recvmsg on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + result = self.original_recvmsg(max_mesg_length, flags, max_control_length, options) + if result.is_a?(::Array) + if result[0].is_a?(::String) + result[0].force_encoding(::Encoding::ASCII_8BIT) + end + end + if @after_receive_data.respond_to?(:call) + @after_receive_data.call(result) + end + end + end + result + end + # + def recv(bytecount=nil, flags=0) + # for now, flags ignored. LATER: study Socket.recv message flags in detail + unless self.instance_variable_defined?(:@before_receive_data) + @before_receive_data = nil + end + unless self.instance_variable_defined?(:@after_receive_data) + @after_receive_data = nil + end + unless bytecount.is_a?(::Numeric) + bytecount = self.buffer_limits(:in,:socket)[:initial] + end + unless flags.is_a?(::Numeric) + flags = 0 + end + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + # on Winderz, just to a buffer sized spoon full until done ?? + # + if self.closed?() + raise IOError, "attempting to recv on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + 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 + buffer_size = self.buffer_limits(:in,whatfor)[:initial] + unless bytecount.is_a?(::Numeric) + bytecount = buffer_size + end + unless flags.is_a?(::Numeric) + flags = 0 + end + # millisecond: 1/1000th of a second (reminder) + read_bytes = 0 + result = "" + result.force_encoding(::Encoding::ASCII_8BIT) + chunk = "abc" + # + until (read_bytes == bytecount || chunk.bytesize() == 0) + begin + if ((bytecount - read_bytes) < buffer_size) + chunk = self.recv_nonblock((bytecount - read_bytes),flags).to_s + else + chunk = self.recv_nonblock(buffer_size,flags).to_s + end + read_bytes += chunk.bytesize() + if chunk.bytesize() > 0 + result << chunk + end + # + rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable + pause + begin + # about 10 ms + selected = GxG::IO::IO::select([self],nil,nil,0.010) + until selected + pause + selected = GxG::IO::IO::select([self],nil,nil,0.010) + end + rescue Exception + pause + retry + end + retry + end + # + pause + end + # + if @after_receive_data.respond_to?(:call) + @after_receive_data.call([result,nil,0,nil]) + end + result + end + else + if self.closed?() + raise IOError, "attempting to recv on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + result = self.original_recv(bytecount.to_i,flags).to_s + result.force_encoding(::Encoding::ASCII_8BIT) + if @after_receive_data.respond_to?(:call) + @after_receive_data.call([result,nil,0,nil]) + end + end + end + result + end + # + end + # + module SocketIORecvFrom + # Note: Allow aliases or existing recvfrom_nonblock to catch method references. + # Do not define recvfrom_nonblock here. + def recvfrom(max_mesg_length=nil, flags=0) + # for now, flags ignored. LATER: study Socket.recv message flags in detail + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + # on Winderz, just to a buffer sized spoon full until done ?? + if self.closed?() + raise IOError, "attempting to recvmsg on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + begin + result = self.recvfrom_nonblock(max_mesg_length, flags) + # + rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable + pause + begin + # about 10 ms + selected = GxG::IO::IO::select([self],nil,nil,0.010) + until selected + pause + selected = GxG::IO::IO::select([self],nil,nil,0.010) + end + rescue Exception + pause + retry + end + retry + end + # + if result.is_a?(::Array) + if result[0].is_a?(::String) + result[0].force_encoding(::Encoding::ASCII_8BIT) + end + end + if @after_receive_data.respond_to?(:call) + @after_receive_data.call(result) + end + end + else + if self.closed?() + raise IOError, "attempting to recvmsg on a closed stream" + else + if @before_receive_data.respond_to?(:call) + @before_receive_data.call() + end + result = self.original_recvfrom(max_mesg_length, flags) + if result.is_a?(::Array) + if result[0].is_a?(::String) + result[0].force_encoding(::Encoding::ASCII_8BIT) + end + end + if @after_receive_data.respond_to?(:call) + @after_receive_data.call(result) + end + end + end + result + end + # + end + # + module SocketRW + # + def eof?() + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.eof?() + end + # + def pos() + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.pos() + end + # + def pos=(*args) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.pos = args[0] + end + # + def lineno() + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.lineno() + end + # + def lineno=(*args) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.lineno = args[0] + end + # + def rewind() + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + @in_buffer.rewind() + end + # + def getbyte() + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.getbyte() + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + # + def getc(*args) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.getc(*args) + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + # + def gets(*args) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.gets(*args) + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + # + def each_char(&block) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.each_char(&block) + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + # + def each_line(*args, &block) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.each_line(*args, &block) + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + # + def read(*args) + unless self.instance_variable_defined?(:@in_buffer) + @in_buffer = ::StringIO.new("",::IO::RDWR) + @in_buffer.set_encoding(::Encoding::ASCII_8BIT) + end + result = @in_buffer.read(*args) + if @in_buffer.eof?() + @in_buffer.rewind + @in_buffer.string.clear + end + result + end + alias :read_nonblock :read + alias :sysread :read + # + def readpartial(*args) + # + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + result = "" + result.force_encoding(self.internal_encoding) + data = self.read(*args) + if data + # non-nil + if data.is_a?(::String) + # 'blocking' or not? + if (self.eof?() || (data && data.to_s.size > 0)) + # no pseudo-blocking + result << data + data = nil + else + # pseudo-blocking + until (self.eof?() || (data && data.to_s.size > 0)) + data = self.read(*args) + # string, buffer, or nil + if data + # + if data.is_a?(::String) + if (data && data.to_s.size > 0) + result << data + data = nil + break + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + else + # buffer + result = data + data = nil + break + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + pause + # + end + end + else + # buffer + result = data + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + if result + if result.is_a?(::String) + if result.bytesize > 0 + result + else + nil + end + else + # buffer + result + end + else + nil + end + # + end + end + end + # + def write(data="") + data = self.transcode_to_external(data) + written_bytes = data.bytesize() + self.send(data,0) + written_bytes + end + alias :write_nonblock :write + alias :syswrite :write + # + def <<(*args) + self.write(*args) + self + end + # + def ungetbyte() + raise NotImplementedError, "unget operations not supported on this type of object" + end + alias :ungetbytes :ungetbyte + alias :ungetc :ungetbyte + alias :ungets :ungetbyte + # + end + # + module StringIOPrepare + # + def process_params_default_object(the_object) + "" + end + def process_params_classes_valid() + [::String] + end + def process_params_classes_error() + "you MUST specify a String" + end + end + # + module IOPrepare + # + def process_params_default_object(the_object=nil) + the_object + end + def process_params_classes_valid() + [::IO, ::GxG::IO::IO, ::StringIO, ::GxG::IO::StringIO, ::Numeric] + end + def process_params_classes_error() + "you MUST specify an IO :object or a :file_descriptor Fixnum" + end + # + end + # + module CommonIOPrepare + # + def process_parameters(*args) + default_object = nil + if args[0].is_a?(Hash) + # GxG style parameters + raw_params = args[0] + unless raw_params[:object].is_any?(self.process_params_classes_valid()) + unless raw_params[:file_descriptor].is_any?(self.process_params_classes_valid()) + default_object = self.process_params_default_object((raw_params[:object] || raw_params[:file_descriptor])) + if default_object + raw_params[:object] = default_object + else + raise ArgumentError, (self.class::process_params_classes_error()) + end + end + end + unless raw_params[:mode].is_a?(Array) + raise ArgumentError, "you MUST specify an Array of :mode flags (as symbols)" + end + raw_params[:mode].to_enum.each do |the_mode_flag| + unless [:read, :write, :readwrite, :create, :overwrite, :truncate, :append, :text, :binary].include?(the_mode_flag) + raw_params[:mode].delete(the_mode_flag) + end + end + unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text)) + raw_params[:mode] << :text + end + else + # standard MRI style parameters + raw_params = {:mode => []} + if args[0].is_any?(self.process_params_classes_valid()) + if args[0].is_a?(::Numeric) + raw_params[:file_descriptor] = args[0].to_i + else + raw_params[:object] = args[0] + end + else + default_object = self.process_params_default_object(args[0]) + if default_object + raw_params[:object] = default_object + else + raise ArgumentError, (self.class::process_params_classes_error()) + end + end + if args[2].is_a?(Hash) + if args[2][:mode] + raw_params[:raw_mode] = args[2][:mode] + end + if args[2][:binmode] + raw_params[:mode] << :binary + else + unless raw_params[:mode].index(:text) + raw_params[:mode] << :text + end + end + extern_encode = nil + intern_encode = nil + if args[2][:encoding] + extern_encode = args[2][:encoding] + intern_encode = args[2][:encoding] + end + if args[2][:external_encoding] + unless extern_encode + extern_encode = args[2][:external_encoding] + end + end + if args[2][:internal_encoding] + unless intern_encode + intern_encode = args[2][:internal_encoding] + end + end + if extern_encode + raw_params[:external_encoding] = extern_encode + end + if intern_encode + raw_params[:internal_encoding] = intern_encode + end + if args[2][:autoclose] + raw_params[:autoclose] = args[2][:autoclose] + end + end + if args[1].is_any?(String,Numeric) + if raw_params[:raw_mode] + raise ArgumentError, "you specify :mode as: 2nd parameter (String || Numeric) or an option in 3rd parameter Hash, but not in both" + else + raw_params[:raw_mode] = args[1] + end + if raw_params[:raw_mode].is_a?(String) + mode_parse = raw_params[:raw_mode].split(":") + mode_spec = mode_parse[0].numeric_values() + if mode_spec.is_a?(Hash) + raw_params[:raw_mode] = mode_spec[:integer] + else + raw_params[:raw_mode] = mode_parse[0] + end + # + if mode_parse.size > 1 + extern_encode = mode_parse[1] + if mode_parse.size > 2 + intern_encode = mode_parse[2] + else + intern_encode = nil + end + else + extern_encode = nil + intern_encode = nil + end + # if specified also in options, it is overwritten here by mode inclusion. + if (extern_encode == intern_encode) + if extern_encode + # raw_params[:encoding] = extern_encode + raw_params[:external_encoding] = extern_encode + raw_params[:internal_encoding] = extern_encode + end + else + if extern_encode + raw_params[:external_encoding] = extern_encode + end + if intern_encode + raw_params[:internal_encoding] = intern_encode + end + end + # + else + # numeric mode arg + raw_params[:raw_mode] = args[1].to_i + end + end + # + unless (raw_params[:object] || raw_params[:file_descriptor]) + raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum" + end + # + if raw_params[:raw_mode] + # decipher :raw_mode + if raw_params[:raw_mode].is_a?(Numeric) + # Numeric + if raw_params[:file_descriptor] + if (raw_params[:raw_mode] & ::File::RDWR) == ::File::RDWR + raw_params[:mode] << :read + raw_params[:mode] << :write + else + if (raw_params[:raw_mode] & ::File::WRONLY) == ::File::WRONLY + raw_params[:mode] << :write + else + if (raw_params[:raw_mode] & ::File::RDONLY) == ::File::RDONLY + raw_params[:mode] << :read + end + end + end + if (raw_params[:raw_mode] & ::File::TRUNC) == ::File::TRUNC + raw_params[:mode] << :overwrite + end + if (raw_params[:raw_mode] & ::File::APPEND) == ::File::APPEND + raw_params[:mode] << :append + end + if (raw_params[:raw_mode] & ::File::CREAT) == ::File::CREAT + raw_params[:mode] << :append + end + else + if (raw_params[:raw_mode] & ::IO::RDWR) == ::IO::RDWR + raw_params[:mode] << :read + raw_params[:mode] << :write + else + if (raw_params[:raw_mode] & ::IO::WRONLY) == ::IO::WRONLY + raw_params[:mode] << :write + else + if (raw_params[:raw_mode] & ::IO::RDONLY) == ::IO::RDONLY + raw_params[:mode] << :read + end + end + end + if (raw_params[:raw_mode] & ::IO::TRUNC) == ::IO::TRUNC + raw_params[:mode] << :overwrite + end + if (raw_params[:raw_mode] & ::IO::APPEND) == ::IO::APPEND + raw_params[:mode] << :append + end + if (raw_params[:raw_mode] & ::IO::CREAT) == ::IO::CREAT + raw_params[:mode] << :create + end + end + else + # String + unless (raw_params[:mode].include?(:binary) || raw_params[:mode].include?(:text)) + if raw_params[:raw_mode].to_s.include?("b") + raw_params[:mode] << :binary + else + raw_params[:mode] << :text + end + end + if raw_params[:raw_mode].to_s.include?("r") + raw_params[:mode] << :read + if raw_params[:raw_mode].to_s.include?("+") + raw_params[:mode] << :write + end + else + if raw_params[:raw_mode].to_s.include?("w") + raw_params[:mode] << :write + raw_params[:mode] << :overwrite + if raw_params[:raw_mode].to_s.include?("+") + raw_params[:mode] << :read + end + else + if raw_params[:raw_mode].to_s.include?("a") + raw_params[:mode] << :write + raw_params[:mode] << :append + if raw_params[:raw_mode].to_s.include?("+") + raw_params[:mode] << :read + end + end + end + end + end + raw_params.delete(:raw_mode) + end + # + end + # + if raw_params[:external_encoding] + unless ::Encoding::constants.include?(raw_params[:external_encoding].to_s.to_sym) + raise ArgumentError, ":external_encoding :#{raw_params[:external_encoding].to_s} unsupported" + end + end + if raw_params[:internal_encoding] + unless ::Encoding::constants.include?(raw_params[:internal_encoding].to_s.to_sym) + raise ArgumentError, ":internal_encoding :#{raw_params[:internal_encoding].to_s} unsupported" + end + end + # + raw_params + end + # + end + # + end + end +end +# +module GxG + module IO + # local input / output objects: Stream, Pipe, File, Device + # replacement File/Pipe/Socket IO object and methods built upon EM/EM-Synchrony (uses std. IO obj. for low-level work but attempts actual non-blocking) + # built-in 'non-blocking' does not appear to actually work (looking at you matz) + # .fd: numeric file descriptor or IO object + # mode: file mode. a string or an integer + # opt: hash for specifying mode by name. + # :mode + # + # + #Same as mode parameter + # + #:external_encoding + # + # + #External encoding for the IO. “-” is a synonym for the default external encoding. + # + #:internal_encoding + # + # + #Internal encoding for the IO. “-” is a synonym for the default internal encoding. + # + #If the value is nil no conversion occurs. + # + #:encoding + # + # + #Specifies external and internal encodings as “extern:intern”. + # + #:textmode + # + # + #If the value is truth value, same as “t” in argument mode. + # + #:binmode + # + # + #If the value is truth value, same as “b” in argument mode. + # + #:autoclose + # + # + #If the value is false, the fd will be kept open after this IO instance gets finalized. + # + class IO < ::IO + # See : http://pleac.sourceforge.net/pleac_ruby/fileaccess.html + # Also See : http://stackoverflow.com/questions/6701103/understanding-ruby-and-os-i-o-buffering + # And See : http://ruby.runpaint.org/io + # + # Valid mode values + @@valid_modes = {:flags => {:io => {}, :tty => {}, :pipe => {}, :socket => {}, :file => {}}, :io =>{}, :tty => {}, :pipe => {}, :socket => {}, :file => {}} + + # + @@valid_modes[:flags][:io][:read] = ::IO::RDONLY + @@valid_modes[:flags][:io][:write] = ::IO::WRONLY + @@valid_modes[:flags][:io][:readwrite] = ::IO::RDWR + @@valid_modes[:flags][:io][:binary] = ::IO::BINARY + # ### FIX : blows up on Windows -- not defined - FOR NOW SYNC = DSYNC + @@valid_modes[:flags][:io][:sync] = ::IO::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk. + @@valid_modes[:flags][:io][:dsync] = ::IO::DSYNC # only normal data be synchronized after each write operation, not metadata. + @@valid_modes[:flags][:io][:rsync] = ::IO::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC + @@valid_modes[:flags][:io][:tty] = ::IO::TTY + @@valid_modes[:flags][:io][:noctty] = ::IO::NOCTTY # If the named file is a terminal device, don’t make it the controlling terminal for the process. + @@valid_modes[:flags][:io][:duplex] = ::IO::DUPLEX + @@valid_modes[:flags][:io][:append] = ::IO::APPEND + @@valid_modes[:flags][:io][:create] = ::IO::CREAT + @@valid_modes[:flags][:io][:exclusive] = ::IO::EXCL + @@valid_modes[:flags][:io][:wsplit] = ::IO::WSPLIT + @@valid_modes[:flags][:io][:wsplit_initialized] = ::IO::WSPLIT_INITIALIZED + @@valid_modes[:flags][:io][:trunc] = ::IO::TRUNC + @@valid_modes[:flags][:io][:text] = ::IO::TEXT + # Note: need to verify this int value before using: + @@valid_modes[:flags][:io][:setenc_by_bom] = ::IO::SETENC_BY_BOM + @@valid_modes[:flags][:io][:seek_set] = ::IO::SEEK_SET + @@valid_modes[:flags][:io][:seek_current] = ::IO::SEEK_CUR + @@valid_modes[:flags][:io][:seek_end] = ::IO::SEEK_END + # ### file region locking: + @@valid_modes[:flags][:io][:shared_lock] = ::IO::LOCK_SH # for reading + @@valid_modes[:flags][:io][:exclusive_lock] = ::IO::LOCK_EX # for writing (implies blocking) + @@valid_modes[:flags][:io][:nonblock_lock] = ::IO::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing. + @@valid_modes[:flags][:io][:unlock] = ::IO::LOCK_UN + # + @@valid_modes[:flags][:io][:nonblocking] = ::IO::NONBLOCK # Neither the open() call, nor any other operation will cause the process to block (sleep) on the I/O. This behavior may be defined only for FIFOs. + @@valid_modes[:flags][:io][:ndelay] = ::IO::NONBLOCK + @@valid_modes[:flags][:io][:nofollow] = ::IO::NOFOLLOW # Do not follow symlinks. + @@valid_modes[:flags][:io][:noaccesstime] = ::IO::NOATIME # Do not update the access time (atime) of the file. + @@valid_modes[:flags][:io][:match_noescape] = ::IO::FNM_NOESCAPE # + @@valid_modes[:flags][:io][:match_pathname] = ::IO::FNM_PATHNAME # + @@valid_modes[:flags][:io][:match_dotmatch] = ::IO::FNM_DOTMATCH # + @@valid_modes[:flags][:io][:match_casefold] = ::IO::FNM_CASEFOLD # + @@valid_modes[:flags][:io][:match_systemcase] = ::IO::FNM_SYSCASE # + # ### file modes + @@valid_modes[:flags][:file][:read] = ::File::RDONLY + @@valid_modes[:flags][:file][:write] = ::File::WRONLY + @@valid_modes[:flags][:file][:readwrite] = ::File::RDWR + @@valid_modes[:flags][:file][:binary] = ::File::BINARY + @@valid_modes[:flags][:file][:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk. + @@valid_modes[:flags][:file][:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata. + @@valid_modes[:flags][:file][:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC + @@valid_modes[:flags][:file][:tty] = ::File::TTY + @@valid_modes[:flags][:file][:noctty] = ::File::NOCTTY + @@valid_modes[:flags][:file][:duplex] = ::File::DUPLEX + @@valid_modes[:flags][:file][:append] = ::File::APPEND + @@valid_modes[:flags][:file][:create] = ::File::CREAT + @@valid_modes[:flags][:file][:exclusive] = ::File::EXCL + @@valid_modes[:flags][:file][:wsplit] = ::File::WSPLIT + @@valid_modes[:flags][:file][:wsplit_initialized] = ::File::WSPLIT_INITIALIZED + @@valid_modes[:flags][:file][:trunc] = ::File::TRUNC + @@valid_modes[:flags][:file][:text] = ::File::TEXT + @@valid_modes[:flags][:file][:setenc_by_bom] = ::File::SETENC_BY_BOM + @@valid_modes[:flags][:file][:seek_set] = ::File::SEEK_SET + @@valid_modes[:flags][:file][:seek_current] = ::File::SEEK_CUR + @@valid_modes[:flags][:file][:seek_end] = ::File::SEEK_END + # ### file region locking: + @@valid_modes[:flags][:file][:shared_lock] = ::File::LOCK_SH # for reading + @@valid_modes[:flags][:file][:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking) + @@valid_modes[:flags][:file][:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing. + @@valid_modes[:flags][:file][:unlock] = ::File::LOCK_UN + # + @@valid_modes[:flags][:file][:nonblocking] = ::File::NONBLOCK + @@valid_modes[:flags][:file][:ndelay] = ::File::NONBLOCK + @@valid_modes[:flags][:file][:nofollow] = ::File::NOFOLLOW # Do not follow symlinks. + @@valid_modes[:flags][:file][:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file. + @@valid_modes[:flags][:file][:match_noescape] = ::File::FNM_NOESCAPE # + @@valid_modes[:flags][:file][:match_pathname] = ::File::FNM_PATHNAME # + @@valid_modes[:flags][:file][:match_dotmatch] = ::File::FNM_DOTMATCH # + @@valid_modes[:flags][:file][:match_casefold] = ::File::FNM_CASEFOLD # + @@valid_modes[:flags][:file][:match_systemcase] = ::File::FNM_SYSCASE # + # Extended IO mode flags + @@valid_modes[:flags][:io][:write_create] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:create] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:write_trunc] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:trunc]) + @@valid_modes[:flags][:io][:write_append] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:append]) + @@valid_modes[:flags][:io][:readwrite_create] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:create] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:trunc]) + @@valid_modes[:flags][:io][:readwrite_append] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:append]) + @@valid_modes[:flags][:io][:binary_read] = (@@valid_modes[:flags][:io][:read] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_write] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_write_create] = (@@valid_modes[:flags][:io][:write_create] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:binary_write_trunc] = (@@valid_modes[:flags][:io][:write_trunc] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_write_append] = (@@valid_modes[:flags][:io][:write_append] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_readwrite] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_readwrite_create] = (@@valid_modes[:flags][:io][:readwrite_create] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:binary_readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite_trunc] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:binary_readwrite_append] = (@@valid_modes[:flags][:io][:readwrite_append] | @@valid_modes[:flags][:io][:binary] | @@valid_modes[:flags][:io][:setenc_by_bom]) + @@valid_modes[:flags][:io][:text_read] = (@@valid_modes[:flags][:io][:read] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_write] = (@@valid_modes[:flags][:io][:write] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_write_create] = (@@valid_modes[:flags][:io][:write_create] | @@valid_modes[:flags][:io][:text] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:text_write_trunc] = (@@valid_modes[:flags][:io][:write_trunc] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_write_append] = (@@valid_modes[:flags][:io][:write_append] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_readwrite] = (@@valid_modes[:flags][:io][:readwrite] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_readwrite_create] = (@@valid_modes[:flags][:io][:readwrite_create] | @@valid_modes[:flags][:io][:text] | @@valid_modes[:flags][:io][:exclusive]) + @@valid_modes[:flags][:io][:text_readwrite_trunc] = (@@valid_modes[:flags][:io][:readwrite_trunc] | @@valid_modes[:flags][:io][:text]) + @@valid_modes[:flags][:io][:text_readwrite_append] = (@@valid_modes[:flags][:io][:readwrite_append] | @@valid_modes[:flags][:io][:text]) + # File mode flags + @@valid_modes[:flags][:file][:write_create] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:create] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:write_trunc] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:trunc]) + @@valid_modes[:flags][:file][:write_append] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:append]) + @@valid_modes[:flags][:file][:readwrite_create] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:create] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:trunc]) + @@valid_modes[:flags][:file][:readwrite_append] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:append]) + @@valid_modes[:flags][:file][:binary_read] = (@@valid_modes[:flags][:file][:read] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_write] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_write_create] = (@@valid_modes[:flags][:file][:write_create] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:binary_write_trunc] = (@@valid_modes[:flags][:file][:write_trunc] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_write_append] = (@@valid_modes[:flags][:file][:write_append] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_readwrite] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_readwrite_create] = (@@valid_modes[:flags][:file][:readwrite_create] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:binary_readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite_trunc] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:binary_readwrite_append] = (@@valid_modes[:flags][:file][:readwrite_append] | @@valid_modes[:flags][:file][:binary] | @@valid_modes[:flags][:file][:setenc_by_bom]) + @@valid_modes[:flags][:file][:text_read] = (@@valid_modes[:flags][:file][:read] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_write] = (@@valid_modes[:flags][:file][:write] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_write_create] = (@@valid_modes[:flags][:file][:write_create] | @@valid_modes[:flags][:file][:text] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:text_write_trunc] = (@@valid_modes[:flags][:file][:write_trunc] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_write_append] = (@@valid_modes[:flags][:file][:write_append] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_readwrite] = (@@valid_modes[:flags][:file][:readwrite] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_readwrite_create] = (@@valid_modes[:flags][:file][:readwrite_create] | @@valid_modes[:flags][:file][:text] | @@valid_modes[:flags][:file][:exclusive]) + @@valid_modes[:flags][:file][:text_readwrite_trunc] = (@@valid_modes[:flags][:file][:readwrite_trunc] | @@valid_modes[:flags][:file][:text]) + @@valid_modes[:flags][:file][:text_readwrite_append] = (@@valid_modes[:flags][:file][:readwrite_append] | @@valid_modes[:flags][:file][:text]) + # IO modes + #Mode | Meaning + #-----+-------------------------------------------------------- + #"r" | Read-only, starts at beginning of file (default mode). + #-----+-------------------------------------------------------- + #"r+" | Read-write, starts at beginning of file. + #-----+-------------------------------------------------------- + #"w" | Write-only, truncates existing file + # | to zero length or creates a new file for writing. + #-----+-------------------------------------------------------- + #"w+" | Read-write, truncates existing file to zero length + # | or creates a new file for reading and writing. + #-----+-------------------------------------------------------- + #"a" | Write-only, starts at end of file if file exists, + # | otherwise creates a new file for writing. + #-----+-------------------------------------------------------- + #"a+" | Read-write, starts at end of file if file exists, + # | otherwise creates a new file for reading and + # | writing. + #-----+-------------------------------------------------------- + # "b" | Binary file mode (may appear with + # | any of the key letters listed above). + # | Suppresses EOL <-> CRLF conversion on Windows. And + # | sets external encoding to ASCII-8BIT unless explicitly + # | specified. + #-----+-------------------------------------------------------- + # "t" | Text file mode (may appear with + # | any of the key letters listed above except "b"). + # Valid IO modes + # Things to remember: + # tty operates over an fs pipe and appears to use the exact same file modes as a file therefore. + # Unresolved Questions: + # 1. :setenc_by_bom : set on io and tty also? does tty have binary mode? + # 2. :setenc_by_bom : only set on Windows? or *any* platform? + # 3. :wsplit & :wsplit_initialize : under what conditions is this actually set to what (io, tty, file), and on what platforms? + # 5. :sync : applied to io, tty, file? + # 6. Are :tty, :pipe, and :socket able to use the *exact* same mode codes, or do they differ, and in what ways? + # + # text io modes + @@valid_modes[:io][:text_read] = {:synonyms => ["r", "rt", "tr", :text_read, @@valid_modes[:flags][:io][:text_read]], :if_exist => @@valid_modes[:flags][:io][:text_read]} + @@valid_modes[:io][:text_readwrite] = {:synonyms => ["r+","r+t", "tr+", :text_readwrite, @@valid_modes[:flags][:io][:text_readwrite]], :if_exist => @@valid_modes[:flags][:io][:text_readwrite], :if_create => (@@valid_modes[:flags][:io][:text_readwrite] | @@valid_modes[:flags][:io][:readwrite_create])} + @@valid_modes[:io][:text_write_overwrite] = {:synonyms => ["w","wt", "tw", :text_write, :text_write_overwrite, :text_write_truncate, @@valid_modes[:flags][:io][:text_write_trunc], @@valid_modes[:flags][:io][:text_write_create],(@@valid_modes[:flags][:io][:text_write_trunc] | @@valid_modes[:flags][:io][:text_write_create])], :if_exist => @@valid_modes[:flags][:io][:text_write_trunc], :if_create => @@valid_modes[:flags][:io][:text_write_create]} + @@valid_modes[:io][:text_readwrite_overwrite] = {:synonyms => ["w+","w+t", "tw+", :text_readwrite_overwrite, :text_readwrite_truncate, @@valid_modes[:flags][:io][:text_readwrite_trunc], @@valid_modes[:flags][:io][:text_readwrite_create], (@@valid_modes[:flags][:io][:text_readwrite_trunc] | @@valid_modes[:flags][:io][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:text_readwrite_trunc], :if_create => @@valid_modes[:flags][:io][:text_readwrite_create]} + @@valid_modes[:io][:text_write_append] = {:synonyms => ["a","at", "ta", :text_write_append, @@valid_modes[:flags][:io][:text_write_append], (@@valid_modes[:flags][:io][:text_write_append] | @@valid_modes[:flags][:io][:text_write_create])], :if_exist => @@valid_modes[:flags][:io][:text_write_append], :if_create => @@valid_modes[:flags][:io][:text_write_create]} + @@valid_modes[:io][:text_readwrite_append] = {:synonyms => ["a+","a+t", "ta+", :text_readwrite_append, @@valid_modes[:flags][:io][:text_readwrite_append], (@@valid_modes[:flags][:io][:text_readwrite_append] | @@valid_modes[:flags][:io][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:text_readwrite_append], :if_create => @@valid_modes[:flags][:io][:text_readwrite_create]} + # + # binary io modes + @@valid_modes[:io][:binary_read] = {:synonyms => ["rb", "br", :binary_read, @@valid_modes[:flags][:io][:binary_read]], :if_exist => @@valid_modes[:flags][:io][:binary_read]} + @@valid_modes[:io][:binary_readwrite] = {:synonyms => ["r+b", "br+", :binary_readwrite, @@valid_modes[:flags][:io][:binary_readwrite]], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite], :if_create => (@@valid_modes[:flags][:io][:binary_readwrite] | @@valid_modes[:flags][:io][:readwrite_create])} + @@valid_modes[:io][:binary_write_overwrite] = {:synonyms => ["wb", "bw", :binary_write, :binary_write_overwrite, :binary_write_truncate, @@valid_modes[:flags][:io][:binary_write_trunc], @@valid_modes[:flags][:io][:binary_write_create], (@@valid_modes[:flags][:io][:binary_write_trunc] | @@valid_modes[:flags][:io][:binary_write_create])], :if_exist => @@valid_modes[:flags][:io][:binary_write_trunc], :if_create => @@valid_modes[:flags][:io][:binary_write_create]} + @@valid_modes[:io][:binary_readwrite_overwrite] = {:synonyms => ["w+b", "bw+", :binary_readwrite_overwrite, :binary_readwrite_truncate, @@valid_modes[:flags][:io][:binary_readwrite_trunc], @@valid_modes[:flags][:io][:binary_readwrite_create], (@@valid_modes[:flags][:io][:binary_readwrite_trunc] | @@valid_modes[:flags][:io][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite_trunc], :if_create => @@valid_modes[:flags][:io][:binary_readwrite_create]} + @@valid_modes[:io][:binary_write_append] = {:synonyms => ["ab", "ba", :binary_write_append, @@valid_modes[:flags][:io][:binary_write_append], (@@valid_modes[:flags][:io][:binary_write_append] | @@valid_modes[:flags][:io][:binary_write_create])], :if_exist => @@valid_modes[:flags][:io][:binary_write_append], :if_create => @@valid_modes[:flags][:io][:binary_write_create]} + @@valid_modes[:io][:binary_readwrite_append] = {:synonyms => ["a+b", "ba+", :binary_readwrite_append, @@valid_modes[:flags][:io][:binary_readwrite_append], (@@valid_modes[:flags][:io][:binary_readwrite_append] | @@valid_modes[:flags][:io][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:io][:binary_readwrite_append], :if_create => @@valid_modes[:flags][:io][:binary_readwrite_create]} + # + # tty modes + # is there a 'binary' mode for a tty? seems it would only be text - make sure. + # @@valid_modes[:tty][:read] + # + # pipe modes + # + # io and file new valid modes : [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary] + # + # text file modes + @@valid_modes[:file][:text_read] = {:synonyms => ["r", "rt", "tr", :text_read, @@valid_modes[:flags][:file][:text_read]], :if_exist => @@valid_modes[:flags][:file][:text_read]} + @@valid_modes[:file][:text_readwrite] = {:synonyms => ["r+","r+t", "tr+", :text_readwrite, @@valid_modes[:flags][:file][:text_readwrite]], :if_exist => @@valid_modes[:flags][:file][:text_readwrite], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]} + @@valid_modes[:file][:text_write_overwrite] = {:synonyms => ["w","wt", "tw", :text_write, :text_write_overwrite, :text_write_truncate, @@valid_modes[:flags][:file][:text_write_trunc], @@valid_modes[:flags][:file][:text_write_create],(@@valid_modes[:flags][:file][:text_write_trunc] | @@valid_modes[:flags][:file][:text_write_create])], :if_exist => @@valid_modes[:flags][:file][:text_write_trunc], :if_create => @@valid_modes[:flags][:file][:text_write_create]} + @@valid_modes[:file][:text_readwrite_overwrite] = {:synonyms => ["w+","w+t", "tw+", :text_readwrite_overwrite, :text_readwrite_truncate, @@valid_modes[:flags][:file][:text_readwrite_trunc], @@valid_modes[:flags][:file][:text_readwrite_create], (@@valid_modes[:flags][:file][:text_readwrite_trunc] | @@valid_modes[:flags][:file][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:text_readwrite_trunc], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]} + @@valid_modes[:file][:text_write_append] = {:synonyms => ["a","at", "ta", :text_write_append, @@valid_modes[:flags][:file][:text_write_append], (@@valid_modes[:flags][:file][:text_write_append] | @@valid_modes[:flags][:file][:text_write_create])], :if_exist => @@valid_modes[:flags][:file][:text_write_append], :if_create => @@valid_modes[:flags][:file][:text_write_create]} + @@valid_modes[:file][:text_readwrite_append] = {:synonyms => ["a+","a+t", "ta+", :text_readwrite_append, @@valid_modes[:flags][:file][:text_readwrite_append], (@@valid_modes[:flags][:file][:text_readwrite_append] | @@valid_modes[:flags][:file][:text_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:text_readwrite_append], :if_create => @@valid_modes[:flags][:file][:text_readwrite_create]} + # + # binary file modes + @@valid_modes[:file][:binary_read] = {:synonyms => ["rb", "br", :binary_read, @@valid_modes[:flags][:file][:binary_read]], :if_exist => @@valid_modes[:flags][:file][:binary_read]} + @@valid_modes[:file][:binary_readwrite] = {:synonyms => ["r+b", "br+", :binary_readwrite, @@valid_modes[:flags][:file][:binary_readwrite]], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]} + @@valid_modes[:file][:binary_write_overwrite] = {:synonyms => ["wb", "bw", :binary_write, :binary_write_overwrite, :binary_write_truncate, @@valid_modes[:flags][:file][:binary_write_trunc], @@valid_modes[:flags][:file][:binary_write_create], (@@valid_modes[:flags][:file][:binary_write_trunc] | @@valid_modes[:flags][:file][:binary_write_create])], :if_exist => @@valid_modes[:flags][:file][:binary_write_trunc], :if_create => @@valid_modes[:flags][:file][:binary_write_create]} + @@valid_modes[:file][:binary_readwrite_overwrite] = {:synonyms => ["w+b", "bw+", :binary_readwrite_overwrite, :binary_readwrite_truncate, @@valid_modes[:flags][:file][:binary_readwrite_trunc], @@valid_modes[:flags][:file][:binary_readwrite_create], (@@valid_modes[:flags][:file][:binary_readwrite_trunc] | @@valid_modes[:flags][:file][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite_trunc], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]} + @@valid_modes[:file][:binary_write_append] = {:synonyms => ["ab", "ba", :binary_write_append, @@valid_modes[:flags][:file][:binary_write_append], (@@valid_modes[:flags][:file][:binary_write_append] | @@valid_modes[:flags][:file][:binary_write_create])], :if_exist => @@valid_modes[:flags][:file][:binary_write_append], :if_create => @@valid_modes[:flags][:file][:binary_write_create]} + @@valid_modes[:file][:binary_readwrite_append] = {:synonyms => ["a+b", "ba+", :binary_readwrite_append, @@valid_modes[:flags][:file][:binary_readwrite_append], (@@valid_modes[:flags][:file][:binary_readwrite_append] | @@valid_modes[:flags][:file][:binary_readwrite_create])], :if_exist => @@valid_modes[:flags][:file][:binary_readwrite_append], :if_create => @@valid_modes[:flags][:file][:binary_readwrite_create]} + # + # ### + @@valid_modes.freeze + # ### + protected + # + def self.valid_modes() + @@valid_modes + end + def self.interpret_mode(params={}) + # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom] + # + end + def self.valid_mode_set(params={}) + # [:read, :write, :binary, :sync, :tty, :duplex, :append, :create, :wsplit, :wsplit_initialized, :trunc, :text, :setenc_by_bom] + valid_modes = [] + valid_flags = [] + if params[:binary] + if params[:read] + if params[:write] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_readwrite] + else + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_read] + end + else + if params[:write] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:binary_write] + end + end + else + if params[:read] + if params[:write] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_readwrite] + else + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_read] + end + else + if params[:write] + valid_flags << @@valid_modes[:flags][(params[:type] || :io)][:text_write] + end + end + end + @@valid_modes[(params[:type] || :io)].keys.to_enum.each do |mode_key| + @@valid_modes[(params[:type] || :io)][(mode_key)][:synonyms].to_enum.each do |synonym| + if synonym.is_a?(Numeric) + valid_flags.to_enum.each do |flag| + if synonym & flag == flag + unless valid_modes.include?(@@valid_modes[(params[:type] || :io)][(mode_key)]) + valid_modes << @@valid_modes[(params[:type] || :io)][(mode_key)] + end + end + end + end + end + end + # + valid_modes + end + # + # + include ::GxG::Support::Library::IOPrepare + include ::GxG::Support::Library::CommonIOPrepare + include ::GxG::Support::Library::Transcoding + # + public + # + alias :closed_write? :closed? + alias :closed_read? :closed? + # + include ::GxG::Support::Library::TranscodingIO + # + def self.pipe(*args) + # Goal: make IO.pipe available on ALL platforms a la false-pipe for code portability (via VFS??). Duplex contains an @input and an @output IO channel + # pipes are not 'select' able on Winderz, so a faux-pipe must be shimmed in. + if [:kazoo_os].include?(GxG::SYSTEM.platform[:platform]) + # TODO: GxG::IO::IO::pipe : provide a ring buffer and return read and write endpoint ios for it where the platform does not support it + # See: https://en.wikipedia.org/wiki/Circular_buffer + # See also: http://comments.gmane.org/gmane.comp.lang.ruby.io-splice.general/11 + # + else + super(*args) + end + end + # Public Instance Methods: + def external_encoding=(*args) + # + unless self.binmode?() + if args[0].is_any?(::Encoding, ::NilClass) + # + if args[0] == ::Encoding::ASCII_8BIT + self.binmode() + else + # old_data = self.string().dup + # + if args[0].is_a?(::Encoding) + self.set_encoding(args[0]) + if @internal_encoding + if @internal_encoding != self.external_encoding + # preserve newline settings + nl_op = self.newline_option_used(:external) + options = {} + if nl_op + options[(nl_op)] = true + end + @conversion_options[:external] = ::String::transcoding_options(self.external_encoding,@internal_encoding,options) + end + end + else + self.set_encoding(::Encoding.default_external()) + end + # convert existing data??? + # if self.external_encoding() != old_data.encoding() + # # preserve newline settings + # nl_op = self.newline_option_used(:external) + # options = {} + # if nl_op + # options[(nl_op)] = true + # end + # old_data.transcode!(self.external_encoding(),options) + # # + # self.string.replace(old_data) + # end + # + end + # + else + raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}" + end + end + # + end + # + def initialize(*args) + # Sockets : http://www.tutorialspoint.com/ruby/ruby_socket_programming.htm + # io.c : http://rxr.whitequark.org/mri/source/io.c?v=1.9.3#8028 + # File Permission Modes : http://www.tutorialspoint.com/ruby/ruby_input_output.htm + params = self.process_parameters(*args) + mode_string = "" + mode_numeric = 0 + if params[:file_descriptor] + mode_flags = {:binary => ::File::BINARY, :text => ::File::TEXT, :read => ::File::RDONLY, :write => ::File::WRONLY, :readwrite => ::File::RDWR, :create => ::File::CREAT, :overwrite => ::File::TRUNC, :append => ::File::APPEND} + modes = GxG::IO::IO::valid_mode_set({:type => :file, :read => true, :write => true, :text => true, :binary => true}) + else + mode_flags = {:binary => ::IO::BINARY, :text => ::IO::TEXT, :read => ::IO::RDONLY, :write => ::IO::WRONLY, :readwrite => ::IO::RDWR, :create => ::IO::CREAT, :overwrite => ::IO::TRUNC, :append => ::IO::APPEND} + modes = GxG::IO::IO::valid_mode_set({:type => :io, :read => true, :write => true, :text => true, :binary => true}) + end + the_mode_used = nil + options = {} + # :mode + #Same as mode parameter + if params[:mode] + if params[:mode].index(:binary) + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "binary_readwrite" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite]) + else + mode_string = "binary_read" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read]) + end + else + if params[:mode].index(:write) + mode_string = "binary_write" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write]) + end + end + else + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "text_readwrite" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite]) + else + mode_string = "text_read" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read]) + end + else + if params[:mode].index(:write) + mode_string = "text_write" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write]) + end + end + end + if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) ) + mode_string << "_overwrite" + mode_numeric = (mode_numeric | mode_flags[:overwrite]) + else + if params[:mode].index(:append) + mode_string << "_append" + mode_numeric = (mode_numeric | mode_flags[:append]) + else + if params[:mode].index(:create) + mode_string << "_create" + mode_numeric = (mode_numeric | mode_flags[:create]) + end + end + end + # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary] + modes.to_enum.each do |mode_entry| + mode_entry[:synonyms].to_enum.each do |the_synonym| + if the_synonym.is_a?(Symbol) + if the_synonym == mode_string.to_sym + the_mode_used = mode_entry + break + end + else + if the_synonym.is_a?(Numeric) + if (the_synonym & mode_numeric == mode_numeric) + the_mode_used = mode_entry + break + end + end + end + end + if the_mode_used + break + end + # + end + if (the_mode_used) + if params[:file_descriptor] + # At this point, only fs objects that already exist will be referenced here. + if (mode_numeric & mode_flags[:create]) == mode_flags[:create] + if the_mode_used[:if_create] + options[:mode] = the_mode_used[:if_create].to_i + else + raise ArgumentError, "you cannot open read-only on a non-existent file system object" + end + else + options[:mode] = the_mode_used[:if_exist].to_i + end + else + # IO-ish object. + # When the mode of original IO is read only, the mode cannot be changed to be writable. Similarly, + # the mode cannot be changed from write only to readable. When such a change is attempted + # the error is raised in different locations according to the platform. + if params[:object].is_any?(::IO, GxG::IO::IO) + if (params[:object].stat.readable? && (params[:mode].include?(:write) || params[:mode].include?(:readwrite)) && (params[:object].stat.writable? == false)) + raise ArgumentError, "you cannot make a read-only IO writable" + end + if (params[:object].stat.writable? && (params[:mode].include?(:read) || params[:mode].include?(:readwrite)) && (params[:object].stat.readable? == false)) + raise ArgumentError, "you cannot make a write-only IO readable" + end + if (params[:object].stat.binmode? && params[:mode].include?(:text)) + raise ArgumentError, "you cannot make a binary IO text-only" + end + else + unless params[:object].is_any?( ::StringIO, GxG::IO::StringIO) + raise ArgumentError, "you MUST specify an IO :object or a :file_descriptor Fixnum" + end + end + # if params[:mode].include?(:binary) + # unless params[:object].binmode? + # params[:object].binmode + # end + # end + if (mode_numeric & mode_flags[:create]) == mode_flags[:create] + if the_mode_used[:if_create] + options[:mode] = the_mode_used[:if_create].to_i + else + raise ArgumentError, "you cannot open read-only on a non-existent file system object" + end + else + options[:mode] = the_mode_used[:if_exist].to_i + end + end + # + else + modes_list = [] + modes.to_enum.each do |item| + modes_list << item[:synonyms] + end + raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}" + end + end + # + @conversion_options = {:external => {}, :internal => {}} + # + if params[:mode].include?(:binary) + options[:external_encoding] = ::Encoding::ASCII_8BIT + else + if params[:external_encoding].is_a?(::Encoding) + options[:external_encoding] = params.delete(:external_encoding) + #:2nd internal_encoding parameter and 3rd parameter optional hash ignored + # if you want the supplied string's encoding - best to simply pass it on the .new method call params. + else + options[:external_encoding] = ::Encoding.default_external + end + end + # + # if external_encoding is BINARY/ASCII_8BIT - just nullify internal_encoding, and skip external/internal conversion option mapping. + if params[:mode].include?(:binary) + @internal_encoding = nil + else + if (params[:internal_encoding].is_a?(::Encoding) && params[:internal_encoding] != options[:external_encoding]) + @internal_encoding = params.delete(:internal_encoding) + else + @internal_encoding = ::Encoding.default_internal + end + # + if @internal_encoding + if @internal_encoding != options[:external_encoding] + if params[:external_conversion] + # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding + @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding,params[:external_conversion]) + else + @conversion_options[:external] = ::String::transcode_options(options[:external_encoding],@internal_encoding) + end + # + if @internal_encoding == ::Encoding::ASCII_8BIT + # + if params[:internal_conversion] + nl_op = self.newline_option_used(params[:internal_conversion]) + conv_options = {} + if nl_op + conv_options[(nl_op)] = true + end + @conversion_options[:internal] = conv_options + end + else + if params[:internal_conversion] + @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding],params[:internal_conversion]) + else + @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,options[:external_encoding]) + end + end + # + end + end + # + end + # + @external_field_separator = nil + if params[:external_field_separator] + @external_field_separator = params[:external_field_separator] + end + @external_record_separator = nil + if params[:external_record_separator] + @external_record_separator = params[:external_record_separator] + end + @internal_field_separator = nil + if params[:internal_field_separator] + @internal_field_separator = params[:internal_field_separator] + end + @internal_record_separator = nil + if params[:internal_record_separator] + @internal_record_separator = params[:internal_record_separator] + end + #:autoclose + # If the value is false, the fd will be kept open after this IO instance gets finalized. + if params[:autoclose] + options[:autoclose] = params.delete(:autoclose) + end + # + mode_numeric = options.delete(:mode) + if params[:file_descriptor] + super(params[:file_descriptor],mode_numeric,options) + else + super(params[:object],mode_numeric,options) + end + # + unless params[:object].is_any?(::StringIO, GxG::IO::StringIO) + self.fcntl(::Fcntl::F_SETFL,::Fcntl::O_NONBLOCK) + end + # + self + end + # portable, cross-platform facade for Fcntl (even under Winderz) + def fcntl(*args) + # Thanks to Jon Cooper for the find at http://www.mail-archive.com/beanstalk-talk@googlegroups.com/msg01092.html + #The FD_CLOEXEC flag is part of the POSIX API, which Windows doesn't support, + #but it does provide analogous functionality. + # + #See http://www.perlmonks.org/index.pl?node_id=574349 + # + #You can implement a patch with https://github.com/jarib/childprocess/ which + #appears to provide a consistent facade. + # + #See + #http://rubydoc.info/github/jarib/childprocess/master/ChildProcess#close_on_exec-class_method + # + #Cheers, + #Jon + # + # Attribution: https://github.com/jarib for Childprocess work. + # + # For Win32 See: http://rxr.whitequark.org/mri/source/win32/win32.c?v=1.9.3#fcntl + # ... and older stuff at : http://rxr.whitequark.org/mri/source/wince/fcntl.h?v=1.8.7 + # Ruby General Fcntl Stuff: http://ruby-doc.org/stdlib-1.9.3/libdoc/fcntl/rdoc/Fcntl.html + # + # ### fcntl commands: + # F_DUPFD Integer Positive Integer; -1 on error Find the lowest numbered available file descriptor greater than or equal to arg and make it a copy of the receiver’s file descriptor. If arg is omitted, it is assumed to be equal to the receiver’s file descriptor. + # F_GETFD N/A File descriptor flags Retrieves the associated file descriptor flags. Currently, these are either 0 or FD_CLOEXEC. These flags may be set with F_SETFD. + # F_GETFL N/A Integer Returns the file status flags, i.e. a bitwise OR of O_APPEND, O_ASYNC, O_DIRECT, etc. O_ACCMODE is a bitmask for extracting the access mode from these flags. + # F_GETLK struct flock * N/A The argument describes a lock the caller wishes to place on the file. If this is possible, the l_type field of the struct is set to Fcntl::F_UNLCK; otherwise the struct is updated with details of the current lock holder. + # F_SETFD FD_CLOEXEC or 0 0; -1 on error Sets the file descriptor flags to arg.When arg is FD_CLOEXEC, this is equivalent to #close_on_exec=true. + # F_SETFL Integer 0; -1 on error Set the file status flags to arg + # F_SETLK struct flock * 0; -1 on error. When the struct’s l_type field has the value F_RDLCK or F_WRLCK, acquires the lock; when it has the value F_UNLCK, releases the lock. + # F_SETLKW struct flock * 0; -1 on error. Behaves like F_SETLK, except when a conflicting lock is held this call blocks until the lock is released or a signal is caught. + # + fcntl_modes = {} + case GxG::SYSTEM.platform()[:platform] + when :windows + # unless nil + # fcntl_modes[:io] = {} + # end + # I *think* as of 1.9.2+ winderz now has fcntl support in ruby code, see: win32.c + # TODO: test fcntl under Winderz + super(*args) + else + super(*args) + end + end + # + def read(length=0,outbuffer="") + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + if outbuffer.is_a?(::String) + outbuffer = self.transcode_to_internal(outbuffer) + if self.internal_encoding + if outbuffer.encoding != self.internal_encoding() + outbuffer.force_encoding(self.internal_encoding()) + end + end + end + # + # Attribution: MRI 1.9.3 docs. + # TODO: research *proper* non-blocking reads and writes + #read_nonblock just calls the read(2) system call. It causes all errors the + #read(2) system call causes: Errno::EWOULDBLOCK, Errno::EINTR, etc. The caller + #should care such errors. + # + #If the exception is Errno::EWOULDBLOCK or Errno::AGAIN, it is extended by + #IO::WaitReadable. So IO::WaitReadable can be used to rescue the exceptions for + #retrying read_nonblock. + # + #read_nonblock causes EOFError on EOF. + # + #If the read byte buffer is not empty, read_nonblock reads from the buffer like + #readpartial. In this case, the read(2) system call is not called. + # + #When read_nonblock raises an exception kind of IO::WaitReadable, read_nonblock + #should not be called until io is readable for avoiding busy loop. This can be + #done as follows. + # + # # emulates blocking read (readpartial). + # begin + # result = io.read_nonblock(maxlen) + # rescue IO::WaitReadable + # IO.select([io]) + # retry + # end + # + #Although IO#read_nonblock doesn't raise IO::WaitWritable. + #OpenSSL::Buffering#read_nonblock can raise IO::WaitWritable. If IO and SSL + #should be used polymorphically, IO::WaitWritable should be rescued too. See + #the document of OpenSSL::Buffering#read_nonblock for sample code. + # + #Note that this method is identical to readpartial except the non-blocking flag + #is set. + # millisecond: 1/1000th of a second (reminder) + read_bytes = 0 + # unless IO is some other type, use default buffer size + if (self.tty? || self.stat.pipe?) + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i + else + if self.stat.socket? + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:read][:initial].to_i + else + # assume file + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i + end + end + # + until (read_bytes == length) + begin + if ((length - read_bytes) < buffer_size) + outbuffer << self.transcode_to_internal(read_nonblock((length - read_bytes))) + read_bytes += (length - read_bytes) + else + outbuffer << self.transcode_to_internal(read_nonblock(buffer_size)) + read_bytes += buffer_size + end + # + rescue ::IO::WaitReadable, ::IO::WaitWritable, GxG::IO::IO::WaitReadable, GxG::IO::IO::WaitWritable + pause + begin + # about 10 ms + selected = GxG::IO::IO::select([self],nil,nil,0.010) + until selected + pause + selected = GxG::IO::IO::select([self],nil,nil,0.010) + end + rescue Exception + pause + retry + end + retry + end + # + pause + end + # + outbuffer + else + result = self.transcode_to_internal(super(length)) + end + result + end + # + def readpartial(*args) + # args[0] maxlen (Fixnum) + # args[1] (optional) buffer_object + # Note: use the same approach to pause/retry as IO.read_nonblock. + # + # Reads at most maxlen bytes from the I/O stream. It blocks only if ios has no data immediately available. It doesn’t block if some data available. + # If the optional outbuf argument is present, it must reference a String, which will receive the data. It raises EOFError on end of file. + # readpartial is designed for streams such as pipe, socket, tty, etc. It blocks only when no data immediately available. + # This means that it blocks only when following all conditions hold: + # the byte buffer in the IO object is empty. + # the content of the stream is empty. + # the stream is not reached to EOF. + # When readpartial blocks, it waits data or EOF on the stream. If some data is reached, readpartial returns with the data. If EOF is reached, + # readpartial raises EOFError. + # When readpartial doesn’t blocks, it returns or raises immediately. If the byte buffer is not empty, it returns the data in the buffer. + # Otherwise if the stream has some content, it returns the data in the stream. Otherwise if the stream is reached to EOF, it raises EOFError. + # + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + result = "" + result.force_encoding(self.internal_encoding) + data = self.read(*args) + if data + # non-nil + if data.is_a?(::String) + # 'blocking' or not? + if (self.eof?() || (data && data.to_s.size > 0)) + # no pseudo-blocking + result << data + data = nil + else + # pseudo-blocking + until (self.eof?() || (data && data.to_s.size > 0)) + data = self.read(*args) + # string, buffer, or nil + if data + # + if data.is_a?(::String) + if (data && data.to_s.size > 0) + result << data + data = nil + break + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + else + # buffer + result = data + data = nil + break + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + pause + # + end + end + else + # buffer + result = data + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + if result + if result.is_a?(::String) + if result.bytesize > 0 + result + else + nil + end + else + # buffer + result + end + else + nil + end + # + end + end + end + # + def write(data="") + if self.fcntl(::Fcntl::F_GETFL,::Fcntl::O_NONBLOCK) & ::Fcntl::O_NONBLOCK == ::Fcntl::O_NONBLOCK + # on Winderz, just to a buffer sized spoon full until done ?? + # written_bytes = self.write_nonblock(data) + # TODO: research *proper* non-blocking reads and writes + data = self.transcode_to_external(data) + length = data.size + written_bytes = 0 + # unless IO is some other type, use default buffer size + if (self.tty? || self.stat.pipe?) + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:terminal].to_i + else + if self.stat.socket? + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:socket][:ipc][:write][:initial].to_i + else + # assume file + buffer_size = GxG::SYSTEM.memory_limits()[:buffers][:default].to_i + end + end + until (written_bytes == length) + # on Winderz, use self.write(data) ?? + chunk = "" + chunk.force_encoding(data.encoding) + if ((written_bytes) + (buffer_size - 1)) > length + chunk << data.slice((written_bytes),(length - 1)) + else + chunk << data.slice((written_bytes),((written_bytes) + (buffer_size - 1))) + end + # + begin + # On some platforms such as Windows, write_nonblock is not supported according + # to the kind of the IO object. In such cases, write_nonblock raises + # Errno::EBADF. + # # write_nonblock writes only 65536 bytes and return 65536. + # (The pipe size is 65536 bytes on this environment.) + # s = "a" * 100000 + # p w.write_nonblock(s) #=> 65536 + # + # # write_nonblock cannot write a byte and raise EWOULDBLOCK (EAGAIN). + # p w.write_nonblock("b") # Resource temporarily unavailable (Errno::EAGAIN) + # + #If the write buffer is not empty, it is flushed at first. + # + #When write_nonblock raises an exception kind of IO::WaitWritable, + #write_nonblock should not be called until io is writable for avoiding busy + #loop. This can be done as follows. + # + # bytes_written = super(data) + written_bytes += write_nonblock(chunk).to_i + rescue ::IO::WaitWritable, GxG::IO::IO::WaitWritable, ::Errno::EINTR, ::Errno::EWOULDBLOCK, ::Errno::EAGAIN + pause + begin + selected = GxG::IO::IO::select(nil,[self],nil,0.010) + until selected + pause + selected = GxG::IO::IO::select(nil,[self],nil,0.010) + end + rescue Exception + pause + retry + end + retry + end + # + pause + end + else + written_bytes = super(self.transcode_to_external(data)) + end + written_bytes + end + # + def <<(*args) + self.write(*args) + self + end + # + # + end + # + class StringIO < ::StringIO + # Wrapper class around ::StringIO ensuring cooperative processing, and optional in-line auto-transcoding, on most methods + # + protected + # + # Protected Instance Methods + include ::GxG::Support::Library::StringIOPrepare + include ::GxG::Support::Library::CommonIOPrepare + include ::GxG::Support::Library::Transcoding + # + public + # Public class methods + include ::GxG::Support::Library::TranscodingIO + # + def self.open(the_string="",mode=::IO::RDWR,&block) + result = nil + the_io = GxG::IO::StringIO.new(the_string,mode) + # + if block.respond_to?(:call) + result = block.call(the_io) + the_io.close + else + result = the_io + end + # + result + end + # + # Public instance methods + def flags() + result = [] + current = self.fcntl(::Fcntl::F_GETFL) + flags = {} + if self.binmode?() + result << :binary + if self.external_encoding == ::Encoding::ASCII_8BIT + result << :setenc_by_bom + end + else + result << :text + result << :wsplit + end + if self.tty? + result << :tty + end + # + #flag[:read] = 0x00000001 + #flag[:write] = 0x00000002 + #flag[:readwrite] = (flag[:read] | flag[:write]) + #flag[:binmode] = 0x00000004 + #flag[:readwrite] = (flag[:read] | flag[:write]) + flags[:read] = ::Fcntl::O_RDONLY + flags[:write] = ::Fcntl::O_WRONLY + flags[:readwrite] =::Fcntl::O_RDWR + #flags[:binary] = 0x00000004 + flags[:sync] = ::File::SYNC # no internal buffering -- The file will be opened for synchronous I/O. No write operation will complete until the data has been physically written to disk. + flags[:dsync] = ::File::DSYNC # only normal data be synchronized after each write operation, not metadata. + flags[:rsync] = ::File::RSYNC # the synchronization of read requests as well as write requests. It must be used with one of IO::SYNC or IO::DSYNC + #flags[:tty] = 0x00000010 + flags[:notcontrol_tty] = ::Fcntl::O_NOCTTY + flags[:duplex] = 0x00000020 + flags[:append] = ::Fcntl::O_APPEND + flags[:create] = ::Fcntl::O_CREAT + flags[:exclusive] = ::Fcntl::O_EXCL + # flags[:wsplit] = 0x00000200 + flags[:wsplit_initialized] = 0x00000400 + flags[:trunc] = ::Fcntl::O_TRUNC + #flags[:text] = 0x00001000 + #flags[:setenc_by_bom] = 0x00100000 + flags[:seek_set] = ::File::SEEK_SET + flags[:seek_current] = ::File::SEEK_CUR + flags[:seek_end] = ::File::SEEK_END + # ### file region locking: + flags[:shared_lock] = ::File::LOCK_SH # for reading + flags[:exclusive_lock] = ::File::LOCK_EX # for writing (implies blocking) + flags[:nonblock_lock] = ::File::LOCK_NB # combine with exclusive_lock for immediate non-blocking lock for writing. + flags[:unlock] = ::File::LOCK_UN + # + flags[:nonblocking] = ::Fcntl::O_NONBLOCK + flags[:ndelay] = ::Fcntl::O_NDELAY + # unrelated to in-memory IO-like objects + # flags[:nofollow] = ::File::NOFOLLOW # Do not follow symlinks. + # flags[:noaccesstime] = ::File::NOATIME # Do not update the access time (atime) of the file. + # flags[:match_noescape] = ::File::FNM_NOESCAPE # + # flags[:match_pathname] = ::File::FNM_PATHNAME # + # flags[:match_dotmatch] = ::File::FNM_DOTMATCH # + # flags[:match_casefold] = ::File::FNM_CASEFOLD # + # flags[:match_systemcase] = ::File::FNM_SYSCASE # + # + flags.keys.to_enum.each do |the_flag_key| + if (current & flags[(the_flag_key)]) == flags[(the_flag_key)] + case the_flag_key + when :readwrite + if result.index(:write) + result << :read + else + if result.index(:read) + result << :write + end + end + when :wsplit_initialized + if result.index(:wsplit) + result[(result.index(:wsplit))] = :wsplit_initialized + else + result << :wsplit_initialized + end + else + result << the_flag_key + end + end + end + # + result + end + # + def initialize(*args) + # TODO: override methods to operate in cooperative processing event system + # TODO: provide a REAL external:internal encoding translation aspect (stock version is poorly designed/wrong, it would act *exactly* like a real IO) + # TODO: optimize this class for memory and call efficiency (see Question at super() call) + # LATER: GxG::StringIO : create/learn/whatever an optimized any-encoding-to-any-encoding transcoding/normalization matrix : possibly extend Encoding Class? This should include a conversion newline/field_separator/record_separator for each pair of Encodings. + # Question: is there some possible benefit to using a ByteArray instead of a stock StringIO ? + params = self.process_parameters(*args) + unless params[:mode].size > 0 + params[:mode] << :read + params[:mode] << :write + end + if params[:external_encoding] == ::Encoding::ASCII_8BIT + unless params[:mode].include?(:binary) + params[:mode] << :binary + end + end + # + mode_string = "" + mode_numeric = 0 + @fcntl_mode = ::Fcntl::O_NONBLOCK + mode_flags = {:binary => ::IO::BINARY, :text => ::IO::TEXT, :read => ::IO::RDONLY, :write => ::IO::WRONLY, :readwrite => ::IO::RDWR, :create => ::IO::CREAT, :overwrite => ::IO::TRUNC, :append => ::IO::APPEND} + modes = GxG::IO::IO::valid_mode_set({:type => :io, :read => true, :write => true, :text => true, :binary => true}) + the_mode_used = nil + options = {} + # :mode + #Same as mode parameter + if params[:mode] + if params[:mode].index(:binary) + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "binary_readwrite" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR) + else + mode_string = "binary_read" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY) + end + else + if params[:mode].index(:write) + mode_string = "binary_write" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY) + end + end + else + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "text_readwrite" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDWR) + else + mode_string = "text_read" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_RDONLY) + end + else + if params[:mode].index(:write) + mode_string = "text_write" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_WRONLY) + end + end + end + if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) ) + mode_string << "_overwrite" + mode_numeric = (mode_numeric | mode_flags[:overwrite]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_TRUNC) + else + if params[:mode].index(:append) + mode_string << "_append" + mode_numeric = (mode_numeric | mode_flags[:append]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_APPEND) + else + if params[:mode].index(:create) + mode_string << "_create" + mode_numeric = (mode_numeric | mode_flags[:create]) + @fcntl_mode = (@fcntl_mode | ::Fcntl::O_CREAT) + end + end + end + # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary] + modes.to_enum.each do |mode_entry| + mode_entry[:synonyms].to_enum.each do |the_synonym| + if the_synonym.is_a?(Symbol) + if the_synonym == mode_string.to_sym + the_mode_used = mode_entry + break + end + else + if the_synonym.is_a?(Numeric) + if (the_synonym & mode_numeric == mode_numeric) + the_mode_used = mode_entry + break + end + end + end + end + if the_mode_used + break + end + # + end + if (the_mode_used) + options[:mode] = the_mode_used[:if_exist].to_i + else + modes_list = [] + modes.to_enum.each do |item| + modes_list << item[:synonyms] + end + raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}" + end + end + # + mode_numeric = options.delete(:mode) + # + @conversion_options = {:external => {}, :internal => {}} + # init-text clobber work-around : defer :write of init-text until the end. + # init_text = params[:object].dup + # params[:object].clear + # FORNOW: just use default behavior + super(params[:object],mode_numeric) + # + if params[:mode].include?(:binary) + self.set_encoding(::Encoding::ASCII_8BIT) + else + if params[:external_encoding].is_a?(::Encoding) + self.set_encoding(params.delete(:external_encoding)) + #:2nd internal_encoding parameter and 3rd parameter optional hash ignored + # if you want the supplied string's encoding - best to simply pass it on the .new method call params. + else + self.set_encoding(::Encoding.default_external) + end + end + # + # if external_encoding is BINARY/ASCII_8BIT - just nullify internal_encoding, and skip external/internal conversion option mapping. + if params[:mode].include?(:binary) + @internal_encoding = nil + else + if params[:internal_encoding].is_a?(::Encoding) + @internal_encoding = params.delete(:internal_encoding) + else + @internal_encoding = ::Encoding.default_internal + end + # + if @internal_encoding + if @internal_encoding != self.external_encoding + if params[:external_conversion] + # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding + @conversion_options[:external] = ::String::transcode_options(self.external_encoding,@internal_encoding,params[:external_conversion]) + else + @conversion_options[:external] = ::String::transcode_options(self.external_encoding,@internal_encoding) + end + # + if @internal_encoding == ::Encoding::ASCII_8BIT + # + if params[:internal_conversion] + nl_op = self.newline_option_used(params[:internal_conversion]) + conv_options = {} + if nl_op + conv_options[(nl_op)] = true + end + @conversion_options[:internal] = conv_options + end + else + if params[:internal_conversion] + @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,self.external_encoding,params[:internal_conversion]) + else + @conversion_options[:internal] = ::String::transcode_options(@internal_encoding,self.external_encoding) + end + end + # + end + end + # + end + # + @external_field_separator = nil + if params[:external_field_separator] + self.external_field_separator = params[:external_field_separator] + end + @external_record_separator = nil + if params[:external_record_separator] + self.external_record_separator = params[:external_record_separator] + end + @internal_field_separator = nil + if params[:internal_field_separator] + self.external_field_separator = params[:internal_field_separator] + end + @internal_record_separator = nil + if params[:internal_record_separator] + self.external_field_separator = params[:internal_record_separator] + end + # + # init-text clobber workaround : deferred :write + #self.write(init_text) + self + end + # + # :reopen, :string, :string, :lineno, :lineno, :close_read, :close_write, :closed?, :closed_read?, :closed_write?, :eof, :eof?, :fcntl, :flush, :fsync, :pos, :pos, :rewind, :seek, :sync, :sync, :tell, :each, :each_line, :lines, :each_codepoint, :codepoints, :getc, :ungetc, :ungetbyte, :readchar, :getbyte, :readbyte, :gets, :readline, :readlines, :read, :sysread, :readpartial, :read_nonblock, :write, :, :print, :printf, :putc, :puts, :syswrite, :write_nonblock, :isatty, :tty?, :pid, :fileno, :size, :length, :truncate, :external_encoding, :internal_encoding, :set_encoding + # + def inspect() + ("<#" << "#{self.class}:#{super().split(" ")[0].split(":").last}") + end + # + def binmode?() + if self.external_encoding == ::Encoding::ASCII_8BIT + true + else + false + end + end + # + def binmode() + if self.binmode? + self + else + @internal_encoding = nil + @conversion_options[:internal] = {} + @conversion_options[:external] = {} + self.set_encoding(::Encoding::ASCII_8BIT) + super() + end + end + # + def external_encoding=(*args) + # + unless self.binmode? + if args[0].is_any?(::Encoding, ::NilClass) + # + if args[0] == ::Encoding::ASCII_8BIT + self.binmode() + else + old_data = self.string().dup + # + if args[0].is_a?(::Encoding) + self.set_encoding(args[0]) + if @internal_encoding + if @internal_encoding != self.external_encoding + # preserve newline settings + nl_op = self.newline_option_used(:external) + options = {} + if nl_op + options[(nl_op)] = true + end + @conversion_options[:external] = ::String::transcoding_options(self.external_encoding,@internal_encoding,options) + end + end + else + self.set_encoding(::Encoding.default_external()) + end + # + if self.external_encoding() != old_data.encoding() + # preserve newline settings + nl_op = self.newline_option_used(:external) + options = {} + if nl_op + options[(nl_op)] = true + end + old_data.transcode!(self.external_encoding(),options) + # + self.string.replace(old_data) + end + # + end + # + else + raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}" + end + end + # + end + # + def read(*args) + # read length bytes from StringIO and optionally route to a buffer object + if self.closed_read? + raise IOError, "not open for reading" + else + if args[0].is_a?(Numeric) + # length + data = super(args[0].to_i) + else + # LATER: provide for StringIO buffer size manipulation??? what is default read length?? + data = super() + end + if data.is_a?(::String) + data = self.transcode_to_internal(data) + # buffer + if args[1].is_any?(::String, ::IO, ::StringIO, ::GxG::ByteArray) + # check which classes this can use and in what ways. + data.to_enum(:chars).each do |the_character| + args[1] << the_character + end + # return buffer object + args[1] + else + # return string + data + end + else + # return nil + nil + end + # + end + end + alias :read_nonblock :read + alias :sysread :read + # + def write(data="") + # writes from (self.pos) forward : this will clobber any init string that was passed. + written = 0 + if self.closed_write? + raise IOError, "not open for writing" + else + if data.is_a?(::String) + data = self.transcode_to_external(data.dup) + else + data = self.transcode_to_external(data) + end + if data.size > 0 + written = super(data) + end + end + written + end + alias :write_nonblock :write + alias :syswrite :write + # + def <<(*args) + self.write(*args) + self + end + # + def readpartial(*args) + # args[0] maxlen (Fixnum) + # args[1] (optional) buffer_object + # Note: use the same approach to pause/retry as IO.read_nonblock. + # + # Reads at most maxlen bytes from the I/O stream. It blocks only if ios has no data immediately available. It doesn’t block if some data available. + # If the optional outbuf argument is present, it must reference a String, which will receive the data. It raises EOFError on end of file. + # readpartial is designed for streams such as pipe, socket, tty, etc. It blocks only when no data immediately available. + # This means that it blocks only when following all conditions hold: + # the byte buffer in the IO object is empty. + # the content of the stream is empty. + # the stream is not reached to EOF. + # When readpartial blocks, it waits data or EOF on the stream. If some data is reached, readpartial returns with the data. If EOF is reached, + # readpartial raises EOFError. + # When readpartial doesn’t blocks, it returns or raises immediately. If the byte buffer is not empty, it returns the data in the buffer. + # Otherwise if the stream has some content, it returns the data in the stream. Otherwise if the stream is reached to EOF, it raises EOFError. + # + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + result = "" + result.force_encoding(self.internal_encoding) + data = self.read(*args) + if data + # non-nil + if data.is_a?(::String) + # 'blocking' or not? + if (self.eof?() || (data && data.to_s.size > 0)) + # no pseudo-blocking + result << data + data = nil + else + # pseudo-blocking + until (self.eof?() || (data && data.to_s.size > 0)) + data = self.read(*args) + # string, buffer, or nil + if data + # + if data.is_a?(::String) + if (data && data.to_s.size > 0) + result << data + data = nil + break + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + else + # buffer + result = data + data = nil + break + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + pause + # + end + end + else + # buffer + result = data + end + else + if self.eof?() + raise EOFError, "end of stream reached" + end + end + # + if result + if result.is_a?(::String) + if result.bytesize > 0 + result + else + nil + end + else + # buffer + result + end + else + nil + end + # + end + end + end + # + def close() + super() + end + # + def close_read() + super() + end + # + def close_write() + super() + end + # + end + # + class Input < GxG::IO::IO + # ensure mode is read-only, else raise Exception + def initialize(*args) + params = self.process_parameters(*args) + if params[:mode].index(:write) + params[:mode].delete(:write) + end + if params[:mode].index(:readwrite) + params[:mode].delete(:readwrite) + end + super(params) + self + end + end + # + class Output < GxG::IO::IO + # ensure mode is write-only, else raise Exception + def initialize(*args) + params = self.process_parameters(*args) + if params[:mode].index(:read) + params[:mode].delete(:read) + end + if params[:mode].index(:readwrite) + params[:mode].delete(:readwrite) + end + unless params[:mode].index(:write) + params[:mode] << :write + end + super(params) + self + end + end + # + class Duplex < GxG::IO::IO + def initialize(*args) + params = self.process_parameters(*args) + if params[:mode].index(:read) + params[:mode].delete(:read) + end + if params[:mode].index(:write) + params[:mode].delete(:write) + end + unless params[:mode].index(:readwrite) + params[:mode] << :readwrite + end + super(params) + self + end + end + # + class File < GxG::IO::IO + # As of Ruby 1.9.2: if a file is not set to :binary mode, it defaults to :text mode. + # See: http://www.tutorialspoint.com/ruby/ruby_input_output.htm + # .stat table: (mode example : 0100664) + # 0700 rwx mask for owner + # 0400 r for owner + # 0200 w for owner + # 0100 x for owner + # 0070 rwx mask for group + # 0040 r for group + # 0020 w for group + # 0010 x for group + # 0007 rwx mask for other + # 0004 r for other + # 0002 w for other + # 0001 x for other + # 4000 Set user ID on execution + # 2000 Set group ID on execution + # 1000 Save swapped text, even after use + # + def initialize(*args) + if args[0].is_a?(String) + the_path = args[0] + the_mode = (args[1] || "r") + the_permissions = args[2] + the_options = args[3] + else + raise ArgumentError, "filename or path must be passed as a String" + end + # + if the_options + params = self.process_parameters(0,the_mode,the_options) + else + params = self.process_parameters(0,the_mode) + end + # + mode_flags = {:binary => ::File::BINARY, :text => ::File::TEXT, :read => ::File::RDONLY, :write => ::File::WRONLY, :readwrite => ::File::RDWR, :create => ::File::CREAT, :overwrite => ::File::TRUNC, :append => ::File::APPEND} + modes = GxG::IO::IO::valid_mode_set({:type => :file, :read => true, :write => true, :text => true, :binary => true}) + the_mode_used = nil + mode_string = "" + mode_numeric = 0 + # + if params[:mode] + if params[:mode].index(:binary) + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "binary_readwrite" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:readwrite]) + else + mode_string = "binary_read" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:read]) + end + else + if params[:mode].index(:write) + mode_string = "binary_write" + mode_numeric = (mode_numeric | mode_flags[:binary] | mode_flags[:write]) + end + end + else + if params[:mode].index(:read) + if params[:mode].index(:write) + mode_string = "text_readwrite" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:readwrite]) + else + mode_string = "text_read" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:read]) + end + else + if params[:mode].index(:write) + mode_string = "text_write" + mode_numeric = (mode_numeric | mode_flags[:text] | mode_flags[:write]) + end + end + end + if (params[:mode].index(:overwrite) || params[:mode].index(:truncate) ) + mode_string << "_overwrite" + mode_numeric = (mode_numeric | mode_flags[:overwrite]) + else + if params[:mode].index(:append) + mode_string << "_append" + mode_numeric = (mode_numeric | mode_flags[:append]) + end + end + # [:read, :write, :readwrite, :overwrite, :truncate:, :append, :text, :binary] + modes.to_enum.each do |mode_entry| + if (mode_entry[:synonyms].include?(mode_string.to_sym) && mode_entry[:synonyms].include?(mode_numeric)) + the_mode_used = mode_entry + break + end + # + end + if (the_mode_used) + options = {} + the_mode = nil + if params[:external_encoding] + options[:external_encoding] = params.delete(:external_encoding) + end + if params[:internal_encoding] + #If the value is nil no conversion occurs. + if params[:internal_encoding] == params[:external_encoding] + options[:internal_encoding] = nil + else + options[:internal_encoding] = params.delete(:internal_encoding) + end + end + if params[:autoclose] + options[:autoclose] = params.delete(:autoclose) + end + if ::File::exist?(the_path) + the_mode = the_mode_used[:if_exist].to_i + else + if the_mode_used[:if_create] + the_mode = the_mode_used[:if_create].to_i + else + raise ArgumentError, "you cannot operate upon a non-existent file in read-only mode" + end + end + if the_mode + file_descriptor = GxG::IO::IO::sysopen(the_path,the_mode) + else + file_descriptor = nil + end + # + if file_descriptor + # + if ::File::directory?(the_path) + raise ArgumentError, "you cannot operate upon a directory as if it were a file" + else + if the_permissions + super(file_descriptor, the_mode, the_permissions, options) + else + super(file_descriptor, the_mode, nil, options) + end + end + # + else + raise Exception, "unable to create a valid file descriptor with: #{args.inspect}" + end + # + else + modes_list = [] + modes.to_enum.each do |item| + modes_list << item[:synonyms] + end + raise ArgumentError, "#{params[:mode].inspect} is an invalid :mode, use one of the following: #{modes_list.inspect}" + end + end + # + @path = the_path + self + end + # + def to_path() + @path.dup + end + end + # End IO Module + end + # End GxG Module +end \ No newline at end of file diff --git a/lib/gxg/gxg_net.rb b/lib/gxg/gxg_net.rb new file mode 100644 index 0000000..500f97b --- /dev/null +++ b/lib/gxg/gxg_net.rb @@ -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 +## \ No newline at end of file diff --git a/lib/gxg/gxg_transcode.rb b/lib/gxg/gxg_transcode.rb new file mode 100644 index 0000000..5ffe48b --- /dev/null +++ b/lib/gxg/gxg_transcode.rb @@ -0,0 +1,1053 @@ +# Auto-Transcoding Support Library Module ############################################ +module GxG + module Support + module Library + # + module Transcoding + # Instance Methods + def prepare_conversion_options(options={}) + # obsoleted by ::String::transcode_options + unless options.is_a?(::Hash) + raise ArgumentError, "You must pass conversion options parameter as a Hash" + end + # + new_options = {} + # + valid_options = [:invalid,:undef,:replace,:fallback,:xml,:cr_newline,:crlf_newline,:universal_newline] + # + for the_option_key in valid_options + # + if options[(the_option_key)] + # Note Source: http://ruby-doc.org/core-1.9.3/String.html#method-i-encode + if the_option_key == :fallback + # Sets the replacement string by the given object for undefined character. The object should be a Hash, a Proc, a Method, + # or an object which has [] method. Its key is an undefined character encoded in the source encoding of current transcoder. + # Its value can be any encoding until it can be converted into the destination encoding of the transcoder. + unless new_options[:replace] + if (options[:fallback].is_any?(::Hash, ::Struct, ::Proc, ::Method) || options[:fallback].respond_to?(:[])) + # if :fallback will not process an Array as mentioned above ... remove :[] respond_to? condition. + new_options[:fallback] = options[:fallback] + end + end + end + # + if the_option_key == :invalid + # If the value is :replace, encode replaces invalid byte sequences in str with the replacement character. The default is to + # raise the Encoding::InvalidByteSequenceError exception + unless new_options[:fallback] + if options[:invalid] == :replace + new_options[:invalid] = :replace + end + end + end + # + if (the_option_key == :undef) + # If the value is :replace, encode replaces characters which are undefined in the destination encoding with the replacement character. + # The default is to raise the Encoding::UndefinedConversionError. + unless new_options[:fallback] + if options[:undef] == :replace + new_options[:undef] = :replace + end + end + end + if (the_option_key == :replace) + # Sets the replacement string to the given value. The default replacement string is “uFFFD” for Unicode encoding forms, and “?” otherwise. + unless new_options[:fallback] + if options[:replace].is_a?(::String) + new_options[:replace] = options[:replace] + end + end + end + if the_option_key == :xml + # The value must be :text or :attr. If the value is :text encode replaces undefined characters with their (upper-case hexadecimal) numeric character + # references. ‘&’, ‘<’, and ‘>’ are converted to “&”, “<”, and “>”, respectively. If the value is :attr, encode also quotes the replacement + # result (using ‘“’), and replaces ‘”’ with “"”. + if [:text, :attr].include?(options[:xml]) + new_options[:xml] = options[:xml] + end + end + if the_option_key == :cr_newline + # Replaces LF (“n”) with CR (“r”) if value is true. + unless (new_options[:crlf_newline] || new_options[:universal_newline]) + if options[(the_option_key)] == true + new_options[(the_option_key)] = true + end + end + end + # + if the_option_key == :crlf_newline + # Replaces LF (“n”) with CRLF (“rn”) if value is true. + unless (new_options[:cr_newline] || new_options[:universal_newline]) + if options[(the_option_key)] == true + new_options[(the_option_key)] = true + end + end + end + # + if the_option_key == :universal_newline + # Replaces CRLF (“rn”) and CR (“r”) with LF (“n”) if value is true. + unless (new_options[:cr_newline] || new_options[:crlf_newline]) + if options[(the_option_key)] == true + new_options[(the_option_key)] = true + end + end + end + # + end + # + pause + end + new_options + end + # + def newline_option_used(io_channel=:external) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + # determine the correct new_line trancoding option used + if io_channel.is_a?(::Hash) + options = io_channel + else + options = (@conversion_options[(io_channel.to_sym)] || {}) + end + if options[:cr_newline] + :cr_newline + else + if options[:crlf_newline] + :crlf_newline + else + if options[:universal_newline] + :universal_newline + else + # use nil if none specified + nil + end + end + end + # + end + # + def newline_used(io_channel=:external) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + newline = "" + if io_channel == :internal + newline.force_encoding((@internal_encoding || self.external_encoding)) + else + io_channel = :external + newline.force_encoding(self.external_encoding) + end + # determine the correct new_line_separator + if @conversion_options[(io_channel)][:cr_newline] + newline << "\r" + else + if @conversion_options[(io_channel)][:crlf_newline] + newline << "\r\n" + else + if @conversion_options[(io_channel)][:universal_newline] + newline << "\n" + else + # use global default if none specified + newline << $/ + end + end + end + newline + end + # + def field_separator_used(io_channel=:external) + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + unless self.instance_variable_defined?(:@internal_field_separator) + @internal_field_separator = nil + end + unless self.instance_variable_defined?(:@external_field_separator) + @external_field_separator = nil + end + field_separator = "" + if io_channel == :internal + field_separator.force_encoding((@internal_encoding || self.external_encoding)) + field_separator << (@internal_field_separator || ($,).to_s) + else + io_channel = :external + field_separator.force_encoding(self.external_encoding) + field_separator << (@external_field_separator || ($,).to_s) + end + if field_separator.size > 0 + field_separator + else + nil + end + end + # + def record_separator_used(io_channel=:external) + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + unless self.instance_variable_defined?(:@internal_record_separator) + @internal_record_separator = nil + end + unless self.instance_variable_defined?(:@external_record_separator) + @external_record_separator = nil + end + record_separator = "" + if io_channel == :internal + record_separator.force_encoding((@internal_encoding || self.external_encoding)) + record_separator << (@internal_record_separator || ($\).to_s) + else + io_channel = :external + record_separator.force_encoding(self.external_encoding) + record_separator << (@external_record_separator || ($\).to_s) + end + if record_separator.size > 0 + record_separator + else + nil + end + end + # + def convert_to_string(the_object=nil,encoding=::Encoding.default_external,conversion_options={}) + # return the supplied string, or supported object string data, or UTF_8 codepoint, un-transcoded + # encoding is used for when a byte value integer is passed to know what encoding to set. + the_string = "" + the_string.force_encoding(encoding || ::Encoding.default_external) + if the_object + if the_object.is_a?(::Array) + dataset = the_object.flatten + else + dataset = [(the_object)] + end + dataset.to_enum(:each).each do |object| + buffer = "" + if object.is_a?(::String) + # find the string content of these classes and pass them for transcoding: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray + buffer = object.dup + else + if object.is_any?(::IO, ::StringIO) + # Note: what issues arise when the_object is in binary mode? Also, how does this scale to very large things or files? + buffer = object.gets().to_s + end + if object.is_any?(::GxG::ByteArray) + buffer = object.to_s + end + if object.is_a?(::Numeric) + if object.to_i >= 0 + data = "" + if (0xFFFFFFFF && object.to_i) > 255 + # interprets as unicode code point + data.force_encoding(::Encoding::UTF_8) + else + # interprets as a char value in its own encoding, no transcoding needed. + data.force_encoding(encoding || ::Encoding.default_external) + end + buffer = (data << object.to_i) + data = nil + else + # signed integers not supported for char values or codepoints at this time. + end + end + end + # + if buffer.size > 0 + if (buffer.encoding == (encoding || ::Encoding.default_external) && ! self.binmode?()) + the_string << buffer + else + # ::String::transcode_options((encoding || ::Encoding.default_external),buffer.encoding, (conversion_options || {})) + if self.binmode?() + buffer.to_enum(:chars).each do |the_char| + the_char.to_enum(:bytes).each do |the_byte| + the_string << the_byte + end + end + else + nl_op = self.newline_option_used(conversion_options) + options = {} + if nl_op + options[(nl_op)] = true + end + # + the_string << buffer.transcode!((encoding || ::Encoding.default_external),options) + end + end + end + # + end + end + # + the_string + end + # + # TODO: GxG::StringIO : transcoding methods : tie into conversion-options-set-selector supports (dst-encoding,src-encoding). Hash deep-merge method needed. + # + def transcode_to_external(the_string="") + # return the supplied string, or supported object string data, or UTF_8 codepoint, transcoded + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + # + unless the_string.is_a?(::String) + # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding + the_string = self.convert_to_string(the_string,self.external_encoding,@conversion_options[:external]) + end + if the_string.size > 0 + if self.binmode?() + the_string.force_encoding(::Encoding::ASCII_8BIT) + else + if (the_string.encoding != self.external_encoding) + if the_string.encoding == @internal_encoding + options = ::String::transcode_options(self.external_encoding,the_string.encoding,@conversion_options[:external]) + else + # External conversion options will have to be dynamically generated upon transcode_to_external as *any* encoding is possible, not just internal_encoding + nl_op = self.newline_option_used(:external) + options = {} + if nl_op + options[(nl_op)] = true + end + options = ::String::transcode_options(self.external_encoding,the_string.encoding,options) + end + the_string.transcode!(self.external_encoding,options) + end + end + end + # + the_string + end + # + def transcode_to_internal(the_string="") + # return the supplied string, or supported object string data, or UTF_8 codepoint, transcoded + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + # + if (@internal_encoding && ! self.binmode?()) + unless the_string.is_a?(::String) + the_string = self.convert_to_string(the_string,@internal_encoding,@conversion_options[:internal]) + end + if the_string.size > 0 + if @internal_encoding == ::Encoding::ASCII_8BIT + # for cases where the external encoding is anything but binary, but the internal is set to BINARY. + # instead of actually transcoding something char by char into binary - just sets the encoding and filters for newline settings. + # + the_string.force_encoding(::Encoding::ASCII_8BIT) + nl_op = self.newline_option_used(:internal) + options = {} + if nl_op + options[(nl_op)] = true + end + the_string = the_string.encode(::Encoding::ASCII_8BIT,::Encoding::ASCII_8BIT,options) + else + if (the_string.encoding != @internal_encoding) + if the_string.encoding == self.external_encoding + options = ::String::transcode_options(@internal_encoding,the_string.encoding,@conversion_options[:internal]) + else + nl_op = self.newline_option_used(:internal) + options = {} + if nl_op + options[(nl_op)] = true + end + options = ::String::transcode_options(@internal_encoding,the_string.encoding,options) + end + the_string.transcode!(@internal_encoding,options) + end + end + end + else + the_string = self.transcode_to_external(the_string) + end + # + the_string + end + # + # + end + # + module TranscodingIO + # + def external_conversion() + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + @conversion_options[:external].clone + end + # + def external_conversion=(*args) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + @conversion_options[:external] = self.prepare_conversion_options(*args) + @conversion_options[:external].clone + end + # + def external_newline() + self.newline_option_used(:external) + end + # + def external_newline=(*args) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + if [:cr_newline, :crlf_newline, :universal_newline].include?(args[0]) + @conversion_options[:external].delete(:cr_newline) + @conversion_options[:external].delete(:crlf_newline) + @conversion_options[:external].delete(:universal_newline) + @conversion_options[:external][(args[0])] = true + end + end + # + def external_field_separator() + unless self.instance_variable_defined?(:@external_field_separator) + @external_field_separator = nil + end + @external_field_separator.dup + end + # + def external_field_separator=(*args) + unless self.instance_variable_defined?(:@external_field_separator) + @external_field_separator = nil + end + if args[0].is_a?(::String) + @external_field_separator = args[0] + else + case args[0] + when :globalcopy + @external_field_separator = ($,).dup + when :global + @external_field_separator = $, + end + end + end + # + def external_record_separator() + unless self.instance_variable_defined?(:@external_record_separator) + @external_record_separator = nil + end + @external_record_separator.dup + end + # + def external_record_separator=(*args) + unless self.instance_variable_defined?(:@external_record_separator) + @external_record_separator = nil + end + if args[0].is_a?(::String) + @external_record_separator = args[0] + else + case args[0] + when :globalcopy + @external_record_separator = ($\).dup + when :global + @external_record_separator = $\ + end + end + end + # + def internal_encoding() + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + @internal_encoding + end + # + def internal_encoding=(*args) + unless self.instance_variable_defined?(:@internal_encoding) + @internal_encoding = nil + end + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + unless self.binmode?() + if args[0].is_any?(::Encoding, ::NilClass) + if args[0].is_a?(::Encoding) + @internal_encoding = args[0] + # preserve newline settings + nl_op = self.newline_option_used(:internal) + options = {} + if nl_op + options[(nl_op)] = true + end + @conversion_options[:internal] = ::String::transcoding_options(@internal_encoding,self.external_encoding,options) + else + @internal_encoding = nil + @conversion_options[:internal] = {} + end + else + raise ArgumentError, "Expected an Encoding or NilClass, you provided #{args[0].class}" + end + end + end + # + def internal_conversion() + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + @conversion_options[:internal].clone + end + # + def internal_conversion=(*args) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + @conversion_options[:internal] = self.prepare_conversion_options(*args) + @conversion_options[:internal].clone + end + # + def internal_newline() + self.newline_option_used(:internal) + end + # + def internal_newline=(*args) + unless self.instance_variable_defined?(:@conversion_options) + @conversion_options = {:internal => {}, :external => {}} + end + if [:cr_newline, :crlf_newline, :universal_newline].include?(args[0]) + @conversion_options[:internal].delete(:cr_newline) + @conversion_options[:internal].delete(:crlf_newline) + @conversion_options[:internal].delete(:universal_newline) + @conversion_options[:internal][(args[0])] = true + end + end + # + def internal_field_separator() + unless self.instance_variable_defined?(:@internal_field_separator) + @internal_field_separator = nil + end + @internal_field_separator.dup + end + # + def internal_field_separator=(*args) + unless self.instance_variable_defined?(:@internal_field_separator) + @internal_field_separator = nil + end + if args[0].is_a?(::String) + @internal_field_separator = args[0] + else + case args[0] + when :globalcopy + @internal_field_separator = ($,).dup + when :global + @internal_field_separator = $, + end + end + end + # + def internal_record_separator() + unless self.instance_variable_defined?(:@internal_record_separator) + @internal_record_separator = nil + end + @internal_record_separator.dup + end + # + def internal_record_separator=(*args) + unless self.instance_variable_defined?(:@internal_record_separator) + @internal_record_separator = nil + end + if args[0].is_a?(::String) + @internal_record_separator = args[0] + else + case args[0] + when :globalcopy + @internal_record_separator = ($\).dup + when :global + @internal_record_separator = $\ + end + end + end + # + def gets(*args) + # Reads the next “line” from the I/O stream; lines are separated by sep. A separator of nil reads the entire contents, + # and a zero-length separator reads the input a paragraph at a time (two successive newlines in the input separate paragraphs). + # The stream must be opened for reading or an IOError will be raised. The line read in will be returned and also assigned to $_. + # Returns nil if called at end of file. If the first argument is an integer, or optional second argument is given, the returning string + # would not be longer than the given value in bytes. + if self.closed_read? + raise IOError, "not open for reading" + else + data = super(*args) + if data.is_a?(::String) + data = self.transcode_to_internal(data) + $_ = data.dup + data + else + nil + end + end + end + # + def getbyte() + # regarding getbyte : I don't see any practical way to do auto-transcoding ... leaving that one alone for now. + super() + end + # + def getbytes() + result = GxG::ByteArray.new + until self.eof?() + data = self.getbyte() + if data + result << data + end + pause + end + result + end + # + def getc() + data = super() + if data.is_a?(::String) + data = self.transcode_to_internal(data) + end + data + end + # + def readbyte() + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + self.getbyte() + end + end + end + # + def readchar(*args) + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + self.transcode_to_internal(self.read(1)) + end + end + end + # + def readline(*args) + if self.closed_read? + raise IOError, "not open for reading" + else + if self.eof? + raise EOFError, "end of stream reached" + else + self.gets(*args) + end + end + end + # + def each_char(&block) + if block.respond_to?(:call) + if self.closed_read? + raise IOError, "not open for reading" + else + # + # Note: this approach burns another 20 or 40 bytes (depending on arch-bits) for extra cooperative Enumerator encapsulation, + # but avoids stack-too-deep errors.. + super().to_enum.each do |the_character| + block.call(self.transcode_to_internal(the_character)) + end + # + self + end + else + # Note: when each_char alias is called, will be cosmetic off, but will work. + self.to_enum(:each_char) + end + end + alias :chars :each_char + # + def each_byte(&block) + if block.respond_to?(:call) + if self.closed_read? + raise IOError, "not open for reading" + else + until self.eof?() + data = self.getbyte() + if data + block.call(data) + end + pause + end + # + self + end + else + # Note: when each_byte alias is called, will be cosmetic off, but will work. + self.to_enum(:each_byte) + end + end + alias :bytes :each_byte + # + def each_transcoded_byte(&block) + if block.respond_to?(:call) + if self.closed_read? + raise IOError, "not open for reading" + else + # transcodes at the character level prior to byte passage to the block. + self.each_char do |the_character| + # the_character is pre-transcoded by each_char where appropriate. + the_character.to_enum(:bytes).each do |the_byte| + block.call(the_byte) + end + end + # + self + end + else + # Note: when each_transcoded_byte alias is called, will be cosmetic off, but will work. + self.to_enum(:each_transcoded_byte) + end + end + alias :transcoded_bytes :each_transcoded_byte + # + def each_codepoint(&block) + if block.respond_to?(:call) + if self.closed_read? + raise IOError, "not open for reading" + else + # transcodes at the character level prior to codepoint passage to the block. + self.each_char do |the_character| + block.call(the_character.codepoints.first) + end + # + self + end + else + # Note: when each_codepoint alias is called, will be cosmetic off, but will work. + self.to_enum(:each_codepoint) + end + end + alias :codepoints :each_codepoint + # + def each_line(separator=$/, limit=nil,&block) + # should not do an *args param capture for interface behavior compatibility. + # use of limit and separator appear exclusive in this method. + if separator.is_a?(Numeric) + limit = separator.to_i + separator = nil + end + # if transcoding is set, overwrite default separator according to translation flag settings. + if (self.internal_encoding() && self.internal_encoding() != self.external_encoding) + if separator == $/ + #Note : line separator supplied as method parameter will be applied to the transcoded string, not the StringIO data itself. + separator = self.newline_used(:internal) + end + end + # + if limit + limit = limit.to_i + unless limit > 0 + # Note: a work-around : irb FREAKS entire system when you call readlines with limit equal to 0 (memleak: IO lock up) + limit = 1 + end + args = [(separator),(limit)] + else + args = [(separator)] + end + # + if block.respond_to?(:call) + if self.closed_read? + raise IOError, "not open for reading" + else + # Note: this approach burns another 20 or 40 bytes (depending on arch-bits) for extra cooperative Enumerator encapsulation, + # but avoids stack-too-deep errors.. + super(*args) do |the_line| + block.call(self.transcode_to_internal(the_line)) + pause + end + # + self + end + else + # Note: when each_line alias is called, will be cosmetic off, but will work. + self.to_enum(:each_line,*args) + end + end + alias :each :each_line + alias :lines :each_line + # + def readlines(*args) + # Reads all of the lines in ios, and returns them in anArray. Lines are separated by the optional sep. + # If sep is nil, the rest of the stream is returned as a single record. If the first argument is an integer, + # or optional second argument is given, the returning string would not be longer than the given value in bytes. + # The stream must be opened for reading or an IOError will be raised. + if self.closed_read? + raise IOError, "not open for reading" + else + data = [] + self.each_line(*args) { |the_line| data << the_line } + data + end + end + # Transcoding 'write' methods: + def putc(the_object=nil) + # make multi-byte safe version of putc for StringIO. (alternate3) + # unlike stock putc : multi-byte char safe, can accept arrays of objects : where this will read the first char available on each object and write it. + if the_object + if the_object.is_a?(::Array) + dataset = the_object.flatten + else + dataset = [(the_object)] + end + dataset.to_enum(:each).each do |object| + buffer = "" + if object.is_a?(::String) + # find the string content of these classes and pass them for transcoding: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray + buffer = object.chars.first + else + if object.is_any?(::IO, ::StringIO) + # Note: what issues arise when the_object is in binary mode? Also, how does this scale to very large things or files? + buffer = object.getc.to_s + end + if object.is_any?(::GxG::ByteArray) + buffer = object.to_s.chars.first + end + if object.is_a?(::Numeric) + if object.to_i >= 0 + data = "" + if (0xFFFFFFFF && object.to_i) > 255 + # interprets as unicode code point + data.force_encoding(::Encoding::UTF_8) + else + # interprets as a char value in its own encoding, no transcoding needed. + data.force_encoding(self.external_encoding) + end + buffer = (data << object.to_i) + data = nil + else + # signed integers not supported for char values or codepoints at this time. + end + end + end + # + if buffer.size > 0 + if buffer.encoding == (self.external_encoding) + self.write(buffer.chars.first) + else + self.write(self.transcode_to_external(buffer).chars.first) + end + end + # + end + end + # + nil + end + alias :put_codepoint :putc + alias :putwc :putc + # + def printf(*args) + if self.closed_write? + raise IOError, "not open for writing" + else + formatting = "" + args = args.flatten + if args[0].is_a?(::String) + formatting = args.delete_at(0) + end + args.to_enum(:each_with_index) do |element,index| + if element.is_any?(::String, ::Numeric, ::IO, ::StringIO, ::GxG::ByteArray) + args[(index)] = self.transcode_to_external(element) + else + args[(index)] = self.transcode_to_external(element.inspect) + end + end + data = "" + data.force_encoding(self.external_encoding) + data << formatting.%(*args) + # + if data.size > 0 + self.write(data) + end + # + end + # + nil + end + # + def print(*args) + if self.closed_write? + raise IOError, "not open for writing" + else + fieldseparator = self.field_separator_used(:external) + recordseparator = self.record_separator_used(:external) + args = args.flatten + if args.size > 0 + data = "" + data.force_encoding(self.external_encoding) + args.to_enum(:each_with_index) do |element,index| + if element.is_any?(::String, ::Numeric, ::IO, ::StringIO, ::GxG::ByteArray) + args[(index)] = self.transcode_to_external(element) + else + args[(index)] = self.transcode_to_external(element.to_s) + end + end + args.to_enum(:each_with_index).each do |element,index| + data << element + unless index == (args.size - 1) + if fieldseparator + data << fieldseparator + end + end + end + if recordseparator + data << recordseparator + end + if data.size > 0 + self.write(data) + end + end + end + # + nil + end + # + def puts(*args) + # Writes the given objects to ios as with IO#print. Writes a record separator (typically a newline) after any + # that do not already end with a newline sequence. If called with an array argument, writes each element on a new line. + # If called without arguments, outputs a single record separator. + # ### + # determine the correct new_line_separator + newline = self.newline_used(:external) + # + if self.closed_write? + raise IOError, "not open for writing" + else + args = args.flatten + if args.size > 0 + args.to_enum(:each_with_index) do |element,index| + if element.is_any?(::String, ::Numeric, ::IO, ::StringIO, ::GxG::ByteArray) + args[(index)] = self.transcode_to_external(element) + else + args[(index)] = self.transcode_to_external(element.to_s) + end + end + # + data = "" + data.force_encoding(self.external_encoding) + args.to_enum.each do |element| + if newline.size > 0 + # sense if newline is already present, add if not + if element.size >= newline.size + if element.slice((newline.size * -1)..-1) == newline + data << element + else + data << element + data << newline + end + else + data << element + data << newline + end + else + data << element + end + end + if data.size > 0 + self.write(data) + end + # + else + self.write(newline) + end + end + # + nil + end + # + def ungetbyte(the_byte=nil) + if self.closed_write? + raise IOError, "not open for writing" + else + # + if the_byte.is_a?(::Numeric) + if (0..255).include?(the_byte.to_i) + super(the_byte.to_i) + else + self.ungetbytes(the_byte) + end + else + self.ungetbytes(the_byte) + end + # + end + nil + end + alias :unget_transcoded_byte :ungetbyte + # + def ungetbytes(*args) + # unget the string content of these classes and pre-pend them to the StringIO: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray + if self.closed_write? + raise IOError, "not open for writing" + else + args = args.flatten + args.to_enum.each do |the_object| + # + the_string = self.transcode_to_external(the_object) + if the_string.size > 0 + the_string.reverse.each_char do |the_character| + bytes = [] + the_character.each_byte do |the_byte_value| + bytes.unshift(the_byte_value) + end + bytes.to_enum.each do |the_byte_value| + self.ungetbyte(the_byte_value) + end + end + end + # + end + end + nil + end + alias :unget_transcoded_bytes :ungetbytes + # + def ungetc(the_character="") + if self.closed_write? + raise IOError, "not open for writing" + else + unless the_character.is_a?(::String) + the_character = the_character.to_s.chars.first + end + if the_character.size > 1 + the_character = the_character.chars.first + end + self.ungetbytes(the_character) + end + nil + end + # + def ungets(*args) + # unget the string content of these classes and pre-pend them to the StringIO: Numeric, String, ::IO via gets, ::StringIO via gets, ::GxG::ByteArray + if self.closed_write? + raise IOError, "not open for writing" + else + args = args.flatten + args.to_enum.each do |the_object| + # + the_string = self.transcode_to_external(the_object) + if the_string.size > 0 + the_string.reverse.to_enum(:each_char).each do |the_character| + self.ungetc(the_character) + end + end + # + end + end + nil + end + # + end + # + end + end +end diff --git a/lib/gxg/gxg_units.rb b/lib/gxg/gxg_units.rb new file mode 100644 index 0000000..0b63a2f --- /dev/null +++ b/lib/gxg/gxg_units.rb @@ -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() +# \ No newline at end of file diff --git a/lib/gxg/gxg_zmq.rb b/lib/gxg/gxg_zmq.rb new file mode 100644 index 0000000..f2a885f --- /dev/null +++ b/lib/gxg/gxg_zmq.rb @@ -0,0 +1,1220 @@ +#!/usr/bin/env ruby +require 'ffi-rzmq' +require 'ezmq' +# Patches to EZMQ: +module EZMQ + class Publisher + def send(message="", topic='', **options) + message = "#{ topic } #{ (options[:encode] || @encode).call message }" + @socket.send_string message + end + end +end +# gxg_ipc is there to pick the bones, but will be discarded : use this file. +module GxG + module Networking + # + module ZMQ + # + class ZmqContext < ::EZMQ::Context + # + end + # + @@details = {} + @@details[:zmq_context] = ::GxG::Networking::ZMQ::ZmqContext.new + def self.zmq_default_context() + @@details[:zmq_context] + end + # + def self.zmq_supported_protocols() + if ::GxG::SYSTEM.platform()[:platform] == :windows + ["inproc", "tcp", "tcp4", "tcp6", "pgm", "epgm"] + else + ["inproc", "ipc", "tcp", "tcp4", "tcp6", "pgm", "epgm"] + end + end + # + class ZmqAdapter + # Proposed: {:bind/connect => , :context => , :encode => , :decode => } + # mode (:bind, :connect) — a mode for the socket. + # Options Hash: + #context (ZMQ::Context) — a context to use for this socket (one will be created if not provided). + #encode (lambda) — how to encode messages. + #decode (lambda) — how to decode messages. + #transport (Symbol) — default: :tcp — transport for transport. + #address (String) — default: '127.0.0.1' — address for endpoint. + #port (Fixnum) — default: 5555 — port for endpoint. + def vetted_parameters(params={}) + unless params.is_a?(::Hash) + raise ArgumentError, "You MUST provide a Hash as parameters." + end + if params[:bind] + # bind + mode = :bind + else + # connect + mode = :connect + end + options = {} + if params[:context].is_a?(::ZMQ::Context) + options[:context] = params[:context] + end + uri = params[(mode)].clone + unless uri.port + uri.port = 5555 + end + options[:port] = uri.port + unless uri.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI to bind or connect to." + end + if ::GxG::Networking::ZMQ::zmq_supported_protocols().include?(uri.scheme()) + if mode == :bind + if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(uri.scheme()) + uri.resolve_host() + if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(uri)) + raise Errno::EADDRINUSE, "Address and port already in use: #{uri.to_s}" + end + end + end + else + raise Exception, "Unsupported networking protocol: #{uri.scheme().inspect}" + end + options[:transport] = uri.scheme.to_s.to_sym + options[:address] = uri.hostname + if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(uri.scheme()) + options[:port] = (uri.port || 5555) + end + options[:transport] = uri.scheme.to_s.to_sym + if params[:encode].respond_to?(:call) + options[:encode] = params[:encode] + end + if params[:decode].respond_to?(:call) + options[:decode] = params[:decode] + end + {:mode => mode, :options => options} + end + # + def open?() + @open + end + # + def close() + if @open == true + @connector.socket.close + @open = false + true + else + false + end + end + # + def context() + if @open == true + @connector.context() + end + end + # + def socket() + if @open == true + @connector.socket() + end + end + # + def identity() + if @open == true + @connector.socket.identity + end + end + # + def listen(&block) + if @open == true + @connector.listen(&block) + end + end + # + def receive(**options, &block) + if @open == true + @connector.receive(options, &block) + end + end + # + def send(message="", **options) + if @open == true + @connector.send(message.to_s,options) + end + end + # + end + # + class ZmqPair < ZmqAdapter + # + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Pair.new(params[:mode], params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + end + # + class ZmqPusher < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Pusher.new(params[:mode], params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + end + # + class ZmqPuller < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Puller.new(params[:mode], params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + end + # + class ZmqClient < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Client.new(params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + def request(message="", **options) + @connector.request(message.to_s,options) + end + end + # + class ZmqServer < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Server.new(params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + end + # + class ZmqPublisher < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Publisher.new(params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + def send(message="", topic='', **options) + if @open == true + @connector.send(message.to_s, topic.to_s,options) + end + end + # + end + # + class ZmqSubscriber < ZmqAdapter + def initialize(params={}) + params = self.vetted_parameters(params) + @connector = ::EZMQ::Subscriber.new(params[:options]) + @connector.socket.identity = ::Celluloid::UUID::random_generate.to_s + @open = true + self + end + # + def subscribe(topic="") + if @open == true + @connector.subscribe(topic) + end + end + # + def unsubscribe(topic="") + if @open == true + @connector.unsubscribe(topic) + end + end + end + # + end + # + # xxx +# class AbstractZMQSocket +# # Abstract class, never instantiated +# def self.supported_protocols() +# if ::GxG::SYSTEM.platform()[:platform] == :windows +# ["inproc", "tcp", "tcp4", "tcp6", "pgm", "epgm"] +# else +# ["inproc", "ipc", "tcp", "tcp4", "tcp6", "pgm", "epgm"] +# end +# end +# # +# def initialize(settings={}) +# # {:context => nil, :type => nil, :bind => {:thisone => "", :thatone => ""}, :connect => nil} +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# @thread_safety = ::Mutex.new +# if settings[:context].is_a?(::ZMQ::Context) +# @context = settings[:context] +# else +# @context = ::GxG::Networking::zmq_context() +# end +# if settings[:type].is_a?(::Numeric) +# @socket = @context.socket(settings[:type]) +# else +# raise ArgumentError, "you must supply a type for the socket" +# end +# @name = ::Celluloid::UUID.random_generate().to_sym +# socket_id = (settings[:identity] || @name).to_s.dup +# if socket_id.bytesize() > 255 +# socket_id.force_encoding(::Encoding::ASCII_8BIT) +# socket_id = socket_id[(0..255)] +# end +# @socket.identity = socket_id +# # +# @recv_buffer_size = 1024 +# @send_buffer_size = 1024 +# # +# @bindings = {} +# if settings[:bind].is_a?(::Hash) +# # +# settings[:bind].keys.to_enum(:each).each do |binding_key| +# if settings[:bind][(binding_key)].is_a?(::URI::Generic) +# address = settings[:bind][(binding_key)] +# else +# address = ::URI::parse(settings[:bind][(binding_key)]) +# end +# if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme()) +# address.resolve_host() +# if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(address.scheme()) +# if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address)) +# raise Errno::EADDRINUSE, "Address and port already in use: #{address.to_s}" +# end +# end +# begin +# @socket.bind(address.to_s) +# @bindings[(binding_key)] = address +# rescue Exception +# @socket.close +# raise +# end +# else +# raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol" +# end +# end +# # +# end +# # +# @connections = {} +# if settings[:connect].is_a?(::Hash) +# settings[:connect].keys.to_enum(:each).each do |connection_key| +# if settings[:connect][(connection_key)].is_a?(::URI::Generic) +# address = settings[:connect][(connection_key)] +# else +# address = ::URI::parse(settings[:connect][(connection_key)]) +# end +# # address = ::URI::parse(settings[:connect][(connection_key)]) +# if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme()) +# address.resolve_host() +# unless ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address)) +# raise Errno::EADDRNOTAVAIL, "Address and port not bound: #{address.to_s}" +# end +# begin +# @socket.connect(address.to_s) +# @connections[(connection_key)] = address +# rescue Exception +# @socket.close +# raise +# end +# else +# raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol" +# end +# end +# end +# # +# @recv_buffer_size = this.recv_buffer_size() +# @send_buffer_size = this.send_buffer_size() +# # +# this +# end +# # +# def context() +# @thread_safety.synchronize { @context } +# end +# # +# def socket() +# @thread_safety.synchronize { @socket } +# end +# # +# def identity() +# @socket.identity() +# end +# # +# def recv_buffer_size(*args) +# # +# if args.size > 0 +# if args[0].is_a?(::Numeric) +# the_size = args.first.to_i +# if the_size > 0 +# valid_min = [16384] +# valid_max = [65536] +# [@bindings, @connections].to_enum(:each).each do |the_set| +# the_set.keys.to_enum(:each).each do |the_terminus| +# setting = ::GxG::Networking::buffer_limits(:in, (the_set[(the_terminus)].scheme).to_sym)[:valid] +# valid_min << (setting.min) +# valid_max << (setting.max) +# end +# end +# if ((valid_min.min)..(valid_max.max)).include?(the_size) +# @thread_safety.synchronize { +# @recv_buffer_size = the_size +# @socket.setsockopt(::ZMQ::RCVBUF,the_size) +# } +# else +# raise Exception, "New buffer size #{the_size} is outside acceptable range: #{((valid_min.min)..(valid_max.max)).inspect}" +# end +# end +# end +# else +# data = [] +# @thread_safety.synchronize { @socket.getsockopt(::ZMQ::RCVBUF,data) } +# the_size = (data.first || 0).to_i +# if the_size == 0 +# # find scheme of connection/binding : the_scheme +# # buffer_size = (::GxG::Networking::buffer_limits(:out, the_scheme.to_sym)[:initial] || 65536) +# buffer_sizes = [65536] +# [@bindings, @connections].to_enum(:each).each do |the_set| +# the_set.keys.to_enum(:each).each do |the_terminus| +# buffer_sizes << (::GxG::Networking::buffer_limits(:in, (the_set[(the_terminus)].scheme).to_sym)[:valid].min) +# end +# end +# # FORNOW: use the smallest buffer size found across all bindings and/or connections +# @thread_safety.synchronize { @recv_buffer_size = buffer_sizes.min } +# else +# @thread_safety.synchronize { @recv_buffer_size = the_size } +# end +# end +# @thread_safety.synchronize { @recv_buffer_size.dup } +# end +# # +# def send_buffer_size(*args) +# # +# if args.size > 0 +# if args[0].is_a?(::Numeric) +# the_size = args.first.to_i +# if the_size > 0 +# valid_min = [16384] +# valid_max = [65536] +# [@bindings, @connections].to_enum(:each).each do |the_set| +# the_set.keys.to_enum(:each).each do |the_terminus| +# setting = ::GxG::Networking::buffer_limits(:out, (the_set[(the_terminus)].scheme).to_sym)[:valid] +# valid_min << (setting.min) +# valid_max << (setting.max) +# end +# end +# if ((valid_min.min)..(valid_max.max)).include?(the_size) +# @thread_safety.synchronize { +# @send_buffer_size = the_size +# @socket.setsockopt(::ZMQ::SNDBUF,the_size) +# } +# else +# raise Exception, "New buffer size #{the_size} is outside acceptable range: #{((valid_min.min)..(valid_max.max)).inspect}" +# end +# end +# end +# else +# data = [] +# @thread_safety.synchronize { @socket.getsockopt(::ZMQ::SNDBUF,data) } +# the_size = (data.first || 0).to_i +# if the_size == 0 +# # find scheme of connection/binding : the_scheme +# # buffer_size = (::GxG::Networking::buffer_limits(:out, the_scheme.to_sym)[:initial] || 65536) +# buffer_sizes = [65536] +# [@bindings, @connections].to_enum(:each).each do |the_set| +# the_set.keys.to_enum(:each).each do |the_terminus| +# buffer_sizes << (::GxG::Networking::buffer_limits(:out, (the_set[(the_terminus)].scheme).to_sym)[:valid].min) +# end +# end +# # FORNOW: use the smallest buffer size found across all bindings and/or connections +# @thread_safety.synchronize { @send_buffer_size = buffer_sizes.min } +# else +# @thread_safety.synchronize { @send_buffer_size = the_size } +# end +# end +# @thread_safety.synchronize { @send_buffer_size.dup } +# end +# # +# def binding(binding_key=nil) +# if binding_key +# @thread_safety.synchronize { @bindings[(binding_key)] } +# else +# @thread_safety.synchronize { @bindings } +# end +# end +# # +# def bind(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of keyed bind point(s) for the binding" +# end +# result = false +# settings.keys.to_enum(:each).each do |binding_key| +# if settings[(binding_key)].is_a?(::URI::Generic) +# address = settings[(binding_key)] +# else +# address = ::URI::parse(settings[(binding_key)]) +# end +# # address = ::URI::parse(settings[(binding_key)]) +# if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme()) +# address.resolve_host() +# if ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].include?(address.scheme()) +# if ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address)) +# raise Errno::EADDRINUSE, "Address and port already in use: #{address.to_s}" +# end +# end +# begin +# @thread_safety.synchronize { +# @socket.bind(address.to_s) +# @bindings[(binding_key)] = address +# } +# result = true +# rescue Exception +# @thread_safety.synchronize { @socket.close } +# raise +# end +# else +# raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol" +# end +# end +# @recv_buffer_size = this.recv_buffer_size() +# @send_buffer_size = this.send_buffer_size() +# # +# result +# end +# # +# def connection(connection_key=nil) +# if connection_key +# @thread_safety.synchronize { @connections[(connection_key)] } +# else +# @thread_safety.synchronize { @connections } +# end +# end +# # +# def connect(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of keyed address(es) for the connection" +# end +# result = false +# settings.keys.to_enum(:each).each do |connection_key| +# if settings[(connection_key)].is_a?(::URI::Generic) +# address = settings[(connection_key)] +# else +# address = ::URI::parse(settings[(connection_key)]) +# end +# # address = ::URI::parse(settings[(connection_key)]) +# if ::GxG::Networking::AbstractZMQSocket::supported_protocols().include?(address.scheme()) +# address.resolve_host() +# if ["tcp", "tcp4", "tcp6"].include?(address.scheme()) +# unless ::GxG::SYSTEM.network_port_used?(::Addrinfo::parse(address)) +# raise Errno::EADDRNOTAVAIL, "Address and port not bound: #{address.to_s}" +# end +# end +# begin +# @thread_safety.synchronize { +# @socket.connect(address.to_s) +# @connections[(connection_key)] = address +# } +# result = true +# rescue Exception +# @thread_safety.synchronize { @socket.close() } +# raise +# end +# else +# raise ArgumentError, "#{address.scheme().to_s} is an unsupported protocol" +# end +# end +# @recv_buffer_size = this.recv_buffer_size() +# @send_buffer_size = this.send_buffer_size() +# # +# result +# end +# # +# def close() +# @thread_safety.synchronize { @socket.close() } +# end +# # +# def triage_strings(data=[]) +# # takes raw output from @socket.recv_strings([]) and separates routing from content +# result = {:route => [], :content => []} +# prior_element = nil +# data.to_enum(:each).each do |the_element| +# # Empty strings terminate routes -- Do NOT use in payload as parse element +# if the_element.to_s.size > 0 +# if prior_element.to_s.size > 0 +# result[:content] << prior_element +# end +# prior_element = the_element +# else +# if prior_element.to_s.size > 0 +# result[:route] << prior_element +# end +# prior_element = nil +# end +# end +# if prior_element.to_s.size > 0 +# result[:content] << prior_element +# end +# result +# end +# # +# def recv(options={:autojoin => true}) +# unless options.is_a?(::Hash) +# options = {:autojoin => true} +# end +# payload = [] +# # @thread_safety.lock +# status = @socket.recv_strings(payload,0) +# if ::ZMQ::Util.resultcode_ok?(status) +# data = this.triage_strings(payload) +# if (data[:route].size > 0 || data[:content].size > 0) +# if options[:autojoin] +# ::GxG::Events::Message.new({:sender => this, :body => data[:content].join(), :route => data[:route]}) +# else +# ::GxG::Events::Message.new({:sender => this, :body => data[:content], :route => data[:route]}) +# end +# else +# nil +# end +# else +# begin +# raise Exception, ("ZMQ - " + ::ZMQ::Util.error_string()) +# rescue => the_error +# log_error({:error => the_error}) +# end +# nil +# end +# # @thread_safety.unlock +# end +# # +# def send(data=nil, route=[]) +# if data +# # FORNOW: use the smallest buffer size found across all bindings and/or connections +# buffer_size = @thread_safety.synchronize { @send_buffer_size } +# payload = [] +# messages = [] +# unless route.is_a?(::Array) +# route = [(route)] +# end +# # +# if data.is_a?(::GxG::Events::Message) +# if data.body().is_a?(::String) +# data = [(data.body())] +# else +# data = [(data.body().serialize())] +# end +# end +# if data.is_a?(::String) +# data = [(data)] +# end +# # +# if route.is_a?(::Array) +# route.flatten.to_enum(:each).each do |the_route| +# if the_route.is_a?(::String) +# payload << the_route.slice_bytes(0..255) +# payload << "" +# end +# end +# end +# payload << @socket.identity() +# payload << "" +# # +# if data.is_a?(::Array) +# data.flatten.to_enum(:each).each do |the_element| +# if the_element.is_a?(::String) +# if the_element.size > 0 +# # Disallow parsing payload with empty strings : used to parse routes +# # Break into appropriate buffer size as needed : respect buffer sizes +# if the_element.bytesize > buffer_size +# length = the_element.bytesize() +# passes = ::GxG::Networking::passes_needed(length, buffer_size) +# total = (length - 1) +# starting = 0 +# ending = ([length,buffer_size].min - 1) +# passes.times do +# messages << the_element.slice_bytes(starting..ending) +# # +# starting = ending + 1 +# ending = (starting + (buffer_size - 1)) +# if ending > total +# ending = ending - (ending - total) +# end +# end +# # +# else +# messages << the_element +# end +# end +# end +# end +# if messages.size > 0 +# payload << messages +# payload.flatten! +# status = @thread_safety.synchronize { @socket.send_strings(payload) } +# if status == -1 +# begin +# raise Exception, "ZMQ - Message could not be enqueued." +# rescue => the_error +# log_error({:error => the_error, :parameters => {:data => data, :route => route}}) +# end +# end +# end +# end +# # +# end +# end +# # +# end + # +# class ZMQRouter < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# unless settings[:context].is_a?(::ZMQ::Context) +# settings[:context] = ::GxG::Networking::zmq_context() +# end +# settings[:type] = ::ZMQ::ROUTER +# bindings = (settings.delete(:bind) || {}) +# super(settings) +# unless bindings.keys.size > 0 +# bindings[:inproc] = ::URI::parse("inproc://#{@name}") +# end +# this.bind(bindings) +# # +# this +# end +# # +# end + # +# class ZMQDealer < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::DEALER +# super(settings) +# # +# this +# end +# # +# end + # +# class ZMQRequest < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::REQ +# super(settings) +# # +# this +# end +# # +# end + # +# class ZMQReply < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::REP +# super(settings) +# # +# this +# end +# # +# end + # +# class ZMQPuller < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::PULL +# super(settings) +# # +# this +# end +# # +# def send(*args) +# # no-op +# end +# # +# end + # +# class ZMQPusher < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::PUSH +# super(settings) +# # +# this +# end +# # +# def recv() +# nil +# end +# # +# end + # +# class ZMQPair < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::PAIR +# super(settings) +# # +# this +# end +# # +# end + # +# class ZMQSubscriber < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::SUB +# super(settings) +# @topics = [] +# # +# this +# end +# # +# def topics() +# @thread_safety.synchronize { @topics.dup } +# end +# # +# def subscribe_topic(the_topic=nil) +# the_topic = the_topic.to_s +# if the_topic.bytesize > 255 +# the_topic = the_topic.slice_bytes(0..255).to_s +# end +# if the_topic.bytesize > 0 +# @thread_safety.synchronize { +# unless @topics.include?(the_topic) +# @topics << the_topic +# @socket.setsockopt(::ZMQ::SUBSCRIBE,the_topic) +# end +# } +# end +# end +# # +# def unsubscribe_topic(the_topic=nil) +# the_topic = the_topic.to_s +# if the_topic.bytesize > 255 +# the_topic = the_topic.slice_bytes(0..255).to_s +# end +# if the_topic.bytesize > 0 +# @thread_safety.synchronize { +# if @topics.include?(the_topic) +# @topics.delete(the_topic) +# @socket.setsockopt(::ZMQ::UNSUBSCRIBE,the_topic) +# end +# } +# end +# end +# # +# def send(*args) +# # no-op +# end +# # +# end + # +# class ZMQPublisher < ::GxG::Networking::AbstractZMQSocket +# # +# def initialize(settings={}) +# # +# unless settings.is_a?(::Hash) +# raise ArgumentError, "you must supply a hash of settings for the socket" +# end +# settings[:type] = ::ZMQ::PUB +# bindings = (settings.delete(:bind) || {}) +# super(settings) +# unless bindings.keys.size > 0 +# bindings[:inproc] = ::URI::parse("inproc://#{@name}") +# end +# this.bind(bindings) +# # +# this +# end +# # +# def recv() +# nil +# end +# # +# def publish(the_data=nil,the_topic=nil) +# if the_data +# the_topic = the_topic.to_s +# if the_topic.bytesize > 255 +# the_topic = the_topic.slice_bytes(0..255).to_s +# end +# if the_topic.bytesize > 0 +# this.send(the_data,[(the_topic)]) +# else +# this.send(the_data) +# end +# end +# end +# end + # +# class ZMQ_RequestReplyBroker < ::GxG::Events::Actor +# # class ZMQ_RequestReplyBroker is a derivative work based upon: http://zguide.zeromq.org/rb:rrbroker Copyright (c) 2013 iMatix Corporation +# # accordingly, class ZMQ_RequestReplyBroker is licensed under this license: http://creativecommons.org/licenses/by-sa/3.0/legalcode +# def initialize(settings={}) +# unless settings.is_a?(::Hash) +# raise ArgumentError, "You must provide a hash of settings for the Broker." +# end +# unless settings[:frontend].is_a?(::URI::Generic) +# raise ArgumentError, "You must provide a URI as :frontend." +# end +# unless settings[:backend].is_a?(::URI::Generic) +# raise ArgumentError, "You must provide a URI as :backend." +# end +# super() +# # +# @context = ::GxG::Networking::zmq_context() +# @frontend = ::GxG::Networking::ZMQRouter.new({:context => @context, :bind => {:frontend => settings[:frontend]}}) +# @backend = ::GxG::Networking::ZMQDealer.new({:context => @context, :bind => {:backend => settings[:backend]}}) +# # @queue = ::ZMQ::Device.new(::ZMQ::QUEUE, @frontend.socket(), @backend.socket()) +# @queue = ::ZMQ::Poller.new +# @queue.register(@frontend.socket(), ::ZMQ::POLLIN) +# @queue.register(@backend.socket(), ::ZMQ::POLLIN) +# # +# @circulating = false +# @is_circulating = false +# @circulate_safety = ::Mutex.new +# # +# this.resume_circulating() +# ::Celluloid::current_actor() +# end +# # +# def terminate() +# this.halt_circulating() +# @queue.deregister(@frontend.socket()) +# @queue.deregister(@backend.socket()) +# @frontend.close() +# @backend.close() +# # +# super() +# end +# # +# def circulating?() +# @circulate_safety.synchronize { @circulating } +# end +# # +# def halt_circulating() +# @circulate_safety.synchronize { @circulating = false } +# end +# # +# def resume_circulating() +# unless this.circulating?() +# @circulate_safety.synchronize { @circulating = true } +# this.async.circulate +# end +# end +# # +# def circulate() +# # Circulate Data as required +# if this.alive?() +# front_socket = @frontend.socket() +# front_id = @frontend.identity() +# back_socket = @backend.socket() +# back_id = @frontend.identity() +# busy = @circulate_safety.synchronize { @is_circulating } +# while (this.circulating?() && !busy) do +# # +# @circulate_safety.synchronize { @is_circulating = true } +# @circulate_safety.lock +# # +# @queue.poll(:blocking) +# @queue.readables.each do |the_socket| +# if the_socket === front_socket +# data = @frontend.triage_strings(the_socket.recv_strings([])) +# payload = [] +# if (data[:route].size > 0 || data[:content].size > 0) +# data[:route].to_enum(:each).each do |the_route| +# payload << the_route +# payload << "" +# end +# payload << back_id +# payload << "" +# payload << data[:content] +# payload.flatten! +# if payload.size > 0 +# back_socket.send_strings(payload) +# end +# end +# end +# if the_socket === back_socket +# data = @backend.triage_strings(the_socket.recv_strings([])) +# payload = [] +# if (data[:route].size > 0 || data[:content].size > 0) +# data[:route].to_enum(:each).each do |the_route| +# payload << the_route +# payload << "" +# end +# payload << front_id +# payload << "" +# payload << data[:content] +# payload.flatten! +# if payload.size > 0 +# front_socket.send_strings(payload) +# end +# end +# end +# end +# # +# @circulate_safety.unlock +# @circulate_safety.synchronize { @is_circulating = false } +# busy = @circulate_safety.synchronize { @is_circulating } +# # +# if this.alive?() +# if this.circulating?() +# sleep 0.033 +# end +# end +# # +# end +# end +# end +# # +# end + # + # Deprecated: completely rethink this. + class ZmqNode + # + def initialize(*args) + # + @zmq_thread_safety = ::Mutex.new + @zmq_processing = false + @zmq_is_processing = false + @zmq_processing_handler = nil + @zmq_processing_error_handler = nil + @zmq_inputs = {} + @zmq_outputs = {} + # + self + end + # + def terminate() + # Halt any zmq processing first + this.halt_zmq_processing() + # Remove any references to input and output sockets + # Question: should I close all input/output sockets here??? + @zmq_thread_safety.synchronize { + @zmq_inputs = {} + @zmq_outputs = {} + } + super() + end + # + def zmq_inputs() + result = {} + @zmq_thread_safety.synchronize { + @zmq_inputs.keys.to_enum(:each).each do |the_key| + result[(the_key)] = @zmq_inputs[(the_key)] + end + # + } + result + end + # + def add_zmq_input(the_key=nil, the_socket=nil) + valid = [::GxG::Networking::ZMQPair, ::GxG::Networking::ZMQPuller, ::GxG::Networking::ZMQSubscriber, ::GxG::Networking::ZMQReply, ::GxG::Networking::ZMQRequest] + # + unless the_key.is_a?(::Symbol) + raise ArgumentError, "you need so supply a unique symbol key for the socket" + end + if (@zmq_thread_safety.synchronize { @zmq_inputs[(the_key)] }) + raise ArgumentError, "inputs already exists, you need so supply a unique symbol key for the socket" + end + unless the_socket.is_any?(valid) + raise ArgumentError, "you supplied a #{the_socket.class.inspect}, you need so supply one of these as socket type: #{valid.inspect}" + end + @zmq_thread_safety.synchronize { @zmq_inputs[(the_key)] = the_socket } + true + end + # + def remove_zmq_input(the_key=nil) + # + result = false + the_inputs = this.zmq_inputs() + if the_inputs[(the_key)] + @zmq_thread_safety.synchronize { @zmq_inputs.delete(the_key) } + result = true + end + result + end + # + def zmq_outputs() + result = {} + @zmq_thread_safety.synchronize { + @zmq_outputs.keys.to_enum(:each).each do |the_key| + result[(the_key)] = @zmq_outputs[(the_key)] + end + # + } + result + end + # + def add_zmq_output(the_key=nil, the_socket=nil) + valid = [::GxG::Networking::ZMQPair, ::GxG::Networking::ZMQPusher, ::GxG::Networking::ZMQPublisher, ::GxG::Networking::ZMQReply, ::GxG::Networking::ZMQRequest] + # + unless the_key.is_a?(::Symbol) + raise ArgumentError, "you need so supply a unique symbol key for the socket" + end + if (@zmq_thread_safety.synchronize { @zmq_outputs[(the_key)] }) + raise ArgumentError, "inputs already exists, you need so supply a unique symbol key for the socket" + end + unless the_socket.is_any?(valid) + raise ArgumentError, "you supplied a #{the_socket.class.inspect}, you need so supply one of these as socket type: #{valid.inspect}" + end + @zmq_thread_safety.synchronize { @zmq_outputs[(the_key)] = the_socket } + true + end + # + def remove_zmq_output(the_key=nil) + # + result = false + the_outputs = this.zmq_outputs() + if the_outputs[(the_key)] + @zmq_thread_safety.synchronize { @zmq_outputs.delete(the_key) } + result = true + end + result + end + # + def zmq_handler(params={}) + result = {:result => false} + begin + unless params.is_a?(:Hash) + raise ArgumentError, "you must provide a Hash as parameter container" + end + if params[:block].respond_to(:call) + @zmq_thread_safety.synchronize { @zmq_processing_handler = params[:block] } + result[:result] = true + else + raise ArgumentError, "you must provide something that responds to :call with the :block parameter" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => params}) + end + result + end + # + def zmq_error_handler(params={}) + result = {:result => false} + begin + unless params.is_a?(:Hash) + raise ArgumentError, "you must provide a Hash as parameter container" + end + if params[:block].respond_to(:call) + @zmq_thread_safety.synchronize { @zmq_processing_error_handler = params[:block] } + result[:result] = true + else + raise ArgumentError, "you must provide something that responds to :call with the :block parameter" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => params}) + end + result + end + # + def zmq_processing?() + @zmq_thread_safety.synchronize { @zmq_processing } + end + # + def halt_zmq_processing() + @zmq_thread_safety.synchronize { @zmq_processing = false;@zmq_is_processing = false } + end + # + def resume_zmq_processing() + unless this.processing?() + @zmq_thread_safety.synchronize { @zmq_processing = true } + this.async.zmq_process + end + end + # + def zmq_process() + # Process items on the zmq inputs and potentially send things via outputs + while (this.zmq_processing?() && !(@zmq_thread_safety.synchronize { @zmq_is_processing })) do + # + @zmq_thread_safety.synchronize { @zmq_is_processing = true } + # Process event item + begin + my_handler = @zmq_thread_safety.synchronize { @zmq_processing_error_handler } + begin + processor = @zmq_thread_safety.synchronize { @zmq_processing_handler } + if processor.respond_to?(:call) + processor.call(this, this.zmq_inputs(), this.zmq_outputs()) + end + rescue Exception => the_error + # TODO: Revise logging/error-handling system generally, after display manager construction + this.halt_zmq_processing() + if my_handler.respond_to?(:call) + # error handler provided + my_handler.call({:error => the_error}) + else + # log error and forget about it + log_error({:error => the_error}) + end + end + rescue Exception => error_handling + log_error({:error => error_handling}) + end + # + @zmq_thread_safety.synchronize { @zmq_is_processing = false } + if this.alive?() + if this.zmq_processing?() + sleep 0.033 + end + end + # + end + # + end + # + end + # + end +end \ No newline at end of file diff --git a/lib/gxg/net_clients.rb b/lib/gxg/net_clients.rb new file mode 100644 index 0000000..4a5642a --- /dev/null +++ b/lib/gxg/net_clients.rb @@ -0,0 +1,4797 @@ +require 'net/http' +require 'net/ftp' +require 'net/ssh' +require 'net/scp' +require 'net/sftp' +require 'xmlrpc/client' +require 'handsoap' +require 'rest-client' +require 'net/pop' +require 'net/smtp' +require 'net/imap' +require 'gmail_xoauth' +require 'mail' +require 'matrix_sdk' +# GxG: +module GxG + # + module Networking + # + class ClientDispatcher + def initialize() + @thread_safety = ::Mutex.new + @client_database = {} + end + # + def new_client(the_url=nil, options={}) + result = nil + if the_url.is_any?(::String, ::Symbol, ::URI::Generic) + if the_url.is_any?(::String, ::Symbol) + the_url = ::URI.parse(the_url.to_s) + else + the_url = the_url.clone + end + client_scheme = the_url.scheme.to_s + client_class = @thread_safety.synchronize { + if @client_database[(client_scheme.to_sym)].is_a?(::Hash) + @client_database[(client_scheme.to_sym)][:client] + else + nil + end + } + if client_class + if the_url.scheme.to_s.include?("-") + the_url.scheme = the_url.scheme.to_s.split("-")[1] + end + if client_class.respond_to?(:connect) + result = client_class::connect(the_url, options) + else + result = client_class.new(the_url, options) + end + else + log_warn("Unable to find a client for protocol: #{client_scheme}") + end + else + log_warn("Unable to find a client for protocol: #{the_url.inspect}") + end + result + end + # + def register_client(the_scheme=nil, client_class=nil) + result = false + if the_scheme.is_any?(::Symbol, ::String) && client_class.is_a?(::Class) + if the_scheme.to_s.include?("-") + protocol = the_scheme.to_s.split("-")[0] + service = the_scheme.to_s.split("-")[1] + else + protocol = the_scheme.to_s + service = protocol + end + # + ::GxG::SYSTEM.service_ports_register_client(service, the_scheme.to_s) + @thread_safety.synchronize { + @client_database[(the_scheme.to_s.to_sym)] = {:client => client_class} + } + # + result = true + end + result + end + # + def unregister_client() + end + end + DISPATCHER = ::GxG::Networking::ClientDispatcher.new + # To Generate OAuth2 Tokens, See: https://github.com/google/gmail-oauth2-tools + # IMAP Classes: + class ImapClient + # ??? + def connector() + @client + end + # + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + @user = the_url.user + @password = the_url.password + @client = nil + @host = the_url.hostname + if options[:use_ssl] == true || the_url.scheme.to_s == "imaps" + @port = (the_url.port || 993) + else + @port = (the_url.port || 143) + end + # + @capability = [] + # + @mounted = nil + @mount_mode = :read + # + if options[:use_ssl] == true + if options[:ignore_ssl_errors] == true + @client = Net::IMAP.new(@host, @port, true, nil, false) + else + @client = Net::IMAP.new(@host, @port, true) + end + else + @client = Net::IMAP.new(@host, @port) + end + @capability = @client.capability + # + self + end + # + def inspect() + "" + end + # + def capability() + @capability.clone + end + # + def login(user_id=nil, password=nil, options={:method => :auth}) + result = false + if user_id.is_a?(::Hash) + options = user_id + user_id = nil + password = nil + end + begin + if @client + unless @client.disconnected? + unless user_id + user_id = @user + unless user_id + raise ArgumentError, "You MUST provide a USER id, like this: ,,()" + end + end + unless password + password = @password + unless password + raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: ,,()" + end + end + case options[:method] + when :auth + @client.login(user_id, password) + when :login + @client.authenticate("LOGIN", user_id, password) + when :cram_md5 + @client.authenticate("CRAM-MD5", user_id, password) + when :oauth2 + @client.authenticate("XOAUTH2", user_id, password) + else + raise ArgumentError, "Authentication method #{options[:method].inspect} not supported. Choose one of these: [:auth, :login, :cram_md5, :oauth2]" + end + # + result = true + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:user => user_id, :options => options}}) + end + result + end + # + def logout() + result = false + if @client + begin + @client.logout + @client.disconnect + result = @client.disconnected? + @client = nil + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + end + result + end + # + def mounted? + result = false + if @client + if @mounted + result = true + end + end + result + end + # + def mount_mode() + if self.mounted? + @mount_mode + else + :unmounted + end + end + # + def mount(mailbox=nil,mode=:read_write) + result = false + if @client + begin + unless [:read, :read_write].include?(mode) + raise ArgumentError, "Mode #{mode.inspect} is invalid. Choose one of these: [:read, :read_write]." + end + if self.mounted? + if @mounted == mailbox + if self.mount_mode() == :read && mode == :read_write + response = @client.select(mailbox) + if response.is_a?(::Net::IMAP::TaggedResponse) + if response.name == "OK" + @mounted = mailbox + @mount_mode = :read_write + result = true + else + raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write." + end + else + raise Exception, "Failed to re-mount #{mailbox.inspect} as :read_write." + end + else + result = true + end + else + raise Exception, "You must unmount mailbox #{@mounted.inspect} first, then mount #{mailbox.inspect}." + end + else + # mount + if mode == :read_write + response = @client.select(mailbox) + if response.is_a?(::Net::IMAP::TaggedResponse) + if response.name == "OK" + @mounted = mailbox + @mount_mode = :read_write + result = true + else + raise Exception, "Failed to mount #{mailbox.inspect} as :read_write." + end + else + raise Exception, "Failed to mount #{mailbox.inspect} as :read_write." + end + else + # mount :read only + response = @client.examine(mailbox) + if response.is_a?(::Net::IMAP::TaggedResponse) + if response.name == "OK" + @mounted = mailbox + @mount_mode = :read + result = true + else + raise Exception, "Failed to mount #{mailbox.inspect} as :read." + end + else + raise Exception, "Failed to mount #{mailbox.inspect} as :read." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox, :mode => mode}}) + end + end + result + end + # + def unmount() + result = false + if self.mounted?() + @mounted = nil + @mount_mode = :read + result = true + end + result + end + # + def status(mailbox=nil) + result = {:connected => false, :unseen => 0, :recent => 0, :messages => 0} + if @client + begin + result[:connected] = (! @client.disconnected?) + the_mailbox = (mailbox || @mounted) + unless the_mailbox + raise ArgumentError, "You must either MOUNT an inbox, or specify one to get status." + end + if result[:connected] == true + raw_response = @client.status(the_mailbox, ["UNSEEN","MESSAGES", "RECENT"]) + if raw_response.is_a?(::Hash) + result[:unseen] = raw_response["UNSEEN"] + result[:recent] = raw_response["RECENT"] + result[:messages] = raw_response["MESSAGES"] + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}}) + end + end + result + end + # + def list(reference=nil, mailbox=nil) + result = [] + if @client + begin + unless reference + raise ArgumentError, "You MUST sepcify a REFERENCE to list." + end + unless mailbox + raise ArgumentError, "You MUST sepcify a MAILBOX to list." + end + raw_list = @client.list(reference, mailbox) + if raw_list + raw_list.each do |entry| + if entry.is_a?(::Net::IMAP::MailboxList) + result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim} + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}}) + end + end + result + end + # + def xlist(reference=nil, mailbox=nil) + result = [] + if @client + begin + unless reference + raise ArgumentError, "You MUST sepcify a REFERENCE to list." + end + unless mailbox + raise ArgumentError, "You MUST sepcify a MAILBOX to list." + end + raw_list = @client.xlist(reference, mailbox) + if raw_list + raw_list.each do |entry| + if entry.is_a?(::Net::IMAP::MailboxList) + result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim} + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}}) + end + end + result + end + # + def upload_message(mailbox=nil, gxg_message=nil, flags=[:Seen], timestamp=::Time.now) + result = false + if @client + begin + if mailbox.is_a?(::GxG::Events::Message) + if gxg_message.is_any?(::Symbol,::Array) + flags = gxg_message + end + gxg_message = mailbox + mailbox = nil + end + unless mailbox + mailbox = @mounted + unless @mount_mode == :read_write + raise Exception, "You should re-mount #{@mounted.inspect} as :read_write, or select another MAILBOX." + end + end + unless mailbox + raise ArgumentError, "You MUST sepcify a MAILBOX to append the message to." + end + if flags.is_a?(::Symbol) + flags = [(flags)] + end + if flags.is_a?(::Array) + unless flags.include?(:Seen) + flags << :Seen + end + end + unless gxg_message.is_a?(::GxG::Events::Message) + raise ArgumentError, "You MUST provide a ::GxG::Events::Message object as the message. See: .message_template()" + end + # Build suitable mail message + message = Mail.new + the_fields = [] + gxg_message[:header].each_pair do |the_key, the_value| + if the_value.is_a?(::Array) + the_value.each do |the_field_value| + the_fields << "#{the_key.to_s}: #{the_field_value.to_s}" + end + else + the_fields << "#{the_key.to_s}: #{the_value.to_s}" + end + end + message.header = the_fields.join("\r\n") + message.from = gxg_message[:sender] + message.reply_to = gxg_message[:sender] + if gxg_message[:id].to_s.include?("@") + message.message_id = gxg_message[:id].to_s + else + message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>" + end + message.to = gxg_message[:to] + if gxg_message[:cc] + message.cc = gxg_message[:cc] + end + if gxg_message[:bcc] + message.bcc = gxg_message[:bcc] + end + message.subject = gxg_message[:subject] + message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s) + if gxg_message[:attachments].size > 0 + multipart = true + else + part_count = 0 + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + part_count += 1 + end + end + if part_count > 1 + multipart = true + else + multipart = false + end + end + if multipart + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + type_info = "Content-Type: #{part[:content_type][:type]}" + if part[:content_type][:parameters].keys.size > 0 + part[:content_type][:parameters].each_pair do |the_key, the_value| + type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'" + end + end + new_part = Mail::Part.new(type_info) + new_part.body = part[:content] + message.add_part(new_part) + end + end + # Add file attachments: + gxg_message[:attachments].each do |attachment| + if attachment[:content].size == 0 + file_size = ::File.size(attachment[:path]) + file_handle = ::File.open(attachment[:path],"rb") + ::GxG::apportioned_ranges(file_size,65536).each do |the_range| + file_handle.seek(the_range.first) + attachment[:content] << file_handle.read(the_range.size) + end + file_handle.close + end + message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s}) + end + else + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + message.body = part[:content] + break + end + end + end + # Send it + @client.append(mailbox, message.to_s, flags, timestamp) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox, :message => gxg_message, :flags => flags}}) + end + end + result + end + # + def fetch_message(uid=nil) + result = nil + if @client + begin + unless self.mounted? + raise Exception, "You MUST mount a Mailbox prior to calling this operation." + end + unless uid.is_a?(::Integer) + raise ArgumentError, "You MUST provide an Integer to specify the Message UID Number." + end + parse_message = Proc.new do |the_raw_message| + header = {} + body = [] + attachments = [] + parser = ::Mail.new(the_raw_message) + parser.header_fields.each do |field| + the_key = field.name.to_s.to_sym + the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8) + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!("\r\n","") + if header[(the_key)] + unless header[(the_key)].is_a?(::Array) + header[(the_key)] = [(header[(the_key)])] + end + header[(the_key)] << the_data + else + header[(the_key)] = the_data + end + end + if parser.multipart? + link_db = [] + body << {:preamble => parser.body.preamble} + parser.parts.each do |part| + if part.multipart? + link_db << part + else + if part.attachment? + attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)} + else + body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + end + end + while link_db.size > 0 do + part = link_db.shift + if part.multipart? + part.parts.each do |the_sub_part| + link_db << the_sub_part + end + else + if part.attachment? + attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)} + else + body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + end + end + body << {:epilogue => parser.body.epilogue} + else + body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + mime_type = "text/plain" + parameters = {"charset" => "UTF-8"} + body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8) + if body_text.xml? + mime_type = "text/xml" + end + if body_text.html? + mime_type = "text/html" + end + body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text} + body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + gxg_message = new_message + gxg_message[:sender] = parser.from + gxg_message[:id] = parser.message_id + gxg_message[:to] = parser.to + gxg_message[:cc] = parser.cc + gxg_message[:bcc] = parser.bcc + gxg_message[:subject] = parser.subject + gxg_message[:date] = parser.date + gxg_message[:header] = header + gxg_message[:body] = body + gxg_message[:attachments] = attachments + gxg_message + end + # + raw_response = @client.uid_fetch(uid,"BODY[]") + if raw_response.is_a?(::Array) + if raw_response.size > 0 + result = parse_message.call(raw_response[0].attr()["BODY[]"]) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:unique_id => uid}}) + end + end + result + end + # + def set_message_attributes(uid=nil, attributes=nil, flags=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID to set an ATTRIBUTE." + end + unless attributes.is_a?(::String) + raise ArgumentError, "You MUST specify what ATTRIBUTE to set." + end + unless flags.is_a?(::Array) + raise ArgumentError, "You MUST specify what FLAGS to set/unset." + end + @client.uid_store(uid, attributes, flags) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid, :attributes => attributes, :flags => flags}}) + end + end + result + end + # + def add_message_flags(uid=nil, flags=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID name to set FLAGS." + end + unless flags.is_a?(::Array) + raise ArgumentError, "You MUST specify what FLAGS to set." + end + self.set_message_attributes(uid, "+FLAGS", flags) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}}) + end + end + result + end + # + def remove_message_flags(uid=nil, flags=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID name to clear FLAGS." + end + unless flags.is_a?(::Array) + raise ArgumentError, "You MUST specify what FLAGS to clear." + end + self.set_message_attributes(uid, "-FLAGS", flags) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid, :flags => flags}}) + end + end + result + end + # + def remove_messages(uid=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID to remove." + end + self.add_message_flags(uid, [:Deleted]) + @client.expunge + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid}}) + end + end + result + end + # + def copy_to_mailbox(uid=nil, mailbox=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID to copy the message." + end + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX to copy the message to." + end + self.uid_copy(uid, mailbox) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}}) + end + end + result + end + # + def move_to_mailbox(uid=nil, mailbox=nil) + result = false + if @client + begin + unless uid.is_any?(::Integer, ::Array) + raise ArgumentError, "You MUST specify a UID to move the message." + end + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX to move the message to." + end + self.uid_move(uid, mailbox) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid, :mailbox => mailbox}}) + end + end + result + end + # + def fetch_manifest(uid_set=[]) + result = [] + if @client + begin + unless self.mounted? + raise Exception, "You MUST mount a Mailbox prior to calling this operation." + end + if uid_set.is_a?(::Integer) + uid_set = [(uid_set)] + end + unless uid_set.is_a?(::Array) + raise ArgumentError, "You MUST provide an Array of Message UID Integers." + end + # + parse_header = Proc.new do |the_raw_message| + header = {} + parser = ::Mail.new(the_raw_message) + parser.header_fields.each do |field| + the_key = field.name.to_s.to_sym + the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8) + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!("\r\n","") + if header[(the_key)] + unless header[(the_key)].is_a?(::Array) + header[(the_key)] = [(header[(the_key)])] + end + header[(the_key)] << the_data + else + header[(the_key)] = the_data + end + end + header + end + # + uid_set.each do |uid| + record = {:uid => uid, :date => nil, :from => "", :to => "", :subject => ""} + raw_response = @client.uid_fetch(uid,"BODY[HEADER]") + if raw_response.is_a?(::Array) + if raw_response.size > 0 + header = parse_header.call(raw_response[0].attr()["BODY[HEADER]"]) + record[:date] = ::DateTime.parse(header[:Date].to_s) + record[:from] = header[:From] + record[:to] = header[:To] + record[:subject] = header[:Subject] + result << record + end + end + # + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:uid_set => uid_set}}) + end + end + result + end + # + def uid_list(criteria=["NEW"]) + result = [] + if @client + begin + unless self.mounted? + raise Exception, "You MUST mount a Mailbox prior to calling this operation." + end + unless criteria.is_a?(::Array) + raise ArgumentError, "You MUST provide a search criteria in the form of an Array of Strings." + end + result = @client.uid_search(criteria) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:criteria => criteria}}) + end + end + result + end + # + def theaded_list(algorithm=nil,search_keys=nil, charset="UTF-8") + result = [] + if @client + begin + unless self.mounted? + raise Exception, "You MUST mount a Mailbox prior to calling this operation." + end + unless ["orderedsubject","references"].include?(algorithm.to_s.downcase) + raise ArgumentError, "The algorithm you specified is invalid. Choose one of these: ['ORDEREDSUBJECT', 'REFERENCES']" + end + algorithm = algorithm.to_s.upcase + unless search_keys.is_a?(::Array) + raise ArgumentError, "You MUST provide search keys in the form of an Array of Strings." + end + unless charset + raise ArgumentError, "You MUST provide a CHARSET specifier. Try one of these: ['US-ASCII', 'UTF-8']" + end + result = @client.uid_thread(algorithm, search_keys, charset) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:algorithm => algorithm, :search_keys => search_keys, :charset => charset}}) + end + end + result + end + # + def sort(sort_keys=["DATE"], search_keys=["ALL"], charset="UTF-8") + result = [] + if @client + begin + unless self.mounted? + raise Exception, "You MUST mount a Mailbox prior to calling this operation." + end + unless @mount_mode == :read_write + raise Exception, "You need to re-mount #{@mounted.inspect} as :read_write to peform this operation." + end + result = @client.uid_sort(sort_keys, search_keys, charset) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:sort_keys => sort_keys, :search_keys => search_keys, :charset => charset}}) + end + end + result + end + # + def create_mailbox(mailbox=nil) + result = [] + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name to create it." + end + result = @client.create(mailbox) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox}}) + end + end + result + end + # + def remove_mailbox(mailbox=nil) + result = [] + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name to delete it." + end + result = @client.delete(mailbox) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox}}) + end + end + result + end + # + def rename_mailbox(mailbox=nil, new_name=nil) + result = false + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name to rename it." + end + unless new_name + raise ArgumentError, "You MUST specify a NEW_NAME name to rename the MAILBOX." + end + @client.rename(mailbox, new_name) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox, :new_name => new_name}}) + end + end + result + end + # + def subscribe(mailbox=nil) + result = false + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name to subscribe to." + end + @client.subscribe(mailbox) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox}}) + end + end + result + end + # + def unsubscribe(mailbox=nil) + result = false + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name to unsubscribe from." + end + @client.unsubscribe(mailbox) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox}}) + end + end + result + end + # + def list_subscriptions(reference=nil, mailbox=nil) + result = [] + if @client + begin + raw_list = @client.lsub(reference, mailbox) + if raw_list + raw_list.each do |entry| + if entry.is_a?(::Net::IMAP::MailboxList) + result << {:name => entry.name, :flags => entry.attr, :delimiter => entry.delim} + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:reference => reference, :mailbox => mailbox}}) + end + end + result + end + # + def get_quota(mailbox=nil) + result = nil + if @client + begin + unless mailbox + raise ArgumentError, "You MUST specify a MAILBOX name." + end + raw_response = @client.getquotaroot(mailbox)[1] + if raw_response.is_a?(::Net::IMAP::MailboxQuota) + result = {:usage => raw_response.usage.to_i, :quota => raw_response.quota.to_i} + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:mailbox => mailbox}}) + end + end + result + end + # + end + # + ::GxG::Networking::DISPATCHER.register_client("imap", ::GxG::Networking::ImapClient) + ::GxG::Networking::DISPATCHER.register_client("imaps", ::GxG::Networking::ImapClient) + # SMTP Classes: + class SmtpClient + # + def initialize(the_url=nil,options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + @user = the_url.user + @password = the_url.password + @client = nil + @host = the_url.hostname + if options[:use_ssl] == true || the_url.scheme == "ssmtp" + @port = (the_url.port || 465) + if options[:ssl_method] + # Valid: [:TLSv1, :TLSv1_server, :TLSv1_client, :TLSv1_1, :TLSv1_1_server, :TLSv1_1_client, :TLSv1_2, :TLSv1_2_server, :TLSv1_2_client, :SSLv23, :SSLv23_server, :SSLv23_client] + unless OpenSSL::SSL::SSLContext::METHODS.include?(options[:ssl_method]) + raise ArgumentError, "Encryption method #{options[:ssl_method].inspect} is not valid. Choose one of these: #{OpenSSL::SSL::SSLContext::METHODS.inspect}" + end + openssl_context = OpenSSL::SSL::SSLContext.new(options[:ssl_method]) + else + openssl_context = ::Net::SMTP.default_ssl_context + end + if options[:ignore_ssl_errors] == true + openssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE + else + openssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + else + @port = (the_url.port || 25) + openssl_context = nil + end + @client = ::Net::SMTP.new(@host, @port) + if openssl_context + @client.enable_ssl(openssl_context) + end + # + self + end + # + def host() + @host.clone + end + # + def port() + @port.clone + end + # + def login(user_id=nil, password=nil, options={:authentication_method=> :plain}) + result = false + if user_id.is_a?(::Hash) + options = user_id + user_id = nil + password = nil + end + unless options.is_a?(::Hash) + raise ArgumentError, "You MUST provide a options as a Hash" + end + authentication_method = (options[:authentication_method] || :plain) + if @client + begin + unless user_id + user_id = @user + unless user_id + raise ArgumentError, "You MUST provide a USER id, like this: ,,()" + end + end + unless password + password = @password + unless password + raise ArgumentError, "You MUST provide a PASSWORD/OAUTH-TOKEN, like this: ,,()" + end + end + unless @client.started? + unless [:plain, :login, :cram_md5, :oauth2].include?(authentication_method.to_s.to_sym) + raise ArgumentError, "Authentication method #{authentication_method.inspect} is invalid. Choose one of these: [:plain, :login, :cram_md5, :oauth2]" + end + # Internal cosmetics for interface consistency. + if authentication_method == :oauth2 + authentication_method = :xoauth2 + end + @client.start(@host, user_id, password, authentication_method) + end + result = true + rescue Exception => the_error + # Internal cosmetics for interface consistency. + if authentication_method == :xoauth2 + authentication_method = :oauth2 + end + log_error({:error => the_error, :parameters => {:user => user_id, :authentication => authentication_method}}) + end + end + result + end + # + def logout() + if @client + begin + @client.finish + @client = nil + true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + false + end + else + false + end + end + # + def attachment_template(the_path="", content_parameters={"name" => ""}) + result = nil + begin + unless the_path.to_s.valid_path? + raise ArgumentError, "You MUST provide a valid file system path to the file." + end + unless content_parameters.is_a?(::Hash) + raise ArgumentError, "You MUST provide content_parameters as a Hash." + end + unless content_parameters["name"].to_s.size > 0 + content_parameters["name"] = ::File.basename(the_path) + end + discovered_mime = ::MimeMagic.by_magic(::File.open(the_path,"rb")) + if discovered_mime + discovered_mime = discovered_mime.type + else + discovered_mime = "application/octet-stream" + end + result = {:content_type => {:type => discovered_mime, :parameters => content_parameters}, :filename => (::File.basename(the_path)), :path => the_path, :content => ::GxG::ByteArray.new} + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :content_parameters => content_parameters}}) + end + result + end + # + def message_template(mime_type="text/plain", content_parameters={"charset"=>"UTF-8"}) + result = nil + begin + unless ['text/plain', 'text/html', 'text/xhtml', 'text/xml'].include?(mime_type.to_s) + raise ArgumentError, "Content Type specified was invalid. Choose one of these: ['text/plain', 'text/html', 'text/xhtml', 'text/xml']" + end + unless content_parameters.is_a?(::Hash) + raise ArgumentError, "You MUST provide content_parameters as a Hash." + end + gxg_message = new_message + gxg_message[:sender] = "" + gxg_message[:id] = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>" + gxg_message[:to] = "" + gxg_message[:cc] = nil + gxg_message[:bcc] = nil + gxg_message[:subject] = "" + gxg_message[:date] = ::DateTime.now + gxg_message[:header] = {} + gxg_message[:body] = [{:prologue => ""},{:content_type => {:type => mime_type.to_s, :parameters => content_parameters}, :content => ""},{:epilogue => ""}] + gxg_message[:attachments] = [] + result = gxg_message + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:content_type => mime_type, :content_parameters => content_parameters}}) + end + result + end + # + def send_message(gxg_message=nil) + result = false + if @client + begin + unless @client.started? + raise Exception, "SMTP session not started yet." + end + unless gxg_message.is_a?(::GxG::Events::Message) + raise ArgumentError, "You MUST provide a valid message object of class ::GxG::Events::Message. Suggested: .message_template()" + end + # Build suitable mail message + message = Mail.new + the_fields = [] + gxg_message[:header].each_pair do |the_key, the_value| + if the_value.is_a?(::Array) + the_value.each do |the_field_value| + the_fields << "#{the_key.to_s}: #{the_field_value.to_s}" + end + else + the_fields << "#{the_key.to_s}: #{the_value.to_s}" + end + end + message.header = the_fields.join("\r\n") + message.from = gxg_message[:sender] + message.reply_to = gxg_message[:sender] + if gxg_message[:id].to_s.include?("@") + message.message_id = gxg_message[:id].to_s + else + message.message_id = "<#{gxg_message[:id].to_s}@#{::Socket.gethostname}.mail>" + end + message.to = gxg_message[:to] + if gxg_message[:cc] + message.cc = gxg_message[:cc] + end + if gxg_message[:bcc] + message.bcc = gxg_message[:bcc] + end + message.subject = gxg_message[:subject] + message.date = (gxg_message[:date].to_s || ::DateTime.now.to_s) + if gxg_message[:attachments].size > 0 + multipart = true + else + part_count = 0 + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + part_count += 1 + end + end + if part_count > 1 + multipart = true + else + multipart = false + end + end + if multipart + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + type_info = "Content-Type: #{part[:content_type][:type]}" + if part[:content_type][:parameters].keys.size > 0 + part[:content_type][:parameters].each_pair do |the_key, the_value| + type_info << "; '#{the_key.to_s}': '#{the_value.to_s}'" + end + end + new_part = Mail::Part.new(type_info) + new_part.body = part[:content] + message.add_part(new_part) + end + end + # Add file attachments: + gxg_message[:attachments].each do |attachment| + if attachment[:content].size == 0 + file_size = ::File.size(attachment[:path]) + file_handle = ::File.open(attachment[:path],"rb") + ::GxG::apportioned_ranges(file_size,65536).each do |the_range| + file_handle.seek(the_range.first) + attachment[:content] << file_handle.read(the_range.size) + end + file_handle.close + end + message.add_file({:filename => attachment[:filename], :content => attachment[:content].to_s}) + end + else + gxg_message[:body].each do |part| + unless part[:prologue] || part[:epilogue] + message.body = part[:content] + break + end + end + end + # Send it + @client.send_message(message.to_s, gxg_message[:sender], message.destinations) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:message => gxg_message}}) + end + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("smtp", ::GxG::Networking::SmtpClient) + ::GxG::Networking::DISPATCHER.register_client("ssmtp", ::GxG::Networking::SmtpClient) + # POP3 Classes: + class Pop3Client + # + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + @user = the_url.user + @password = the_url.password + @client = nil + @host = the_url.hostname + if options[:use_ssl] == true || the_url.scheme.to_s == "pop3s" + @port = (port || 995) + else + @port = (port || 110) + end + @client = ::Net::POP3.new(@host, @port, (options[:use_apop] || false)) + if options[:use_ssl] == true + if options[:ignore_ssl_errors] == true + @client.enable_ssl(OpenSSL::SSL::VERIFY_NONE) + else + @client.enable_ssl(OpenSSL::SSL::VERIFY_PEER) + end + end + # + self + end + # + def login(user_id=nil,password=nil,options={}) + if @client + unless user_id + user_id = @user + end + unless password + password = @password + end + unless password + raise ArgumentError, "You MUST provide a PASSWORD, like this: ,,()" + end + @client.start(user_id, password) + true + else + false + end + # + end + # + def logout() + if @client + @client.finish + @client = nil + true + end + end + # + def message_count() + # Return the number of emails on the server. + if @client + @client.n_mails() + else + 0 + end + end + # + def retrieve_messages(options={:read_only=>false},&block) + result = [] + begin + if @client + if @client.started? + if @client.active? + parse_message = Proc.new do |the_raw_message| + header = {} + body = [] + attachments = [] + parser = ::Mail.new(the_raw_message.pop) + parser.header_fields.each do |field| + the_key = field.name.to_s.to_sym + the_data = field.to_s.transcode({:replace => "."},::Encoding::UTF_8) + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!(" ","") + the_data.gsub!("\r\n","") + if header[(the_key)] + unless header[(the_key)].is_a?(::Array) + header[(the_key)] = [(header[(the_key)])] + end + header[(the_key)] << the_data + else + header[(the_key)] = the_data + end + end + if parser.multipart? + link_db = [] + body << {:preamble => parser.body.preamble} + parser.parts.each do |part| + if part.multipart? + link_db << part + else + if part.attachment? + attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)} + else + body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + end + end + while link_db.size > 0 do + part = link_db.shift + if part.multipart? + part.parts.each do |the_sub_part| + link_db << the_sub_part + end + else + if part.attachment? + attachments << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :filename => part.filename, :path => nil, :content => ::GxG::ByteArray.new(part.decoded)} + else + body << {:content_type => {:type => part.mime_type, :parameters => part.content_type_parameters}, :content => part.body.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + end + end + body << {:epilogue => parser.body.epilogue} + else + body << {:preamble => parser.body.preamble.to_s.transcode({:replace => "."},::Encoding::UTF_8)} + mime_type = "text/plain" + parameters = {"charset" => "UTF-8"} + body_text = parser.body.to_s.transcode({:replace => "."},::Encoding::UTF_8) + if body_text.xml? + mime_type = "text/xml" + end + if body_text.html? + mime_type = "text/html" + end + body << {:content_type => {:type => mime_type, :parameters => parameters}, :content => body_text} + body << {:epilogue => parser.body.epilogueto_s.transcode({:replace => "."},::Encoding::UTF_8)} + end + gxg_message = new_message + gxg_message[:sender] = parser.from + gxg_message[:id] = parser.message_id + gxg_message[:to] = parser.to + gxg_message[:cc] = parser.cc + gxg_message[:bcc] = parser.bcc + gxg_message[:subject] = parser.subject + gxg_message[:date] = parser.date + gxg_message[:header] = header + gxg_message[:body] = body + gxg_message[:attachments] = attachments + gxg_message + end + @client.each_mail do |raw_message| + if block.respond_to?(:call) + block.call(parse_message.call(raw_message)) + else + result << parse_message.call(raw_message) + end + unless options[:read_only] == true + raw_message.delete + end + end + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("pop3", ::GxG::Networking::Pop3Client) + ::GxG::Networking::DISPATCHER.register_client("pop3s", ::GxG::Networking::Pop3Client) + # REST Classes: + class RestClient + # + def self.set_system_proxy(the_url=nil) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid proxy URI." + end + ::RestClient.proxy = the_url.to_s + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url}}) + end + result + end + # + def self.clear_system_proxy() + result = false + begin + ::RestClient.proxy = nil + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def intitialize(the_url=nil, options={}) + # REST Servers to test with: + # http://services.groupkt.com/country/get/all + # http://services.groupkt.com/state/get/{countryCode}/all + # https://httpbin.org/get + # + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + @url = the_url.clone + if options[:query].is_a?(::Hash) + @url.query = ::URI.encode_www_form(options[:query]) + end + unless options[:follow_redirects] == false + options.merge({:follow_redirects => true}) + end + # + if @url.scheme.downcase == "https" + options[:use_ssl] = true + end + if the_url.user.to_s.size > 0 + @authenticated = true + @user = the_url.user + @password = the_url.password + else + @authenticated = false + @user = nil + @password = nil + end + @pem_option = nil + if options[:pem_source].is_any?(::File, ::GxG::Database::PersistedArray) + pem_data = nil + if options[:pem_source].is_a?(::File) + options[:pem_source].rewind + pem_data = options[:pem_source].read() + else + buffer = ::GxG::ByteArray.new + options[:pem_source].each_index do |segment| + buffer << options[:pem_source][(segment)].to_s + options[:pem_source].unload(segment) + end + pem_data = buffer.to_s + end + if pem_data + @pem_option = {:ssl_client_cert => OpenSSL::X509::Certificate.new(pem_data), :ssl_client_key => OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]), :verify_ssl => OpenSSL::SSL::VERIFY_PEER} + end + end + @proxy_option = nil + if options[:proxy].is_a?(::URI::Generic) + @proxy_option = options[:proxy] + end + # Review : update initialization parameters to track client changes. + # ???? + if the_url.scheme.downcase.to_s == "https" + @client = ::GxG::Networking::HttpsClient.new + else + @client = ::GxG::Networking::HttpClient.new + end + @host = the_url.hostname + # + @cookies = nil + self + end + # + def clear_cache() + @cookies = nil + true + end + # + def get(the_url=nil, options={}, &block) + result = nil + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + if options.is_a?(::Hash) + options = @options.merge(options) + else + options = @options + end + header_options = (options[:header_options] || {}).merge({:accept => :json}) + if @cookies + unless header_options[:cookies] + header_options[:cookies] = @cookies + end + end + if @pem_option + header_options = header_options.merge(@pem_option) + end + if @proxy_option + header_options[:proxy] = @proxy_option + end + if options[:save_to].is_a?(::File, ::GxG::Database::PersistedArray) + client = ::GxG::Networking::HttpClient.new(the_url, options) + response = client.get(the_url, {:save_to => options[:save_to], :proxy => options[:proxy]}) + if response == true + result = options[:save_to] + if block.respond_to?(:call) + block.call(result) + end + end + else + if options[:parameters].is_a?(::Hash) + response = ::RestClient.get(the_url.to_s, header_options.merge({:params => options[:parameters]}), &block) + else + response = ::RestClient.get(the_url.to_s, header_options, &block) + end + # + unless @cookies + if response.cookies.is_a?(::Hash) + if response.cookies.keys.size > 0 + @cookies = response.cookies + end + end + end + result = response + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def put(the_url=nil, data=nil, options={}, &block) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + unless data.is_a?(::Hash) + raise Exception, "You MUST provide data to PUT to the server (in the form of a Hash)." + end + if options.is_a?(::Hash) + options = @options.merge(options) + else + options = @options + end + if @cookies + unless header_options[:cookies] + header_options[:cookies] = @cookies + end + end + if @pem_option + header_options = header_options.merge(@pem_option) + end + if @proxy_option + header_options[:proxy] = @proxy_option + end + header_options = (options[:header_options] || {}).merge({:accept => :json}) + if options[:to_json] + data = data.to_json() + header_options[:content_type] = :json + else + # Review : completely redo file uploads with PUT ???? + # BufferedSegments + if options[:upload_file].is_any?(::File, ::GxG::Database::PersistedArray) + if options[:upload_file].is_a?(::GxG::Database::PersistedArray) + the_upload = ::GxG::Storage::BufferedSegments.new(options[:upload_file]) + else + the_upload = options[:upload_file] + end + data[(::GxG::uuid_generate().to_sym)] = the_upload + else + data.each_pair do |the_key, the_value| + if the_value.is_a?(::GxG::Database::PersistedArray) + data[(the_key)] = ::GxG::Storage::BufferedSegments.new(the_value) + end + end + end + end + result = ::RestClient.put(the_url.to_s, data, header_options, &block) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def post(the_url=nil, form_data=nil, options={}, &block) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + unless form_data.is_a?(::Hash) + raise Exception, "You MUST provide valid form data (in the form of a Hash)." + end + unless options.is_a?(::Hash) + options = {} + end + header_options = (options[:header_options] || {}).merge({:accept => :json}) + if @cookies + unless header_options[:cookies] + header_options[:cookies] = @cookies + end + end + if @pem_option + header_options = header_options.merge(@pem_option) + end + if @proxy_option + header_options[:proxy] = @proxy_option + end + if options[:upload_file].is_any?(::File, ::GxG::Database::PersistedArray) + if the_value.is_a?(::GxG::Database::PersistedArray) + the_upload = ::GxG::Storage::BufferedSegments.new(the_value) + else + the_upload = options[:upload_file] + end + form_data[:file] = the_upload + form_data[:multipart] = true + else + form_data.each_pair do |the_key, the_value| + if the_value.is_a?(::GxG::Database::PersistedArray) + form_data[(the_key)] = ::GxG::Storage::BufferedSegments.new(the_value) + end + end + end + result = ::RestClient.post(the_url.to_s, form_data, header_options, &block) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def delete(the_url=nil, options={}, &block) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + unless options.is_a?(::Hash) + options = {} + end + header_options = (options[:header_options] || {}).merge({:accept => :json}) + if @cookies + unless header_options[:cookies] + header_options[:cookies] = @cookies + end + end + if @pem_option + header_options = header_options.merge(@pem_option) + end + if @proxy_option + header_options[:proxy] = @proxy_option + end + result = ::RestClient.delete(the_url.to_s, header_options, &block) + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("rest-https", ::GxG::Networking::RestClient) + ::GxG::Networking::DISPATCHER.register_client("rest-http", ::GxG::Networking::RestClient) + # SOAP Classes: + class SoapDriver < ::Handsoap::Service + # Example sites to test with: + # http://www.restfulwebservices.net/rest/WeatherForecastService.svc?wsdl + # http://www.restfulwebservices.net/wcf/WeatherForecastService.svc?wsdl + # https://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl + # http://www.webservicex.com/CurrencyConvertor.asmx?wsdl + # + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + super() + @user = nil + @password = nil + @interface = [] + @tns_url = nil + ::Handsoap::http_driver = :net_http + ::Handsoap::xml_query_driver = :rexml + if the_url.query.to_s.downcase.include?("wsdl") || (the_url.path.to_s.downcase.include?("wsdl")) + point = (the_url.scheme.to_s + "://") + if the_url.user + point << the_url.user + @user = the_url.user + end + if the_url.password + point << (":" << the_url.password) + @password = the_url.password + end + if the_url.hostname + if the_url.user + point << ("@" << the_url.hostname) + else + point << the_url.hostname + end + end + if the_url.path + point << the_url.path + end + # Process WSDL, determine actual version number from that. + http_tool = ::GxG::Networking::HttpClient.new(the_url, options) + response = http_tool.get(the_url) + if response + wsdl_data = response.body.transcode({:replace => "."},::Encoding::UTF_8) + if wsdl_data.include?("http://schemas.xmlsoap.org/soap/envelope/") + wsdl_version = 1 + else + wsdl_version = 2 + end + ::GxG::Networking::SoapDriver::endpoint({:uri => (point), :version => (wsdl_version)}) + # Build Interface Data: + document = ::Oga.parse_xml(wsdl_data) + nodes = document.css("definitions") + nodes.each do |node| + next unless node.is_a?(Oga::XML::Element) + node.attributes.each do |attribute| + if ["tns", "targetNamespace"].include?(attribute.name) + @tns_url = attribute.value + break + end + end + if @tns_url + break + end + end + unless @tns_url + raise Exception, "Failed to load namespace (required)." + end + nodes = document.css("portType") + base_names = [] + nodes.each do |node| + next unless node.is_a?(::Oga::XML::Element) + node.children.each do |the_operation| + next unless the_operation.is_a?(::Oga::XML::Element) + basename = nil + the_operation.attributes.each do |attribute| + if attribute.name == "name" + basename = attribute.value + break + end + end + if basename + record = {:base => (basename), :message => (("tns:" << basename).to_sym), :input => nil, :output => nil} + the_operation.children.each do |the_io| + next unless the_io.is_a?(::Oga::XML::Element) + if ["input", "output"].include?(the_io.name) + the_io.attributes.each do |the_message| + if the_message.name == "message" + record[(the_io.name.to_sym)] = the_message.value + end + end + end + end + if record[:input] || record[:output] + base_names << record + end + end + end + end + messages = document.css("message") + base_names.each do |the_method| + record = {:method => (the_method[:message]), :input => nil, :output => nil} + messages.each do |the_message| + next unless the_message.is_a?(::Oga::XML::Element) + the_message.attributes.each do |the_attribute| + direction_key = nil + if the_method[:input].gsub(/^.+:/, "") == the_attribute.value + direction_key = :input + end + if the_method[:output].gsub(/^.+:/, "") == the_attribute.value + direction_key = :output + end + if direction_key + parameters = {} + the_message.children.each do |the_part| + next unless the_part.is_a?(::Oga::XML::Element) + if the_part.name == "part" + the_name = nil + the_type = nil + the_part.attributes.each do |the_parameter| + if the_parameter.name == "name" + the_name = the_parameter.value + end + if the_parameter.name == "type" + the_type = the_parameter.value + end + if the_parameter.name == "element" + the_type = the_parameter.value + end + if (the_name && the_type) + parameters[(the_name.to_sym)] = the_type + the_name = nil + the_type = nil + end + end + end + end + if parameters.keys.size > 0 + record[(direction_key)] = parameters + end + end + end + end + if record[:input] || record[:output] + @interface << record + end + end + # + else + raise Exception, "Failed to retrieve WSDL data." + end + else + raise ArgumentError, "No WSDL endpoint detected (required)." + end + # + self + end + # Handsoap support hooks: + def on_create_document(the_document=nil) + if @tns_url + the_document.alias('tns', @tns_url) + end + end + # + def on_before_dispatch(the_document=nil) + # + end + # + def on_response_document(the_document=nil) + if @tns_url + the_document.add_namespace('ns', @tns_url) + end + end + # + def interface() + @interface + end + # + end + # + class SoapClient + # Standard: + def initialize(the_url=nil, options={}) + @client = ::GxG::Networking::SoapDriver.new(the_url, options) + self + end + # + def login(the_url=nil, options={}) + result = false + begin + # Review - investigate how to do basic-auth or other method on a SOAP server. + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def logout() + @client = nil + true + end + # + def closed?() + if @client + false + else + true + end + end + # + def callmethod(*args) + result = nil + begin + if args.size == 1 + the_method = args[0].to_s + arguments = {} + else + if args.size > 1 + the_method = args[0].to_s + unless args[1].is_any?(::Hash, ::Array) + raise ArgumentError, "You MUST provide a Hash or Array to specify parameters for this method." + end + arguments = args[1] + else + raise ArgumentError, "You MUST provide a valid SOAP method." + end + end + if @client + response_tag = (the_method.gsub(/^.+:/, "") + "Response") + response = @client.invoke(the_method) do |message| + if arguments.is_a?(::Hash) + arguments.each_pair do |the_key, the_value| + message.add(the_key.to_s, the_value) + end + end + if arguments.is_a?(::Array) + arguments.each do |the_value| + message.add("literal", the_value) + end + end + end + data = ::Oga::parse_xml(response.to_xml).css(response_tag) + if data + found_numbers = data.text.numeric_values + found_value = [] + if found_numbers.is_a?(::Array) + found_numbers.each do |the_subarray| + if the_subarray.is_a?(::Array) + if the_subarray[0].is_a?(::Hash) + the_key = the_subarray[0].keys[0] + found_value << the_subarray[0][(the_key)] + end + end + end + end + result = {} + result[(response_tag.to_sym)] = data.text + # if found_value.size > 0 + # if found_value.size == 1 + # result[(response_tag.to_sym)] = found_value[0] + # else + # result[(response_tag.to_sym)] = found_value + # end + # else + # result[(response_tag.to_sym)] = data.text + # end + else + raise Exception, "Failed to interpret response data. Data: #{data.inspect}" + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}}) + end + result + end + # + def interface() + if @client + @client.interface + end + end + # + end + ::GxG::Networking::DISPATCHER.register_client("soap-https", ::GxG::Networking::SoapClient) + ::GxG::Networking::DISPATCHER.register_client("soap-http", ::GxG::Networking::SoapClient) + # XMLRPC Classes: + class XmlrpcClient + # + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https' + client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 443)] + else + client_parameters = [(the_url.hostname), (the_url.path), (the_url.port || 80)] + end + if options[:proxy].is_a?(::URI::Generic) + client_parameters << options[:proxy].host + client_parameters << options[:proxy].port + else + client_parameters << nil + client_parameters << nil + end + if the_url.user && the_url.password + client_parameters << the_url.user + client_parameters << the_url.password + else + client_parameters << nil + client_parameters << nil + end + if options[:use_ssl] == true || the_url.scheme.to_s.downcase == 'https' + client_parameters << true + else + client_parameters << nil + end + if options[:timeout].is_a?(::Numeric) + client_parameters << options[:timeout].to_i + else + client_parameters << 30 + end + @client = ::XMLRPC::Client.new(*client_parameters) + @interface = nil + self.interface() + self + end + # + def login(the_url=nil, options={}) + result = false + begin + # Review - research how/when;/if you do basic-auth or other method of authentication for XML-RPC. + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url}}) + end + result + end + # + def logout() + @client = nil + true + end + # + def closed?() + if @client + false + else + true + end + end + # + def callmethod(the_method = nil, arguments = nil) + # The +method+ parameter is converted into a String and should + # be a valid XML-RPC method-name. + # + # Each parameter of +args+ must be of one of the following types, + # where Hash, Struct and Array can contain any of these listed _types_: + # + # * Fixnum, Bignum + # * TrueClass, FalseClass, +true+, +false+ + # * String, Symbol + # * Float + # * Hash, Struct + # * Array + # * Date, Time, XMLRPC::DateTime + # * XMLRPC::Base64 + # * A Ruby object which class includes XMLRPC::Marshallable + # (only if Config::ENABLE_MARSHALLABLE is +true+). + # That object is converted into a hash, with one additional key/value + # pair ___class___ which contains the class name + # for restoring that object later. + result = nil + begin + unless the_method.is_a?(::String) + raise ArgumentError, "You MUST provide a valid method String." + end + if @client + calldata = [] + calldata << the_method + # + # TODO: scour arguments structure for type conversions to be done. + if arguments.is_a?(::Array) + arguments.each do |entry| + calldata << entry + end + else + if arguments + calldata << arguments + end + end + result = @client.call(*calldata) + # TODO: scour result structure for type conversions to gxg standard. + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:method => the_method, :arguments => arguments}}) + end + result + end + # + def interface() + begin + if @client + unless @interface + @interface = @client.call("interface") + end + unless @interface + @interface = @client.call("Interface") + end + end + rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + end + @interface + end + # + end + ::GxG::Networking::DISPATCHER.register_client("xmlrpc-https", ::GxG::Networking::XmlrpcClient) + ::GxG::Networking::DISPATCHER.register_client("xmlrpc-http", ::GxG::Networking::XmlrpcClient) + # ### SSH Client + class SshClient + def initialize(he_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + if the_url.password + options[:password] = the_url.password + end + @client = ::Net::SSH.start(the_url.hostname, the_url.user, options) + self + end + # + def login(the_url=nil, options={}) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + if the_url.password + options[:password] = the_url.password + end + @client = ::Net::SSH.start(the_url.hostname, the_url.user, options) + result = true + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def send_command(the_command=nil) + result = nil + begin + if @client + unless the_command.is_a?(::String) + raise ArgumentError, "You MUST provide a command as a String." + end + result = @client.exec!(the_command) + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:command => the_command}}) + end + result + end + # + def channel_operations(&block) + result = nil + begin + if @client + unless block.respond_to?(:call) + raise ArgumentError, "You MUST provide a valid Block/Proc to execute." + end + result = @client.open_channel(&block) + end + # + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:block => block}}) + end + result + end + # + def logout() + result = false + begin + if @client + @client.exec!("exit") + @client = nil + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def closed?() + if @client + false + else + true + end + end + # + def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + result = false + begin + if @client + unless the_local_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + if progress_block.respond_to?(:call) + channel = @client.scp.upload(the_local_source, the_destination, options, &progress_block) + else + channel = @client.scp.upload(the_local_source, the_destination, options) + end + channel.wait + # True result means the request was sent, not that it completed. + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}}) + end + result + end + # + def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + # options: + #:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote + # on the remote server. + # + #:preserve - the atime and mtime of the file should be preserved. + # + #:verbose - the process should result in verbose output on the server end (useful for debugging). + # + #:chunk_size - the size of each "chunk" that should be sent. Defaults to 2048. Changing this value may + # improve throughput at the expense of decreasing interactivity. + result = false + begin + if @client + unless the_local_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + if progress_block.respond_to?(:call) + result = @client.scp.upload!(the_local_source, the_destination, options, &progress_block) + else + result = @client.scp.upload!(the_local_source, the_destination, options) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}}) + end + result + end + # + def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + result = false + begin + if @client + unless the_remote_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + if progress_block.respond_to?(:call) + channel = @client.scp.download(the_remote_source, the_destination, options, &progress_block) + else + channel = @client.scp.download(the_remote_source, the_destination, options) + end + channel.wait + # True result means the request was sent, not that it completed. + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}}) + end + result + end + # + def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + # options: + #:recursive - the local parameter refers to a local directory, which should be uploaded to a new directory named remote + # on the remote server. + # + #:preserve - the atime and mtime of the file should be preserved. + # + #:verbose - the process should result in verbose output on the server end (useful for debugging). + # + result = false + begin + if @client + unless the_remote_source.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a local path (or file name) as a String." + end + if progress_block.respond_to?(:call) + result = @client.scp.download!(the_remote_source, the_destination, options, &progress_block) + else + result = @client.scp.download!(the_remote_source, the_destination, options) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}}) + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("ssh", ::GxG::Networking::SshClient) + # + class SftpClient + # Review : make BufferedSegments compatible + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + @user = the_url.user + if the_url.password + options[:password] = the_url.password + end + if the_url.port + options[:port] = the_url.port + end + @client = ::Net::SFTP.start(the_url.hostname, the_url.user, options) + @client.connect! + if @client.closed? + @client = nil + raise Exception, "Failed to open a connection." + end + self + end + # + def login(the_url=nil, options={}) + result = false + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + @user = the_url.user + if the_url.password + options[:password] = the_url.password + end + if the_url.port + options[:port] = the_url.port + end + @client = ::Net::SFTP.start(the_url.hostname, the_url.user, options) + @client.connect! + if @client.closed? + @client = nil + raise Exception, "Failed to open a connection." + else + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def closed?() + if @client + @client.closed? + else + true + end + end + # + def logout() + if @client + @client.close_channel + @client = nil + true + end + end + # + def async_upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + # options: + #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring" + # below. + # + #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory + # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve + # throughput. Reducing it will reduce throughput. + # + #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might + # improve throughput. It defaults to 32,000 bytes. + # + #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults + # to "". + result = false + begin + if @client + unless the_local_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + unless options[:name] + options[:name] = File.basename(the_local_source) + end + if progress_block.respond_to?(:call) + channel = @client.upload(the_local_source, the_destination, options, &progress_block) + else + channel = @client.upload(the_local_source, the_destination, options) + end + channel.wait + # True result means the request was sent, not that it completed. + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}}) + end + result + end + # + def upload(the_local_source=nil, the_destination=nil, options={}, &progress_block) + # options: + #:progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring" + # below. + # + #:requests - the number of pending SFTP requests to allow at any given time. When uploading an entire directory + # tree recursively, this will default to 16, otherwise it will default to 2. Setting this higher might improve + # throughput. Reducing it will reduce throughput. + # + #:read_size - the maximum number of bytes to read at a time from the source. Increasing this value might + # improve throughput. It defaults to 32,000 bytes. + # + #:name - the filename to report to the progress monitor when an IO object is given as local. This defaults + # to "". + result = false + begin + if @client + unless the_local_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + unless options[:name] + options[:name] = File.basename(the_local_source) + end + if progress_block.respond_to?(:call) + result = @client.upload!(the_local_source, the_destination, options, &progress_block) + else + result = @client.upload!(the_local_source, the_destination, options) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:local_path => the_local_source, :remote_path => the_destination, :options => options}}) + end + result + end + # + def async_download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring" + # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html. + # + # :requests - the number of pending SFTP requests to allow at any given time. When downloading an + # entire directory tree recursively, this will default to 16. Setting this higher might improve throughput. + # Reducing it will reduce throughput. + # + # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might + # improve throughput. It defaults to 32,000 bytes. + # + result = false + begin + if @client + unless the_remote_source.is_a?(::String) + raise ArgumentError, "You MUST provide a local path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + if progress_block.respond_to?(:call) + channel = @client.download(the_remote_source, the_destination, options, &progress_block) + else + channel = @client.download(the_remote_source, the_destination, options) + end + channel.wait + # True result means the request was sent, not that it completed. + result = true + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}}) + end + result + end + # + def download(the_remote_source=nil, the_destination=nil, options={}, &progress_block) + # progress - either a block or an object to act as a progress callback. See the discussion of "progress monitoring" + # at http://net-ssh.github.io/net-sftp/classes/Net/SFTP/Operations/Download.html. + # + # :requests - the number of pending SFTP requests to allow at any given time. When downloading an + # entire directory tree recursively, this will default to 16. Setting this higher might improve throughput. + # Reducing it will reduce throughput. + # + # :read_size - the maximum number of bytes to read at a time from the source. Increasing this value might + # improve throughput. It defaults to 32,000 bytes. + # + result = false + begin + if @client + unless the_remote_source.is_a?(::String) + raise ArgumentError, "You MUST provide a remote path as a String." + end + unless the_destination.is_a?(::String) + raise ArgumentError, "You MUST provide a local path (or file name) as a String." + end + if progress_block.respond_to?(:call) + result = @client.download!(the_remote_source, the_destination, options, &progress_block) + else + result = @client.download!(the_remote_source, the_destination, options) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_remote_source, :local_path => the_destination, :options => options}}) + end + result + end + # Remote File System Operations and Objects + def entries(the_path=nil, pattern=nil, flags=0, &block) + # List entries in a given remote path. + # If block is provided, resultant list is iterated over the block - (each_entry). + result = [] + begin + if @client + # Validate the_path: /.*(?:\\|\/)(.+)$/ from: https://www.experts-exchange.com/questions/22088316/Extract-Filename-from-PATH-using-REGEX.html + unless (/.*(?:\\|\/)(.+)$/.match(the_path.to_s) || the_path.to_s.split("/").size > 0 || the_path == "/") + raise ArgumentError, "You MUST provide a valid remote file system path." + end + if pattern.is_a?(::String) + # use glob + unless flags.is_a?(::Numeric) + flags = 0 + end + raw_list = @client.dir.glob(the_path, pattern, flags) + else + # use entries + raw_list = @client.dir.entries(the_path) + end + list = [] + raw_permission = { :execute => false, :rename => false, :move => false, :destroy => false, :create => false, :write => false, :read => false } + blank_permissions = {:effective => nil, :owner => raw_permission.clone, :group => raw_permission.clone, :other => raw_permission.clone} + # + raw_list.each do |details| + # + unless details.name == "." || details.name == ".." + # + record = {:title => (details.name), :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil} + permission_record = blank_permissions.clone + # + record = record.merge({:size => (details.attributes.size), :accessed => (Time.at(details.attributes.atime)).to_datetime, :modified => (Time.at(details.attributes.mtime)).to_datetime, :state_modified => (Time.at(details.attributes.mtime)).to_datetime, :user_id => (details.attributes.uid), :group_id => (details.attributes.gid)}) + if details.directory?() + record[:type] = :directory + record[:owner_type] = :directory + end + if details.file?() + record[:type] = :file + record[:owner_type] = :file + end + if details.symlink?() + record[:type] = :symlink + record[:owner_type] = :symlink + end + # puts "Got: #{details.attributes.permissions.inspect}" + record[:permissions] = ::File.mode_permission_to_gxg(details.attributes.permissions.to_i) + current_groups = @client.session.exec!("groups").gsub("\n","").split(" ") + if current_groups.size > 0 + effective_permissions = raw_permission.clone + if @user == details.attributes.owner + include_owner = true + else + include_owner = false + end + if current_groups.include?(details.attributes.group) + include_group = true + else + include_group = false + end + # + permissions_stack = [] + if include_owner + permissions_stack << record[:permissions][:owner] + end + if include_group + permissions_stack << record[:permissions][:group] + end + permissions_stack << record[:permissions][:other] + permissions_stack.each do |entry| + entry.keys do |the_key| + if entry[(the_key)] == true + effective_permissions[(the_key)] = true + end + end + end + record[:permissions][:effective] = effective_permissions + if record[:permissions][:effective][:read] + record[:flags] << :read + end + if record[:permissions][:effective][:write] + record[:flags] << :write + end + end + list << record + end + # + end + # + if block.respond_to?(:call) + list.each do |entry| + block.call(entry) + end + end + result = list + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_path}}) + end + result + end + # + def open_remote_file(the_path=nil, permissions={}) + result = nil + begin + if @client + filename = ::File.basename(the_path) + entries = this().entries(::File.dirname(the_path)) + open_only = false + flags = [] + entries.each do |item| + if item[:title] == filename + open_only = true + flags = item[:flags] + break + end + end + if open_only + if flags.include?(:read) + if flags.include?(:write) + open_mode = "w+" + else + open_mode = "r" + end + else + if flags.include?(:write) + open_mode = "w" + else + open_mode = "r" + end + end + #open + result = @client.file.open(the_path.to_s, open_mode) + else + #create + if permissions.is_a?(::Hash) + if permissions[:owner].is_a?(::Hash) && permissions[:group].is_a?(::Hash) && permissions[:other].is_a?(::Hash) + permissions = ::File.gxg_permissions_to_mode(permissions) + end + end + if permissions.is_a?(::Numeric) + unless permissions > 0 + permissions = 0644.to_i + end + else + permissions = 0644.to_i + end + result = @client.file.open(the_path.to_s, "w+", permissions) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:remote_path => the_path, :permissions => permissions}}) + end + result + end + # + def change_ownership(the_path=nil, new_owners=nil, &block) + result = false + begin + unless the_path.is_a?(::String) + raise ArgumentError, "You MUST supply a remote path. (/path/to/something)." + end + unless new_owners.is_a?(::Hash) + raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String." + end + unless new_owners[:user].is_a?(::String) + raise ArgumentError, "You MUST supply a Hash with :user, and optionally :group, defined with a String." + end + response = @client.setstat!(the_path, new_owners) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :new_owners => new_owners}}) + end + result + end + # + def change_permissions(the_path=nil, new_permissions=nil, &block) + result = false + begin + unless the_path.is_a?(::String) + raise ArgumentError, "You MUST supply a remote path. (/path/to/something)." + end + unless new_permissions + raise ArgumentError, "You MUST supply a new permission value. (GxG permission or Integer/Octal)." + end + if new_permissions.is_a?(::Hash) + new_permissions = ::File.gxg_permissions_to_mode(new_permissions) + end + response = @client.setstat!(the_path, {:permissions => new_permissions}) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path, :new_permissions => new_permissions}}) + end + result + end + # + def remove_remote_file(the_path=nil, &block) + result = false + begin + unless the_path.is_a?(::String) + raise ArgumentError, "You MUST supply a remote file path. (/path/to/file)." + end + response = @client.remove!(the_path) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:path => the_path}}) + end + result + end + # + def make_link(new_link_path=nil, existing_path=nil, symlink=true, &block) + result = false + begin + unless new_link_path.is_a?(::String) + raise ArgumentError, "You MUST supply a new link path. (/path/to/new_link)." + end + unless existing_path.is_a?(::String) + raise ArgumentError, "You MUST supply an existing file path. (/path/to/file)." + end + unless symlink + symlink = true + end + response = @client.link!(new_link_path,existing_path,symlink) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:new_link_path => new_link_path, :existing_path => existing_path, :symlink => symlink}}) + end + result + end + # + def rename(name=nil, new_name=nil,&block) + result = false + begin + unless name.is_a?(::String) + raise ArgumentError, "You MUST supply a remote file path. (/path/to/old_name)." + end + unless new_name.is_a?(::String) + raise ArgumentError, "You MUST supply a new file name path. (/path/to/new_name)." + end + response = @client.rename!(name,new_name,0x0004) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}}) + end + result + end + # + def make_directory(the_path=nil, &block) + result = false + begin + unless the_path.is_a?(::String) + raise ArgumentError, "You MUST supply a new directory path. (/path/to/new_dir)." + end + response = @client.mkdir!(the_path) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}}) + end + result + end + # + def remove_directory(the_path=nil, &block) + result = false + begin + unless the_path.is_a?(::String) + raise ArgumentError, "You MUST supply a directory path to remove. (/path/to/dir)." + end + response = @client.rmdir!(the_path) + if block.respond_to?(:call) + block.call(response) + end + result = response.ok? + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:original_name => name, :new_name => new_name}}) + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("sftp-ssh", ::GxG::Networking::SftpClient) + ::GxG::Networking::DISPATCHER.register_client("ftp-ssh", ::GxG::Networking::SftpClient) + # + class FtpClient + # Review : make BufferedSegments compatible + def initialize(the_url=nil, options={}) + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + # The available options are: + # + # port:: Port number (default value is 21) + if options[:port].is_a?(::Numeric) + login_options[:port] = options[:port].to_i + end + # ssl:: If options[:ssl] is true, then an attempt will be made + # to use SSL (now TLS) to connect to the server. For this + # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] + # extensions need to be installed. If options[:ssl] is a + # hash, it's passed to OpenSSL::SSL::SSLContext#set_params + # as parameters. + if options[:use_ssl] == true + login_options[:ssl] = true + end + # private_data_connection:: If true, TLS is used for data connections. + # Default: +true+ when options[:ssl] is true. + # username:: Username for login. If options[:username] is the string + # "anonymous" and the options[:password] is +nil+, + # "anonymous@" is used as a password. + login_options[:username] = the_url.user + # password:: Password for login. + login_options[:password] = the_url.password + # account:: Account information for ACCT. + if options[:account_info] + login_options[:account] = options[:account_info] + end + # passive:: When +true+, the connection is in passive mode. Default: + # +true+. + if options[:passive] == false + login_options[:passive] = false + end + # open_timeout:: Number of seconds to wait for the connection to open. + # See Net::FTP#open_timeout for details. Default: +nil+. + if options[:open_timeout].is_a?(::Numeric) + login_options[:open_timeout] = options[:open_timeout] + end + # read_timeout:: Number of seconds to wait for one block to be read. + # See Net::FTP#read_timeout for details. Default: +60+. + if options[:read_timeout].is_a?(::Numeric) + login_options[:read_timeout] = options[:read_timeout] + end + # ssl_handshake_timeout:: Number of seconds to wait for the TLS + # handshake. + # See Net::FTP#ssl_handshake_timeout for + # details. Default: +nil+. + if options[:ssl_handshake_timeout].is_a?(::Numeric) + login_options[:ssl_handshake_timeout] = options[:ssl_handshake_timeout] + end + # + @client = Net::FTP.new(the_url.hostname, login_options) + if @client.is_a?(Net::FTP) + @client.login + if @client.closed? + raise Exception, "Failed to log into FTP session" + end + else + raise Exception, "Failed to create FTP session" + end + self + end + # + def login(the_url=nil, options={}) + result = false + login_options = {} + begin + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI" + end + # The available options are: + # + # port:: Port number (default value is 21) + if options[:port].is_a?(::Numeric) + login_options[:port] = options[:port].to_i + end + # ssl:: If options[:ssl] is true, then an attempt will be made + # to use SSL (now TLS) to connect to the server. For this + # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] + # extensions need to be installed. If options[:ssl] is a + # hash, it's passed to OpenSSL::SSL::SSLContext#set_params + # as parameters. + if options[:use_ssl] == true + login_options[:ssl] = true + end + # private_data_connection:: If true, TLS is used for data connections. + # Default: +true+ when options[:ssl] is true. + # username:: Username for login. If options[:username] is the string + # "anonymous" and the options[:password] is +nil+, + # "anonymous@" is used as a password. + login_options[:username] = the_url.user + # password:: Password for login. + login_options[:password] = the_url.password + # account:: Account information for ACCT. + if options[:account_info] + login_options[:account] = options[:account_info] + end + # passive:: When +true+, the connection is in passive mode. Default: + # +true+. + if options[:passive] == false + login_options[:passive] = false + end + # open_timeout:: Number of seconds to wait for the connection to open. + # See Net::FTP#open_timeout for details. Default: +nil+. + if options[:open_timeout].is_a?(::Numeric) + login_options[:open_timeout] = options[:open_timeout] + end + # read_timeout:: Number of seconds to wait for one block to be read. + # See Net::FTP#read_timeout for details. Default: +60+. + if options[:read_timeout].is_a?(::Numeric) + login_options[:read_timeout] = options[:read_timeout] + end + # ssl_handshake_timeout:: Number of seconds to wait for the TLS + # handshake. + # See Net::FTP#ssl_handshake_timeout for + # details. Default: +nil+. + if options[:ssl_handshake_timeout].is_a?(::Numeric) + login_options[:ssl_handshake_timeout] = options[:ssl_handshake_timeout] + end + # + @client = Net::FTP.new(the_url.hostname, login_options) + if @client.is_a?(Net::FTP) + @client.login + if @client.closed? + raise Exception, "Failed to log into FTP session" + else + result = true + end + else + raise Exception, "Failed to create FTP session" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def logout() + if @client + @client.close + if @client.closed? + true + else + false + end + end + end + # + def closed?() + if @client + @client.closed? + else + true + end + end + # + def status() + result = nil + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed connection." + else + result = @client.status() + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def system_info() + result = nil + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed connection." + else + result = @client.system() + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def help() + result = nil + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed connection." + else + result = @client.help() + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def send_account_info(acct_info=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed connection." + else + unless acct_info.is_a?(::String) + raise ArgumentError, "You MUST provide account information (as a String)." + end + result = @client.acct(acct_info) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:account_info => acct_info}}) + end + result + end + # + def send_command(command=nil) + result = nil + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed connection." + else + unless command.is_a?(::String) + raise ArgumentError, "You must provide a String to issue FTP commands." + end + result = @client.sendcmd(command) + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:command => command}}) + end + result + end + # + def reopen() + result = false + begin + if @client + if @client.closed? + @client.connect() + if @client.closed? + raise Exception, "Failed to re-open connection." + end + else + result = true + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def get_file(file_name=nil, save_path=nil) + result = nil + begin + if @client + if save_path + result = @client.getbinaryfile(file_name, save_path, 1024) + else + result = ::GxG::ByteArray.new + @client.getbinaryfile(file_name, save_path) do |data_chunk| + result << data_chunk + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_name => file_name, :save_path => save_path}}) + end + result + end + # + def put_file(local_path=nil, remote_file=nil) + result = false + begin + if @client + result = @client.putbinaryfile(local_path, remote_file, 1024) + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:local_path => local_path, :remote_file => remote_file}}) + end + result + end + # + def rename_file(file_name=nil, new_file_name=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + unless file_name + raise ArgumentError, "You MUST provide a current file name to rename." + end + unless new_file_name + raise ArgumentError, "You MUST provide a new name to rename the file." + end + result = @client.rename(file_name, new_file_name) + unless result + raise Exception, "Failed to rename file." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_name => file_name, :new_file_name => new_file_name}}) + end + result + end + # + def remove_file(file_name=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + unless file_name + raise ArgumentError, "You MUST provide a file name to remove." + end + result = @client.delete(file_name) + unless result + raise Exception, "Failed to remove file." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:file_name => file_name}}) + end + result + end + # + def entries(dir_name=nil, filter=nil) + result = [] + unless dir_name + dir_name = @client.pwd() + end + begin + if @client + present_dir = @client.pwd() + @client.chdir(dir_name) + if filter + list = @client.list(filter) + else + list = @client.list() + end + list.each do |entry| + # TODO: process entry text + data = entry.split(" ") + record = {:title => "", :type => :virtual_directory, :owner_type => :virtual_directory, :on_device => nil, :on_device_major => nil, :on_device_minor => nil, :is_device => nil, :is_device_major => nil, :is_device_minor => nil, :inode => nil, :flags => [:read], :hardlinks_to => 0, :user_id => nil, :group_id => nil, :size => 0, :block_size => 0, :blocks => 0, :accessed => nil, :modified => nil, :status_modified => nil, :permissions => nil, :mode=>nil} + # + record[:permissions] = ::File.unix_permissions_to_gxg(data[0].to_s) + if record[:permissions][:other][:write] + record[:flags] << :write + end + # record[:owner] = data[2] + # record[:group] = data[3] + record[:size] = data[4].to_i + record[:modified] = ::DateTime::parse(data[5..7].join(" ")) + record[:title] = data[8] + result << record + end + @client.chdir(present_dir) + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:dir_name => dir_name, :filter => filter}}) + end + result + end + # + def present_directory() + result = nil + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + result = @client.pwd() + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {}}) + end + result + end + # + def change_directory(dir_name=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + unless dir_name + raise ArgumentError, "You MUST provide a directory path to change to." + end + @client.chdir(dir_name) + if @client.pwd() != dir_name + raise Exception, "Failed to change directory." + else + result = true + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:dir_name => dir_name}}) + end + result + end + # + def make_directory(dir_name=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + unless dir_name + raise ArgumentError, "You MUST provide a directory path to create." + end + result = @client.mkdir(dir_name) + unless result + raise Exception, "Failed to change directory." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:dir_name => dir_name}}) + end + result + end + # + def remove_directory(dir_name=nil) + result = false + begin + if @client + if @client.closed? + raise Exception, "Attempted to access a closed session" + else + unless dir_name + raise ArgumentError, "You MUST provide a directory name to remove." + end + result = @client.rmdir(dir_name) + unless result + raise Exception, "Failed to remove directory." + end + end + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:dir_name => dir_name}}) + end + result + end + # + end + ::GxG::Networking::DISPATCHER.register_client("ftp", ::GxG::Networking::FtpClient) + # + class HttpClient + # + def initialize(the_url=nil, options={}) + @cookie = nil + if the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + @url = the_url.clone + if options[:query].is_a?(::Hash) + @url.query = ::URI.encode_www_form(options[:query]) + end + unless options[:follow_redirects] == false + options.merge({:follow_redirects => true}) + end + # + if @url.scheme.downcase == "https" + options[:use_ssl] = true + end + # sets following values by its accessor. + # The keys are ca_file, ca_path, cert, cert_store, ciphers, + # close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout, + # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode. + # If you set :use_ssl as true, you can use https and default value of + # verify_mode is set as OpenSSL::SSL::VERIFY_PEER. + client_parameters = [(the_url.hostname), (the_url.port)] + if options[:proxy].is_a?(::URI::Generic) + client_parameters << options[:proxy].host + client_parameters << options[:proxy].port + if options[:proxy].user && options[:proxy].password + client_parameters << options[:proxy].user + client_parameters << options[:proxy].password + else + client_parameters << nil + client_parameters << nil + end + else + client_parameters << nil + client_parameters << nil + client_parameters << nil + client_parameters << nil + end + if options.keys.size > 0 + client_parameters << options + end + @options = options + @client = Net::HTTP.start(*client_parameters) + if options[:pem_source].is_any?(::File, ::GxG::Database::PersistedArray) + pem_data = nil + if options[:pem_source].is_a?(::File) + options[:pem_source].rewind + pem_data = options[:pem_source].read() + else + buffer = ::GxG::ByteArray.new + options[:pem_source].each_index do |segment| + buffer << options[:pem_source][(segment)].to_s + options[:pem_source].unload(segment) + end + pem_data = buffer.to_s + end + if pem_data + @client.use_ssl = true + @client.cert = OpenSSL::X509::Certificate.new(pem_data) + @client.key = OpenSSL::PKey::RSA.new(pem_data, options[:pem_password]) + @client.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + end + else + raise ArgumentError, "You MUST use HTTP or HTTPS as a protcol here." + end + self + end + # + def clear_cache() + @cookie = nil + true + end + # + def cookies() + @cookie.clone + end + # + def get(the_url=nil, options=nil) + result = nil + begin + unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + the_url = @url + end + if options.is_a?(::Hash) + if options[:query].is_a?(::Hash) + the_url.query = ::URI.encode_www_form(options[:query]) + end + options = @options.merge(options) + else + options = @options + end + if the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + if @cookie + if options[:header].is_a?(::Hash) + request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie}.merge(options[:header])) + else + request = Net::HTTP::Get.new(the_url.request_uri, {'Cookie' => @cookie}) + end + else + if options[:header].is_a?(::Hash) + request = Net::HTTP::Get.new(the_url.request_uri, options[:header]) + else + request = Net::HTTP::Get.new(the_url.request_uri) + end + end + if (the_url.user && the_url.password) + # basic auth + request.basic_auth(the_url.user, the_url.password) + end + response = @client.request(request) + @cookie = response.response['set-cookie'] + response_code = response.code.to_i + if options[:follow_redirects] + if response_code >= 300 && response_code < 400 + # Process Redirect + redirect_count = 1 + redirect_limit = (options[:redirect_limit] || 9).to_i + location = response['location'] + while location do + # + if @cookie + redirect_request = Net::HTTP::Get.new(location, {'Cookie' => @cookie}) + else + redirect_request = Net::HTTP::Get.new(location) + end + redirect_response = @client.request(redirect_request) + if redirect_response.code.to_i == 200 + response = redirect_response + response_code = 200 + @cookie = response.response['set-cookie'] + break + else + if redirect_response['location'] + @cookie = redirect_response.response['set-cookie'] + redirect_count += 1 + # Review : allow option to set this limit higher :redirect_limit + if redirect_count > redirect_limit + raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)" + end + response_code = redirect_response.code.to_i + location = redirect_response['location'] + else + break + end + end + # + end + end + end + if options[:save_to].is_any?(::File, ::GxG::Database::PersistedArray) + if options[:save_to].is_a?(::File) + options[:save_to].rewind + response.read_body do |data_chunk| + options[:save_to].write(data_chunk) + end + options[:save_to].rewind + else + response.read_body do |data_chunk| + options[:save_to] << ::GxG::ByteArray.new(data_chunk) + options[:save_to].save + options[:save_to].unload(options[:save_to].size - 1) + end + end + end + if response_code == 200 && options[:save_to] == nil + # Return raw response + result = response + else + if options[:save_to] + result = true + else + raise Exception, "Error occured: Code #{response.code}, Data: #{response.to_s}" + end + end + else + raise Exception, "You MUST provide a valid HTTP or HTTP URI" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + # + def put(the_url=nil, data=nil, options={}) + # + result = false + begin + unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + the_url = @url + end + if options.is_a?(::Hash) + if options[:query].is_a?(::Hash) + the_url.query = ::URI.encode_www_form(options[:query]) + end + options = @options.merge(options) + else + options = @options + end + if the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + if @cookie + request = Net::HTTP::Put.new(the_url.request_uri,data, {'Cookie' => @cookie}) + else + request = Net::HTTP::Put.new(the_url.request_uri,data) + end + if (the_url.user && the_url.password) + # basic auth + request.basic_auth(the_url.user, the_url.password) + end + response = @client.request(request) + @cookie = response.response['set-cookie'] + response_code = response.code.to_i + # + if options[:follow_redirects] + if response_code >= 300 && response_code < 400 + # Process Redirect + redirect_count = 1 + redirect_limit = (options[:redirect_limit] || 9).to_i + location = response['location'] + while location do + # + if @cookie + redirect_request = Net::HTTP::Put.new(location, {'Cookie' => @cookie}) + else + redirect_request = Net::HTTP::Put.new(location) + end + redirect_response = @client.request(redirect_request) + if redirect_response.code.to_i == 200 + response = redirect_response + response_code = 200 + break + else + if redirect_response['location'] + redirect_count += 1 + if redirect_count > redirect_limit + raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)" + end + response_code = redirect_response.code.to_i + location = redirect_response['location'] + else + break + end + end + # + end + end + end + if response.code.to_i == 200 + result = true + else + raise Exception, "Error occured: Code #{response.code}, Data: #{response.to_s}" + end + else + raise Exception, "You MUST provide a valid HTTP URI" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :form_data => data, :options => options}}) + end + result + end + # + def post(the_url=nil, form_data=nil, options={}) + result = false + begin + unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + the_url = @url + end + if options.is_a?(::Hash) + if options[:query].is_a?(::Hash) + the_url.query = ::URI.encode_www_form(options[:query]) + end + options = @options.merge(options) + else + options = @options + end + # Review : figure a way to use VFS objects in multipart Posts (file uploads) + if the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + if @cookie + request = Net::HTTP::Post.new(the_url.request_uri, {'Cookie' => @cookie}) + else + request = Net::HTTP::Post.new(the_url.request_uri) + end + if (the_url.user && the_url.password) + # basic auth + request.basic_auth(the_url.user, the_url.password) + end + request.set_form_data(form_data) + response = @client.request(request) + @cookie = response.response['set-cookie'] + response_code = response.code.to_i + if options[:follow_redirects] + if response_code >= 300 && response_code < 400 + # Process Redirect + redirect_count = 1 + redirect_limit = (options[:redirect_limit] || 9).to_i + location = response['location'] + while location do + # + if @cookie + redirect_request = Net::HTTP::Post.new(location, {'Cookie' => @cookie}) + else + redirect_request = Net::HTTP::Post.new(location) + end + redirect_request.set_form_data(form_data) + redirect_response = @client.request(redirect_request) + if redirect_response.code.to_i == 200 + response = redirect_response + response_code = 200 + break + else + if redirect_response['location'] + redirect_count += 1 + if redirect_count > redirect_limit + raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)" + end + response_code = redirect_response.code.to_i + location = redirect_response['location'] + else + break + end + end + # + end + end + end + if response.code.to_i == 200 + result = true + else + raise Exception, "Error occured: Code #{response.code}" + end + else + raise Exception, "You MUST provide a valid HTTP URI" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :form_data => form_data, :options => options}}) + end + result + end + # + def delete(the_url=nil, options={}) + result = false + begin + unless the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + the_url = @url + end + if options.is_a?(::Hash) + if options[:query].is_a?(::Hash) + the_url.query = ::URI.encode_www_form(options[:query]) + end + options = @options.merge(options) + else + options = @options + end + if the_url.is_any?(::URI::HTTP, ::URI::HTTPS) + if @cookie + request = Net::HTTP::Delete.new(the_url.request_uri, {'Cookie' => @cookie}) + else + request = Net::HTTP::Delete.new(the_url.request_uri) + end + if (the_url.user && the_url.password) + # basic auth + request.basic_auth(the_url.user, the_url.password) + end + response = @client.request(request) + @cookie = response.response['set-cookie'] + response_code = response.code.to_i + if options[:follow_redirects] + if response_code >= 300 && response_code < 400 + # Process Redirect + redirect_count = 1 + redirect_limit = (options[:redirect_limit] || 9).to_i + location = response['location'] + while location do + # + if @cookie + redirect_request = Net::HTTP::Delete.new(location, {'Cookie' => @cookie}) + else + redirect_request = Net::HTTP::Delete.new(location) + end + redirect_response = @client.request(redirect_request) + if redirect_response.code.to_i == 200 + response = redirect_response + response_code = 200 + break + else + if redirect_response['location'] + redirect_count += 1 + if redirect_count > redirect_limit + raise Exception, "Circular redirect chain suspected (#{redirect_limit} or more redirects)" + end + response_code = redirect_response.code.to_i + location = redirect_response['location'] + else + break + end + end + # + end + end + end + if response.code.to_i == 200 + result = true + else + raise Exception, "Error occured: Code #{response.code}" + end + else + raise Exception, "You MUST provide a valid HTTP URI" + end + rescue Exception => the_error + log_error({:error => the_error, :parameters => {:url => the_url, :options => options}}) + end + result + end + end + ::GxG::Networking::DISPATCHER.register_client("http",::GxG::Networking::HttpClient) + # + class HttpsClient + def initialize(the_url=nil, options={:follow_redirects => true, :use_ssl => true}) + @client = ::GxG::Networking::HttpClient.new(the_url, options) + self + end + # + def cookies() + @client.cookies() + end + # + def clear_cache() + @client.clear_cache() + end + # + def get(the_url=nil, options={:follow_redirects => true, :use_ssl => true}) + @client.get(the_url,options) + end + def put(the_url=nil, data=nil, options={}) + @client.put(the_url,data,options) + end + def post(the_url=nil, form_data=nil, options={}) + @client.post(the_url,form_data,options) + end + def delete(the_url=nil, options={}) + @client.post(the_url,options) + end + end + ::GxG::Networking::DISPATCHER.register_client("https",::GxG::Networking::HttpsClient) + # + class MatrixID + def initialize(the_id=nil) + if the_id.is_a?(::MatrixSdk::MXID) + @id = the_id + else + if the_id.is_any?(::String, ::Symbol) + @id = ::MatrixSdk::MXID.new(the_id.to_s) + end + end + self + end + # + def base_object() + @id + end + # + def domain() + @id.domain() + end + # + def localport() + @id.localport() + end + # + def port() + @id.port() + end + # + def sigil() + @id.sigil() + end + # + def event?() + @id.event? + end + # + def group?() + @id.group? + end + # + def homeserver() + @id.homeserver + end + # + def homeserver_suffix() + @id.homeserver_suffix + end + # + def room?() + @id.room? + end + # + def room_alias() + @id.room_alias + end + # + def room_id() + @id.room_id + end + # + def to_s() + @id.to_s + end + # + def type() + @id.type + end + # + def user?() + @id.user? + end + # + def valid?() + @id.valid? + end + end + # + class MatrixUser + def initialize(client=nil, id=nil, options={}) + if client.is_a?(::MatrixSdk::Client) + unless id.is_a?(::String) + raise ArgumentError, "You MUST provide a user_id as a String." + end + @user = ::MatrixSdk::User.new(client, id, options) + else + if client.is_a?(::MatrixSdk::User) + @user = client + else + raise ArgumentError, "You MUST provide a MatrixSdk::Client or MatrixSdk::User as client parameter." + end + end + self + end + # + def base_object() + @user + end + # + def active? + @user.active? + end + # + def avatar_url() + @user.avatar_url() + end + # + def avatar_url=(the_url_string=nil) + if the_url_string.is_any?(::String, ::URI::Generic) + @user.avatar_url = the_url_string.to_s + else + nil + end + end + # + def device_keys() + @user.device_keys() + end + # + def display_name() + @user.display_name() + end + # + def friendly_name() + @user.friendly_name() + end + # + def inspect() + "" + end + # + def last_active() + @user.last_active.to_datetime() + end + # + def presence() + @user.presence() + end + # + def presence=(the_status=nil) + if the_status.is_any?(::String, ::Symbol) + if [:online, :offline, :unavailable].include?(the_status.to_sym) + @user.presence = the_status.to_sym + end + end + end + # + def status_message() + @user.status_msg() + end + # + def status_message=(the_message=nil) + @user.status_msg = the_message.to_s + end + # + def events(symbol_array=[]) + @user.events(*symbol_array) + end + # + def ignore_inspect(symbol_array=[]) + @user.ignore_inspect(*symbol_array) + end + # + end + # + class MatrixRoom + def initialize(client=nil, room_id=nil, options={}) + if client.is_a?(::GxG::Networking::MatrixClient) + if room_id.is_any?(::MatrixSdk::MXID, ::GxG::Networking::MatrixID, ::String, ::Symbol) + room_id = ::MatrixSdk::MXID.new(room_id.to_s) + else + raise ArgumentError, "You MUST provide a GxG::Networking::MatrixID as room_id parameter." + end + @room = ::MatrixSdk::Room.new(client.base_object(), room_id, options) + else + if client.is_a?(::GxG::Networking::MatrixRoom) + @room = client.base_object() + else + if client.is_a?(::MatrixSdk::Room) + @room = client + else + raise ArgumentError, "You MUST provide a MatrixSdk::Client or MatrixSdk::Room as client parameter." + end + end + end + # Parameters: + # client (Client) — + # The underlying connection + # room_id (MXID) — + # The room ID + # data (Hash) (defaults to: {}) — + # Additional data to assign to the room + # Options Hash (data): + # :name (String) — + # The current name of the room + # :topic (String) — + # The current topic of the room + # :canonical_alias (String, MXID) — + # The canonical alias of the room + # :aliases (Array(String, MXID)) — + # All non-canonical aliases of the room + # :join_rule (:invite, :public) — + # The join rule for the room + # :guest_access (:can_join, :forbidden) — + # The guest access setting for the room + # :world_readable (Boolean) — + # If the room is readable by the entire world + # :members (Array(User)) — + # The list of joined members + # :events (Array(Object)) — + # The list of current events in the room + # :members_loaded (Boolean) — + # If the list of members is already loaded + # :event_history_limit (Integer) — default: 10 — + # The limit of events to store for the room + # :avatar_url (String, URI) — + # The avatar URL for the room + # :prev_batch (String) — + # The previous batch token for backfill + @thread_safety = ::Mutex.new + @listening = nil + @invitations = [] + @file_transfers = [] + @inbox = [] + self + end + # + def listening?() + if @thread_safety.synchronize { @listening } + true + else + false + end + end + # + def start_listening(options={}) + listener = @room.on_event.add_handler { |the_event| on_message(the_event) } + @thread_safety.synchronize { @listening = listener } + true + end + # + def stop_listening() + if self.listening? + the_id = 0 + @thread_safety.synchronize { the_id = @listening[:id] } + @room.on_event.remove_handler(the_id) + @thread_safety.synchronize { @listening = nil } + true + else + false + end + end + # + def on_message(the_event=nil) + if the_event + # ### Event Types: + # See : https://matrix.org/docs/spec/client_server/latest#room-events + # See : https://matrix.org/docs/spec/client_server/latest#modules + # if ['m.room.message', 'm.room.member'].include?(the_event.type) && @room.client.mxid != the_event.sender.to_s + # the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event}) + # @thread_safety.synchronize { @inbox << the_message } + # end + # Note: invitation = event.type 'm.room.member' (with the membership value set to "invite") + # Note: ?? = event.type 'm.room.membership' (??) + # Note: room-message = event.type 'm.room.message' + # Note: enrypted-message = event.type 'm.room.encrypted' + # Note: room-topic = event.type 'm.room.topic' + # Note: call invitation = event.type 'm.call.invite' + # Note: call answer = event.type 'm.call.answer' + put "Room Got: #{the_event.inspect}" + if @room.client.mxid != the_event.sender.to_s + the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event}) + # ### Event Routing: + case the_event.type.to_s + when 'm.room.message', 'm.room.encrypted' + @thread_safety.synchronize { @inbox << the_message } + when 'm.room.member' + # Invitation? + if the_event.msgtype.to_s.include?('call.invite') + @thread_safety.synchronize { @invitations << the_message } + end + end + # + end + end + end + # + def invitations() + result = [] + @thread_safety.synchronize { @invitations.each {|the_invitation| result << the_invitation} } + result + end + # + def next_message() + @thread_safety.synchronize { @inbox.shift } + end + # + def base_object() + @room + end + # + def room_id() + ::GxG::Networking::MatrixID.new(@room.id) + end + # + def add_alias(room_alias) + @room.add_alias(room_alias) + end + # + def add_tag(tag, data={}) + @room.add_tag(tag, **data) + end + # + def all_members(**parameters) + @room.all_members(**parameters).map {|item| ::GxG::Networking::MatrixUser.new(item)} + end + # + def allow_guests=(enable=false) + @room.allow_guests = enable + end + # + def avatar_url() + @room.avatar_url() + end + # + def avatar_url=(the_url=nil) + unless the_url.is_a?(::URI::MATRIX) + if the_url.is_a?(::String) + the_url = ::URI.parse(the_url) + else + raise ArgumentError, "You MUST provide a String or URI::MATRIX as the url." + end + end + @room.avatar_url = the_url + end + # + def backfill_messages(reverse=false, limit=10) + @room.backfill_messages(reverse, limit) + end + # + def ban_user(user_id=nil, reason="") + if user_id.is_any?(::String, ::GxG::Networking::MatrixUser) + if user_id.is_a?(::GxG::Networking::MatrixUser) + user_id = user_id.base_object + end + @room.ban_user(user_id, reason) + else + false + end + end + # + def display_name() + @room.display_name() + end + # + def get_account_data(type=nil) + if type + @room.get_account_data(type) + else + {} + end + end + # + def guest_access?() + @room.guest_access?() + end + # + def inspect() + "" + end + # + def invite_only=(enable=false) + if enable == true + @room.invite_only = true + else + @room.invite_only = false + end + end + # + def invite_only?() + @room.invite_only?() + end + # + def invite_user(user_id=nil) + if user_id.is_any?(::String, ::GxG::Networking::MatrixUser) + if user_id.is_a?(::GxG::Networking::MatrixUser) + user_id = user_id.base_object + end + @room.invite_user(user_id) + else + false + end + end + # + def joined_members() + @room.joined_members().map {|item| ::GxG::Networking::MatrixUser.new(item)} + end + # + def kick_user(user_id=nil, reason="") + if user_id.is_any?(::String, ::GxG::Networking::MatrixUser) + if user_id.is_a?(::GxG::Networking::MatrixUser) + user_id = user_id.base_object + end + @room.kick_user(user_id, reason) + else + false + end + end + # + def leave() + @room.leave() + end + # + def modify_required_power_levels(events = nil, params = {}) + @room.modify_required_power_levels(events, params) + end + # + def modify_user_power_levels(users = nil, users_default = nil) + @room.modify_user_power_levels(users, users_default) + end + # + def redact_message(event_id, reason = nil) + @room.redact_message(event_id, reason) + end + # + def reload!() + @room.reload!() + end + # + def reload_aliases!() + @room.reload_aliases!() + end + # + def reload_name!() + @room.reload_name!() + end + # + def reload_topic!() + @room.reload_topic!() + end + # + def remove_tag(tag=nil) + if tag + @room.remove_tag(tag) + else + false + end + end + # + def report_message(event_id, reason="", score = -100) + if event_id.is_any?(::String, ::GxG::Networking::MatrixID) + if event_id.is_a?(::GxG::Networking::MatrixID) + event_id = event_id.base_object() + end + @room.report_message(event_id, reason, score) + else + false + end + end + # + def send_audio(url, name, audio_info={}) + if url.is_any?(::String, ::URI::Generic) + @room.send_audio(url, name, audio_info) + else + false + end + end + # + def send_emote(text=nil) + if text + @room.send_emote(text) + end + end + # + def send_file(url, name, file_info={}) + if url.is_any?(::String, ::URI::Generic) + @room.send_file(url, name, file_info) + else + false + end + end + # + def send_html(html, body=nil, msgtype=nil, format=nil) + @room.send_html(html, body, msgtype, format) + end + # + def send_image(url, name, image_info={}) + if url.is_any?(::String, ::URI::Generic) + @room.send_image(url, name, image_info) + else + false + end + end + # + def send_location(geo_uri, name, thumbnail_url=nil, thumbnail_info={}) + if geo_uri.is_any?(::String, ::URI::Generic) && thumbnail_url.is_any?(::String, ::URI::Generic) + @room.send_location(geo_uri, name, thumbnail_url, thumbnail_info) + else + false + end + end + # + def send_notice(text=nil) + if text + @room.send_notice(text) + end + end + # + def send_text(text=nil) + # Review : waiting for bug fix --> https://github.com/jruby/jruby/issues/6453 + if text + @room.send_text(text) + end + end + # + def send_video(url, name, video_info={}) + if url.is_any?(::String, ::URI::Generic) + @room.send_video(url, name, video_info) + else + false + end + end + # + def set_account_data(type, account_data) + if type.is_a?(::String) && account_data.is_a?(::Hash) + @room.set_account_data(type, account_data) + else + false + end + end + # + def set_user_profile(display_name=nil, avatar_url=nil, reason=nil) + if avatar_url.is_any?(::String, ::URI::Generic) + @room.set_user_profile(display_name, avatar_url, reason) + else + false + end + end + # + def tags() + @room.tags() + end + # + def unban_user(user_id) + if user_id.is_any?(::String, ::GxG::Networking::MatrixUser) + if user_id.is_a?(::GxG::Networking::MatrixUser) + user_id = user_id.base_object() + end + @room.unban_user(user_id) + else + false + end + end + end + # + class MatrixClient + # Review : ensure module supports are provided + # See : https://matrix.org/docs/spec/client_server/latest#modules + # + # Federation Notes: + # you need to make sure other servers can reach your server, and that can be done in one of three ways: + # + # have the server running directly at port 8448 on the server_name you've configured in homeserver.yaml + # + # have a webserver running at that server_name serving a file at /.well-known/matrix/server that tells other servers where the actual server is running + # mkdir -p /var/www//.well-known/matrix + # echo '{ "m.server": ":443" }' > /var/www//.well-known/matrix/server (or /var/www/html/.well-known/matrix/server) + # + # have an SRV record in your DNS at _matrix._tcp.server_name.tld. that points to where your server is. + # + # Once you have one of these working (the federation tester can verify if it is working), you're ready to talk to other servers + # See : https://matrix.org/blog/2020/04/06/running-your-own-secure-communication-service-with-matrix-and-jitsi#synapse + # Note: you do NOT have to enable registration (you can keep closed registration) to federate a room. + # + def initialize(the_url=nil, options={}) + if the_url.is_a?(::MatrixSdk::Client) + @client = the_url + else + unless the_url.is_a?(::URI::Generic) + raise ArgumentError, "You MUST provide a valid URI." + end + # See: https://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk/MatrixSdk/Client.html#initialize-instance_method + @url = the_url + the_url = the_url.clone + @user_id = the_url.user + the_url.user = nil + @password = the_url.password + the_url.password = nil + unless @user_id && @password + raise ArgumentError, "You MUST provide a user_id and password in the URI." + end + @options = options.clone + @options[:user_id] = @user_id + unless [:all, :some, :none].include?(@options[:client_cache]) + @options[:client_cache] = :all + end + if @options[:proxy] + if @options[:proxy].is_a?(::URI::Generic) + @options[:proxy_uri] = @options.delete(:proxy) + else + if @options[:proxy].is_a?(::String) + @options[:proxy_uri] = ::URI.parse(@options.delete(:proxy).to_s) + end + end + end + @client = ::MatrixSdk::Client.new(the_url, **@options) + end + @mxid = ::GxG::Networking::MatrixID.new("@#{@user_id}:#{the_url.hostname}") + @api = @client.api() + @session = nil + @thread_safety = ::Mutex.new + @invitations = [] + @rooms = [] + @watcher = nil + @listening = nil + # + self + end + # + def base_object() + @client + end + # + def inspect() + "" + end + # + def api() + @api + end + # + def listening?() + if @thread_safety.synchronize { @listening } + true + else + false + end + end + # + def start_listening(options={}) + listener = @client.on_event.add_handler { |the_event| on_message(the_event) } + @thread_safety.synchronize { @listening = listener } + true + end + # + def stop_listening() + if self.listening? + the_id = 0 + @thread_safety.synchronize { the_id = @listening[:id] } + @client.on_event.remove_handler(the_id) + @thread_safety.synchronize { @listening = nil } + true + else + false + end + end + # + def on_message(the_event=nil) + if the_event + # ### Event Types: + # See : https://matrix.org/docs/spec/client_server/latest#room-events + # See : https://matrix.org/docs/spec/client_server/latest#modules + # if ['m.room.message', 'm.room.member'].include?(the_event.type) && @room.client.mxid != the_event.sender.to_s + # the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event}) + # @thread_safety.synchronize { @inbox << the_message } + # end + # Note: invitation = event.type 'm.room.member' (with the membership value set to "invite") + # Note: ?? = event.type 'm.room.membership' (??) + # Note: room-message = event.type 'm.room.message' + # Note: enrypted-message = event.type 'm.room.encrypted' + # Note: room-topic = event.type 'm.room.topic' + # Note: call invitation = event.type 'm.call.invite' + # Note: call answer = event.type 'm.call.answer' + put "Got: #{the_event.inspect}" + if @room.client.mxid != the_event.sender.to_s + the_message = new_message({:sender => self, :ufs => ("org.matrix." << the_event.type.to_s).to_sym, :body => the_event}) + # ### Event Routing: + case the_event.type.to_s + when 'm.room.member' + # Invitation? + if the_event.state_key.to_s.include?('invite') + @thread_safety.synchronize { @invitations << the_message } + end + end + # + end + end + end + # + def invitations() + result = [] + @thread_safety.synchronize { @invitations.each {|the_invitation| result << the_invitation} } + result + end + # + def add_listener(room) + # Review : deprecated (I don't think I need this.) + if room.is_a?(::GxG::Networking::MatrixRoom) + room.start_listening + true + else + false + end + end + # + def login(user_id=nil, password=nil, options={}) + if @client.logged_in? + false + else + unless user_id.is_a?(::String) + user_id = @user_id + end + unless password.is_a?(::String) + password = @password + end + unless options.is_a?(::Hash) + options = {} + end + options = options.clone + # + unless options[:sync_timeout].is_a?(::Integer) + options[:sync_timeout] = 15 + end + unless options[:full_state] == true + options[:full_state] = false + end + @client.login(user_id, password, **options) + ::GxG::Engine::reserve_event_descriptor() + @client.start_listener_thread({}) + @watcher = $Dispatcher.every("5 seconds") do + if self.logged_in? && (! self.listening?) + self.start_listener_thread({}) + end + end + self.start_listening + self.rooms.each do |the_room| + the_room.start_listening + end + true + end + end + # + def login_with_token(user_id=nil, token=nil, options={}) + if @client.logged_in?() + false + else + if user_id.is_a?(::String) && token == nil + token = user_id + user_id = nil + end + unless user_id.is_a?(::String) + user_id = @user_id + end + unless password.is_a?(::String) + password = @password + end + unless options.is_a?(::Hash) + options = {} + end + options = options.clone + # + unless options[:sync_timeout].is_a?(::Integer) + options[:sync_timeout] = 15 + end + unless options[:full_state] == true + options[:full_state] = false + end + @client.login_with_token(user_id, token, **options) + ::GxG::Engine::reserve_event_descriptor() + @client.start_listener_thread({}) + @watcher = $Dispatcher.every("5 seconds") do + if self.logged_in? && (! self.listening?) + self.start_listener_thread({}) + end + end + self.start_listening + self.rooms.each do |the_room| + the_room.start_listening + end + true + end + end + # + def session() + @session + end + # + def logout() + $Dispatcher.cancel_timer(@watcher) + @watcher = nil + self.stop_listening + @client.stop_listener_thread() + ::GxG::Engine::release_event_descriptor() + @client.logout() + true + end + # + def mxid() + @mxid + end + # + def access_token() + @api.access_token() + end + # + def device_id() + @api.device_id() + end + # + def proxy() + @api.proxy_uri() + end + # + def protocols() + @api.protocols() + end + # + def transaction_id() + @api.transaction_id() + end + # + def upload_file(the_file=nil, cdn_server_url=nil) + # Review : add CDN upload to chosen server. + result = nil + if the_file.is_any?(::File, ::GxG::Storage::BufferedSegments) + mime_type = ::MimeMagic.by_magic(the_file) + if mime_type + content_type = mime_type.type + else + content_type = "application/octet-stream" + end + if @api + options = {:body_stream => the_file, :headers => {"content-type" => content_type}} + result = @api.request(:post, :media_r0, "/upload", **options) + end + end + result + # data = Net::HTTP.get_response(URI(url)) + # mxc_url = client.api.media_upload(data.body, data.content_type)[:content_uri] if data.is_a? Net::HTTPOK + # api.request(:post, + # :media_r0, + # '/upload', + # body_stream: open('./file'), + # headers: { 'content-type' => 'image/png' }) + # => { :content_uri => "mxc://example.com/AQwafuaFswefuhsfAFAgsw" } + # Parameters:{ + # method (Symbol) — + # The method to use, can be any of the ones under Net::HTTP + # api (Symbol) — + # The API symbol to use, :client_r0 is the current CS one + # path (String) — + # The API path to call, this is the part that comes after the API definition in the spec + # options (Hash) — + # Additional options to pass along to the request + # Options Hash (**options): + # :query (Hash) — + # Query parameters to set on the URL + # :body (Hash, String) — + # The body to attach to the request, will be JSON-encoded if sent as a hash + # :body_stream (IO) — + # A body stream to attach to the request + # :headers (Hash) — + # Additional headers to set on the request + # :skip_auth (Boolean) — default: false — + # Skip authentication} + end + # + def create_room(room_alias=nil, options={}) + if room_alias.is_a?(::String) + room = @client.create_room(room_alias, **options) + if room + room = ::GxG::Networking::MatrixRoom.new(room) + @thread_safety.synchronize { @rooms << room } + room.start_listening + true + else + false + end + else + false + end + end + # + def ensure_room(room_id=nil) + if room_id.is_any?(::String, ::GxG::Networking::MatrixID) + if room_id.is_a?(::String) + room_id = ::GxG::Networking::MatrixID.new(room_id.to_s) + end + room = @client.ensure_room(room_id.base_object()) + if room + id_list = self.rooms.map {|the_room| the_room.base_object.mxid.to_s } + if id_list.include?(room.mxid.to_s) + self.rooms.each do |the_room| + if the_room.base_object.mxid.to_s == room.mxid.to_s + room = the_room + break + end + end + if room.is_a?(::GxG::Networking::MatrixRoom) + room + else + nil + end + else + room = ::GxG::Networking::MatrixRoom.new(room) + @thread_safety.synchronize { @rooms << room } + room.start_listening + room + end + else + nil + end + else + nil + end + end + # + def find_room(room_id=nil, only_canonical=false) + if room_id.is_any?(::String, ::GxG::Networking::MatrixID) + if room_id.is_a?(::String) + room_id = ::GxG::Networking::MatrixID.new(room_id.to_s) + end + room = @client.find_room(room_id.base_object(), only_canonical) + if room + self.rooms.each do |the_room| + if the_room.base_object.mxid.to_s == room.mxid.to_s + room = the_room + break + end + end + if room.is_a?(::GxG::Networking::MatrixRoom) + room + else + ::GxG::Networking::MatrixRoom.new(room) + end + else + nil + end + else + nil + end + end + # + def get_user(user_id) + if user_id.is_any?(::String, ::GxG::Networking::MatrixID) + if user_id.is_a?(::String) + user_id = ::GxG::Networking::MatrixID.new(user_id.to_s) + end + user = @client.get_user(user_id.base_object()) + if user + ::GxG::Networking::MatrixUser.new(user) + else + nil + end + else + nil + end + end + # + def join_room(room=nil, options={:server_name => []}) + if room.is_a?(::GxG::Networking::MatrixRoom) + room = room.room_id.to_s + end + if room.is_any?(::GxG::Networking::MatrixID, ::MatrixSdk::MXID) + room = room.to_s + end + if room.is_a?(::String) + room = @client.join_room(room, **options) + if room + new_room = ::GxG::Networking::MatrixRoom.new(room) + @thread_safety.synchronize { @rooms << new_room } + new_room.start_listening + true + else + false + end + else + false + end + end + # + def listen_forever(timeout=30, bad_sync_timeout=5, sync_interval=0, options={}) + @client.listen_forever(timeout, bad_sync_timeout, sync_interval, **options) + end + # + def listening?() + @client.listening?() + end + # + def logged_in?() + @client.logged_in?() + end + # + def user() + ::GxG::Networking::MatrixUser.new(@client.mxid) + end + # + def presence() + @client.presence() + end + # + def public_rooms() + @client.public_rooms.map {|item| ::GxG::Networking::MatrixRoom.new(item)} + end + # + def register_as_guest() + @client.register_as_guest() + end + # + def register_with_password(username=nil, password=nil, options={}) + if username.is_a?(::String) && password.is_a?(::String) + begin + @client.register_with_password(username, password, **options) + true + rescue Exception => exception + false + end + else + false + end + end + # + def registered_3pids() + @client.registered_3pids() + end + # + def reload_rooms!() + @client.reload_rooms!() + end + # + def remove_room_alias(room_id=nil) + if room_id.is_any?(::GxG::Networking::MatrixID, ::MatrixSdk::MXID) + room_id = room_id.to_s + end + if room_id.is_a?(::String) + room = @client.remove_room_alias(room_id) + if room + true + else + false + end + else + false + end + end + # + def rooms() + result = [] + id_list = [] + @thread_safety.synchronize { + @rooms.each do |the_room| + id_list << the_room.room_id.base_object.to_s + result << the_room + end + } + # Review : find a way to include direct rooms in @room : + # direct = client.api.get_account_data(client.mxid, 'm.direct') + # room_id = direct.fetch mxid.to_s.to_sym + # room = client.ensure_room room_id + @client.rooms.each do |item| + unless id_list.include?(item.room_id.to_s) + new_entry = ::GxG::Networking::MatrixRoom.new(item) + @thread_safety.synchronize { @rooms << new_entry } + result << new_entry + end + end + result + end + # + def set_presence(status, message=nil) + if [:online, :offline, :unavailable].include?(status) + @client.set_presence(status, message) + true + else + false + end + end + # + def start_listener_thread(options) + @client.start_listener_thread(**options) + end + # + def stop_listener_thread() + @client.stop_listener_thread() + end + # + def listen_for_events(skip_store_batch=false, options={}) + # See : https://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk/MatrixSdk/Client.html#create_room-instance_method + unless self.listening? + unless options[:skip_store_batch] == true + options[:skip_store_batch] = false + end + @client.sync(**options) + end + end + # + end + MatrixSdk.logger = ::GxG::LOG + ::GxG::Networking::DISPATCHER.register_client("matrix-https",::GxG::Networking::MatrixClient) + ::GxG::Networking::DISPATCHER.register_client("matrix-http",::GxG::Networking::MatrixClient) + end + # + CLIENTS = GxG::Networking::DISPATCHER + # +end diff --git a/lib/gxg/net_tools.rb b/lib/gxg/net_tools.rb new file mode 100644 index 0000000..1dc7e31 --- /dev/null +++ b/lib/gxg/net_tools.rb @@ -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 \ No newline at end of file diff --git a/lib/gxg/tag_helpers.rb b/lib/gxg/tag_helpers.rb new file mode 100644 index 0000000..9b68625 --- /dev/null +++ b/lib/gxg/tag_helpers.rb @@ -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 => , :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 "
" + sections.each do |the_section| + output.safe_concat "

#{the_section[:heading].to_s}

" + output.safe_concat "
" + output.safe_concat the_section[:content].to_s + output.safe_concat "
" + end + output.safe_concat "
" + 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 "" + output.safe_concat "" + end + # + def gxg_button_tag(the_id=nil, options={}) + # Options: :title => , + # :javascript => , + # :image => {:position => :full,:top,:left,:right,:bottom, :source => :height => , :width => } + 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("
") + 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("
" + image_tag(options[:image][:source], img_opts) + "
") + else + # top, left, right, bottom of title text? + case options[:image][:position] + when :top + output.safe_concat("
" + image_tag(options[:image][:source], img_opts) + "
#{options[:title]}
") + when :bottom + output.safe_concat("
#{options[:title]}
" + image_tag(options[:image][:source], img_opts) + "
") + when :left + output.safe_concat("
" + image_tag(options[:image][:source], img_opts) + "#{options[:title]}
") + when :right + output.safe_concat("
#{options[:title]}" + image_tag(options[:image][:source], img_opts) + "
") + end + end + output.safe_concat("
") + else + output.safe_concat("
#{options[:title].to_s}
") + end + output.safe_concat("") + if options[:javascript].is_a?(::String) + output.safe_concat("") + 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("") + output.safe_concat("") + 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("
#{legend.to_s}") + button_names.each do |the_name| + output.safe_concat(radio_tag(nil,the_name)) + end + output.safe_concat("
") + output.safe_concat("") + output + end + # + def control_group(content="", options={:vertical => false}, &block) + # + output = SafeBuffer.new + if options[:vertical] + output.safe_concat("
") + else + output.safe_concat("
") + end + if block.respond_to?(:call) + output.safe_concat(block.call()) + else + output.safe_concat(content) + end + output.safe_concat("
") + if options[:vertical] + output.safe_concat("") + else + output.safe_concat("") + 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}") + output.safe_concat("") + 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("
") + if block.respond_to?(:call) + output.safe_concat(block.call()) + else + output.safe_concat(content) + end + output.safe_concat("
") + output.safe_concat("") + 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("
") + if options[:content].is_a?(::String) + output.safe_concat(options[:content].to_s) + end + output.safe_concat("
") + if options[:javascript].is_a?(::String) + output.safe_concat("") + else + output.safe_concat("") + 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("") + if options[:javascript].is_a?(::String) + output.safe_concat("") + else + output.safe_concat("") + end + output + end + # + def tabs_tag(the_id=nil, sections=nil, options={}) + # id => , :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 "
" + 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 "" + section_details.each_key do |the_uuid| + output.safe_concat "
#{section_details[(the_uuid)][:content].to_s}
" + end + output.safe_concat "
" + if options[:javascript].is_a?(::String) + output.safe_concat("") + else + output.safe_concat("") + 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 \ No newline at end of file diff --git a/scraps/xmpp_clients.rb b/scraps/xmpp_clients.rb new file mode 100644 index 0000000..fda1edf --- /dev/null +++ b/scraps/xmpp_clients.rb @@ -0,0 +1,2866 @@ + +# require 'xmpp4r/client' +# require 'xmpp4r/reliable' +# require 'xmpp4r/roster' +# require 'xmpp4r/muc' +# require 'xmpp4r/bytestreams' + +# new xmpp client: +# require "blather/client/dsl" +# require "blather/client/dsl/pubsub" +Thread.new { ::EM.run } +# ### MUC extentions for Blather: +# See: https://github.com/maccman/blather and https://github.com/adhearsion/blather/issues/81 +# module Blather +# class Roster +# def push(elem, send = true, groups = ["Buddies"]) +# jid = elem.respond_to?(:jid) && elem.jid ? elem.jid : JID.new(elem) +# @items[key(jid)] = node = RosterItem.new(elem) +# @items[key(jid)].subscription = :both +# @items[key(jid)].groups = groups +# @stream.write(node.to_stanza(:set)) if send +# end +# alias_method :add, :push +# end +# class Client +# def unbind +# # Fix bug where closing connection stops EM for ALL Client Instances. +# # call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop) +# call_handler_for(:disconnected, nil) +# true +# end +# end +# # +# module DSL +# class MultiUserChat +# def initialize(client, jid, nickname = nil, password = nil) +# @client = client +# @room = ::Blather::JID.new(jid) +# @nickname = nickname || @room.resource +# @password = password +# @room.strip! +# end + +# def join(reason = nil) +# join = ::Blather::Stanza::Presence::MUCJoin.new +# join.to = "#{@room}/#{@nickname}" +# join.password = @password +# write join +# end + +# # +# # available +# # +# def status=(state) +# status = ::Blather::Stanza::Presence::Status.new +# status.state = state +# status.to = @room +# write status +# end + +# # +# # +# # +# # comment +# # +# # +# # +# def invite(jid, reason = nil) +# message = ::Blather::Stanza::Message.new(@room, nil, nil) +# message << ::Blather::MUC::Invite.new(jid, reason, @password) + +# write message +# end + +# # +# # foo +# # +# def say(msg, xhtml = nil) +# message = ::Blather::Stanza::Message.new(@room, msg, :groupchat) +# message.xhtml = xhtml if xhtml +# write message +# end + +# # +# # foo +# # +# def subject=(body) +# message = Blather::Stanza::Message.new(@room, nil, :groupchat) +# message.subject = body +# write message +# end + +# def leave +# self.status = :unavailable +# end + +# # +# # +# # +# # Macbeth doth come. +# # +# # +# # +# def destroy(reason = nil) +# destroy = ::Blather::MUC::Owner::Destroy.new(@room) + +# destroy.reason = reason +# write destroy +# end + +# def get_configuration(&block) +# get_configure = ::Blather::MUC::Owner::Configure.new(:get, @room) +# write_with_handler(get_configure) do |stana| +# yield stana +# end +# end +# alias_method :configuration, :get_configuration + +# def set_configuration(values, &block) +# set_configure = ::Blather::MUC::Owner::Configure.new(:set, @room) +# set_configure.data = values +# write_with_handler(set_configure, &block) +# end +# alias_method :configuration=, :set_configuration + +# # +# # +# # +# # +# def set_default_configuration(&block) +# set_configuration(:default) do +# yield if block_given? +# end +# end +# alias_method :unlock, :set_default_configuration + +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# def members(&block) +# members = ::Blather::MUC::Admin::Members.new(@room) + +# write_with_handler(members, &block) +# end + +# def write(stanza) +# @client.write(stanza) +# end + +# def write_with_handler(stanza, &block) +# @client.write_with_handler(stanza, &block) +# end +# end # end Class Blather::DSL::MultiUserChat +# end # end Module Blather::DSL +# # ### Support for MUC Presense + +# module MUC +# # +# class Join < ::Blather::XMPPNode +# register :x, "http://jabber.org/protocol/muc" + +# def self.new(password = nil) +# join = super :x +# join.password = password +# join +# end + +# def password=(password) +# return if password.blank? +# create_password.content = password +# end + +# protected +# def create_password +# unless create_password = find_first('ns:password', :ns => self.class.registered_ns) +# self << (create_password = XMPPNode.new('password', self.document)) +# end +# create_password +# end +# end #Join +# # +# # +# # comment +# # +# # +# class Invite < ::Blather::Stanza +# register :muc_invite, :x, "http://jabber.org/protocol/muc#user" + +# def self.import(node) +# self.new(node.element_name).inherit(node) +# end + +# def inherit(node) +# create_invite.remove +# self.from = node[:from] +# invite = node.find_first('ns:x/ns:invite', :ns => self.class.registered_ns) +# self << invite +# self +# end + +# def self.new(jid = nil, reason = nil, password = nil) +# invite = super :x +# invite.invite_to = jid +# invite.reason = reason +# invite.password = password +# invite +# end + +# def invite_to=(jid) +# create_invite[:to] = JID.new(jid) +# end + +# def invite_to +# ::Blather::JID.new(create_invite[:to]) +# end + +# def invite_from +# create_invite[:from] +# end + +# def reason=(reason) +# return if reason.blank? +# create_reason.content = reason +# end + +# def reason +# create_reason.content +# end + +# def password=(password) +# return if password.blank? +# create_password.content = password +# end + +# def password +# create_password.content +# end + +# protected +# def create_password +# unless create_password = find_first('ns:password', :ns => self.class.registered_ns) +# self << (create_password = XMPPNode.new('password', self.document)) +# end +# create_password +# end + +# def create_invite +# unless create_invite = find_first('ns:invite', :ns => self.class.registered_ns) +# self << (create_invite = XMPPNode.new('invite', self.document)) +# end +# create_invite +# end + +# def create_reason +# unless create_reason = create_invite.find_first('ns:reason', :ns => self.class.registered_ns) +# self.create_invite << (create_reason = XMPPNode.new('reason', self.document)) +# end +# create_reason +# end + +# def create_password +# unless create_password = find_first('ns:password', :ns => self.class.registered_ns) +# self << (create_password = XMPPNode.new('password', self.document)) +# end +# create_password +# end +# end #Invite +# class Owner < Blather::Stanza::Iq::Query +# register :owner, :owner, 'http://jabber.org/protocol/muc#owner' + +# def self.new(type = nil, to = nil) +# node = super(type) +# node.to = to +# node +# end + +# class Configure < Owner +# DATA_NAMESPACE = 'jabber:x:data' + +# def data=(data = :default) +# create_data[:type] = 'submit' +# unless data.blank? || data == :default +# raise "Invalid data format" unless data.is_a?(Hash) +# data.each {|key, value| +# create_field = XMPPNode.new('field', self.document) +# create_field[:var] = key + +# create_field_value = XMPPNode.new('value', self.document) +# if [TrueClass, FalseClass].include?(value.class) +# value = value ? 1 : 0 +# end +# create_field_value.content = value.to_s + +# create_field << create_field_value +# create_data << create_field +# } +# end +# end + +# def data +# items = create_data.find('//ns:field', :ns => self.class.registered_ns) +# items.inject({}) do |hash, item| +# key = item[:var] +# value = item.find_first('ns:value', :ns => self.class.registered_ns) +# value = value.content +# hash[key] = value +# hash +# end +# end + +# protected +# def create_data +# unless create_data = query.find_first('ns:x', :ns => DATA_NAMESPACE) +# query << (create_data = XMPPNode.new('x', self.document)) +# create_data.namespace = DATA_NAMESPACE +# end +# create_data +# end +# end + +# # +# # +# # +# # Macbeth doth come. +# # +# # +# # +# class Destroy < Owner + +# def self.new(*args) +# query = super(:set, *args) +# query.create_destroy +# query +# end + +# def reason=(reason) +# return if reason.blank? +# create_reason.content = reason +# end + +# def create_reason # @private +# unless create_reason = create_destroy.find_first('ns:reason', :ns => self.class.registered_ns) +# create_destroy << (create_reason = XMPPNode.new('reason', self.document)) +# end +# create_reason +# end + +# def create_destroy # @private +# unless create_destroy = query.find_first('ns:destroy', :ns => self.class.registered_ns) +# query << (create_destroy = XMPPNode.new('destroy', self.document)) +# end +# create_destroy +# end +# end + +# end +# class Admin < Blather::Stanza::Iq::Query +# register :admin, :admin, 'http://jabber.org/protocol/muc#admin' + +# def self.new(type = nil, to = nil) +# node = super(type) +# node.to = to +# node +# end + +# # +# # +# # +# # +# # +# class Members < Admin +# def self.new(*args) +# query = super(:get, *args) +# query.create_item +# query +# end + +# def create_item # @private +# unless create_item = query.find_first('ns:item', :ns => self.class.registered_ns) +# query << (create_item = XMPPNode.new('item', self.document)) +# create_item[:affiliation] = 'member' +# end +# create_item +# end +# end + +# end # Admin +# end # end module MUC +# # +# class Stanza +# class Presence +# class MUCJoin < ::Blather::Stanza::Presence +# MUC_NS = "http://jabber.org/protocol/muc" + +# register :muc_join, :muc_join + +# def password +# create_muc.content_from :password +# create_password.content +# end + +# def password=(value) +# create_muc.set_content_for :password +# end + +# private +# def create_muc +# unless create_muc = find_first('ns:x', :ns => MUC_NS) +# self << (create_muc = ::Blather::XMPPNode.new('x', self.document)) +# create_muc.namespace = MUC_NS +# end +# create_muc +# end +# end # +# # +# class MUCUser < Presence +# class Status < ::Blather::XMPPNode +# def self.new(code) +# new_node = super :status +# new_node.code = code +# new_node +# end + +# def code +# read_attr :code +# end + +# def code=(var) +# write_attr :code, var +# end +# end + +# MUC_NS = "http://jabber.org/protocol/muc#user" + +# register :muc_user, :muc_user + +# def affiliation +# create_ia[:affiliation] +# end + +# def affiliation=(val) +# create_ia[:affiliation] = val +# end + +# def role +# create_ia[:role] +# end + +# def role=(val) +# create_ia[:role] = val +# end + +# def jid +# create_ia[:jid] +# end + +# def jid=(val) +# create_ia[:jid] = val +# end + +# private +# def create_muc +# unless create_muc = find_first('ns:x', :ns => MUC_NS) +# self << (create_muc = ::Blather::XMPPNode.new('x', self.document)) +# create_muc.namespace = MUC_NS +# end +# create_muc +# end + +# def create_ia +# unless create_ia = create_muc.find_first('ns:item', :ns => self.class.registered_ns) +# create_muc << (create_ia = XMPPNode.new('item', self.document)) +# end +# create_ia +# end +# end +# end +# end +# # +# end # end Module Blather + + # XMPP Classes: + # Review : Blather switchover: + # require ::File.expand_path("./xmpp_client.rb",::File.dirname(__FILE__)) + # # ### Invitations + # class XmppAgentInvitation + # def initialize(parent=nil, invitation=nil) + # @uuid = ::GxG::uuid_generate.to_sym + # @pending = true + # @parent = parent + # @invitation = invitation + # # + # if @invitation.is_a?(::Blather::Stanza::Presence) + # @details = {:sender => @invitation.from.to_s, :type => :association} + # else + # @details = {:sender => @invitation.from.to_s, :type => :unkown} + # end + # # + # self + # end + # # Review : debug only + # def invitation() + # @invitation + # end + # # + # def uuid() + # @uuid + # end + # # + # def inspect() + # @details.inspect + # end + # # + # def pending?() + # @pending + # end + # # + # def keys() + # @details.keys() + # end + # # + # def [](the_key) + # @details[(the_key)] + # end + # # + # def accept() + # if @invitation + # @parent.write(@invitation.approve!) + # @pending = false + # @parent.refresh_invitations() + # true + # else + # false + # end + # end + # # + # def decline() + # if @invitation + # @parent.write(@invitation.cancel!) + # @pending = false + # @parent.refresh_invitations() + # true + # else + # false + # end + # end + # end + # # ### File Transfer + # class XmppAgentFileTransfer + # # Review : totally rewrite this + # def initialize(manager, direction, details, parent) + # @reference = ::GxG::uuid_generate.to_sym + # @session_id = nil + # @pending = true + # @inprogress = false + # @percent_completed = 0 + # @status_thread_safety = ::Mutex.new + # @manager = manager + # @direction = direction + # @info = {:reference => @reference} + # @bytestream = nil + # @file_details = nil + # @cancel = false + # the_value = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid") + # if the_value.is_a?(::Range) + # @valid_write_buffer_sizes = the_value + # else + # @valid_write_buffer_sizes = (4096..65536) + # end + # @buffer_size = 4096 + # @client = parent + # if @direction == :download + # @invitation = details[:iq] + # @sender = details[:iq].from + # @info[:sender] = @sender.to_s + # @info[:recipient] = @client.jid.to_s + # @file_details = {:filename => details[:file_info].fname, :path => (details[:download_directory] + "/" + details[:file_info].fname), :size => details[:file_info].size, :md5 => details[:file_info].hash, :description => details[:file_info].description} + # @download_directory = details[:download_directory] + # @info[:details] = @file_details + # @recipient = nil + # # + # # + # end + # if @direction == :upload + # @info[:sender] = @client.jid.to_s + # @info[:recipient] = details[:jid].to_s + # @recipient = ::Jabber::JID.new(details[:jid]) + # @file_details = {:filename => ::File.basename(details[:path]), :path => details[:path], :size => ::File.size(details[:path]), :md5 => ::Digest::MD5.file(details[:path]), :description => details[:description]} + # @info[:details] = @file_details + # @invitation = nil + # @download_directory = nil + # end + # self + # end + # # + # def inspect() + # "" + # end + # # + # def info() + # @info.clone + # end + # # + # def reference() + # @reference.clone + # end + # # + # def session_id() + # @session_id.clone + # end + # # + # def pending?() + # @status_thread_safety.synchronize { @pending } + # end + # # + # def buffer_size() + # @status_thread_safety.synchronize { @buffer_size } + # end + # # + # def buffer_size=(the_size=4096) + # begin + # unless the_size.is_a?(::Integer) + # raise ArgumentError, "You MUST provide an Integer to set buffer size." + # end + # if @status_thread_safety.synchronize { @direction == :upload } + # unless @valid_write_buffer_sizes.include?(the_size) + # raise ArgumentError, "You MUST provide an Integer between #{@valid_read_buffer_sizes.first} and #{@valid_read_buffer_sizes.last}." + # @status_thread_safety.synchronize { @buffer_size = the_size } + # end + # else + # log_warn("Buffer size is only set on upload file transfers.") + # end + # the_size + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # nil + # end + # end + # # + # def cancelled?() + # @status_thread_safety.synchronize { @cancel } + # end + # # + # def cancel() + # if @client + # begin + # if @status_thread_safety.synchronize { @pending == true } + # @status_thread_safety.synchronize { @pending = false } + # end + # if @status_thread_safety.synchronize { @inprogress == true } + # @status_thread_safety.synchronize { @inprogress = false } + # end + # @status_thread_safety.synchronize { @cancel = true } + # @client.refresh_file_transfers + # true + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # else + # false + # end + # end + # # + # def accept() + # if @client + # if @status_thread_safety.synchronize { @pending == true && @direction == :download } + # Thread.new { + # begin + # bytes_received = 0 + # total_size = @file_details[:size] + # @bytestream = @manager.accept(@invitation) + # if @bytestream + # @bytestream.accept() + # @status_thread_safety.synchronize { + # @pending = false + # @inprogress = true + # @session_id = @bytestream.session_id.to_s + # } + # # + # cancel_callback = Proc.new do |the_iq| + # if the_iq.type == :error + # the_error = ::Jabber::ServerError.new(the_iq.error) + # if @invitation.id.to_s == the_iq.id.to_s + # the_transfer = @client.find_file_transfer(@reference) + # if the_transfer + # the_transfer.cancel + # end + # end + # log_error({:error => the_error, :parameters => {:iq => the_iq}}) + # end + # end + # @client.set_xfr_callback(@session_id,&cancel_callback) + # # + # the_file = ::File.open(@file_details[:path], "w+b",0664) + # the_file.pos = 0 + # buffer = @bytestream.read() + # while buffer && @status_thread_safety.synchronize { @cancel == false } do + # the_file.write(buffer.to_s) + # bytes_received += buffer.to_s.size + # @status_thread_safety.synchronize { @percent_completed = ((bytes_received.to_f / total_size.to_f) * 100.0).to_i } + # @client.notify({:event => :file_transfer_progress, :transfer => @reference, :progress => @status_thread_safety.synchronize { @percent_completed }}) + # buffer = @bytestream.read() + # end + # the_file.close + # @bytestream.close + # @status_thread_safety.synchronize { + # @inprogress = false + # @pending = false + # } + # the_path = @file_details[:path] + # if @status_thread_safety.synchronize { @cancel == true } + # ::File.delete(the_path) + # @client.notify({:event => :file_transfer_cancelled, :transfer => @reference}) + # else + # if @file_details[:md5] + # unless @file_details[:md5] == ::Digest::MD5.file(the_path) + # raise Exception, "MD5 Checksum Failure - file corruption on file: #{the_path}" + # end + # @client.notify({:event => :file_transfer_complete, :transfer => @reference}) + # end + # end + # else + # @status_thread_safety.synchronize { + # @inprogress = false + # @pending = false + # } + # the_file = nil + # raise Exception, "Failed to negotiate a transfer method." + # end + # rescue Exception => the_error + # @status_thread_safety.synchronize { + # @inprogress = false + # @pending = false + # } + # if the_file + # the_file.close + # end + # if @bytestream + # @bytestream.close + # end + # log_error({:error => the_error, :parameters => {}}) + # @client.notify({:event => :file_transfer_error, :transfer => @reference}) + # end + # if @session_id + # @client.clear_xfr_callback(@session_id) + # end + # @client.refresh_file_transfers + # } + # end + # true + # else + # false + # end + # end + # # + # def decline() + # if @client + # if @status_thread_safety.synchronize { @pending == true && @direction == :download } + # begin + # @manager.decline(@invitation) + # @status_thread_safety.synchronize { @pending = false } + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # end + # true + # else + # false + # end + # end + # # + # def in_progress?() + # @status_thread_safety.synchronize { @inprogress } + # end + # # + # def percent_completed() + # @status_thread_safety.synchronize { @percent_completed.clone } + # end + # # + # def file_details() + # @status_thread_safety.synchronize { @file_details.clone } + # end + # # + # def download_directory() + # @status_thread_safety.synchronize { @download_directory.clone } + # end + # # + # def download_directory=(the_path=nil) + # begin + # unless the_path.is_a?(::String) + # raise ArgumentError, "You MUST supply a String as valid directory path." + # end + # the_path = ::File.expand_path(the_path) + # unless the_path.valid_path?() + # raise ArgumentError, "You MUST supply a valid directory path." + # end + # unless ::File.exist?(the_path) + # raise ArgumentError, "You MUST supply a valid directory path - Directory does not exist." + # end + # @status_thread_safety.synchronize { + # unless @inprogress == true + # if @direction == :download + # @download_directory = ::File.expand_path(the_path) + # @file_details[:path] = (@download_directory + "/" + @file_details[:filename]) + # end + # end + # } + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # end + # # + # def upload_file(proxy=nil) + # if @client + # if @direction == :upload + # unless @status_thread_safety.synchronize { @inprogress == true } + # Thread.new { + # begin + # bytes_sent = 0 + # buffer_size = @status_thread_safety.synchronize { @buffer_size } + # the_file_source = ::Jabber::FileTransfer::FileSource.new(@file_details[:path]) + # the_file_source.length = buffer_size + # total_size = the_file_source.size + # @bytestream = nil + # response = @manager.offer(@recipient,the_file_source,@file_details[:description],nil,@reference.to_s) + # if response.is_a?(::Hash) + # @bytestream = response[:bytestream] + # the_iq_id = response[:iq_id] + # if @bytestream + # if @bytestream.is_a?(::Jabber::Bytestreams::SOCKS5BytestreamsInitiator) + # if proxy.is_any?(::String, ::Jabber::JID) + # @bytestream.add_streamhost(proxy) + # end + # end + # if @bytestream.is_a?(::Jabber::Bytestreams::IBBInitiator) + # @bytestream.block_size = buffer_size + # end + # # + # @status_thread_safety.synchronize { + # @pending = false + # @inprogress = true + # } + # open_response = @bytestream.open() + # if open_response + # puts "Got: (open_response) - #{open_response.inspect}" + # if open_response.type == :result + # # + # @session_id = @bytestream.session_id.to_s + # # + # cancel_callback = Proc.new do |the_iq| + # if the_iq.type == :error + # the_error = ::Jabber::ServerError.new(the_iq.error) + # if the_iq_id.to_s == the_iq.id.to_s + # the_transfer = @client.find_file_transfer(@reference) + # if the_transfer + # the_transfer.cancel + # end + # end + # log_error({:error => the_error, :parameters => {:iq => the_iq}}) + # end + # end + # @client.set_xfr_callback(@session_id,&cancel_callback) + # # + # ::GxG.apportioned_ranges(total_size,buffer_size).each do |the_range| + # if @status_thread_safety.synchronize { @cancel == true } + # the_file_source.close + # break + # end + # the_file_source.seek(the_range.first) + # bytes_sent += @bytestream.write(the_file_source.read(the_range.size)).to_i + # @status_thread_safety.synchronize { @percent_completed = ((bytes_sent.to_f / total_size.to_f) * 100.0).to_i } + # # + # end + # @bytestream.flush + # @bytestream.close + # @status_thread_safety.synchronize { @inprogress = false } + # else + # # Error? + # @status_thread_safety.synchronize { + # @pending = false + # @inprogress = false + # } + # end + # else + # raise Exception, "Failed to open the stream." + # end + # end + # else + # @status_thread_safety.synchronize { + # @pending = false + # @inprogress = false + # } + # end + # rescue Exception => the_error + # the_file_source.close + # @bytestream.close + # @status_thread_safety.synchronize { @inprogress = false } + # log_error({:error => the_error, :parameters => {:proxy => proxy}}) + # end + # @client.clear_xfr_callback(@reference.to_s) + # if @session_id + # @client.clear_xfr_callback(@session_id) + # end + # @client.refresh_file_transfers + # } + # end + # end + # end + # end + # # + # end + # # + # # ### Conversation Channel + # class XmppAgentConversation + # # + # def default_room_configuration(options={}) + # unless options.is_a?(::Hash) + # options = {} + # end + # default_config = { + # 'FORM_TYPE' => 'http://jabber.org/protocol/muc#roomconfig', + # 'form' => 'config', + # 'muc#roomconfig_roomname' => nil, + # 'muc#roomconfig_roomdesc' => nil, + # 'leave' => 'has left', + # 'join' => 'has become available', + # 'rename' => 'is now known as', + # 'muc#roomconfig_changesubject' => 0, + # 'muc#roomconfig_maxusers' => 0, + # 'privacy' => 0, + # 'muc#roomconfig_publicroom' => 1, + # 'muc#roomconfig_persistentroom' => 1, + # 'legacy' => 0, + # 'muc#roomconfig_moderatedroom' => 0, + # 'defaulttype' => 0, + # 'privmsg' => 0, + # 'muc#roomconfig_membersonly' => 0, + # 'muc#roomconfig_allowinvites' => 1, + # 'muc#roomconfig_passwordprotectedroom' => 0, + # 'muc#roomconfig_roomsecret' => nil, + # 'muc#roomconfig_whois' => 'moderators', + # 'muc#roomconfig_enablelogging' => 0, + # 'logformat' => 'xml' + # } + # default_config.merge(options) + # end + # # + # def initialize(parent=nil, details={}) + # if ::GxG::valid_uuid?(details[:context]) + # @uuid = details[:context].to_s.to_sym + # else + # @uuid = ::GxG::uuid_generate.to_sym + # end + # @parent = parent + # # @details should have a JID for the sender-jid/room-jid + # # @details should indicate whether a private direct message or MUC ROOM message (:chat? or :groupchat?) + # @details = details + # # + # @received = [] + # @received_thread_safety = ::Mutex.new + # # Add call backs + # # @parent.notify({:event => :new_message, :conversation => @uuid, :message => message.id}) + # # @parent.notify({:event => :new_private_message, :conversation => @uuid, :message => message.id}) + # self + # end + # # + # def uuid() + # @uuid + # end + # # + # def jid() + # @details[:sender].to_s + # end + # # + # def my_jid() + # result = nil + # if @parent + # result = @parent.jid().to_s + # end + # result + # end + # # + # def [](the_key=nil) + # result = nil + # if the_key.is_a?(::Symbol) + # result = @details[(the_key)] + # end + # result + # end + # # + # def title() + # result = nil + # # Review : TODO - return ROOM title + # result + # end + # # + # def inspect() + # {:jid => this().jid, :title => this().title}.inspect + # end + # # + # def closed?() + # # Review : integrate with new system + # false + # end + # # + # def process_received(&block) + # begin + # if block.respond_to?(:call) + # count = 0 + # @received_thread_safety.synchronize { + # count = @received.size + # } + # record = nil + # count.times do + # @received_thread_safety.synchronize { + # record = @received.shift + # } + # if record + # block.call(record) + # end + # end + # true + # else + # false + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # false + # end + # end + # # + # def add_message(the_message=nil) + # if the_message + # @received_thread_safety.synchronize { @received << the_message } + # true + # else + # false + # end + # end + # # + # def join(the_jid=nil, password=nil, options={}) + # result = false + # begin + # # Review : TODO - MUC join + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:the_jid => the_jid, :options => options}}) + # end + # result + # end + # # + # def leave(exit_string=nil) + # result = false + # begin + # # Review : TODO - MUC leave + # # @parent.refresh_conversations + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:exit_string => exit_string}}) + # end + # result + # end + # # + # def chat_buddies() + # result = [] + # begin + # # Review : TODO - like XmppAgent.buddies but for this ROOM only. + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def say_something(the_message=nil, to_jid=nil) + # result = false + # begin + # if @details[:type] == :chat + # recipient = (to_jid || the_message[:to]).to_s + # @parent.say(::Blather::JID.new(recipient), @parent.gxg_message_to_xmpp(the_message)) + # else + # # Review : TODO - send chat message to ROOM + # end + # result = true + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:message => the_message, :to => to_jid}}) + # end + # result + # end + # # + # def send_invitations(the_recipients=nil, reason=nil) + # result = false + # begin + # # Review : TODO - ROOM invitation list processing list.each { |i| send i } + # # "You MUST provide an Array of JID Strings." + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:recipients => the_recipients, :message => reason}}) + # end + # result + # end + # # + # def configuration() + # result = nil + # begin + # # Review : TODO - get ROOM configuration + # # if @handler + # # if @handler.owner? + # # current_configuration = {} + # # iq = ::Jabber::Iq.new(:get, @handler.jid.to_s) + # # iq.to = @handler.jid.to_s + # # iq.from = @handler.my_jid.to_s + # # iq.add(::Jabber::MUC::IqQueryMUCOwner.new) + # # field_keys = this().default_room_configuration.keys + # # response = @stream.send_with_id(iq) + # # if response + # # if (response.query && response.query.x(::Jabber::Dataforms::XData)) + # # field_keys.each do |the_key| + # # field = response.query.x(::Jabber::Dataforms::XData).field(the_key.to_s) + # # if field.is_a?(::Jabber::Dataforms::XDataField) + # # current_configuration[(the_key.to_s)] = field.value + # # end + # # end + # # end + # # end + # # result = current_configuration + # # else + # # raise Exception, "You are not the owner of this conversation room." + # # end + # # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def change_configuration(the_configuration=nil) + # result = false + # begin + # # Review : TODO - alter ROOM configuration if admin/owner + # # if @handler + # # if @handler.owner? + # # unless the_configuration.is_a?(::Hash) + # # raise ArgumentError, "You MUST provide a configuration Hash." + # # end + # # the_configuration = this().default_room_configuration(the_configuration) + # # @handler.submit_room_configuration(the_configuration) + # # result = true + # # else + # # raise Exception, "You are not the owner of this conversation room." + # # end + # # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:configuration => the_configuration}}) + # end + # result + # end + # # + # end + # # ### XMPP Client + # class XmppAgent + # # ### + # def self.online_availability_values() + # {:online => :chat, :busy => :dnd, :away => :away, :extended_away => :xa, :offline => :unavailable, :error => :error} + # end + # # + # def self.registration_fields() + # # XEP-0077 Support + # {:username => 'Account name associated with the user', :nick => 'Familiar name of the user', :password => 'Password or secret for the user', :name => 'Full name of the user', :first => 'First name or given name of the user', :last => 'Last name, surname, or family name of the user', :email => 'Email address of the user', :address => 'Street portion of a physical or mailing address', :city => 'Locality portion of a physical or mailing address', :state => 'Region portion of a physical or mailing address', :zip => 'Postal code portion of a physical or mailing address', :phone => 'Telephone number of the user', :url => 'URL to web page describing the user', :date => 'Some date (e.g., birth date, hire date, sign-up date)'}.clone + # end + # # + # def self.registration_info(the_url=nil) + # result = nil + # begin + # if the_url.is_a?(::URI::Generic) + # the_jid = "anonymous" + # if the_url.hostname() + # the_jid << ("@" + the_url.hostname.to_s) + # else + # raise ArgumentError, "You MUST provide a vailid host or IP Address in a valid URI." + # end + # # Review : convert this code to Blather compatible + # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. + # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. + # result = [] + # # client = ::Jabber::Client.new(the_jid) + # # client.connect(the_url.hostname.to_s, (the_url.port || 5222)) + # # info = client.register_info() + # # if client.status == 2 + # # client.close + # # end + # # if info.is_a?(::Array) + # # fields = ::GxG::Networking::XmppClient::registration_fields() + # # result = {:instructions => info[0], :registration_fields => {}} + # # info[1].each do |the_raw_key| + # # result[:registration_fields][(the_raw_key.to_sym)] = fields[(the_raw_key.to_sym)] + # # end + # # end + # else + # raise ArgumentError, "You MUST provide a vailid URL." + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:url => the_url}}) + # end + # result + # end + # # + # def self.remove_registration(the_url=nil) + # # Review : convert this code to Blather compatible + # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. + # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. + # result = false + # begin + # if the_url.is_a?(::URI::Generic) + # if the_url.password() == nil + # raise ArgumentError, "You MUST provide a vailid password." + # end + # the_jid = "" + # if the_url.user() + # the_jid << the_url.user.to_s + # else + # raise ArgumentError, "You MUST provide a vailid user name." + # end + # if the_url.hostname() + # the_jid << ("@" + the_url.hostname.to_s) + # else + # raise ArgumentError, "You MUST provide a vailid host or IP Address." + # end + # if the_url.path() + # the_jid << ("/" + File.basename(the_url.path.to_s)) + # end + # # Here ??? + # # Connect + # client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222)) + # client.run + # # xxx + # # client.remove_registration + # # xxx + # client.close + # result = true + # else + # raise ArgumentError, "You MUST provide a vailid URL." + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}}) + # end + # result + # end + # # + # def self.register_with_server(the_url=nil, fields={}) + # # Review : convert this code to Blather compatible + # # Note : see https://www.rubydoc.info/gems/blather/Blather/Stanza/Iq for researching ideas. + # # Note : see https://www.rubydoc.info/gems/xmpp4r/Jabber/Client#register-instance_method for researching ideas. + # result = false + # begin + # if the_url.is_a?(::URI::Generic) + # if the_url.password() == nil + # raise ArgumentError, "You MUST provide a vailid password." + # else + # fields['password'] = the_url.password.to_s + # end + # the_jid = "" + # if the_url.user() + # the_jid << the_url.user.to_s + # fields['username'] = the_url.user.to_s + # else + # raise ArgumentError, "You MUST provide a vailid user name." + # end + # if the_url.hostname() + # the_jid << ("@" + the_url.hostname.to_s) + # else + # raise ArgumentError, "You MUST provide a vailid host or IP Address." + # end + # if the_url.path() + # the_jid << ("/" + File.basename(the_url.path.to_s)) + # end + # # xxx + # # client = ::Jabber::Client.new(the_jid) + # # client.connect(the_url.hostname.to_s, (the_url.port || 5222)) + # # valid_fields = ::GxG::Networking::XmppClient::registration_fields().keys + # # the_fields = {} + # # fields.each_pair do |key, entry| + # # if valid_fields.include?(key.to_sym) + # # the_fields[(key.to_s)] = entry + # # end + # # end + # # if the_fields.keys.size > 0 + # # client.register(the_url.password.to_s, the_fields) + # # else + # # client.register(the_url.password.to_s) + # # end + # # if client.status == 2 + # # client.close + # # end + # result = true + # else + # raise ArgumentError, "You MUST provide a vailid URL." + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:url => the_url, :fields => fields}}) + # end + # result + # end + # # + # # ### Support Methods + # def write(stanza) + # if @client + # @client.write stanza + # end + # true + # end + # # + # def <<(stanza) + # if @client + # @client.write stanza + # end + # self + # end + # # + # def publish_and_subscribe + # @pubsub + # end + # # ### Setup a before filter + # # + # # @param [Symbol] handler (optional) the stanza handler the filter should + # # run before + # # @param [guards] guards (optional) a set of guards to check the stanza + # # against + # # @yield [Blather::Stanza] stanza + # def before(handler = nil, *guards, &block) + # if @client + # @client.register_filter :before, handler, *guards, &block + # end + # end + # # ### Setup an after filter + # # + # # @param [Symbol] handler (optional) the stanza handler the filter should + # # run after + # # @param [guards] guards (optional) a set of guards to check the stanza + # # against + # # @yield [Blather::Stanza] stanza + # def after(handler = nil, *guards, &block) + # if @client + # @client.register_filter :after, handler, *guards, &block + # end + # end + # # ### Set handler for a stanza type + # # + # # @param [Symbol] handler the stanza type it should handle + # # @param [guards] guards (optional) a set of guards to check the stanza + # # against + # # @yield [Blather::Stanza] stanza + # def handle(handler, *guards, &block) + # if @client + # @client.register_handler handler, *guards, &block + # end + # end + # # ### Wrapper for "handle :ready" (just a bit of syntactic sugar) + # # + # # This is run after the connection has been completely setup + # def when_ready(&block) + # self.handle :ready, &block + # end + # # ### Wrapper for "handle :disconnected" + # # + # # This is run after the connection has been shut down. + # # + # # @example Reconnect after a disconnection + # # disconnected { client.run } + # def disconnected(&block) + # self.handle :disconnected, &block + # end + # # ### Set current status + # # + # # @param [Blather::Stanza::Presence::State::VALID_STATES] state the current + # # state + # # @param [#to_s] msg the status message to use + # def set_status(state = nil, msg = nil) + # if @client + # @client.status = state, msg + # end + # end + # # + # def get_status() + # if @client + # @client.status + # else + # :unavailable + # end + # end + # # ### Direct access to the roster + # # + # # @return [Blather::Roster] + # def my_roster + # if @client + # @client.roster + # end + # end + # # ### Write data to the stream + # # + # # @param [#to_xml, #to_s] stanza the data to send down the wire. + # def write_to_stream(stanza) + # self.write stanza + # end + # # ### Helper method to join a MUC room + # # + # # @overload join(room_jid, nickname) + # # @param [Blather::JID, #to_s] room the JID of the room to join + # # @param [#to_s] nickname the nickname to join the room as + # # @overload join(room_jid, nickname) + # # @param [#to_s] room the name of the room to join + # # @param [Blather::JID, #to_s] service the service domain the room is hosted at + # # @param [#to_s] nickname the nickname to join the room as + # def join(room, service, nickname = nil) + # join = Blather::Stanza::Presence::MUC.new + # if nickname + # join.to = "#{room}@#{service}/#{nickname}" + # else + # join.to = "#{room}/#{service}" + # end + # self.write join + # end + # # ### Helper method to make sending basic messages easier + # # + # # @param [Blather::JID, #to_s] to the JID of the message recipient + # # @param [#to_s] msg the message to send + # # @param [#to_sym] the stanza method to use + # def say(to, msg, using = :chat) + # self.write Blather::Stanza::Message.new(to, msg, using) + # end + # # ### The JID according to the server + # # + # # @return [Blather::JID] + # def jid + # if @client + # @client.jid + # else + # nil + # end + # end + # # other supporting JID methods + # def jid_node() + # jid = self.jid + # if jid + # jid.node() + # else + # nil + # end + # end + # def jid_domain() + # jid = self.jid + # if jid + # jid.domain() + # else + # nil + # end + # end + # def jid_resource() + # jid = self.jid + # if jid + # jid.resource() + # else + # nil + # end + # end + # # ### Halt the handler chain + # # + # # Use this to stop the propogation of the stanza though the handler chain. + # # + # # @example Ignore all IQ stanzas + # # + # # before(:iq) { halt } + # def halt + # # Review : how to actually use this? I see no way to call 'throw' yet. (research) + # # throw :halt + # end + # # ### Pass responsibility to the next handler + # # + # # Use this to jump out of the current handler and let the next registered + # # handler take care of the stanza + # # + # # @example Pass a message to the next handler + # # + # # This is contrive and should be handled with guards, but pass a message + # # to the next handler based on the content + # # + # # message { |s| puts "message caught" } + # # message { |s| pass if s.body =~ /pass along/ } + # def pass + # # Review : how to actually use this? I see no way to call 'throw' yet. (research) + # # throw :pass + # end + # # ### Request items or info from an entity + # # discover (items|info), [jid], [node] do |response| + # # end + # def discover(what, who, where, &callback) + # if @client + # stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new + # stanza.to = who + # stanza.node = where + + # @client.register_tmp_handler stanza.id, &callback + # @client.write stanza + # end + # end + # # ### Set the capabilities of the client + # # + # # @param [String] node the URI + # # @param [Array] identities an array of identities + # # @param [Array] features an array of features + # def set_caps(node, identities, features) + # if @client + # @client.caps.node = node + # @client.caps.identities = identities + # @client.caps.features = features + # end + # end + # # Send capabilities to the server + # def send_caps + # if @client + # @client.register_handler :disco_info, :type => :get, :node => @client.caps.node do |s| + # r = @client.caps.dup + # r.to = s.from + # r.id = s.id + # @client.write r + # end + # @client.write client.caps.c + # end + # end + # # ### Conversion Tools + # def xmpp_message_to_gxg(the_message=nil) + # # need: :to, :body, :state, :subject, :context, :type + # # ::Celluloid::UUID::random_generate().to_s + # unless the_message.is_a?(Blather::Stanza::Message) + # raise ArgumentError, "You MUST provide a valid message object: Blather::Stanza::Message." + # end + # gxg_message = ::GxG::Events::Message.new({:sender => :unknown}) + # gxg_message[:id] = (the_message.id || ::GxG::uuid_generate.to_sym) + # gxg_message[:sender] = the_message.from.to_s + # gxg_message[:to] = the_message.to.to_s + # gxg_message[:type] = the_message.type + # if the_message.thread.is_a?(::Hash) + # gxg_message[:context] = the_message.thread.values[0] + # else + # gxg_message[:context] = the_message.thread + # end + # gxg_message[:subject] = the_message.subject + # gxg_message[:body] = the_message.body + # gxg_message + # end + # # + # def gxg_message_to_xmpp(the_message=nil) + # # need: :to, :body, :state, :subject, :context, :type + # # ::Celluloid::UUID::random_generate().to_s + # unless the_message.is_a?(::GxG::Events::Message) + # raise ArgumentError, "You MUST provide a valid message object: GxG::Events::Message or new_message Hash." + # end + # xmpp_message = Blather::Stanza::Message.new(the_message[:to], the_message[:body]) + # # chat states: active composing gone inactive paused + # # xmpp_message.set_chat_state(the_message[:state].to_s.to_sym) + # xmpp_message.id = (the_message[:id].to_s) + # xmpp_message.from = (the_message[:sender].to_s) + # xmpp_message.to = (the_message[:to].to_s) + # if the_message[:context].to_s.size > 0 + # xmpp_message.thread = (the_message[:context].to_s) + # end + # xmpp_message.subject = (the_message[:subject].to_s) + # # + # # The following Symbols are allowed: + # # * :chat + # # * :error + # # * :groupchat + # # * :headline + # # * :normal + # xmpp_message.type = (the_message[:type] || :chat) + # xmpp_message.body = the_message[:body] + # xmpp_message + # end + # # + # # ### Initialization + # def initialize(options={}) + # @client = nil + # @pubsub = nil + # @roster = nil + # @browser = nil + # @transfer_manager = nil + # @xfr_callbacks = nil + # @conversations = [] + # @conversation_thread_safety = ::Mutex.new + # @invitations = [] + # @invitation_thread_safety = ::Mutex.new + # @download_directory = ::File.expand_path(".") + # @file_transfers = [] + # @file_transfers_thread_safety = ::Mutex.new + # @notification_object = options[:notify] + # self + # end + # # Review : debug only method + # def client() + # @client + # end + # # + # def notify(notification_type=nil) + # if notification_type + # if @notification_object + # if @notification_object.respond_to?(:notify) + # @notification_object.notify(notification_type) + # end + # end + # end + # end + # # + # def login(the_url=nil, options={}) + # result = false + # begin + # if the_url.is_a?(::URI::Generic) + # if the_url.password() == nil && the_url.user != "anonymous" + # raise ArgumentError, "You MUST provide a vailid password." + # end + # the_jid = "" + # if the_url.user() + # the_jid << the_url.user.to_s + # else + # raise ArgumentError, "You MUST provide a vailid user name." + # end + # if the_url.hostname() + # the_jid << ("@" + the_url.hostname.to_s) + # else + # raise ArgumentError, "You MUST provide a vailid host or IP Address." + # end + # if the_url.path() + # the_jid << ("/" + File.basename(the_url.path.to_s)) + # end + # # Connect + # @client = ::Blather::Client::setup(the_jid, the_url.password.to_s, the_url.hostname.to_s, (the_url.port || 5222)) + # # @client.run + # # Initialize Supports + # @pubsub = ::Blather::DSL::PubSub.new(@client, self.jid_domain) + # # Auto-reconnect if disconnected + # self.handle :disconnected do + # @client.run + # end + # # ### Setup Message Reception: (skipping compose etc messages for now.) + # # Handlers Map : https://github.com/adhearsion/blather#handlers-hierarchy + # # Private Message (type :normal) + # self.handle :message, :normal?, :body do |message| + # the_message = self.xmpp_message_to_gxg(message) + # the_channel = self.conversations(the_message[:sender].to_s)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # the_channel.add_message(the_message) + # self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) + # true + # end + # # Private Message (type :chat) + # self.handle :message, :chat?, :body do |message| + # the_message = self.xmpp_message_to_gxg(message) + # the_channel = self.conversations(the_message[:sender].to_s)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # the_channel.add_message(the_message) + # self.notify({:event => :new_private_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) + # true + # end + # # Group Message + # self.handle :message, :groupchat?, :body do |message| + # the_message = self.xmpp_message_to_gxg(message) + # the_channel = self.conversations(the_message[:sender].to_s)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :groupchat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # the_channel.add_message(the_message) + # self.notify({:event => :new_message, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) + # true + # end + # # Announcement Message + # self.handle :message, :headline?, :body do |message| + # the_message = self.xmpp_message_to_gxg(message) + # the_channel = self.conversations(the_message[:sender].to_s)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # the_channel.add_message(the_message) + # self.notify({:event => :new_announcement, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) + # true + # end + # # Error Message + # self.handle :message, :error?, :body do |message| + # the_message = self.xmpp_message_to_gxg(message) + # the_channel = self.conversations(the_message[:sender].to_s)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_message[:sender], :type => :chat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # the_channel.add_message(the_message) + # self.notify({:event => :new_error, :conversation => the_channel.uuid, :message => the_message.id, :sender => the_message[:sender].to_s}) + # true + # end + # # Presence + # self.handle :iq do |update| + # if update.respond_to?(:type) + # if update.is_a?(::Blather::Stanza::Iq::Ping) + # # Auto-reply to Pings: + # reply = update.reply + # reply.type = :result + # # Review : how to add a data payload to the reply? + # self.write_to_stream(reply) + # else + # if update.is_a?(::Blather::Stanza::Iq) + # # reply = update.reply + # # reply.type = :result + # # # Review : how to add a data payload to the reply? + # # self.write_to_stream(reply) + # # puts "Got: #{update.class.inspect}\n-----------------------------------------\n#{update.inspect}\n Sent: #{reply.inspect}\n-----------------------------------------\n" + # end + # end + # end + # # Note : returning 'false' signals that subsequent process of this stanza is desired. + # false + # end + # # Invitation + # self.handle :subscription do |invitation| + # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) + # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } + # self.notify({:event => :new_invitation, :invitation => gxg_invitation.uuid, :sender => gxg_invitation.from.to_s}) + # true + # end + # # Presence + # self.handle :presence do |presence_update| + # the_sender = "#{presence_update.from.node.to_s}@#{presence_update.from.domain.to_s}" + # self.my_roster.each do |roster_item| + # if roster_item.jid.to_s == the_sender + # roster_item.status = presence_update + # break + # end + # end + # false + # end + # # File Transfer + # self.handle :file_transfer do |invitation| + # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) + # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } + # puts "Got (file transfer) : #{invitation.inspect}" + # false + # end + # # Multi-User Chat (MUC) Invitation + # self.handle :muc_join do |invitation| + # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) + # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } + # puts "Got (MUC join) : #{invitation.inspect}" + # true + # end + # self.handle :muc_user do |invitation| + # # try also: :muc_user, :invite? + # # gxg_invitation = ::GxG::Networking::XmppAgentInvitation.new(self, invitation) + # # @invitation_thread_safety.synchronize { @invitations << gxg_invitation } + # puts "Got (MUC user) : #{invitation.inspect}" + # true + # end + # # Connect + # @client.run + # result = true + # timeout = Time.now.to_f + 30.0 + # until @client.connected? do + # if Time.now.to_f >= timeout + # result = false + # break + # end + # end + # end + # rescue Exception => the_error + # puts the_error.to_s + # end + # # + # result + # end + # # + # def logout + # if @client + # @client.clear_handlers :disconnected + # @client.close + # @client = nil + # end + # # + # true + # end + # # + # def buddies() + # result = [] + # begin + # if @client + # @client.roster.items.each_pair do |key_object, data| + # new_record = {:jid => "", :title => "Untitled", :groups => [], :status => :offline} + # new_record[:jid] = key_object.to_s + # new_record[:title] = data.name.to_s + # new_record[:groups] = data.groups + # if data.status() + # new_record[:status] = data.status().state() + # end + # result << new_record + # end + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def add_buddy(the_jid=nil) + # result = false + # begin + # if @client + # if the_jid.is_a?(::String) + # unless the_jid.valid_jid? + # raise ArgumentError, "You MUST provide a valid JID string." + # end + # @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "subscribe")) + # result = true + # end + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:the_jid => the_jid}}) + # end + # result + # end + # # + # def remove_buddy(the_jid=nil) + # result = false + # begin + # if @client + # if the_jid.is_a?(::String) + # unless the_jid.valid_jid? + # raise ArgumentError, "You MUST provide a valid JID string." + # end + # @client.write(Blather::Stanza::Presence::Subscription.new(::Blather::JID.new(the_jid), "unsubscribe")) + # result = true + # end + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:the_jid => the_jid}}) + # end + # result + # end + # # + # def invitations() + # result = [] + # begin + # if @client + # @invitation_thread_safety.synchronize { + # @invitations.each do |entry| + # if entry.pending? + # result << entry + # end + # end + # } + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def refresh_invitations() + # result = false + # begin + # if @client + # deadlist = [] + # @invitation_thread_safety.synchronize { + # @invitations.each_index do |index| + # unless @invitations[(index)].pending? + # deadlist << index + # end + # end + # if deadlist.size > 0 + # deadlist.reverse.each do |the_index| + # @invitations.delete_at(the_index) + # end + # end + # result = true + # } + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def refresh_conversations() + # result = false + # begin + # if @client + # deadlist = [] + # @conversation_thread_safety.synchronize { + # @conversations.each_index do |index| + # if @conversations[(index)].closed? + # deadlist << index + # end + # end + # if deadlist.size > 0 + # deadlist.reverse.each do |the_index| + # @conversations.delete_at(the_index) + # end + # end + # result = true + # } + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def conversations(select_jid=nil) + # result = [] + # begin + # if @client + # @conversation_thread_safety.synchronize { + # @conversations.each do |entry| + # if select_jid + # if select_jid.to_s == entry.jid.to_s + # unless entry.closed? + # result << entry + # break + # end + # end + # else + # unless entry.closed? + # result << entry + # end + # end + # end + # } + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def create_conversation(the_jid=nil, password=nil, configuration={}) + # result = false + # begin + # if @client + # unless the_jid.is_a?(::String) + # raise ArgumentError, "You MUST provide a valid JID String." + # end + # unless the_jid.valid_jid?() + # raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed." + # end + # if (self.buddies.collect {|entry| entry[:jid]}).include?(the_jid) + # # private channel + # the_channel = self.conversations(the_jid)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :chat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # else + # # groupchat channel + # the_channel = self.conversations(the_jid)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # end + # if the_channel + # the_channel.join(the_jid) + # if configuration.is_any?(::Hash, ::GxG::Database::PersistedHash) + # if configuration.keys.size > 0 + # the_channel.change_configuration(configuration) + # end + # end + # end + # end + # result = true + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:jid => the_jid}}) + # end + # result + # end + # # + # def join_conversation(the_jid=nil, password=nil, options={}) + # result = false + # begin + # if @client + # unless the_jid.is_a?(::String) + # raise ArgumentError, "You MUST provide a valid JID String." + # end + # unless the_jid.valid_jid?() + # raise ArgumentError, "You MUST provide a valid JID String. The one you provided is malformed." + # end + # the_channel = self.conversations(the_jid)[0] + # unless the_channel + # the_channel = ::GxG::Networking::XmppAgentConversation.new(self, {:sender => the_jid, :type => :groupchat}) + # @conversation_thread_safety.synchronize { @conversations << the_channel } + # the_channel.join(the_jid) + # end + # result = true + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:jid => the_jid}}) + # end + # result + # end + # # + # def download_directory() + # @file_transfers_thread_safety.synchronize { @download_directory } + # end + # # + # def download_directory=(the_path=nil) + # unless the_path.is_a?(::String) + # raise ArgumentError, "You MUST supply a String as valid file path." + # end + # the_path = ::File.expand_path(the_path) + # unless the_path.valid_path?() + # raise ArgumentError, "You MUST supply a valid file path." + # end + # unless ::File.exist?(the_path) + # raise ArgumentError, "You MUST supply a valid file path - File does not exist." + # end + # @file_transfers_thread_safety.synchronize { @download_directory = the_path } + # end + # # + # def send_file(the_jid=nil,the_path=nil, description=nil, buffer_size=4096, proxy=nil) + # if @client + # begin + # unless the_jid.is_a?(::String) + # raise ArgumentError, "You MUST supply a valid JID String to specify the recipient of the file." + # end + # unless the_path.is_a?(::String) + # raise ArgumentError, "You MUST supply a String as valid file path." + # end + # the_path = ::File.expand_path(the_path) + # unless the_path.valid_path?() + # raise ArgumentError, "You MUST supply a valid file path." + # end + # unless ::File.exist?(the_path) + # raise ArgumentError, "You MUST supply a valid file path - File does not exist." + # end + # unless description.is_a?(::String) + # description = "File Transfer: #{::File.basename(the_path)}" + # end + # unless buffer_size.is_a?(::Integer) + # raise ArgumentError, "You MUST provide an Integer to set buffer size." + # end + # valid_buffer_size_range = ::GxG::SYSTEM.maximum_buffer_size.get_at_path("/:ipv4/:tcp/:write/:valid") + # unless valid_buffer_size_range.is_a?(::Range) + # valid_buffer_size_range = (4096..65536) + # end + # unless valid_buffer_size_range.include?(buffer_size) + # raise ArgumentError, "You MUST provide an Integer between #{valid_buffer_size_range.first} and #{valid_buffer_size_range.last}." + # end + # # Review : rebuild/rewrite this section and XmppAgentFileTransfer + # # the_transfer = ::GxG::Networking::XmppAgentFileTransfer.new(@transfer_manager, :upload, {:jid => the_jid, :path => the_path, :description => description}, this()) + # # @file_transfers_thread_safety.synchronize { + # # @file_transfers << the_transfer + # # } + # # the_transfer.buffer_size = buffer_size + # # the_transfer.upload_file(proxy) + # # the_transfer.reference + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {:jid => the_jid, :path => the_path, :description => description, :buffer_size => buffer_size}}) + # end + # else + # nil + # end + # end + # # + # def file_transfers() + # result = [] + # if @client + # begin + # @file_transfers.each do |entry| + # if (entry.pending? || entry.in_progress? || entry.percent_completed < 100) + # result << entry + # end + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # end + # result + # end + # # + # def refresh_file_transfers() + # result = false + # begin + # if @client + # manifest = [] + # deadlist = [] + # @file_transfers_thread_safety.synchronize { + # @file_transfers.each do |entry| + # manifest << entry + # end + # } + # manifest.each_with_index do |entry, index| + # unless (entry.pending?() || entry.in_progress?()) + # deadlist << index + # end + # end + # @file_transfers_thread_safety.synchronize { + # if deadlist.size > 0 + # deadlist.reverse.each do |the_index| + # @file_transfers.delete_at(the_index) + # end + # end + # } + # result = true + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # def find_file_transfer(reference=nil, use_session = false) + # result = nil + # begin + # if @client + # manifest = [] + # @file_transfers_thread_safety.synchronize { + # @file_transfers.each do |entry| + # manifest << entry + # end + # } + # manifest.each do |entry| + # if use_session == true + # if (reference == entry.session_id()) + # result = entry + # break + # end + # else + # if (reference == entry.reference()) + # result = entry + # break + # end + # end + # end + # end + # rescue Exception => the_error + # log_error({:error => the_error, :parameters => {}}) + # end + # result + # end + # # + # # + # end + + # ### XMPP + # class BridgeAdapterXMPP < ::GxG::Communications::BridgeAdapter + # def self.protocol() + # :xmpp + # end + # # + # def self.abilities() + # [:message, :file_transfer, :request] + # end + # # + # def self.limits() + # {:message_size => 1048576, :file_transfer_size => nil, :request_size => 65536} + # 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: { => {:channel => , :entities => [, ...]}} + # @channels = {} + # # Keychain format: {:original => "", :current => ""} + # @keychain = {} + # # Messages format: { => [<::GxG::Events::Message>, ...]} + # @messages = {} + # @messages_busy_semaphore = false + # @invitations = {} + # @file_transfers = {} + # # + # @connector = ::GxG::Networking::XmppClient.new({:notify => self}) + # self.open + # # + # self + # end + # # + # def my_id() + # if @connector + # @connector.my_jid.to_s + # else + # nil + # end + # end + # # ### Information Refresh + # def notify(details=nil) + # 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::SERVER_PATHS[:temporary] + "/" + reference.to_s) + # ::FileUtils.mkpath(GxG::SERVER_PATHS[:temporary] + "/" + reference.to_s) + # end + # the_transfer.download_directory = (GxG::SERVER_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::SERVER_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::SERVER_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 + # \ No newline at end of file