Module: SequenceServer

Defined in:
lib/sequenceserver.rb,
lib/sequenceserver/job.rb,
lib/sequenceserver/sys.rb,
lib/sequenceserver/links.rb,
lib/sequenceserver/config.rb,
lib/sequenceserver/doctor.rb,
lib/sequenceserver/logger.rb,
lib/sequenceserver/report.rb,
lib/sequenceserver/routes.rb,
lib/sequenceserver/server.rb,
lib/sequenceserver/version.rb,
lib/sequenceserver/database.rb,
lib/sequenceserver/sequence.rb,
lib/sequenceserver/blast/hit.rb,
lib/sequenceserver/blast/hsp.rb,
lib/sequenceserver/blast/job.rb,
lib/sequenceserver/api_errors.rb,
lib/sequenceserver/exceptions.rb,
lib/sequenceserver/blast/error.rb,
lib/sequenceserver/blast/query.rb,
lib/sequenceserver/job_remover.rb,
lib/sequenceserver/makeblastdb.rb,
lib/sequenceserver/blast/report.rb,
lib/sequenceserver/blast/constants.rb,
lib/sequenceserver/blast/formatter.rb

Overview

Define constants used by SequenceServer module

Defined Under Namespace

Modules: BLAST, Links Classes: BLAST_DATABASE_ERROR, BLAST_NOT_COMPATIBLE, BLAST_NOT_INSTALLED_OR_NOT_EXECUTABLE, CONFIG_FILE_ERROR, CommandFailed, Config, DATABASE_DIR_NOT_SET, Database, DatabaseUnreachableError, Doctor, ENOENT, INCOMPATIBLE_BLAST_DATABASES, InputError, Job, JobRemover, Logger, MAKEBLASTDB, NO_BLAST_DATABASE_FOUND, NUM_THREADS_INCORRECT, NotFound, Report, Routes, Sequence, Server, SystemError

Constant Summary collapse

BLAST_VERSION =

The default version of BLAST that will be downloaded and configured for use.

'2.12.0+'.freeze
DEFAULT_CONFIG_FILE =

Default location of configuration file.

'~/.sequenceserver.conf'.freeze
DOTDIR =

Constant for denoting the path ~/.sequenceserver

File.expand_path('~/.sequenceserver').freeze
VERSION =
'3.0.1'.freeze
Error =
Class.new(Sinatra::Error)
APIError =

API errors have an http status, title, message, and additional information like stacktrace or information from program output.

Class.new(Error)

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.configObject (readonly)

Holds SequenceServer configuration object for this process. This is available only after calling SequenceServer.init.



110
111
112
# File 'lib/sequenceserver.rb', line 110

def config
  @config
end

Class Method Details

.call(env) ⇒ Object

Rack-interface.

Add our logger to Rack env and let Routes do the rest.



115
116
117
118
# File 'lib/sequenceserver.rb', line 115

def call(env)
  env['rack.logger'] = logger
  Routes.call(env)
end

.development?Boolean Also known as: verbose?

Returns true if RACK_ENV is set to ‘development’. Raw JS and CSS files are served in development mode and the logger is made more verbose.

Returns:

  • (Boolean)


43
44
45
# File 'lib/sequenceserver.rb', line 43

def development?
  environment == 'development'
end

.environmentObject

Returns ENV. This environment variable determines if we are in development on in production mode (default).



37
38
39
# File 'lib/sequenceserver.rb', line 37

def environment
  ENV['RACK_ENV']
end

.init(config = {}) ⇒ Object

SequenceServer initialisation routine.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/sequenceserver.rb', line 67

def init(config = {})
  # Reset makeblastdb cache, because configuration may have changed.
  @makeblastdb = nil

  # Use default config file if caller didn't specify one.
  config[:config_file] ||= DEFAULT_CONFIG_FILE

  # Initialise global configuration object from the above config hash.
  @config = Config.new(config)

  # When in development mode, cause SequenceServer to terminate if any
  # thread spawned by the main process raises an unhandled exception. In
  # production mode the expectation is to log at appropriate severity level
  # and continue operating.
  Thread.abort_on_exception = true if development?

  # Now locate binaries, scan databases directory, require any plugin files.
  load_extension
  init_binaries
  init_database

  # The above methods validate bin dir, database dir, and path to plugin
  # files. Port and host settings don't need to be validated: if running
  # in self-hosted mode, WEBrick will handle incorrect values and if
  # running via Apache+Passenger host and port settings are not used.
  # Let's validate remaining configuration keys next.

  # Validate number of threads to use with BLAST.
  check_num_threads

  # Doesn't make sense to activate JobRemover when testing. It anyway
  # keeps stumbling on the mock test jobs that miss a few keys.
  unless environment == 'test'
    @job_remover = JobRemover.new(@config[:job_lifetime])
  end

  # 'self' is the most meaningful object that can be returned by this
  # method.
  self
end

.irbObject

This method is invoked by the -i switch to start an IRB shell with SequenceServer loaded.



173
174
175
176
177
178
179
180
# File 'lib/sequenceserver.rb', line 173

def irb
  ARGV.clear
  require 'irb'
  IRB.setup nil
  IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
  require 'irb/ext/multi-irb'
  IRB.irb nil, self
end

.loggerObject

Logger object used in the initialisation routine and throughout the application.



50
51
52
53
54
55
56
57
58
59
# File 'lib/sequenceserver.rb', line 50

def logger
  @logger ||= case environment
              when 'development'
                Logger.new(STDERR, Logger::DEBUG)
              when 'test'
                Logger.new(STDERR, Logger::WARN)
              else
                Logger.new(STDERR, Logger::INFO)
              end
end

.makeblastdbObject

MAKEBLASTDB service object.



62
63
64
# File 'lib/sequenceserver.rb', line 62

def makeblastdb
  @makeblastdb ||= MAKEBLASTDB.new(config[:database_dir])
end

.on_startObject

This method is called after WEBrick has bound to the host and port and is ready to accept connections.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/sequenceserver.rb', line 138

def on_start
  puts '** SequenceServer is ready.'
  puts "   Go to #{server_url} in your browser and start BLASTing!"
  if ip_address
    puts '   To share your setup, try one of the following addresses. These'
    puts '   may only work within your home, office, or university network.'
    puts "     -  http://#{ip_address}:#{config[:port]}"
    puts "     -  http://#{hostname}:#{config[:port]}" if hostname
    puts '   To share your setup with anyone in the world, ask your IT team'
    puts '   for a public IP address or consider the SequenceServer cloud'
    puts '   hosting service: https://sequenceserver.com/cloud'
    puts '   To disable sharing, set :host: key in config file to 127.0.0.1'
    puts '   and restart server.'
  end
  puts '   To terminate server, press CTRL+C'
  open_in_browser(server_url)
end

.on_stopObject

This method is called when WEBrick is terminated.



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/sequenceserver.rb', line 157

def on_stop
  puts
  puts '** Thank you for using SequenceServer :).'
  puts '   Please cite: '
  puts '       Priyam A, Woodcroft BJ, Rai V, Moghul I, Munagala A, Ter F,'
  puts '       Chowdhary H, Pieniak I, Maynard LJ, Gibbins MA, Moon H,'
  puts '       Davis-Richardson A, Uludag M, Watson-Haigh N, Challis R,'
  puts '       Nakamura H, Favreau E, Gómez EA, Pluskal T, Leonard G,'
  puts '       Rumpf W & Wurm Y.'
  puts '       Sequenceserver: A modern graphical user interface for'
  puts '       custom BLAST databases.'
  puts '       Molecular Biology and Evolution (2019)'
end

.runObject

Run SequenceServer using WEBrick.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/sequenceserver.rb', line 121

def run
  Server.run(self)
rescue Errno::EADDRINUSE
  puts "** Could not bind to port #{config[:port]}."
  puts "   Is SequenceServer already accessible at #{server_url}?"
  puts '   No? Try running SequenceServer on another port, like so:'
  puts
  puts '       sequenceserver -p 4570.'
rescue Errno::EACCES
  puts "** Need root privilege to bind to port #{config[:port]}."
  puts '   It is not advisable to run SequenceServer as root.'
  puts '   Please use Apache/Nginx to bind to a privileged port.'
  puts '   Instructions available on http://sequenceserver.com.'
end

.sys(command, options = {}) ⇒ Object

‘sys’ executes a shell command.

‘sys’ can write the stdout and/or stderr from a shell command to files, or

return these values.

‘sys’ can get from a failed shell command stdout, stderr, and exit status.

Supply ‘sys’ with the shell command and optionally: dir: A directory to change to for the duration of the execution of the shell command. path: A directory to change the PATH environment variable to for the duration of the execution of the shell command. stdout: A path to a file to store stdout. stderr: A path to a file to store stderr.

Usage:

sys(command, dir: ‘/path/to/directory’, path: ‘/path/to/directory’,

stdout: '/path/to/stdout_file', stderr: '/path/to/stderr_file')

rubocop:disable Metrics/CyclomaticComplexity

Raises:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/sequenceserver/sys.rb', line 25

def self.sys(command, options = {})
  # Available output channels
  channels = i[stdout stderr]

  # Make temporary files to store output from stdout and stderr.
  temp_files = {
    stdout: Tempfile.new('sequenceserver-sys'),
    stderr: Tempfile.new('sequenceserver-sys')
  }

  # Log the command we are going to run - use -D option to view.
  logger.debug("Executing: #{command}")

  # Run command in a child process. This allows us to control PATH
  # and pwd of the running process.
  child_pid = fork do
    # Set the PATH environment variable to the binary directory or
    # safe directory.
    ENV['PATH'] = options[:path] if options[:path]

    # Change to the specified directory.
    Dir.chdir(options[:dir]) if options[:dir] && Dir.exist?(options[:dir])

    # Execute the shell command, redirect stdout and stderr to the
    # temporary files.
    exec(command, out: temp_files[:stdout].path.to_s, \
                  err: temp_files[:stderr].path.to_s)
  end

  # Wait for the termination of the child process.
  _, status = Process.wait2(child_pid)

  # If a full path was given for stdout and stderr files, move the
  # temporary files to this path. If the path given does not exist,
  # create it.
  channels.each do |channel|
    filename = options[channel]
    break unless filename

    # If the given path has a directory component, ensure it exists.
    file_dir = File.dirname(filename)
    FileUtils.mkdir_p(file_dir) unless File.directory?(file_dir)

    # Now move the temporary file to the given path.
    # TODO: don't we need to explicitly close the temp file here?
    FileUtils.cp(temp_files[channel], filename)
  end

  # Read the remaining temp files into memory. For large outputs,
  # the caller should supply a file path to prevent loading the
  # output in memory.
  temp_files.each do |channel, tempfile|
    temp_files[channel] = tempfile.read
  end

  # Finally, return contents of the remaining temp files if the
  # command completed successfully or raise CommandFailed error.
  return temp_files.values if status.success?
  raise CommandFailed.new(status.exitstatus, **temp_files)
end