Manav Sethi
Manav Sethi

Reputation: 33

ruby MS windows gem: win32/daemon: Service_Main thread exited abnormally

I am writing a ruby script to send files to a server as soon as they are added in a folder.

The script needs to be run as a service on a MS windows machine and uses win32/daemon gem to make it a service. But I am not able to start the script.

The filename is check.rb and, on running ruby check.rb via cmd (as Administrator) on Windows, I get this error:

Service_Main thread exited abnormally

Here are some screenshots of the run script and the error that I get:

enter image description here

error which I get

Here is the code file:

require 'rubygems'
require 'win32/daemon'
require "csv"
require "rest_client"
require "colorize"
require 'logger'

include Win32

def working_dir
  # '/home/manav/Desktop/work/tripbeam'
  'C:\\AIR'
end

def sub_dir_seperator
  # '/'
  '\\'
end

$log = Logger.new(working_dir + sub_dir_seperator + 'logs.txt')

def print_it(message)
  puts message
  $log.debug(message)
end

def get_files
  begin
      Dir.entries(working_dir).select { |f| !File.directory? f }.select { |f| f =~ /AIR\d{5}.Txt/  }.sort_by{|f| File.ctime(working_dir + sub_dir_seperator + f)}
  rescue Exception => e
      print_it "Error getting files in directory: #{working_dir}"
      print_it "Err class #{e.class}, message: #{e.message}"
  end
end

def add_record_to_csv_with_fail(filename)
  begin
    print_it "Adding #{filename} to records.csv"
    CSV.open(working_dir + sub_dir_seperator + "records.csv", "a") do |csv|
        created_time = File.ctime(working_dir + sub_dir_seperator + filename).to_s
        csv << [filename, created_time, "", "fail"]
    end
    print_it "Added #{filename} to records.csv with status fail"
  rescue => e
    print_it "Error adding #{filename} to records.csv"
    print_it "Err class #{e.class}, message: #{e.message}"
  end
end

def add_record_to_csv_with_success(filename)
  begin
    print_it "Adding #{filename} to records.csv"
    CSV.open(working_dir + sub_dir_seperator + "records.csv", "a") do |csv|
        created_time = File.ctime(working_dir + sub_dir_seperator + filename).to_s
        csv << [filename, created_time, "", "success"]
    end
    print_it "Added #{filename} to records.csv with success"
  rescue => e
    print_it "Error adding #{filename} to records.csv"
    print_it "Err class #{e.class}, message: #{e.message}"
  end
end

def send_file_to_server(url, params, headers, filename)
    print_it "sending #{filename} to server..."
    begin
        response = RestClient.post(url, params, headers)
        responsebody = eval(response.body)
    print_it "Response from Server: " + responsebody[:message]
    if responsebody[:message].eql?("Success")
      print_it "Sent file to server"
      add_record_to_csv_with_success(filename)
    else
      add_record_to_csv_with_fail(filename)
    end
    rescue Exception => e
        print_it "Can not send the file to server"
    print_it "Err class #{e.class}, message: #{e.message}"
    add_record_to_csv_with_fail(filename)
  ensure
    $number_of_files += 1
    end
end

def api_url
    'https://tripbeam.us/api/v1/files'
    # 'http://192.168.1.18:3002/api/v1/files'
end

def delete_files_from_records
  current_files = get_files
  records = CSV.read(working_dir + sub_dir_seperator + "records.csv", headers:true)
  print_it "Updating records.csv"
  records.each do |row|
      if !(current_files.include? row['name'])
          #update delete column
          row['deleted'] = Time.now.to_s
      end
  end

  #save record
  CSV.open(working_dir + sub_dir_seperator + "records.csv", "w") do |csv_out|
      csv_out << ["name", "created", "deleted", "sent_status"]
      records.each do |row|
          csv_out << row
      end
  end

  print_it "Updated records.csv"

  $number_of_files = get_files.length

end

def add_files_added_while_program_not_running
  current_files = get_files

  records = CSV.read(working_dir + sub_dir_seperator + 'records.csv', headers:true)

  recorded_files = records.by_col[0]

  added_files = current_files - recorded_files

  if added_files.length != 0
      print_it "#{added_files.length} file(s) were added while the program was shut down."
  end

  added_files.each do |added_file|
      file_to_send = File.new(working_dir + sub_dir_seperator + added_file)
      params = {:attachment => file_to_send}
      headers = {:"Accept" => 'application/vnd.tripbeam.v1'}
      send_file_to_server(api_url, params, headers, added_file)
  end

end

begin
    class DemoDaemon < Daemon

    def service_init
      print_it "Initializing background service..."
      10.times{ |i|
        print_it i.to_s
        sleep 1
      }
      print_it "Initialized background service..."
    end

    def service_main(*args)
      #before listen
      $number_of_files = get_files.length
      current_files = get_files
      records = CSV.read(working_dir + sub_dir_seperator + 'records.csv', headers:true)
      recorded_files_names = records.by_col[0]

      #check which files were added aftrt program shut down
      add_files_added_while_program_not_running()

      #Listen for new files
      $number_of_files = get_files.length

      print_it "Number of files in dir: #{working_dir} : #{$number_of_files}"

      # While we're in here the daemon is running.
      while running?
        if state == RUNNING
          #listen here....
          while true
            begin
              sleep(1)
              current_files = get_files

              if $number_of_files > current_files.length
                #file deleted
                number_of_files_deleted = $number_of_files - current_files.length
                print_it "#{number_of_files_deleted} file(s) deleted"

                #update records.csv
                delete_files_from_records

              elsif $number_of_files < current_files.length
                #file added
                number_of_files_added = current_files.length - $number_of_files
                print_it "#{number_of_files_added} file(s) added"

                #send to server
                files_added = current_files[($number_of_files - current_files.length)..current_files.length]

                files_added.each do |added_file|
                    file_to_send = File.new(working_dir + sub_dir_seperator + added_file)
                    params = {:attachment => file_to_send}
                    headers = {:"Accept" => 'application/vnd.tripbeam.v1'}
                    send_file_to_server(api_url, params, headers, added_file)
                end
              end
            rescue Exception => e
              print_it "Error while listening, #{e.class}, message: #{e.message}"
            end
          end

        else # PAUSED or IDLE
          print_it "Somebody has paused the service..."
          sleep 100
        end
      end

      msg = 'service_main left at: ' + Time.now.to_s
      print_it msg
    end

    def service_stop
      msg = 'Received stop signal at: ' + Time.now.to_s
      print_it msg
    end

    # This event triggers when the service receives a signal to pause.
    #
    def service_pause
      msg = 'Received pause signal at: ' + Time.now.to_s
      print_it msg
    end

    # This event triggers when the service receives a signal to resume
    # from a paused state.
    #
    def service_resume
      msg = 'Received resume signal at: ' + Time.now.to_s
      print_it msg
    end
  end

  # Create an instance of the Daemon and put it into a loop. I borrowed the
  # method name 'mainloop' from Tk, btw.
  #
  DemoDaemon.mainloop
rescue Exception => err
  print_it "service failure: #{err.class} .... #{err.message}"
  raise
end

Upvotes: 3

Views: 248

Answers (1)

MarkDBlackwell
MarkDBlackwell

Reputation: 2122

I am writing a ruby script to send files to a server as soon as they are added in a folder.

First of all, a loop in your code (in method service_main) re-reads the disk every second, 24/7. (That's all day, and all night—and on weekends, too!) That would wear out your client's disk, quickly and unexpectedly. :(

Ultimately, an alternative to that bad technique is to ask Windows' operating system services for a notification, whenever any file in the directory has been changed.

You can web-search the following:

directory notification windows ruby

That yields (for example) the program WatchDirectory, which can be run as a Windows service.

Using an off-the-shelf program is better, because your team would have less code to maintain.

WatchDirectory can invoke a custom batch file whenever anything changes in a target directory. "There is also a special plugin that can start a .bat file script giving you a 100% flexibility." So the batch file can start a Ruby program (which you can write). The Ruby program can compare the directory's contents and perform any other desired functionality.

Or WatchDirectory may already include plugins for everything you need (which would be better), without writing (and maintaining) any Ruby program. "Tasks are performed by running a plugin. An example of a task would be copying the new file to another directory." (From the help page.)

That's possible. Again, this way would be better. Remember, a line of code not written is a line of code that needn't be maintained.

For alternatives to using WatchDirectory, you can web-search the following:

watchdirectory alternatives

One result is a page on AlternativeTo's website: Alternatives to WatchDirectory (for Windows).

If you prefer to write your own code (creating a polylingual solution with Ruby and a bit of C++, rather than employing an off-the-shelf program, such as WatchDirectory), a link to some useful sample C++ code from Microsoft (which monitors folders) also resulted from the first web-search I mentioned above.

The MS windows machine is used by many people, so we dont want anyone to close the cmd terminal by mistake.

If you wish for any (non-Windows service) program always to be hidden from the taskbar (for any reason), you can web-search the following:

windows program hide

One result is Best Ways to Quickly Hide Windows Applications, which yields the configurable and apparently useful "Windows Hide Tool" and "ClickyGone."

Upvotes: 1

Related Questions