Sinatra not sending response once I add an extra method call - ruby

post '/payment/transactions/amount' do # => creates a new charge
content_type :json, :charset => 'utf-8'
class Transaction
def initialize(endUserId, code, referenceCode, callback=false)
#endUserId = endUserId
#code = code
#referenceCode = referenceCode
$callback = callback
$transId = rand(1000)
end
def transId
return $transId
end
def callback
return $callback
end
def postCallback
sleep(5)
RestClient.post"#{$callback}", :message => 'CALLBACK SUCCESS', :content_type => :json
end
def to_json(*a)
{"amountTransaction" => {
"endUserId" => #endUserId,
"paymentAmount" =>{
"chargingInformation" => {"code" => #code, "description" => #description},
"totalAmountCharged"=> "0"
},
"referenceCode" => #referenceCode,
"serverReferenceCode" =>"#{(0...6).map{65.+(rand(25)).chr}.join}", # just an example, can be any string
"resourceURL" => "http://localhost:4567/payment/#{#endUserId}/transactions/amount/#{Trans.transId}",
"transactionOperationStatus" => "Processing"
}}.to_json(*a)
end
end
Trans = Transaction.new(params[:endUserId],params[:code], params[:referenceCode], params[:callback])
jsonTrans = Trans.to_json
receipts.addReceipt(Trans.transId,jsonTrans)
jsonTrans
# fire callback
if Trans.callback then
sleep(10)
Trans.postCallback
end
end
Problem for me is the code following #fire callback. If I omit this if...then , the jsonTrans JSON object is returned as expected. When I include the if...then to fire the callback, then the desired Trans.postcallback occurs after 5 seconds - but the preceding jsonTrans is not returned, it is simply ignored. Any ideas on what I'm doing wrong? To confirm, the desired behaviour is to return the jsonTrans in the HTTP response and then call the postCallback method after 5 seconds (assuming the if evaluates to true). Cheers!
EDIT: Solved by spawning a new thread:
Thread.new{
if Trans.callback then
sleep(5)
Trans.postCallback
end}
jsonTrans

The last statement is what the method returns(outputs) so it ignores your jsonTrans in the output.
The first idea I have would be to reorder it:
#...
Trans = Transaction.new(params[:endUserId],params[:code], params[:referenceCode], params[:callback])
jsonTrans = Trans.to_json
receipts.addReceipt(Trans.transId,jsonTrans)
# fire callback
if Trans.callback then
sleep(10)
Trans.postCallback
end
jsonTrans
end
This should work if you don't expect any output from that RestClient.post-call.

Thanks for the suggestion daddz - actually I managed to solve it by wrapping by spawning a new thread: Thread.new{
if Trans.callback then
sleep(5)
Trans.postCallback
end}
jsonTrans

Related

puppet - unexpected result from 'each' in a custom function

I have a simple function which takes a JSON and 'does something' with it. The main part works good BUT the function returns not only what I want but additionally the result of .each loop!
The code:
module Puppet::Parser::Functions
newfunction(:mlh, :type => :rvalue) do |args|
lvm_default_hash = args[0]
lvm_additional_hash = args[1]
if lvm_additional_hash.keys.length == 1
if lvm_additional_hash.keys.include? 'logical_volumes'
# do stuff - we have only 'logical_volumes'
lvm_default_hash.keys.each do |key|
pv_array = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
lv_hash = lvm_default_hash[key]['logical_volumes']
new_lv_hash = lvm_additional_hash['logical_volumes']
merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]
# this is what I want to return to init.pp
puts Hash[key => pv_array.merge(merged_lv_hash)]
end
end
end
end
end
Variables in the init.pp are:
$default_volume_groups = {
'sys' => {
'physical_volumes' => [
'/dev/sda2',
],
'logical_volumes' => {
'root' => {'size' => '4G'},
'swap' => {'size' => '256M'},
'var' => {'size' => '8G'},
'docker' => {'size' => '16G'},
},
},
}
and the second argument from a hieradata:
modified_volume_groups:
logical_volumes:
cloud_log:
size: '16G'
In the init.pp I have something like this to test it:
notice(mlh($default_volume_groups, $modified_volume_groups))
which gives me a result:
syslogical_volumesvarsize8Gdockersize16Gcloud_logsize16Gswapsize256Mrootsize4Gphysical_volumes/dev/sda2
Notice: Scope(Class[Ops_lvm]): sys
The "long" part before the Notice is the proper result from the puts but the Notice: Scope(): sys is this what I do not want to!
I know that this is the result of this each loop over the default_volumes_groups:
lvm_default_hash.keys.each do |key|
# some stuff
end
How to block of this unwanted result? It blows my puppet's logic because my init.pp sees this sys and not what I want.
Does someone knows how to handle such problem?
Thank you!
I found how to handle this problem but maybe someone could explain me why it works in this way :)
This does not work (short version):
module Puppet::Parser::Functions
newfunction(:mlh, :type => :rvalue) do |args|
lvm_default_hash = args[0]
lvm_additional_hash = args[1]
if lvm_additional_hash.keys.length == 1
if lvm_additional_hash.keys.include? 'logical_volumes'
lvm_default_hash.keys.each do |key|
pv_array = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
lv_hash = lvm_default_hash[key]['logical_volumes']
new_lv_hash = lvm_additional_hash['logical_volumes']
merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]
puts Hash[key => pv_array.merge(merged_lv_hash)]
end
end
end
end
end
but this works:
module Puppet::Parser::Functions
newfunction(:mlh, :type => :rvalue) do |args|
lvm_default_hash = args[0]
lvm_additional_hash = args[1]
# empty Hash
hash_to_return = {}
if lvm_additional_hash.keys.length == 1
if lvm_additional_hash.keys.include? 'logical_volumes'
lvm_default_hash.keys.each do |key|
pv_array = Hash['physical_volumes' => lvm_default_hash[key]['physical_volumes']]
lv_hash = lvm_default_hash[key]['logical_volumes']
new_lv_hash = lvm_additional_hash['logical_volumes']
merged_lv_hash = Hash['logical_volumes' => lv_hash.merge(new_lv_hash)]
# assigned value in the 'each' loop we want to return to puppet
hash_to_return = Hash[key => pv_array.merge(merged_lv_hash)]
end
# returned Hash - instead of previous 'puts'
return hash_to_return
end
end
end
end
Now I have what I need!
Notice: Scope(Class[Ops_lvm]): sysphysical_volumes/de
You've got it -- the first one doesn't work because in Ruby, the return value of a block or function is the last evaluated statement. In the case of the one that didn't work, the last evaluated statement was the .each. As it turns out, each evaluates to the enumerable that it was looping through.
A simple example:
def foo
[1, 2, 3].each do |n|
puts n
end
end
If I were to run this, the return value of the function would be the array:
> foo
1
2
3
=> [1, 2, 3]
So what you have works, because the last thing evaluated is return hash_to_return. You could even just go hash_to_return and it'd work.
If you wanted to get rid of the return and clean that up a little bit (and if you're using Ruby 1.9 or above), you could replace your each line with:
lvm_default_hash.keys.each_with_object({}) do |key, hash_to_return|
This is because each_with_object evaluates to the "object" (in this case the empty hash passed into the method, and referred to as hash_to_return in the block params). If you do this you can remove the return as well as the initialization hash_to_return = {}.
Hope this helps!
Your custom function has rvalue type which means it needs to return value. If you don't specify return <something> by default, your last statement is implicitly your return.
In the example above, first one that does not work correctly, has last statement inside each block:
puts Hash[key => pv_array.merge(merged_lv_hash)]
Your second example is correct simply because you set value for hash_to_return in each block and then "return" it outside of each block. Not sure if this is the behavior you want since last assigned hash value (in last loop inside each block) will be the one that will be returned from this function.

forward email with attachments

I would like to do this without downloading the attachments and then re/attaching to the new email.
This is what i have tried:
$emailslist.each do |e|
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
subject "[Events] #{subjectstr}"
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
end
end
#error in 'e.attachments.each'=>undefined method `attachments' for
#<TypeError: can't convert nil into String>
EDIT
I have been using this code for months and it worked fine.
The new stuff i have introduced now is the code above.
Anyways I'm pasting the whole code upon request.
require 'mail'
$subscribers=[]
File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/subscribers2.txt",'r').each do |line|
line=line.sub("\n","")
$subscribers.push(line) if line =~ /#/
end
puts $subscribers
$errorfile=File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/error_log2.txt",'a+')
$errorfile.write("#{Time.now}\n")
$errorfile.flush
def deleteSubjectRecursion(subjstr)
if subjstr =~ /(.\[FORMA 2013\])+/
subjstr.gsub!(/.\[FORMA 2013\]/,"")
end
if subjstr =~ /((?i)Re: ){2,}/
subjstr.gsub!(/((?i)Re: ){2,}/,"Re: ")
end
return subjstr
end
def UserIsRegistered(mailaddr)
registered = false
$subscribers.each{|s| registered = true if mailaddr==s}
if registered == false
$errorfile.write("#{Time.now} : user #{mailaddr} attempted to mailman\n")
$errorfile.flush
end
return registered
end
Mail.defaults do
retriever_method :imap, { :address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false }
delivery_method :smtp, { :address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true }
end
#$emailslist=Mail.find(keys: ['NOT','SEEN'])
$emailslist=[Mail.last]
$emailslist.each do |e|
eplain_part = e.text_part ? e.text_part.body.decoded : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type=e.charset
type_plain=eplain_part ? e.text_part.charset.to_s : nil
type_html=ehtml_part ? e.html_part.charset.to_s : nil
bodystr= type ? e.body.decoded.to_s.force_encoding(type) : nil
type=type ? type.to_s : type_plain
puts type.inspect
subjectstr=e.subject.to_s.encode(type)
fromstr=e.from.first.to_s.encode(type)
puts fromstr
bodystr_plain=eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html=ehtml_part ? ehtml_part.force_encoding(type_html) : nil
$subscribers.each do |tostr|
puts tostr.inspect
if (not subjectstr =~ /^\[FORMA 2013\]/ ) && (UserIsRegistered(fromstr) == true)
subjectstr=deleteSubjectRecursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc tostr
subject "[FORMA 2013] #{subjectstr}"
if ehtml_part != nil
html_part do
content_type("text/html; charset=# {type_html}")
#content_transfer_encoding("7bit")
body "# {bodystr_html}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part != nil
text_part do
content_type("text/plain; charset=# {type_plain}")
#content_transfer_encoding("7bit")
body "#{bodystr_plain}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part == nil && ehtml_part == nil
body "#{bodystr}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
charset=type
end
#puts e.attachments.inspect
if e.attachments.length>0
e.attachments.each do |a|
add_file a.encoded
end
end
end
puts "1 email sent"
rescue => e
puts "error: #{e}"
$errorfile.write("#{Time.now}\nerror sending to #{tostr}: #{e},\nemail subject: #{subjectstr}\n\n")
$errorfile.flush()
end
end
end
end
$errorfile.close()
This is untested, and isn't really an attempt to find or fix the bug. It's to show how your code should look, written in more idiomatic Ruby code. And, as a result, it might fix the problem you're seeing. If not, at least you'll have a better idea how you should be writing your code:
require 'mail'
Define some constants for literal strings that get reused. Do this at the top so you don't have to search through the code to change things in multiple places, making it likely you'll miss one of them.
PATH_TO_FILES = "C:/Users/j.de_miguel/Desktop/mailman.forma"
BODY_BOILERPLATE_FORMAT = "%s\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
Group your methods toward the top of the file, after constants.
We open using 'a', not 'a+'. We don't need read/write, we only need write.
This opens and closes the file as necessary.
Closing the file automatically does a flush.
If you're calling the log method often then there are better ways to do this, but this isn't a heavyweight script.
I'm using File.join to build the filename based on the path. File.join is aware of the path separators and does the right thing automatically.
String.% makes it easy to create a standard output format.
def log(text)
File.open(File.join(PATH_TO_FILES, "error_log2.txt"), 'a') do |log_file|
log_file.puts "%s : %s" % [Time.now, text]
end
end
Method names in Ruby are snake_case, not CamelCase.
There's no reason to have multiple gsub! nor are the conditional tests necessary. If the sub-string you want to purge exists in the string gsub will do it, otherwise it moves on. Chaining the gsub methods reduces the code to one line.
gsub could/should probably be sub unless you know there could be multiple hits to be substituted in the string.
return is redundant so we don't use it unless we're explicitly returning a value to leave a block prematurely.
def delete_subject_recursion(subjstr)
subjstr.gsub(/.\[FORMA 2013\]/,"").gsub(/((?i)Re: ){2,}/, "Re: ")
end
Since registered is supposed to be a boolean, use any? to do the test. If any matches are found any? bails out and returns true.
def user_is_registered(mailaddr)
registered = subscribers.any?{ |s| mailaddr == s }
log("user #{ mailaddr } attempted to mailman") unless registered
registered
end
Use foreach to iterate over the lines of a file.
subscribers = []
File.foreach(File.join(PATH_TO_FILES, "subscribers2.txt")) do |line|
subscribers << line.chomp if line['#']
end
puts subscribers
log('')
Mail.defaults do
retriever_method(
:imap,
{
:address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false
}
)
delivery_method(
:smtp,
{
:address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true
}
)
end
#emailslist=Mail.find(keys: ['NOT','SEEN'])
emailslist = [Mail.last]
emailslist.each do |e|
This use of ternary statements here is probably not desirable but I left it.
Formatting into columns makes it easier to read.
Organize your assignments and uses so they're not strewn all through the file.
eplain_part = e.text_part ? e.text_part.body.decoded : nil
type_plain = eplain_part ? e.text_part.charset.to_s : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type_html = ehtml_part ? e.html_part.charset.to_s : nil
e_charset = e.charset
body_str = e_charset ? e.body.decoded.to_s.force_encoding(e_charset) : nil
e_charset = e_charset ? e_charset.to_s : type_plain
puts e_charset.inspect
subjectstr = e.subject.to_s.encode(e_charset)
fromstr = e.from.first.to_s.encode(e_charset)
puts fromstr
bodystr_plain = eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html = ehtml_part ? ehtml_part.force_encoding(type_html) : nil
subscribers.each do |subscriber|
puts subscriber.inspect
if !subjectstr[/^\[FORMA 2013\]/] && user_is_registered(fromstr)
subjectstr = delete_subject_recursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc subscriber
subject "[FORMA 2013] #{ subjectstr }"
if ehtml_part
html_part do
content_type("text/html; charset=#{ type_html }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_html
end
end
if eplain_part
text_part do
content_type("text/plain; charset=#{ type_plain }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_plain
end
end
if !eplain_part && !ehtml_part
body BODY_BOILERPLATE_FORMAT % body_str
charset = e_charset
end
#puts e.attachments.inspect
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
end
puts "1 email sent"
rescue => e
puts "error: #{ e }"
log("error sending to #{ subscriber }: #{ e },\nemail subject: #{ subjectstr }")
end
end
end
end
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
That can be refactored into a simple, single-line using a trailing conditional if test:
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
Using a single line like this is OK when you're doing something simple. Don't use them for more complex code because you'll induce visual noise, which makes it hard to understand and read your code.
But let's look at what the code above is actually doing. e.attachments in this context appears to be returning an array, or some sort of enumerable collection, otherwise each wouldn't work. length will tell us how many elements exist in the "array" (or whatever it is) that is returned by attachments.
If length is zero, then we don't want to do anything, so we could say:
e.attachments.each { |a| add_file a.encoded } unless e.attachments.empty?
(Assuming attachments implements an empty? method.)
That's kind of redundant too though. If e.attachments is empty already, what will each do? It would check to see if attachments returned an array containing any elements and if it's empty it'd skip its block entirely, effectively acting just like the trailing if condition was triggered. SOOOooo, we can use this instead:
e.attachments.each { |a| add_file a.encoded }
Ruby Style guides:
https://github.com/bbatsov/ruby-style-guide
https://github.com/styleguide/ruby
The second is based on the first.
The Tin Mans answer mostly works. I change how attachments were added since his version was not working for me.
e.attachments.each { |a| attachments[a.filename] = a.decoded } if e.attachments.length > 0

Ruby yaml custom domain type does not keep class

I'm trying to dump duration objects (from the ruby-duration gem) to yaml with a custom type, so they are represented in the form hh:mm:ss. I've tried to modify the answer from this question, but when parsing the yaml with YAML.load, a Fixnum is returned instead of a Duration. Interestingly, the Fixnum is the total number of seconds in the duration, so the parsing seems to work, but convert to Fixnum after that.
My code so far:
class Duration
def to_yaml_type
"!example.com,2012-06-28/duration"
end
def to_yaml(opts = {})
YAML.quick_emit( nil, opts ) { |out|
out.scalar( to_yaml_type, to_string_representation, :plain )
}
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML::add_domain_type("example.com,2012-06-28", "duration") do |type, val|
Duration.from_string_representation(val)
end
To clarify, what results I get:
irb> Duration.new(27500).to_yaml
=> "--- !example.com,2012-06-28/duration 7:38:20\n...\n"
irb> YAML.load(Duration.new(27500).to_yaml)
=> 27500
# should be <Duration:0xxxxxxx #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
It look like you’re using the older Syck interface, rather that the newer Psych. Rather than using to_yaml and YAML.quick_emit, you can use encode_with, and instead of add_domain_type use add_tag and init_with. (The documentation for this is pretty poor, the best I can offer is a link to the source).
class Duration
def to_yaml_type
"tag:example.com,2012-06-28/duration"
end
def encode_with coder
coder.represent_scalar to_yaml_type, to_string_representation
end
def init_with coder
split = coder.scalar.split ":"
initialize(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML.add_tag "tag:example.com,2012-06-28/duration", Duration
p s = YAML.dump(Duration.new(27500))
p YAML.load s
The output from this is:
"--- !<tag:example.com,2012-06-28/duration> 7:38:20\n...\n"
#<Duration:0x00000100e0e0d8 #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
(The reason the result you’re seeing is the total number of seconds in the Duration is because it is being parsed as sexagesimal integer.)

EventMachine EM::Iterator being blocked with rabbitmq RPC

I am trying to set up RabbitMQ rpc. I want one queue to listen, and when it receives a message I want it to reply to an anonymous queue that is specified via the reply_to header with multiple messages.
I have the following thor task creates a queue and then uses EM:Iterator to send a number of messages back to the queue specified with the replyt_to routing key:
desc "start_consumer", "start the test consumer"
def start_consumer
conf = {
:host => "localhost",
:user => "guest",
:password => "guest",
:vhost => "/",
:logging => true,
:port => 5672
}
# n = 1
AMQP.start(conf) do |connection|
channel = AMQP::Channel.new(connection)
requests_queue = channel.queue("one")
requests_queue.purge
Signal.trap("INT") do
connection.close do
EM.stop{exit}
end
end
channel.prefetch(1)
requests_queue.subscribe(:ack => true) do |header, body|
url_search = MultiJson.decode(body)
EM::Iterator.new(0..5).each do |n, iter|
lead = get_lead(n, (n == 5))
puts "about to publish #{n} message is_last = #{lead.is_last} at #{Time.now}"
AMQP::Exchange.default.publish(
MultiJson.encode(lead),
:immediate => true,
:routing_key => header.reply_to,
:correlation_id => header.correlation_id
)
iter.next
end
end
puts " [x] Awaiting RPC requests"
end
end
The code beloow sends a message to the queue specified above and also creates a queue that will be used to listen for the messages sent by the EM::Iterator code. This queue's name is the routing key for the first queues reply_to header.
def publish(urlSearch, routing_key)
EM.run do
corr_id = rand(10_000_000).to_s
requests ||= Hash.new
connection = AMQP.connect(:host => "localhost")
callback_queue = AMQP::Channel.new(connection).queue("", :exclusive => false)
callback_queue.subscribe do |header, body|
lead = safe_json_decode(body)
puts "company = #{lead["company"]} is_last = #{lead["is_last"]} received at #{Time.now}"
if lead["is_last"]
puts "in exit"
connection.close do
EM.stop{exit}
end
end
end
callback_queue.append_callback(:declare) do
AMQP::Exchange.default.publish(MultiJson.encode(urlSearch), :routing_key => routing_key, :reply_to => callback_queue.name, :correlation_id => corr_id)
end
puts "initial message sent"
end
end
The above code works as I want with one annoying exception. Something is blocking the EM::Iterator code from being executed asynchronously. It is only after the EM::Iterator code has completed that the messages are sent. I want the messages to be sent asynchronously and handled by the anonymous queue after each iteration. At the moment, it is only after the EM::Iterator code has completed its last iteration that all the messages are sent.
Can anyone see what I am doing wrong or suggest a different approach? I tried EM::defer and had the same behaviour.
Spinning up a new thread was the answer to my problem:
Thread.new do
5.times do
lead = get_lead(n, (n == 5))
puts "message #{n} is_last = #{lead.is_last} at #{Time.now}";
AMQP::Exchange.default.publish(
MultiJson.encode(lead),
:routing_key => header.reply_to,
:correlation_id => header.correlation_id
)
n += 1
sleep(2)
end
end
Creating a new thread stops the EventMachine reactor being blocked and the messages are sent async.

Strange behavior with instance variables in Shoes

Hey, all. I'm working on making a GUI for a Ruby project using Shoes.
I've got a class called Manager (as in memory manager) that loads a 'process list' from a file, splits it up and assigns things to different 'pages' in memory when a certain execution call is made. I really don't think this part matters too much, though. It all works as a terminal application just fine.
However, Shoes is just baffling me. Here's what I've got so far:
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
# #manager.set_exec_list filename
# alert "this makes no sense"
#exec_lines.text = #manager.exec_list.join "\n"
# exec_lines.text = File.read filename
}
}
end
end
What happens when I run this:
The program view loads as expected. I get a header, a paragraph that says "click button....", and a button. I click the button and I select the file. But this is where things get weird.
If I run the last commented line exec_lines.text = File.read filename it does as I would like, but my manager doesn't get any of the information it needs.
If I run the #manager.set_exec_list filename line, nothing from that line on in the block gets run, including the alert, or any other code I try to put in there.
if I run as shown above, however, I get the output I expect, but I don't get to set my data from the file that I select.
I've tried to figure this out from the Shoes Rules page, but this doesn't seem to be an issue that they address, and their "it changes/doesn't change self" I think I grasp, but it's confusing and I don't think it's exactly related to this problem.
Does anyone have any idea how to get this to work? I'm kind of down to crunch time on this project and I can't seem to get any other Ruby GUI toolkit to even run, so I think I'm pretty stuck with Shoes.
Thanks.
Update
I've tried running ruby-debug on the code when I make the call to #manager.set_exec_list filename, and stepping through it shows that this call is made, but the code never actually (from what I can tell) jumps into that method, and acts like it's the last line of code in the block. Do I need to include these classes inside the Shoes.app block?
Update Nope. That does nothing different.
update fullsource code follows:
#!/usr/bin/env shoes
require 'rubygems'
require 'ruby-debug'
class MemSegment
attr_accessor :filled, :pid, :seg, :seg_id
def initialize(filled=false, pid=nil, seg=nil, seg_id=0)
#filled = filled
#pid = pid.to_i
#seg = seg.to_s
#seg_id = seg_id.to_i
self
end
def fill(pid, seg, seg_id)
#filled = true; #pid = pid; #seg = seg; #seg_id = seg_id;
self
end
def clear
self.filled = false; self.pid = nil; self.seg = nil;
self
end
def filled?
#filled
end
def to_s
filled? ? "#{seg} #{seg_id} for pid #{pid}" : "Free"
end
end
class SimProc
include Enumerable
attr_accessor :pid, :code, :data
def initialize(pid, code, data)
#pid = pid.to_i
#code = code.to_i
#data = data.to_i
end
def each
yield :code, code
yield :data, data
end
def to_s
"[SimProc :pid => #{pid}, :code => #{code}, :data => #{data}]"
end
def to_a
[#pid, #code, #data]
end
end
class Manager
attr_reader :segments, :processes, :exec_list, :exec_object
def initialize
#exec_list = [[1, 2], [3, 4], [5, 6]]
#processes = {}
#segments = Array.new(8) { MemSegment.new }
end
def print_activity
#segments.each_with_index {|s, index| puts "Seg #{index} => #{s}" }
#processes.each_value {|s| puts s }
end
def load_process(pcb, exec_index)
if pcb.size == 3
p = SimProc.new(*pcb)
bad_load = false
#processes.store p.pid, p
#processes[p.pid].each do |proc_seg, bsize|
(bsize / 512.0).ceil.times do |seg_id|
#segments.each_with_index do |s, index|
if !s.filled
#find the first empty memory segment
s.fill p.pid, proc_seg, seg_id
break
# if all slots are filled and we couldn't place a proc block
elsif index == #segments.size - 1
bad_load = true
puts "Cannot find a place for #{proc_seg} segment of size #{bsize}. Requeueing..."
break;
end
end
break if bad_load
end
end
# recover pages and queue the process for later
if bad_load
#segments.each_with_index do |seg, seg_index|
# clear any segments that didn't get loaded properly
if seg.pid == p.pid
seg.clear
puts "Seg #{seg_index} => segment cleared: #{seg}"
end
end
# reinsert this process after the next in the execution list
# it will attempt to load and run after the next process is performed
#exec_list.insert(exec_index + 2, p.to_a)
end
print_activity
elsif pcb.size == 2 and pcb[1] == -1
# a process is exiting
puts "removing pid #{pcb[0]}"
#segments.each { |s| s.clear if s.pid == pcb[0] }
#processes.delete pcb[0]
print_activity
end
end
def set_exec_list(filename)
file = File.open filename
file.each { |pcb| #exec_list << pcb.split.map(&:to_i) } unless file.nil?
filename
end
def main
exseq = File.open('exseq2.txt')
set_exec_list exseq
# this is the object that will be used to run each process with .next
#exec_object = #exec_list.each_with_index
# #exec_list.each_with_index { |pcb, exec_index| load_process(pcb, exec_index) }
(#exec_list.size + 1).times do
load_process(*#exec_object.next)
end
end
end
=begin
manager = Manager.new
manager.main
=end
#=begin
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
debugger
#file_button.click {
filename = ask_open_file
#manager.set_exec_list filename
# alert "this makes no sense"
# #exec_lines.text = #manager.exec_list
# #exec_lines.text = File.read filename
#exec_lines.text = #manager.exec_list.join "\n"
}
}
end
end
#=end
So, a few things:
#1, I don't have the implementation of Manager, so I can't tell you why it breaks. Did you try checking the Shoes console for any errors? Hit control-/ to bring that up. If 'nothing runs after it hits that line,' that's probably the issue.
#2, this does work for me, as long as you change exec_lines to #exec_lines on the last line. Here's what I tried:
class Manager;end
Shoes.app(:title => "Paging Simulator", :width => 800, :height => 450) do
#manager = Manager.new
stack(:width => 200) do
#exec_list = stack {
title "Execution Queue", :size => 14
#exec_lines = para "click button to load", :size => 9
#file_button = button "Load Process List"
#file_button.click {
filename = ask_open_file
#alert "this makes no sense"
#exec_lines.text = File.read filename
}
}
end
end
Hope that helps!

Resources