sequenceserver 1.1.0.beta2 → 1.1.0.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sequenceserver might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -5
  3. data/README.md +3 -0
  4. data/lib/sequenceserver/api_errors.rb +67 -0
  5. data/lib/sequenceserver/blast/constants.rb +1 -1
  6. data/lib/sequenceserver/blast/formatter.rb +4 -2
  7. data/lib/sequenceserver/blast/job.rb +17 -62
  8. data/lib/sequenceserver/blast/report.rb +15 -2
  9. data/lib/sequenceserver/blast.rb +1 -0
  10. data/lib/sequenceserver/exceptions.rb +2 -3
  11. data/lib/sequenceserver/job.rb +72 -44
  12. data/lib/sequenceserver/routes.rb +29 -25
  13. data/lib/sequenceserver/version.rb +1 -1
  14. data/lib/sequenceserver.rb +32 -20
  15. data/public/js/errormodal.js +55 -0
  16. data/public/js/report.js +6 -2
  17. data/public/js/sequenceserver.js +0 -12
  18. data/public/sequenceserver-report.min.js +15 -15
  19. data/public/sequenceserver-search.min.js +3 -3
  20. data/sequenceserver.gemspec +2 -1
  21. data/spec/capybara_spec.rb +120 -21
  22. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhd +8 -0
  23. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhi +0 -0
  24. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nhr +0 -0
  25. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nin +0 -0
  26. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nog +0 -0
  27. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsd +16 -0
  28. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsi +0 -0
  29. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.fasta.nsq +0 -0
  30. data/spec/database/sample/genome/Solenopsis_invicta/Solenopsis_invicta_gnG_subset.txt +8 -0
  31. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phd +9140 -0
  32. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phi +0 -0
  33. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.phr +0 -0
  34. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pin +0 -0
  35. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.pog +0 -0
  36. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psd +18280 -0
  37. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psi +0 -0
  38. data/spec/database/sample/proteins/uniprot/2018-04-Swiss-Prot_insecta.fasta.psq +0 -0
  39. data/spec/database/sample/proteins/uniprot/URL +1 -0
  40. data/spec/nucleotide_query.fa +21 -0
  41. data/spec/protein_query.fa +21 -0
  42. data/spec/routes_spec.rb +2 -0
  43. data/spec/sample_reports/blastn_sample/job.yaml +1 -0
  44. data/spec/sample_reports/blastn_sample/{rfile → stdout} +0 -0
  45. data/spec/sample_reports/blastp_sample/job.yaml +1 -0
  46. data/spec/sample_reports/blastp_sample/{rfile → stdout} +0 -0
  47. data/spec/sample_reports/blastx_sample/job.yaml +1 -0
  48. data/spec/sample_reports/blastx_sample/{rfile → stdout} +0 -0
  49. data/spec/sample_reports/no_hits_sample/job.yaml +1 -0
  50. data/spec/sample_reports/no_hits_sample/{rfile → stdout} +0 -0
  51. data/spec/sample_reports/tblastn_sample/job.yaml +1 -0
  52. data/spec/sample_reports/tblastn_sample/{rfile → stdout} +0 -0
  53. data/spec/sample_reports/tblastx_sample/job.yaml +1 -0
  54. data/spec/sample_reports/tblastx_sample/{rfile → stdout} +0 -0
  55. data/spec/sample_reports/with_hits_sample/job.yaml +1 -0
  56. data/spec/sample_reports/with_hits_sample/{rfile → stdout} +0 -0
  57. data/spec/sequenceserver_spec.rb +1 -1
  58. data/spec/spec_helper.rb +2 -16
  59. data/views/layout.erb +0 -45
  60. metadata +55 -16
  61. data/lib/sequenceserver/blast/exceptions.rb +0 -27
  62. data/views/400.erb +0 -29
  63. data/views/500.erb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cccfc5a0df6982dde1187b7f12d043092d6b72c4
4
- data.tar.gz: ce2dbf97a61770f721fd3501e150817e4e33f095
3
+ metadata.gz: 2e60bdefc921484208426a507ff32229e5945b1b
4
+ data.tar.gz: 85a162161cae976cc790ae3fed7b06ab81f11786
5
5
  SHA512:
6
- metadata.gz: 7c75136b0c361e18f5d5d6dedfd4ad3aa8a730d3002f7be1823a47e7c6e6c0de1451116d6369af6c6cb5431e17d30cf64e2049960449cfab31d2566d1c0a7228
7
- data.tar.gz: 4871277a05d19b600fc2611958623aa38b6328a1e271917aeedf840a0901550f2f76f86e6f59ee31793e95a946ab2ba092308622c8e30290b7065f2913719ccf
6
+ metadata.gz: c4511c2651001127d25f0b319984ed4cbc5b4e4d6dccde42823b1c33fd14a99e86045d8416da1d8e514e6825df44933359d32eafe1167e0bdb30243d2017a295
7
+ data.tar.gz: 3917c29dfb8be319b92029295f3996338195aa3bab09a176c625b22e1f7b7fe56588aedf8d16b8724aa07acdfbe35c1c3982a3f2a3bbf47d165745d11cef4523
data/.travis.yml CHANGED
@@ -6,7 +6,7 @@ rvm:
6
6
  - 2.5
7
7
 
8
8
  addons:
9
- firefox: "58.0"
9
+ sauce_connect: true
10
10
 
11
11
  branches:
12
12
  only:
@@ -14,15 +14,26 @@ branches:
14
14
  - master
15
15
 
16
16
  install:
17
- - wget -c "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz"
18
- - mkdir geckodriver && tar xvf geckodriver-*.tar.gz -C geckodriver
19
- - export PATH="$PWD/geckodriver:$PATH"
20
17
  - wget -c "ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/2.6.0/ncbi-blast-2.6.0+-x64-linux.tar.gz"
21
18
  - tar xvf ncbi-blast-*.tar.gz
22
19
  - gem install bundler && bundle
23
20
  - bundle exec bin/sequenceserver -s -b ncbi-blast-2.6.0+/bin -d spec/database/sample
24
21
 
25
- script: xvfb-run bundle exec rspec
22
+ before_script:
23
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter && chmod +x ./cc-test-reporter
24
+ - ./cc-test-reporter before-build
25
+ script:
26
+ - bundle exec rspec --exclude-pattern 'capybara*' # unit tests
27
+ - platform='macOS 10.13' browserName=safari browserVersion=11 bundle exec rspec spec/capybara_spec.rb
28
+ - platform='macOS 10.13' browserName=chrome browserVersion=65 bundle exec rspec spec/capybara_spec.rb
29
+ - platform='macOS 10.13' browserName=firefox browserVersion=59 bundle exec rspec spec/capybara_spec.rb
30
+ - platform='Windows 10' browserName=microsoftedge browserVersion='16.16299' bundle exec rspec spec/capybara_spec.rb
31
+ - platform='Windows 10' browserName=chrome browserVersion=65 bundle exec rspec spec/capybara_spec.rb
32
+ - platform='Windows 10' browserName=firefox browserVersion=59 bundle exec rspec spec/capybara_spec.rb
33
+ - platform='Linux' browserName=chrome browserVersion=48 bundle exec rspec spec/capybara_spec.rb
34
+ - platform='Linux' browserName=firefox browserVersion=45 bundle exec rspec spec/capybara_spec.rb
35
+ after_script:
36
+ - ./cc-test-reporter after-build # --exit-code $TRAVIS_TEST_RESULT
26
37
 
27
38
  cache:
28
39
  directories:
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![build status](https://secure.travis-ci.org/wurmlab/sequenceserver.png)](https://travis-ci.org/wurmlab/sequenceserver)
2
+ [![Sauce test status](https://saucelabs.com/buildstatus/yeban)](https://saucelabs.com/u/yeban)
2
3
  [![code climate](https://codeclimate.com/github/wurmlab/sequenceserver/badges/gpa.svg)](https://codeclimate.com/github/wurmlab/sequenceserver)
3
4
  [![coverage](https://codeclimate.com/github/wurmlab/sequenceserver/badges/coverage.svg)](https://codeclimate.com/github/wurmlab/sequenceserver)
4
5
  [![gem version](https://badge.fury.io/rb/sequenceserver.svg)](http://rubygems.org/gems/sequenceserver)
@@ -6,6 +7,8 @@
6
7
 
7
8
  [![gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/wurmlab/sequenceserver)
8
9
 
10
+ [![browser matrix](https://saucelabs.com/browser-matrix/yeban.svg)](https://saucelabs.com/u/yeban)
11
+
9
12
  # SequenceServer - BLAST searching made easy!
10
13
 
11
14
  SequenceServer lets you rapidly set up a BLAST+ server with an intuitive user
@@ -0,0 +1,67 @@
1
+ module SequenceServer
2
+
3
+ # API errors have an http status, title, message, and additional information
4
+ # like stacktrace or information from program output.
5
+ class APIError < StandardError
6
+ end
7
+
8
+ # Job not found (404).
9
+ class NotFound < APIError
10
+ def http_status
11
+ 404
12
+ end
13
+
14
+ def title
15
+ 'Job not found'
16
+ end
17
+
18
+ def message
19
+ 'The requested job could not be found'
20
+ end
21
+ end
22
+
23
+ # Errors caused due to incorrect user input.
24
+ class InputError < APIError
25
+ def initialize(more_info)
26
+ @more_info = more_info
27
+ super
28
+ end
29
+
30
+ def http_status
31
+ 400
32
+ end
33
+
34
+ def title
35
+ 'Input error'
36
+ end
37
+
38
+ def message
39
+ <<MSG
40
+ Looks like there's a problem with one of the query sequences, selected
41
+ databases, or advanced parameters.
42
+ MSG
43
+ end
44
+
45
+ attr_reader :more_info
46
+ end
47
+
48
+ # Errors caused by everything other than invalid user input.
49
+ class SystemError < APIError
50
+ def http_status
51
+ 500
52
+ end
53
+
54
+ def title
55
+ 'Job failed'
56
+ end
57
+
58
+ def message
59
+ <<MSG
60
+ Sorry BLAST failed - please try again. If this message persists, there is a
61
+ problem with the server. In this case, please report the bug on our
62
+ <a href="https://github.com/wurmlab/sequenceserver/issues" target="_blank">
63
+ issue tracker</a>.
64
+ MSG
65
+ end
66
+ end
67
+ end
@@ -2,7 +2,7 @@
2
2
  module SequenceServer
3
3
  # Define constanst used by BLAST module.
4
4
  module BLAST
5
- ERROR_LINE = /\(CArgException.*\)\s(.*)/
5
+ ERROR_LINE = /Error:\s(.*)/
6
6
 
7
7
  ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
8
8
 
@@ -24,7 +24,7 @@ module SequenceServer
24
24
  attr_reader :format, :mime, :specifiers
25
25
 
26
26
  def file
27
- @file = File.join(File.dirname(archive_file), filename)
27
+ @file ||= File.join(File.dirname(archive_file), filename)
28
28
  end
29
29
 
30
30
  def filename
@@ -41,12 +41,14 @@ module SequenceServer
41
41
  " -outfmt '#{format} #{specifiers}'" \
42
42
  " -out '#{file}'"
43
43
  sys(command, path: config[:bin], dir: DOTDIR)
44
+ rescue CommandFailed => e
45
+ fail SystemError, e.stderr
44
46
  end
45
47
 
46
48
  def validate
47
49
  return true if archive_file && format &&
48
50
  File.exist?(archive_file)
49
- fail ArgumentError, <<MSG
51
+ fail InputError, <<MSG
50
52
  Incorrect request parameters. Please ensure that requested file name is
51
53
  correct and the file type is either xml or tsv.
52
54
  MSG
@@ -1,17 +1,10 @@
1
- require 'sequenceserver/pool'
2
1
  require 'sequenceserver/job'
3
2
 
4
- require_relative 'exceptions'
5
- require_relative 'constants'
6
-
7
3
  module SequenceServer
8
4
 
9
5
  # BLAST module.
10
6
  module BLAST
11
7
  # Extends SequenceServer::Job to describe a BLAST job.
12
- #
13
- # `BLAST::ArgumentError` and `BLAST::RuntimeError` signal errors
14
- # encountered when attempting a BLAST search.
15
8
  class Job < Job
16
9
 
17
10
  def initialize(params)
@@ -25,60 +18,23 @@ module SequenceServer
25
18
  end
26
19
  end
27
20
 
28
-
29
21
  attr_reader :advanced_params
22
+
30
23
  # :nodoc:
31
- # Attributes used by us.
32
- #
33
- # Should be considered private.
24
+ # Attributes used by us - should be considered private.
34
25
  attr_reader :method, :qfile, :databases, :options
35
26
 
36
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
37
- # rubocop:disable Metrics/MethodLength
38
- def run
39
- command = "#{method} -db '#{databases.map(&:name).join(' ')}'" \
40
- " -query '#{qfile}' #{options}"
41
-
42
- sys(command, stdout: rfile, stderr: efile, path: config[:bin])
43
- done!
44
- rescue CommandFailed => e
45
- # Capture error.
46
- status = e.exitstatus
47
- case status
48
- when 1 # error in query sequence or options; see [1]
49
- efile.open
50
-
51
- # Most of the time BLAST+ generates a verbose error message with
52
- # details we don't require. So we parse out the relevant lines.
53
- error = efile.each_line do |l|
54
- break Regexp.last_match[1] if l.match(ERROR_LINE)
55
- end
56
-
57
- # But sometimes BLAST+ returns the exact/ relevant error message.
58
- # Trying to parse such messages returns nil, and we use the error
59
- # message from BLAST+ as it is.
60
- error = efile.rewind && efile.read unless error.is_a? String
61
-
62
- efile.close
63
- fail ArgumentError, error
64
- when 2, 3, 4, 255 # see [1]
65
- efile.open
66
- error = efile.read
67
- efile.close
68
- fail RuntimeError.new(status, error)
69
- end
70
-
71
- success!
72
- end
73
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
74
- # rubocop:enable Metrics/MethodLength
75
-
76
- def rfile
77
- File.join(dir, 'rfile')
27
+ # Returns the command that will be executed. Job super class takes care
28
+ # of actual execution.
29
+ def command
30
+ @command ||= "#{method} -db '#{databases.map(&:name).join(' ')}'" \
31
+ " -query '#{qfile}' #{options}"
78
32
  end
79
33
 
80
- def efile
81
- File.join(dir, 'efile')
34
+ # BLAST's exit status is not definitive of success or error, so we
35
+ # override success? to define custom criteria. :TODO:
36
+ def success?
37
+ super
82
38
  end
83
39
 
84
40
  private
@@ -114,38 +70,37 @@ module SequenceServer
114
70
 
115
71
  def validate_method(method)
116
72
  return true if ALGORITHMS.include? method
117
- fail ArgumentError, 'BLAST algorithm should be one of:' \
73
+ fail InputError, 'BLAST algorithm should be one of:' \
118
74
  " #{ALGORITHMS.join(', ')}."
119
75
  end
120
76
 
121
77
  def validate_sequences(sequences)
122
78
  return true if sequences.is_a?(String) && !sequences.empty?
123
- fail ArgumentError, 'Sequences should be a non-empty string.'
79
+ fail InputError, 'Sequences should be a non-empty string.'
124
80
  end
125
81
 
126
82
  def validate_databases(database_ids)
127
83
  ids = Database.ids
128
84
  return true if database_ids.is_a?(Array) && !database_ids.empty? &&
129
85
  (ids & database_ids).length == database_ids.length
130
- fail ArgumentError, 'Database id should be one of:' \
131
- " #{ids.join("\n")}."
86
+ fail InputError, "Database id should be one of: #{ids.join("\n")}."
132
87
  end
133
88
 
134
89
  # Advanced options are specified by the user. Here they are checked for
135
90
  # interference with SequenceServer operations.
136
91
  #
137
- # Raise ArgumentError if an error has occurred.
92
+ # Raise InputError if an error has occurred.
138
93
  def validate_options(options)
139
94
  return true if !options || (options.is_a?(String) &&
140
95
  options.strip.empty?)
141
96
 
142
97
  unless allowed_chars.match(options)
143
- fail ArgumentError, 'Invalid characters detected in options.'
98
+ fail InputError, 'Invalid characters detected in options.'
144
99
  end
145
100
 
146
101
  if disallowed_options.match(options)
147
102
  failedopt = Regexp.last_match[0]
148
- fail ArgumentError, "Option \"#{failedopt}\" is prohibited."
103
+ fail InputError, "Option \"#{failedopt}\" is prohibited."
149
104
  end
150
105
 
151
106
  true
@@ -43,14 +43,27 @@ module SequenceServer
43
43
 
44
44
  # Generate report.
45
45
  def generate
46
- xml_ir = parse_xml File.read(Formatter.run(job.rfile, 'xml').file)
47
- tsv_ir = parse_tsv File.read(Formatter.run(job.rfile, 'custom_tsv').file)
46
+ assert_job_completed_successfully
47
+ xml_ir = parse_xml File.read(Formatter.run(job.stdout, 'xml').file)
48
+ tsv_ir = parse_tsv File.read(Formatter.run(job.stdout, 'custom_tsv').file)
48
49
  extract_program_info xml_ir
49
50
  extract_params xml_ir
50
51
  extract_stats xml_ir
51
52
  extract_queries xml_ir, tsv_ir
52
53
  end
53
54
 
55
+ def assert_job_completed_successfully
56
+ return true if job.success?
57
+
58
+ stderr = File.readlines(job.stderr)
59
+ if job.exitstatus == 1 # error in query sequence or options; see [1]
60
+ error = stderr.grep(ERROR_LINE)
61
+ error = stderr if error.empty?
62
+ fail InputError, error.join
63
+ end
64
+ fail SystemError, stderr.join
65
+ end
66
+
54
67
  # Make program name and program name + version available via `program`
55
68
  # and `program_version` attributes.
56
69
  def extract_program_info(ir)
@@ -1,2 +1,3 @@
1
1
  require_relative 'blast/job'
2
2
  require_relative 'blast/report'
3
+ require_relative 'blast/constants'
@@ -148,13 +148,12 @@ MSG
148
148
  # Raised if the 'sys' method could not successfully execute a shell command.
149
149
  class CommandFailed < StandardError
150
150
 
151
- def initialize(stdout, stderr, exitstatus)
151
+ def initialize(exitstatus, stdout: nil, stderr: nil)
152
+ @exitstatus = exitstatus
152
153
  @stdout = stdout
153
154
  @stderr = stderr
154
- @exitstatus = exitstatus
155
155
  end
156
156
 
157
157
  attr_reader :stdout, :stderr, :exitstatus
158
-
159
158
  end
160
159
  end
@@ -5,44 +5,46 @@ require 'digest/md5'
5
5
  require 'forwardable'
6
6
  require 'securerandom'
7
7
 
8
+ require 'sequenceserver/pool'
9
+ require 'sequenceserver/api_errors'
10
+
8
11
  module SequenceServer
9
- # Job super class.
12
+ # Abstract job super class.
13
+ #
14
+ # Provides a simple framework to store job data, execute shell commands
15
+ # asynchronously and capture stdout, stderr and exit status. Subclasses
16
+ # must provide a concrete implementation for `command` and may override
17
+ # any other methods as required.
10
18
  #
11
- # Provides access to global `config` and `logger` object as instance methods.
12
- # Sub-classes must at least define `run` instance method.
19
+ # Global `config` and `logger` object are available as instance methods.
20
+ #
21
+ # Singleton methods provide the facility to create and queue a job,
22
+ # fetch a job or all jobs, and delete a job.
13
23
  class Job
14
- UUID_PATTERN = /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/
15
-
16
24
  class << self
17
25
  # Creates and queues a job. Returns created job object.
18
- #
19
- # TODO: Implement dynamic dispatch.
20
26
  def create(params)
21
- queue BLAST::Job.new(params)
27
+ job = BLAST::Job.new(params)# TODO: Dynamic dispatch.
28
+ SequenceServer.pool.queue { job.run }
29
+ job
22
30
  end
23
31
 
24
- # Fetches job object for the given id.
25
- #
26
- # TODO: What if the given job id does not exist?
32
+ # Fetches job with the given id.
27
33
  def fetch(id)
28
- YAML.load_file(File.join(DOTDIR, id, 'job.yaml'))
29
- end
30
-
31
- def all
32
- jobdirs = Dir["#{DOTDIR}/*"].select{ |f| f =~ UUID_PATTERN }
33
- jobdirs.map{ |fname| fetch File.basename(fname) }
34
+ job_file = File.join(DOTDIR, id, 'job.yaml')
35
+ fail NotFound unless File.exist?(job_file)
36
+ YAML.load_file(job_file)
34
37
  end
35
38
 
39
+ # Deletes job with the given id.
36
40
  def delete(id)
37
41
  FileUtils.rm_r File.join(DOTDIR, id)
38
42
  end
39
43
 
40
- private
41
-
42
- # Queues given job on the thread pool. Returns job.
43
- def queue(job)
44
- SequenceServer.pool.queue { job.run }
45
- job
44
+ # Returns an Array of all jobs.
45
+ def all
46
+ Dir["#{DOTDIR}/**/job.yaml"].
47
+ map { |f| fetch File.basename File.dirname f }
46
48
  end
47
49
  end
48
50
 
@@ -66,32 +68,49 @@ module SequenceServer
66
68
  raise e
67
69
  end
68
70
 
69
- attr_reader :id, :completed_at
71
+ attr_reader :id, :completed_at, :exitstatus
70
72
 
71
- # How to execute the job.
73
+ # Shell out and execute the job.
72
74
  #
73
- # Subclasses should provide their own implementation. The method should
74
- # call `done!` and `success!`, as appropriate, to indicate if the job is
75
- # done and whether it was successful.
75
+ # NOTE: This method is called asynchronously by thread pool.
76
76
  def run
77
- raise "To be implemented."
77
+ sys(command, path: config[:bin], stdout: stdout, stderr: stderr)
78
+ done!
79
+ rescue CommandFailed => e
80
+ done! e.exitstatus
78
81
  end
79
82
 
80
- # Is the job done?
81
- def done?
82
- !!@done
83
+ # Returns shell command that will be executed. Subclass needs to provide a
84
+ # concrete implementation.
85
+ def command
86
+ raise "Not implemented."
83
87
  end
84
88
 
85
- # Was the job success?
89
+ # Where will the stdout be written to during execution and read from later.
90
+ def stdout
91
+ File.join(dir, 'stdout')
92
+ end
93
+
94
+ # Where will the stderr be written to during execution and read from later.
95
+ def stderr
96
+ File.join(dir, 'stderr')
97
+ end
98
+
99
+ # Was the job successful?
86
100
  def success?
87
- !!@success
101
+ exitstatus == 0
102
+ end
103
+
104
+ # Is the job done? Yes if exitstatus of the job is available. No otherwise.
105
+ def done?
106
+ !!@exitstatus
88
107
  end
89
108
 
90
109
  private
91
110
 
92
- # Save job state.
111
+ # Saves job object to a YAML file in job directory.
93
112
  def save
94
- File.write(File.join(dir, 'job.yaml'), to_yaml)
113
+ File.write(yfile, to_yaml)
95
114
  end
96
115
 
97
116
  # Save arbitrary blob of data for this job to a file. Returns absolute path
@@ -108,16 +127,20 @@ module SequenceServer
108
127
  filename
109
128
  end
110
129
 
111
- # Marks the job as done.
112
- def done!
113
- @completed_at = Time.now
114
- @done = true
115
- save
130
+ # Retrieve file from job dir with the given name. Raises RuntimeError if
131
+ # the file can't be found.
132
+ #
133
+ # NOTE: Not used.
134
+ def fetch(key)
135
+ filename = File.join(dir, key)
136
+ raise if !File.exist? filename
137
+ filename
116
138
  end
117
139
 
118
- # Marks the job as success.
119
- def success!
120
- @success = true
140
+ # Marks the job as done and save its exitstatus.
141
+ def done!(status = 0)
142
+ @completed_at = Time.now
143
+ @exitstatus = status
121
144
  save
122
145
  end
123
146
 
@@ -125,5 +148,10 @@ module SequenceServer
125
148
  def dir
126
149
  File.join(DOTDIR, id)
127
150
  end
151
+
152
+ # Where to write serialised job object.
153
+ def yfile
154
+ File.join(dir, 'job.yaml')
155
+ end
128
156
  end
129
157
  end
@@ -12,16 +12,11 @@ module SequenceServer
12
12
  disable :method_override
13
13
 
14
14
  # Ensure exceptions never leak out of the app. Exceptions raised within
15
- # the app must be handled by the app. We do this by attaching error
16
- # blocks to exceptions we know how to handle and attaching to Exception
17
- # as fallback.
15
+ # the app must be handled by the app.
18
16
  disable :show_exceptions, :raise_errors
19
17
 
20
18
  # Make it a policy to dump to 'rack.errors' any exception raised by the
21
- # app so that error handlers don't have to do it themselves. But for it
22
- # to always work, Exceptions defined by us should not respond to `code`
23
- # or `http_status` methods. Error blocks must explicitly set http
24
- # status, if needed, by calling `status` method.
19
+ # app.
25
20
  enable :dump_errors
26
21
 
27
22
  # We don't want Sinatra do setup any loggers for us. We will use our own.
@@ -89,8 +84,7 @@ module SequenceServer
89
84
  get '/:jid.json' do |jid|
90
85
  job = Job.fetch(jid)
91
86
  halt 202 unless job.done?
92
- rep = Report.generate job
93
- rep.to_json
87
+ Report.generate(job).to_json
94
88
  end
95
89
 
96
90
  # Returns base HTML. Rest happens client-side: polling for and rendering
@@ -132,24 +126,34 @@ module SequenceServer
132
126
  send_file out.file, :filename => out.filename, :type => out.mime
133
127
  end
134
128
 
135
- # This error block will only ever be hit if the user gives us a funny
136
- # sequence or incorrect advanced parameter. Well, we could hit this block
137
- # if someone is playing around with our HTTP API too.
138
- error BLAST::ArgumentError do
139
- status 400
129
+ # Catches any exception raised within the app and returns error message and
130
+ # the backtrace as response body. If the error class defines `http_status`
131
+ # instance method, its return value will be used to set HTTP status. HTTP
132
+ # status is set to 500 otherwise.
133
+ error 400..500 do
140
134
  error = env['sinatra.error']
141
- erb :'400', :layout => nil, :locals => { :error => error }
142
- end
143
135
 
144
- # This will catch any unhandled error and some very special errors. Ideally
145
- # we will never hit this block. If we do, there's a bug in SequenceServer
146
- # or something really weird going on. If we hit this error block we show
147
- # the stacktrace to the user requesting them to post the same to our Google
148
- # Group.
149
- error Exception, BLAST::RuntimeError do
150
- status 500
151
- error = env['sinatra.error']
152
- erb :'500', :layout => nil, :locals => { :error => error }
136
+ # All errors will have a message.
137
+ error_data = {message: error.message}
138
+
139
+ # If error object has a title method, use that, or use error class for
140
+ # title.
141
+ if error.respond_to? :title
142
+ error_data[:title] = error.title
143
+ else
144
+ error_data[:title] = error.class.to_s
145
+ end
146
+
147
+ # If error object has a more_info method, use that. If the error does
148
+ # not have more_info and the error is not APIError, use backtrace as
149
+ # more_info.
150
+ if error.respond_to? :more_info
151
+ error_data[:more_info] = error.more_info
152
+ else
153
+ error_data[:more_info] = error.backtrace.join("\n")
154
+ end
155
+
156
+ error_data.to_json
153
157
  end
154
158
  end
155
159
  end
@@ -1,4 +1,4 @@
1
1
  # Define version number.
2
2
  module SequenceServer
3
- VERSION = '1.1.0.beta2'
3
+ VERSION = '1.1.0.beta3'
4
4
  end