harbor 0.16.1

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.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. metadata +155 -0
@@ -0,0 +1,74 @@
1
+ require "dm-core"
2
+ require "dm-timestamps"
3
+
4
+ module Harbor
5
+ module Contrib
6
+ class Session
7
+ ##
8
+ # This is a database backed session handle for DataMapper. You can use it
9
+ # instead of the builtin Harbor::Session::Cookie by doing:
10
+ #
11
+ # Harbor::Session.configure do |session|
12
+ # session.store = Harbor::Contrib::Session::DataMapper
13
+ # end
14
+ #
15
+ # A basic Session resource is defined for you.
16
+ ##
17
+ class DataMapper < Harbor::Session::Abstract
18
+
19
+ class SessionHash < Hash
20
+ def initialize(instance)
21
+ super()
22
+ @instance = instance
23
+ merge!(@instance.data)
24
+ end
25
+
26
+ def [](key)
27
+ key == :session_id ? @instance.id : super
28
+ end
29
+
30
+ def []=(key, value)
31
+ raise ArgumentError.new("You cannot manually set the session_id for a session.") if key == :session_id
32
+
33
+ super
34
+ end
35
+
36
+ def to_hash
37
+ {}.merge(reject { |key,| key == :session_id })
38
+ end
39
+
40
+ def instance
41
+ @instance
42
+ end
43
+ end
44
+
45
+ def self.load_session(cookie)
46
+ session = if expire_after = Harbor::Session.options[:expire_after]
47
+ ::Session.first(:id => cookie, :created_at.gte => Time.now - expire_after)
48
+ else
49
+ ::Session.get(cookie)
50
+ end
51
+
52
+ session ||= ::Session.create
53
+
54
+ SessionHash.new(session)
55
+ end
56
+
57
+ def self.commit_session(data)
58
+ record = data.instance
59
+ record.update_attributes(:data => data.to_hash)
60
+ record.id
61
+ end
62
+ end
63
+ end
64
+
65
+ class ::Session #:nodoc:
66
+ include DataMapper::Resource
67
+
68
+ property :id, String, :key => true, :default => lambda { `uuidgen`.chomp }
69
+ property :data, Object, :default => {}
70
+ property :created_at, DateTime
71
+ property :updated_at, DateTime
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,105 @@
1
+ module Harbor
2
+
3
+ class Daemon
4
+
5
+ START_CONTEXT = {
6
+ :argv => ARGV.map { |arg| arg.dup },
7
+ :cwd => `/bin/sh -c pwd`.chomp("\n"),
8
+ :cmd => $0.dup
9
+ }
10
+
11
+ DEFAULT_SLEEP_SECONDS = 60
12
+
13
+ attr_accessor :logger
14
+ attr_reader :worker, :options
15
+
16
+ def initialize(worker, log_file, seconds_between_runs = DEFAULT_SLEEP_SECONDS)
17
+ @worker = worker
18
+
19
+ # The template may include the tag %PID to be replaced with
20
+ # the PID of the forked worker process. Don't use @log_file
21
+ # directly, use self.log_file
22
+ @log_file_template = log_file
23
+
24
+ @seconds_between_runs = seconds_between_runs
25
+ end
26
+
27
+ def log_file
28
+ @log_file ||= @log_file_template.gsub('%PID', Process.pid.to_s)
29
+ end
30
+
31
+ def detach
32
+ srand
33
+ fork and exit
34
+ Process.setsid # detach -- we want to be able to close our shell!
35
+
36
+ log_directory = ::File.dirname(self.log_file)
37
+ ::File.mkdir_p(log_directory) unless ::File.directory?(log_directory)
38
+
39
+ redirect_io(@log_file)
40
+ end
41
+
42
+ def run
43
+ @restart = false
44
+ @alive = true
45
+
46
+ # graceful restart
47
+ trap(:HUP) do
48
+ logger.info "Restarting gracefully." if logger
49
+ @restart = true
50
+ @alive = nil
51
+ @worker.alive = nil
52
+ end
53
+
54
+ # graceful shutdown
55
+ trap(:QUIT) do
56
+ logger.info "Shutting down gracefully." if logger
57
+ @alive = nil
58
+ @worker.alive = nil
59
+ end
60
+
61
+ # quick exit
62
+ [:TERM, :INT].each do |sig|
63
+ trap(sig) do
64
+ logger.info "Shutting down NOW" if logger
65
+ cleanup!
66
+
67
+ exit!(0)
68
+ end
69
+ end
70
+
71
+ begin
72
+ @worker.run
73
+ sleep(@seconds_between_runs) if @alive
74
+ end while @alive
75
+
76
+ if @restart
77
+ logger.info "Starting new daemon..."
78
+ fork do
79
+ Dir.chdir(START_CONTEXT[:cwd])
80
+ exec(START_CONTEXT[:cmd], *START_CONTEXT[:argv])
81
+ end
82
+ end
83
+
84
+ logger.info "Shutting down..." if logger
85
+ cleanup!
86
+ end
87
+
88
+ def cleanup!
89
+ worker.cleanup! if worker.respond_to?(:cleanup!)
90
+ end
91
+
92
+ private
93
+
94
+ def redirect_io(file = nil)
95
+ STDIN.reopen "/dev/null"
96
+ STDOUT.reopen file || "/dev/null"
97
+ STDOUT.sync = true
98
+
99
+ STDERR.reopen STDOUT
100
+ STDERR.sync = true
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,49 @@
1
+ module Harbor
2
+
3
+ class Errors
4
+
5
+ include Enumerable
6
+
7
+ def initialize(errors = [])
8
+ @errors = errors
9
+ end
10
+
11
+ def [](index)
12
+ errors[index]
13
+ end
14
+
15
+ def <<(message)
16
+ if message.is_a?(String)
17
+ errors << message
18
+ elsif message.is_a?(Enumerable)
19
+ message.each do |error_message|
20
+ errors << error_message
21
+ end
22
+ else
23
+ errors << message
24
+ end
25
+ end
26
+
27
+ def each
28
+ errors.each do |error|
29
+ yield error
30
+ end
31
+ end
32
+
33
+ def size
34
+ errors.size
35
+ end
36
+
37
+ def +(other)
38
+ Errors.new((errors + other.errors).uniq)
39
+ end
40
+
41
+ protected
42
+
43
+ def errors
44
+ @errors
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,45 @@
1
+ module Harbor
2
+ module Events
3
+
4
+ def self.included(target)
5
+ target.extend(ClassMethods)
6
+ end
7
+
8
+ def raise_event(name, *args)
9
+ if self.class.events[name].nil?
10
+ return false
11
+ else
12
+ self.class.events[name].each do |event|
13
+ event.call(*args)
14
+ end
15
+ return true
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+
21
+ def events
22
+ class_variable_defined?(:@@events) ? class_variable_get(:@@events) : class_variable_set(:@@events, {})
23
+ end
24
+
25
+ def events=(hash)
26
+ events.replace(hash)
27
+ end
28
+
29
+ def register_event(name, &block)
30
+ events[name] ||= []
31
+ events[name] << block
32
+ end
33
+
34
+ def clear_events!(name=nil)
35
+ if name
36
+ events[name] = nil
37
+ else
38
+ events = {}
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ module Harbor
2
+ ##
3
+ # Utility class for receiving email notifications of exceptions in
4
+ # non-development environments.
5
+ #
6
+ # services.register("mailer", Harbor::Mailer)
7
+ # services.register("mail_server", Harbor::SendmailServer)
8
+ #
9
+ # require 'harbor/exception_notifier'
10
+ #
11
+ # You will then receive email alerts for all 500 errors in the format of:
12
+ #
13
+ # From: errors@request_host
14
+ # Subject: [ERROR] [request_host] [environment] Exception description
15
+ # Body: stack trace found in log, with request details.
16
+ ##
17
+ class ExceptionNotifier
18
+
19
+ def self.notification_address=(address)
20
+ @@notification_address = address
21
+ end
22
+
23
+ def self.notification_address
24
+ @@notification_address
25
+ rescue NameError
26
+ raise "Harbor::ExceptionMailer.notification_address not set."
27
+ end
28
+
29
+ def self.notification_address?
30
+ defined?(@@notification_address)
31
+ end
32
+
33
+ def self.notify(exception, request, response, trace)
34
+ return if request.environment == "development"
35
+
36
+ mailer = request.application.services.get("mailer")
37
+ mailer.to = notification_address
38
+
39
+ host = request.env["HTTP_X_FORWARDED_HOST"] || request.host
40
+ mailer.from = "errors@#{host}"
41
+
42
+ subject = exception.to_s
43
+
44
+ # We can't have multi-line subjects, so we chop off extra lines
45
+ if subject[$/]
46
+ subject = subject.split($/, 2)[0] + "..."
47
+ end
48
+
49
+ mailer.subject = "[ERROR] [#{request.host}] [#{request.environment}] #{subject}"
50
+ mailer.text = trace
51
+ mailer.set_header("X-Priority", 1)
52
+ mailer.set_header("X-MSMail-Priority", "High")
53
+ mailer.send!
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ Harbor::Application.register_event(:exception) { |*args| Harbor::ExceptionNotifier.notify(*args) }
@@ -0,0 +1,66 @@
1
+ require 'fileutils'
2
+
3
+ module Harbor
4
+
5
+ class File
6
+
7
+ attr_accessor :path, :name
8
+
9
+ def initialize(path, name = nil)
10
+ @path = path
11
+ @name = name || ::File.basename(@path)
12
+ end
13
+
14
+ def read(block_size)
15
+ @io ||= File.open(@path, "rb")
16
+ yield @io.read(block_size)
17
+ end
18
+
19
+ def rewind
20
+ @io ||= File.open(@path, "rb")
21
+ @io.rewind
22
+ end
23
+
24
+ def size
25
+ ::File.size(@path)
26
+ end
27
+
28
+ ##
29
+ # The file is first copied, and then the provided block is run.
30
+ # If no errors occur, the source file is deleted. If an error
31
+ # occurs, the copied file is removed and the directory cleaned.
32
+ ##
33
+ def self.move_safely(from, to, mode = 0666 - ::File.umask)
34
+ raise ArgumentError.new("no block given") unless block_given?
35
+
36
+ FileUtils.mkdir_p(::File.dirname(to))
37
+ FileUtils.cp(from, to)
38
+ FileUtils.chmod(mode, to)
39
+ begin
40
+ yield
41
+ FileUtils.rm(from)
42
+ rescue
43
+ FileUtils.rm(to)
44
+ rmdir_p(::File.dirname(to))
45
+ raise $!
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Recursively delete empty directories as mkdir -p recursively
51
+ # creates directories.
52
+ ##
53
+ def self.rmdir_p(directory)
54
+ `rmdir -p #{Shellwords.escape(directory)} &> /dev/null`
55
+ end
56
+
57
+ ##
58
+ # Moves a file and gives it the default file permissions minus the
59
+ # declared umask unless another mode is specified.
60
+ ##
61
+ def self.move(from, to, mode = 0666 - ::File.umask)
62
+ FileUtils.mv(from, to)
63
+ FileUtils.chmod(mode, to)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ module Harbor
2
+ class FileStore
3
+
4
+ require Pathname(__FILE__).dirname + "file_store/file"
5
+ require Pathname(__FILE__).dirname + "file_store/local"
6
+
7
+ @@registrations = []
8
+
9
+ def self.register(name, store)
10
+ @@registrations.push name
11
+ file_stores[name] = store
12
+ end
13
+
14
+ def self.file_stores
15
+ @@file_stores ||= {}
16
+ end
17
+
18
+ def self.[](name)
19
+ file_stores[name]
20
+ end
21
+
22
+ def self.exists?(path)
23
+ @@registrations.each do |registration|
24
+ if file_stores[registration].exists?(path) == true
25
+ return true
26
+ end
27
+ end
28
+ false
29
+ end
30
+
31
+ def self.get(path)
32
+ @@registrations.each do |registration|
33
+ if file_stores[registration].exists?(path) == true
34
+ return file_stores[registration].get(path)
35
+ end
36
+ end
37
+ nil
38
+ end
39
+
40
+ def put(path, file)
41
+ raise NotImplementedError.new("You must define your own implementation of FileStore#put")
42
+ end
43
+
44
+ def delete(path)
45
+ raise NotImplementedError.new("You must define your own implementation of FileStore#delete")
46
+ end
47
+
48
+ def exists?(path)
49
+ raise NotImplementedError.new("You must define your own implementation of FileStore#exists?")
50
+ end
51
+
52
+ def open(path)
53
+ raise NotImplementedError.new("You must define your own implementation of FileStore#open")
54
+ end
55
+
56
+ def size(path)
57
+ raise NotImplementedError.new("You must define your own implementation of FileStore#size")
58
+ end
59
+
60
+ ##
61
+ # Defines whether a file store is local, and thus could be directly copied rather
62
+ # than read and then written.
63
+ ##
64
+ def local?
65
+ raise NotImplementedError.new("You must define your own implementation of FileStore#local?")
66
+ end
67
+
68
+ end
69
+ end