Created
February 17, 2009 10:54
-
-
Save stepheneb/65683 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'rubygems' | |
require 'activerecord' | |
require 'builder' | |
require 'fileutils' | |
require 'yaml' | |
require 'erb' | |
require 'zlib' | |
require 'base64' | |
require 'hpricot' | |
require 'open-uri' | |
require 'optparse' | |
require 'rdoc/usage' | |
require 'ostruct' | |
require 'date' | |
require 'uuidtools' | |
# @logger = Logger.new $stderr | |
# ActiveRecord::Base.logger = @logger | |
# ActiveRecord::Base.colorize_logging = false | |
# http://www.graphviz.org/pdf/dotguide.pdf | |
# show_wg_bundles.rb 32937 | dot -Tpng > bundles_wg_32937.png ; open bundles_wg_32937.png | |
# Creating a new wg_database.yml database configuration file: | |
# | |
# wg_db_config = { | |
# :host => 'host.org', | |
# :username => 'username', | |
# :password => 'password', | |
# :database => 'database' | |
# } | |
# | |
# File.open('wg_database.yml', 'w') {|f| f.write wg_db_config.to_yaml } | |
# | |
wg_db_config = YAML::load(IO.read('wg_database.yml')) | |
pool = ActiveRecord::Base.establish_connection( | |
:adapter => RUBY_PLATFORM =~ /java/ ? 'jdbcmysql' : 'mysql', | |
:host => wg_db_config[:host], | |
:username => wg_db_config[:username], | |
:password => wg_db_config[:password], | |
:database => wg_db_config[:database] | |
) | |
module B64 | |
class B64 | |
def self.folding_encode(str, eol = "\n", limit = 60) | |
[str].pack('m') | |
end | |
def self.encode(str) | |
[str].pack('m').tr( "\r\n", '') | |
end | |
def self.decode(str, strict = false) | |
str.unpack('m').first | |
end | |
end | |
end | |
class SailUser < ActiveRecord::Base | |
has_many :workgroup_memberships | |
has_many :workgroups, :through => :workgroup_memberships, :select => 'DISTINCT workgroups.*' | |
def name | |
"#{first_name} #{last_name}" | |
end | |
end | |
class Sock < ActiveRecord::Base | |
belongs_to :bundle | |
belongs_to :pod | |
def text(ignore_file = true) | |
value = case self.pod.encoding | |
when 'gzip+b64' | |
self.unpack_gzip_b64_value(ignore_file) | |
when 'escaped' | |
self.unescape_value | |
end | |
case self.pod.mime_type | |
when /xml/ | |
value ? value : self.value | |
when /java_object/ | |
"java object: #{value.length.to_s} bytes" | |
when /text/ | |
value ? value : self.value | |
else | |
"can't determine how to render this sock as text" | |
end | |
end | |
def unescape_value | |
CGI.unescapeHTML(self.value) | |
end | |
def unpack_gzip_b64_value(ignore_file = false) | |
begin | |
if self.pod.bytearray? | |
if ((! ignore_file) && File.exist?(self.path + self.filename_decoded)) | |
File.read(self.path + self.filename_decoded) | |
else | |
Zlib::GzipReader.new(StringIO.new(B64::B64.decode(self.value))).read | |
end | |
else | |
"" | |
end | |
rescue => e | |
if e == Zlib::GzipFile::Error | |
$! | |
else | |
"Couldn't match the error: #{e}" | |
end | |
end | |
end | |
# instance method returns filesystem path to | |
# sock directory root | |
def path | |
"#{self.bundle.path}socks/" | |
end | |
def filename_raw | |
"raw/sock_#{self.id.to_s}_#{self.pod.pas_type}_#{self.pod.encoding}" | |
end | |
def filename_decoded | |
"decoded/sock_#{self.id.to_s}_#{self.pod.pas_type}.#{self.pod.extension}" | |
end | |
end | |
class Pod < ActiveRecord::Base | |
has_many :socks | |
has_many :bundles, :through => :socks | |
@@pod_shape_map = {'[B' => 'bytearray', '' => 'text'} | |
@@pod_type_keys = ['mime_type', 'encoding', 'pas_type', 'extension'] | |
@@pod_type_map = { | |
['bytearray', 'ot.learner.data' ] => ['application/xml+otrunk', 'gzip+b64', 'ot_learner_data', 'otml'], | |
['bytearray', 'otrunk_drawing' ] => ['application/xml+otrunk-drawing', 'gzip+b64', 'otrunk_drawing', 'otml'], | |
['bytearray', 'trialData' ] => ['java_object/gzip+b64', 'gzip+b64', 'trial_data', 'pojo'], | |
['bytearray', 'findingsData' ] => ['java_object/gzip+b64', 'gzip+b64', 'findings_data', 'pojo'], | |
['bytearray', '' ] => ['java_object/gzip+b64', 'gzip+b64', 'generic_pas_object', 'pojo'], | |
['text', 'model.activity.data'] => ['application/xml+pas-modelreport', 'escaped', 'model_activity_data', 'xml' ], | |
['text', 'modelActivityData' ] => ['application/xml+pas-modelreport', 'escaped', 'model_activity_data', 'xml' ], | |
['text', 'navigation_log' ] => ['application/xml+pas-navigation-log', 'escaped', 'navigation_log', 'xml' ], | |
['text', 'curnit_map' ] => ['application/xml+pas-curnit-map', 'escaped', 'curnit_map', 'xml' ], | |
['text', 'session_state' ] => ['application/xml+pas-session-state', 'escaped', 'session_state', 'xml' ], | |
['text', 'airbag99' ] => ['application/xml+svg', 'escaped', 'pedraw', 'svg' ], | |
['text', 'airbag999' ] => ['application/xml+svg', 'escaped', 'pedraw', 'svg' ], | |
['text', 'undefined' ] => ['text/plain', 'escaped', 'note', 'txt' ] | |
} | |
cattr_reader :pod_shape_map | |
cattr_reader :pod_type_keys | |
cattr_reader :pod_type_map | |
# calculates and returns a hash: {:mime_type, :encoding, :pas_type, :extension] | |
def kind | |
shape = self.rim_shape | |
name = self.rim_name | |
name = name[/(undefined).*/, 1] || name | |
value_array = @@pod_type_map[[shape, name]] | |
if value_array == nil | |
case shape | |
when 'bytearray' | |
value_array = @@pod_type_map[[shape, '']] | |
when 'text' | |
value_array = @@pod_type_map[[shape, 'undefined']] | |
end | |
end | |
Hash[*[@@pod_type_keys, value_array].transpose.flatten] | |
end | |
def bytearray? | |
self.rim_shape == 'bytearray' | |
end | |
# returns filesystem path to | |
# pod directory root | |
def path | |
"#{self.curnit.path}pods/#{self.uuid}/" | |
end | |
def filename | |
self.html_body.blank? ? nil : "#{self.rim_name}_body.html" | |
end | |
end | |
class BundleContent < ActiveRecord::Base | |
has_one :bundle | |
def ot_learner_data | |
sock = self.bundle.socks.detect{|i| i.pod.rim_name == 'ot.learner.data'} | |
if sock | |
ot = sock.text | |
if ot =~ /anon_single_user/ | |
ot [/anon_single_user/] = self.bundle.workgroup.member_names | |
end | |
ot | |
else | |
self.bundle.workgroup.blank_ot_learner_data | |
end | |
end | |
end | |
class Bundle < ActiveRecord::Base | |
belongs_to :workgroup | |
belongs_to :bundle_content | |
has_many :socks | |
attr_accessor :parent | |
def calc_modified_time | |
# take the sail_session_start_time and increment by the msOffset time of the newest sockentry in the bundle | |
starttime = self.sail_session_start_time | |
if ! starttime | |
return (self.sail_session_end_time ? self.sail_session_end_time : self.created_at) | |
else | |
if self.socks.count > 0 | |
modtime = starttime + (self.socks.sort{|a,b| b.ms_offset <=> a.ms_offset}.compact[0].ms_offset/1000) | |
return modtime.getgm | |
else | |
return (self.sail_session_end_time ? self.sail_session_end_time : self.created_at) | |
end | |
end | |
end | |
end | |
class WorkgroupMembership < ActiveRecord::Base | |
belongs_to :workgroup | |
belongs_to :sail_user | |
end | |
class Workgroup < ActiveRecord::Base | |
belongs_to :offering | |
has_many :bundles | |
has_many :workgroup_memberships | |
has_many :sail_users, :through => :workgroup_memberships do | |
def version(version) | |
find :all, :conditions => ['version = ?', version] | |
end | |
end | |
def members | |
self.sail_users.version(self.version) | |
end | |
def member_names | |
self.members.collect {|m| m.name}.join(', ') | |
end | |
def blank_ot_learner_data | |
xml = Builder::XmlMarkup.new(:indent=>2) | |
xml.otrunk("id" => UUID.timestamp_create().to_s) { | |
xml.imports { | |
xml.import("class" => "org.concord.otrunk.OTStateRoot") | |
xml.import("class" => "org.concord.otrunk.user.OTUserObject") | |
xml.import("class" => "org.concord.otrunk.user.OTReferenceMap") | |
} | |
xml.objects { | |
xml.OTStateRoot("formatVersionString" => "1.0") { | |
xml.userMap { | |
userkey = self.uuid | |
xml.entry("key" => userkey) { | |
xml.OTReferenceMap { | |
xml.user { | |
xml.OTUserObject("name" => "#{self.member_names}", "id" => userkey) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
end | |
end | |
class Offering < ActiveRecord::Base | |
has_many :workgroups | |
has_many :bundles, :through => :workgroups | |
end | |
def find_parent(bundle, list) | |
# sort the list by sail_session_modified_time | |
sorted_list = list.sort {|a,b| a.sail_session_modified_time <=> b.sail_session_modified_time} | |
# find all bundles with a modified time earlier than the current bundle's start time | |
# this means the bundle should have existed in the SDS when the current bundle's session was launched | |
prior_bundles = sorted_list.select{|c| c.sail_session_modified_time < bundle.sail_session_start_time} | |
# return the most recent one | |
return prior_bundles[-1] | |
end | |
class App | |
VERSION = '0.0.1' | |
attr_reader :options | |
def initialize(arguments, stdin) | |
@arguments = arguments | |
@stdin = stdin | |
# Set defaults | |
@options = OpenStruct.new | |
@options.verbose = false | |
@options.quiet = false | |
# TO DO - add additional defaults | |
end | |
# Parse options, check arguments, then process the command | |
def run | |
if parsed_options? && arguments_valid? | |
output_options if @options.verbose # [Optional] | |
process_arguments | |
process_command | |
else | |
output_usage | |
end | |
end | |
protected | |
def parsed_options? | |
# Specify options | |
opts = OptionParser.new | |
opts.on('-v', '--version') { output_version ; exit 0 } | |
opts.on('-h', '--help') { output_help } | |
# opts.on('-V', '--verbose') { @options.verbose = true } | |
# opts.on('-q', '--quiet') { @options.quiet = true } | |
# TO DO - add additional options | |
opts.parse!(@arguments) rescue return false | |
process_options | |
true | |
end | |
# Performs post-parse processing on options | |
def process_options | |
# @options.verbose = false if @options.quiet | |
end | |
def output_options | |
puts "Options:\n" | |
@options.marshal_dump.each do |name, val| | |
puts " #{name} = #{val}" | |
end | |
end | |
# True if required arguments were provided | |
def arguments_valid? | |
# TO DO - implement your real logic here | |
true if @arguments.length == 1 | |
end | |
# Setup the arguments | |
def process_arguments | |
@workgroup = Workgroup.find(@arguments[0]); | |
@names = @workgroup.member_names | |
# @names = @names.join(', ') if @names.length > 1 | |
# TO DO - place in local vars, etc | |
end | |
def output_help | |
output_version | |
RDoc::usage() #exits app | |
end | |
def output_usage | |
RDoc::usage('usage') # gets usage from comments above | |
end | |
def output_version | |
puts "#{File.basename(__FILE__)} version #{VERSION}" | |
end | |
def process_command | |
nbundles = [] | |
# sort the bundles by sail_session_start_time | |
sorted = @workgroup.bundles.sort{|a,b| a.sail_session_start_time <=> b.sail_session_start_time} | |
# find each bundle's parent and then save the bundle into the nbundles list | |
sorted.each do |b| | |
b.sail_session_modified_time = b.calc_modified_time | |
b.parent = find_parent(b, nbundles) | |
nbundles << b | |
end | |
dotfile = '' | |
# print the digraph in DOT format | |
dotfile << "digraph graphname\n{\n" | |
wg_dir = "wg_#{@workgroup.id}" | |
FileUtils.mkdir(wg_dir) unless File.exists?(wg_dir) | |
Dir.chdir(wg_dir) do | |
nbundles.each do |b| | |
FileUtils.rm_f("#{b.id}") if File.exists?("#{b.id}") | |
File.open("#{b.id}.otml", 'w') {|f| f.write b.bundle_content.ot_learner_data } | |
if b.parent | |
dotfile << "\"#{b.parent.id} (#{b.parent.bundle_content.content.size})\" -> \"#{b.id} (#{b.bundle_content.content.size})\";\n" | |
end | |
end | |
end | |
dotfile << "labelloc=t;\n" | |
dotfile << "label=\"Offering: #{@workgroup.offering.name}; Workgroup: #{@workgroup.id}; #{@names}\";\n" | |
dotfile << "}" | |
puts dotfile | |
end | |
def process_standard_input | |
input = @stdin.read | |
# TO DO - process input | |
# [Optional] | |
#@stdin.each do |line| | |
# # TO DO - process each line | |
#end | |
end | |
end | |
# Create and run the application | |
app = App.new(ARGV, STDIN) | |
app.run | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment