MongoDB return codes meaning (ruby driver) - ruby

I'm calling collection update from ruby driver to mongodb and gets a return code 117.
How do I generally interpret the error codes that I get?

If you are using safe mode, the update method returns a hash containing the output of getLastError. However, when you are not using safe mode, we simply return the number of bytes that were sent to the server.
# setup connection & get handle to collection
connection = Mongo::Connection.new
collection = connection['test']['test']
# remove existing documents
collection.remove
=> true
# insert test document
collection.insert(:_id => 1, :a => 1)
=> 1
collection.find_one
=> {"_id"=>1, "a"=>1}
# we sent a message with 64 bytes to a mongod
collection.update({_id: 1},{a: 2.0})
=> 64 # number of bytes sent to server
# with safe mode we updated one document -- output of getLastError command
collection.update({_id: 1},{a: 3.0}, :safe => true)
=> {"updatedExisting"=>true, "n"=>1, "connectionId"=>19, "err"=>nil, "ok"=>1.0}
This is something that could be made clearer in the documentation. I will update it for the next ruby driver release.

Related

How to access data within variable? Error: undefined method

Printing the contents of a variable gives me a bunch of data.
I want to access part of that data, but get an error.
I'm using Viewpoint::EWS and am successfully accessing the data I need.
calendaritems = folder.find_items({:folder_id => folder.folder_id, :calendar_view => {:start_date => sd.rfc3339(), :end_date => ed.rfc3339()}})
calendaritems.each do |event|
...
end
Printing the variable "event", I can see the data I need: "date_time_stamp" (or "appointment_reply_time").
#<Viewpoint::EWS::Types::CalendarItem:0x00005652b332dfa0
#ews_item=
:date_time_stamp=>{:text=>"2019-03-18T12:01:49Z"},
:appointment_reply_time=>{:text=>"2019-03-18T13:01:55+01:00"},
However, trying to access using "event.date_time_stamp" (or "event.appointment_reply_time") leads to the error
undefined method `date_time_stamp' for <Viewpoint::EWS::Types::CalendarItem:0x00005622f83c3d38> (NoMethodError)
Here's the code:
calendaritems = folder.find_items({:folder_id => folder.folder_id, :calendar_view => {:start_date => sd.rfc3339(), :end_date => ed.rfc3339()}})
calendaritems.each do |event|
if event.recurring?
puts "#{event.date_time_stamp} | #{(event.start-event.date_time_stamp).to_i} | #{event.organizer.email_address}"
if (event.start-event.date_time_stamp).to_i == reminderDays
executeSomething()
end
end
end
I'm looking through recurring appointments for a resource within a week. Since those will be silently dropped after a year, the plan is to set up a system to remind people that this will happen, so they can rebook the resource.
At first I tried using the creation date of the appointment (event.date_time_created), which works as expected, but then noticed, that people can update their appointments, thus resetting the 1 year timer.
That's why I also need the date of the last update.
The debug output you supplied says that event variable has an attribute "ews_item" and then it has a hash with an attribute "date_time_stamp", so try event.ews_item[:date_time_stamp]

send email via telnet ruby script

I'm new with ruby and need to send an email via telnet using a relay host with no authentication. I can do it with a linux shell but I need to put it in a script so I can "simplify" its use, I know it's not the best way but I can't find other since the server where i'm working on it's severely restricted and limited.
require 'net/telnet.rb'
mail = Net::Telnet::new(
"Host" => "domain.ip", # default: "localhost"
"Port" => 25, # default: 23
"Output_log" => "output_log", # default: nil (no output)
"Dump_log" => "dump_log", # default: nil (no output)
"Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n
"Telnetmode" => true, # default: true
"Timeout" => 10, # default: 10
"Waittime" => 0, # default: 0
)
mail.cmd('helo MYDOMAIN'){ |c| print c }
mail.cmd('mail from: test#domain.com')
mail.cmd('rcpt to: test2#domain.com')
mail.cmd('data')
mail.cmd("subject: test cmd \n\n mensaje de prueba\n\n")
mail.cmd(".\n")
mail.close
I found the net/telnet.rb ruby class and this is my try... after mail.cmd('helo MYDOMAIN') I can't keep writing other commands, what I get is:
220 mail.server.com ESMTP
250 mail.server.com
After this I'm suposed to write mail from, etc. to create the mail. But I can't in the ruby script. I have try using:
mail.puts('mail from: test...')
mail.write('mail from: test...')
mail.print('mail from: test...')
mail.cmd('mail from: test...')
As written in documentation
Also I don't get the telnetmode(true|false) command maybe you could explain it to me please.
-- Edit --
Shell code trying to emulate:
telnet domain.ip 25
#=> Trying domain.ip...
#=> Connected to domain.ip.
#=> Escape character is '^]'.
#=> 220 mail.server.com ESMTP
helo MYDOMAIN
#=>250 mail.server.com
mail from:test#mydomain.com
#=> 250 2.1.0 Ok
rcpt to:test2#mydomain.com
#=> 250 2.1.0 Ok
data
#=> 354 End data with <CR><LF>.<CR><LF>
subject: test mail
test mail body
.
#=> 250 2.0.0 =k: queued as B6F08480D12
quit
#=> 221 2.0.0 Bye
#=> Connection closed by foreign host.
The telnet protocol is really, really rudimentary which is why the telnet command is useful for testing TCP/IP based services such as SMTP or HTTP. It does not mean those services actually use the telnet protocol, as they don't. They're conveniently plain-text in nature which means it's practical to use telnet for simple tests.
You should not be using the Telnet module for anything other than connecting to telnet services, though given it's 2017 it's unlikely you'll find any of those around.
You should be using something like Socket to connect. This can create a bare TCP/IP connection with full control over sending. As this is a wrapper around a regular POSIX filehandle you can use all the IO methods on it for reading, writing, and other control functions, like a proper socket shutdown.
Writing an SMTP adapter is not as easy as it seems, there's a lot of tricky things to tackle with regard to IO. You'll need to use IO.select to properly test for new data, plus that the socket is clear to write your email.
Here's a new stub:
require 'socket'
mail = TCPSocket.new("smtp.example.com", 25)
mail.write("HELO example.com\r\n")
Another note is that when you call require you should never specify the file extension. It's always handled for you.
Thanks to the help of the user ddubs how suggest the net\smtp gem (One that I didn't know) I was able to create a simple mail sender and using the mailfactory gem
Is it a strict requirement that you use telnet? Using ruby-doc.org/stdlib-2.0.0/libdoc/net/smtp/rdoc/Net/SMTP.html will turn your "difficult to maintain" script into something that is much easier to maintain. Even for someone who is completely new to Ruby. – ddubs
Here is the code sample
require 'net/smtp'
require 'mailfactory'
mail_body_HTML = '<h1> mail title</h1> your text in <b>HTML</b>'
mail_body_PLAIN = 'this is plain text'
mail_subject = 'test email'
mail_from = 'noreply#mydomain.com'
mail_to = 'user#otherdomain.com'
# mail_filePath = ''
mail = MailFactory.new()
mail.to = mail_to
mail.from = mail_from
mail.subject = mail_subject
mail.html = mail_body_HTML
# mail.text = mail_body_PLAIN
# mail.attach(mail_filePath)
relay_ip = x.x.x.x
Net::SMTP.start(relay_ip,25) do |smtp|
smtp.send_message(mail.to_s, mail_from, mail_to)
end

Ruby, Telnet, read multiline response without timeout

I need some hints/help, how can I read multiline response into variable.
My current command results me multiline response but after that I get timeout.
Here's how my connection is setup:
connection = Net::Telnet.new('Host' => host,'Port' => 4800, 'Telnetmode' => false, 'Timeout' => 1)
Here's my request and how I save it:
puts "Weather request\n"
connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"
And here's the error:
/usr/lib/ruby/1.9.1/net/telnet.rb:558:in `waitfor': timed out while waiting for more data (Timeout::Error)
from /usr/lib/ruby/1.9.1/net/telnet.rb:695:in `cmd'
from ruby_check.rb:37:in `<main>'
My response is multiple JSON lines, like this:
{"City":"Tallinn", "Degrees":"23"}
{"City":"Berlin", "Degrees":"23"}
{"City":"Helsinki", "Degrees":"23"}
{"City":"Stockholm", "Degrees":"23"}
Why the timeout?
The Net::Telnet documentation says:
For some protocols, it will be possible to specify the Prompt option once when you create the Telnet object and use cmd() calls; for others, you will have to specify the response sequence to look for as the Match option to every cmd() call, or call puts() and waitfor() directly; for yet others, you will have to use sysread() instead of waitfor() and parse server responses yourself.
This makes more sense when combined with the Net::Telnet#cmd method's documentation, which says that the method:
sends a string to the host, and reads in all received data until is sees the prompt or other matched sequence.
You're not specifying a custom Prompt or Match option, so #cmd is waiting for something from the server that matches the default Net::Telnet prompt (/[$%#>] \z/n) to indicate the end of the message.
If the message doesn't end with that kind of prompt, then it'll be waiting forever.
Possible solutions
Match the server's prompt
If the server does send some kind of prompt to indicate it's finished sending data and you should type the next command, you can pass a regular expression that matches it to the Net::Telnet initialiser. For example, if the server prompted you with command:, you could use:
connection = Net::Telnet.new(
"Prompt" => /command: \z/,
# …
)
Match the end of the response
If there's no prompt, but the response you're waiting for ends with a specific character sequence, you could explicitly specify the Match option when you call #cmd. For example, if your response was a single JSON array it would end with ], so you might be able to use this:
connection.cmd("String" => "{weather}", "Match" => "]") { |c| print c }
Give up on Net::Telnet and use a TCPSocket
If there's no prompt and no known ending, you could try to use the Net::Telnet object's underlying TCPSocket to read the data without using #cmd:
connection.puts("{weather}")
connection.sock.readline
At this point, there might not be much benefit to using Net::Telnet over a plain TCPSocket.
You are setting the timeout to one second and do not specify what str is. You can try increasing the timeout value or even setting it to false. Believieng it is the result from .cmd, try this:
connection = Net::Telnet.new(
"Host" => host, "Port" => 4800,
"Telnetmode" => false, "Timeout" => false)
puts "Weather request...\n"
str = connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"

pushing and pulling to rabbitmq json in ruby

I am trying to push a hash object to a rabbitmq .
this is how i am pushing:
msg[:gateway] = "gat1"
msg[:params] = {
:status => "2",
:token_id => "SADF3434FG2FG",
:mb_tranaction_id => "1235ABC",
:random_num => "#{2*rand(155)}"
}
AmqpClient.instance.publish("#{RABBIT_MQ_CONFIG['ipn_queue']}", msg.to_json)
on the output it looks like this :
{\"gateway\":\"gat1\",\"params\":{\"status\":\"2\",\"token_id\":\"SADF3434FG2FG\",\"mb_tranaction_id\":\"1235ABC\",\"random_num\":\"62\"}}
when I am pulling, i am trying to decode it back to a hash object like so :
msg = JSON.parse(msg)
p "worker: #{msg.inspect}"
and the output is :
"worker: {\"gateway\"=>\"skrill\", \"params\"=>{\"status\"=>\"2\", \"token_id\"=>\"SADF3434FG2FG\", \"mb_tranaction_id\"=>\"1235ABC\", \"random_num\"=>\"62\"}}"
but when I try to access one of it's values by the key (gateway) msg[:gateway]
I get a blank string...
In JSON format, keys are represented as strings - so msg[:gateway] won't work - but msg["gateway"] would.
Alternatively, you can use Rails's [with_indifferent_access][1] to be able to access the hash via symbols.

How do I block on reading a named pipe in Ruby?

I'm trying to set up a Ruby script that reads from a named pipe in a loop, blocking until input is available in the pipe.
I have a process that periodically puts debugging events into a named pipe:
# Open the logging pipe
log = File.open("log_pipe", "w+") #'log_pipe' created in shell using mkfifo
...
# An interesting event happens
log.puts "Interesting event #4291 occurred"
log.flush
...
I then want a separate process that will read from this pipe and print events to the console as they happen. I've tried using code like this:
input = File.open("log_pipe", "r+")
while true
puts input.gets #I expect this to block and wait for input
end
# Kill loop with ctrl+c when done
I want the input.gets to block, waiting patiently until new input arrives in the fifo; but instead it immediately reads nil and loops again, scrolling off the top of the console window.
Two things I've tried:
I've opened the input fifo with both "r" and "r+"--I have the same problem either way;
I've tried to determine if my writing process is sending EOF (which I've heard will cause the read fifo to close)--AFAIK it isn't.
SOME CONTEXT:
If it helps, here's a 'big picture' view of what I'm trying to do:
I'm working on a game that runs in RGSS, a Ruby based game engine. Since it doesn't have good integrated debugging, I want to set up a real-time log as the game runs--as events happen in the game, I want messages to show up in a console window on the side. I can send events in the Ruby game code to a named pipe using code similar to the writer code above; I'm now trying to set up a separate process that will wait for events to show up in the pipe and show them on the console as they arrive. I'm not even sure I need Ruby to do this, but it was the first solution I could think of.
Note that I'm using mkfifo from cygwin, which I happened to have installed anyway; I wonder if that might be the source of my trouble.
If it helps anyone, here's exactly what I see in irb with my 'reader' process:
irb(main):001:0> input = File.open("mypipe", "r")
=> #<File:mypipe>
irb(main):002:0> x = input.gets
=> nil
irb(main):003:0> x = input.gets
=> nil
I don't expect the input.gets at 002 and 003 to return immediately--I expect them to block.
I found a solution that avoids using Cygwin's unreliable named pipe implementation entirely. Windows has its own named pipe facility, and there is even a Ruby Gem called win32-pipe that uses it.
Unfortunately, there appears to be no way to use Ruby Gems in an RGSS script; but by dissecting the win32-pipe gem, I was able to incorporate the same idea into an RGSS game. This code is the bare minimum needed to log game events in real time to a back channel, but it can be very useful for deep debugging.
I added a new script page right before 'Main' and added this:
module PipeLogger
# -- Change THIS to change the name of the pipe!
PIPE_NAME = "RGSSPipe"
# Constant Defines
PIPE_DEFAULT_MODE = 0 # Pipe operation mode
PIPE_ACCESS_DUPLEX = 0x00000003 # Pipe open mode
PIPE_UNLIMITED_INSTANCES = 255 # Number of concurrent instances
PIPE_BUFFER_SIZE = 1024 # Size of I/O buffer (1K)
PIPE_TIMEOUT = 5000 # Wait time for buffer (5 secs)
INVALID_HANDLE_VALUE = 0xFFFFFFFF # Retval for bad pipe handle
#-----------------------------------------------------------------------
# make_APIs
#-----------------------------------------------------------------------
def self.make_APIs
$CreateNamedPipe = Win32API.new('kernel32', 'CreateNamedPipe', 'PLLLLLLL', 'L')
$FlushFileBuffers = Win32API.new('kernel32', 'FlushFileBuffers', 'L', 'B')
$DisconnectNamedPipe = Win32API.new('kernel32', 'DisconnectNamedPipe', 'L', 'B')
$WriteFile = Win32API.new('kernel32', 'WriteFile', 'LPLPP', 'B')
$CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'B')
end
#-----------------------------------------------------------------------
# setup_pipe
#-----------------------------------------------------------------------
def self.setup_pipe
make_APIs
##name = "\\\\.\\pipe\\" + PIPE_NAME
##pipe_mode = PIPE_DEFAULT_MODE
##open_mode = PIPE_ACCESS_DUPLEX
##pipe = nil
##buffer = 0.chr * PIPE_BUFFER_SIZE
##size = 0
##bytes = [0].pack('L')
##pipe = $CreateNamedPipe.call(
##name,
##open_mode,
##pipe_mode,
PIPE_UNLIMITED_INSTANCES,
PIPE_BUFFER_SIZE,
PIPE_BUFFER_SIZE,
PIPE_TIMEOUT,
0
)
if ##pipe == INVALID_HANDLE_VALUE
# If we could not open the pipe, notify the user
# and proceed quietly
print "WARNING -- Unable to create named pipe: " + PIPE_NAME
##pipe = nil
else
# Prompt the user to open the pipe
print "Please launch the RGSSMonitor.rb script"
end
end
#-----------------------------------------------------------------------
# write_to_pipe ('msg' must be a string)
#-----------------------------------------------------------------------
def self.write_to_pipe(msg)
if ##pipe
# Format data
##buffer = msg
##size = msg.size
$WriteFile.call(##pipe, ##buffer, ##buffer.size, ##bytes, 0)
end
end
#------------------------------------------------------------------------
# close_pipe
#------------------------------------------------------------------------
def self.close_pipe
if ##pipe
# Send kill message to RGSSMonitor
##buffer = "!!GAMEOVER!!"
##size = ##buffer.size
$WriteFile.call(##pipe, ##buffer, ##buffer.size, ##bytes, 0)
# Close down the pipe
$FlushFileBuffers.call(##pipe)
$DisconnectNamedPipe.call(##pipe)
$CloseHandle.call(##pipe)
##pipe = nil
end
end
end
To use this, you only need to make sure to call PipeLogger::setup_pipe before writing an event; and call PipeLogger::close_pipe before game exit. (I put the setup call at the start of 'Main', and add an ensure clause to call close_pipe.) After that, you can add a call to PipeLogger::write_to_pipe("msg") at any point in any script with any string for "msg" and write into the pipe.
I have tested this code with RPG Maker XP; it should also work with RPG Maker VX and later.
You will also need something to read FROM the pipe. There are any number of ways to do this, but a simple one is to use a standard Ruby installation, the win32-pipe Ruby Gem, and this script:
require 'rubygems'
require 'win32/pipe'
include Win32
# -- Change THIS to change the name of the pipe!
PIPE_NAME = "RGSSPipe"
Thread.new { loop { sleep 0.01 } } # Allow Ctrl+C
pipe = Pipe::Client.new(PIPE_NAME)
continue = true
while continue
msg = pipe.read.to_s
puts msg
continue = false if msg.chomp == "!!GAMEOVER!!"
end
I use Ruby 1.8.7 for Windows and the win32-pipe gem mentioned above (see here for a good reference on installing gems). Save the above as "RGSSMonitor.rb" and invoke it from the command line as ruby RGSSMonitor.rb.
CAVEATS:
The RGSS code listed above is fragile; in particular, it does not handle failure to open the named pipe. This is not usually an issue on your own development machine, but I would not recommend shipping this code.
I haven't tested it, but I suspect you'll have problems if you write a lot of things to the log without running a process to read the pipe (e.g. RGSSMonitor.rb). A Windows named pipe has a fixed size (I set it here to 1K), and by default writes will block once the pipe is filled (because no process is 'relieving the pressure' by reading from it). Unfortunately, the RPGXP engine will kill a Ruby script that has stopped running for 10 seconds. (I'm told that RPGVX has eliminated this watchdog function--in which case, the game will hang instead of abruptly terminating.)
What's probably happening is the writing process is exiting, and as there are no other writing processes, EOF is sent to the pipe which causes gets to return nil, and so your code loops continually.
To get around this you can usually just open the pipe read-write at the reader end. This works for me (on a Mac), but isn't working for you (you've tried "r" and "r+"). I'm guessing this is to due with Cygwin (POSIX says opening a FIFO read-write is undefined).
An alternative is to open the pipe twice, once read-only and once write-only. You don't use the write-only IO for anything, it's just so that there's always an active writer attached to the pipe so it doesn't get closed.
input = File.open("log_pipe", "r") # note 'r', not 'r+'
keep_open = File.open("log_pipe", "w") # ensure there's always a writer
while true
puts input.gets
end

Resources