catfriend 0.17 → 0.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d3298ab27969cad7ca4f284d27fdfe153f83bbfb
4
- data.tar.gz: dced1579477fff11e1acf0252908539893f6ca6f
3
+ metadata.gz: bf790e35894bdf6de7a9450951dca7930b8601fa
4
+ data.tar.gz: 81180fcbf3e255cf107e66c7971b0804fc30bc19
5
5
  SHA512:
6
- metadata.gz: c16e9adb4de53431a43c83d1b681f1a9ed59e350f4f1885d9d13203b825185c644ea4177a790bfdb8bf39f7b3afd0c0ec43b574d3c7b2c1a56fde2a6c633da8c
7
- data.tar.gz: 46f30291ee03d29ae6ecd795fc207f8b78c760dacc638c4b30e7a3161df86dd42399172201d9ef8baf676eb75473f5de0d1b30122aa280ecee09a28cb3fa64ee
6
+ metadata.gz: f43f0bc6d274435e575efdc2f5ccf5611ea375c29e539253be4cb1282026c842613e49d857296bfffe3497fa49a707723eea31ba2a573aea687c32b7f5109dc3
7
+ data.tar.gz: 32d5d40171b60f2dbff8e9a9210de7e48d9586d0e65351721c22729a02678f494e3a53baa41f028cd2019322742468d1144e210f33cdff83c0989d8584fdb291
data/bin/catfriend CHANGED
@@ -7,7 +7,7 @@
7
7
  # Copyright:: Copyright (c) 2011-2014 James Pike
8
8
  # License:: MIT
9
9
 
10
- $LOAD_PATH << 'lib' if File.exists? 'lib/catfriend'
10
+ $LOAD_PATH.unshift('lib') if File.exists? 'lib/catfriend'
11
11
 
12
12
  require 'catfriend/filetokenstack'
13
13
  require 'catfriend/imap'
@@ -47,10 +47,8 @@ def self.parse_config
47
47
 
48
48
  # obviously assigning it in a loop like this is slow but hey it's
49
49
  # only run-once config and ruby people say DRY a lot.
50
- shift_tokens = lambda do
51
- if tokens.empty?
52
- raise ConfigError, "field #{field} requires parameter"
53
- end
50
+ shift_tokens = -> do
51
+ raise ConfigError, "field #{field} requires parameter" if tokens.empty?
54
52
  return tokens.shift
55
53
  end
56
54
 
@@ -64,17 +62,15 @@ def self.parse_config
64
62
  current[:host] = shift_tokens.call
65
63
  when "notificationTimeout", "errorTimeout", "socketTimeout"
66
64
  # convert from camelCase to camel_case
67
- clean_field =
68
- field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
65
+ clean_field = field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
69
66
  defaults[clean_field] = shift_tokens.call
70
67
  when "checkInterval"
71
68
  shift_tokens.call # deprecated, ignore parameter
72
69
  when "cert_file"
73
70
  cert_file = shift_tokens.call
74
71
  unless File.exists? cert_file
75
- raise ConfigError,
76
- "non-existant SSL certificate `#{cert_file}'" +
77
- ", search path: #{File.dirname(config_file)}/"
72
+ raise ConfigError, "non-existant SSL certificate `#{cert_file}', "
73
+ + "search path: #{File.dirname(config_file)}/"
78
74
  end
79
75
  current[:cert_file] = cert_file
80
76
  when "mailbox", "id", "user", "password"
@@ -84,8 +80,7 @@ def self.parse_config
84
80
  when "work"
85
81
  current[:work_account] = true
86
82
  else
87
- raise ConfigError,
88
- "invalid config parameter '#{field}'"
83
+ raise ConfigError, "invalid config parameter '#{field}'"
89
84
  end
90
85
  end
91
86
 
@@ -96,8 +91,8 @@ def self.parse_config
96
91
  end
97
92
 
98
93
  # Main interface to the application. Reads all servers from config then runs
99
- # each one in a thread. The program exits when all threads encounter an
100
- # unrecoverable error. Perhaps I should make it exit if any thread exits.
94
+ # each one in a thread. If the ImapServer or Dbus threads raise an error then
95
+ # this causes the program to exit.
101
96
  def self.main args
102
97
  work_accounts = false
103
98
  foreground = false
@@ -133,21 +128,19 @@ def self.main args
133
128
  Catfriend.verbose = false unless foreground
134
129
 
135
130
  servers = parse_config
136
- servers.reject! { |s| s.work_account } unless work_accounts
131
+ servers.reject! &:work_account unless work_accounts
137
132
  raise ConfigError, "no servers to check" if servers.empty?
138
133
 
139
134
  if foreground
140
135
  main_loop servers
141
136
  else
142
- pid = fork do
143
- main_loop servers
144
- end
137
+ pid = fork { main_loop servers }
145
138
  Process.detach pid
146
139
  end
147
140
  rescue ConfigError => e
148
141
  puts "misconfiguration: #{e.message}"
149
142
  rescue Interrupt
150
- servers.each { |s| s.kill }
143
+ servers.each &:kill
151
144
  rescue => e
152
145
  puts "unknown error #{e.message}\n#{e.backtrace.join("\n")}"
153
146
  else
@@ -158,10 +151,36 @@ def self.main args
158
151
  end
159
152
 
160
153
  def self.main_loop servers
161
- dbus = DBus.new servers
154
+ mutex = Mutex.new
155
+ error_condition = ConditionVariable.new
156
+ dbus = DBus.new
157
+
158
+ on_error = proc do
159
+ mutex.synchronize { error_condition.signal }
160
+ end
161
+
162
+ dbus.on :disconnect, on_error
163
+
164
+ servers.each do |server|
165
+ server.on(:error) do |message|
166
+ $stderr.puts "#{server.id}: #{message}"
167
+ on_error
168
+ end
169
+ end
170
+
171
+ # start all child threads
162
172
  dbus.start
163
- servers.each { |s| s.start }
164
- servers.each { |s| s.join }
173
+ servers.each &:start
174
+
175
+ # then wait for an error
176
+ mutex.synchronize { error_condition.wait(mutex) }
177
+
178
+ # clean up and exit
179
+ Catfriend.whisper 'caught error condition, disconnecting from servers'
180
+ servers.each &:disconnect
181
+
182
+ Catfriend.whisper 'joining child threads'
183
+ servers.each &:join
165
184
  dbus.join
166
185
  end
167
186
 
data/catfriend.example CHANGED
@@ -15,5 +15,3 @@ host insecure.work.com
15
15
  # time notification remains on screen in milliseconds
16
16
  notificationTimeout 10000
17
17
  errorTimeout 60000 # as above but for error notifications
18
- socketTimeout 60 # server socket timeout in seconds
19
- checkInterval 60 # how often to wait between checks in seconds
@@ -1,6 +1,8 @@
1
- require 'catfriend/thread'
2
- require 'catfriend/server'
3
1
  require 'dbus'
2
+ require 'events'
3
+
4
+ require_relative 'thread'
5
+ require_relative 'server'
4
6
 
5
7
  module Catfriend
6
8
 
@@ -8,13 +10,17 @@ SERVICE = "org.freedesktop.Catfriend"
8
10
  PATH = "/org/freedesktop/Catfriend"
9
11
  INTERFACE = "org.freedesktop.Catfriend.System"
10
12
 
13
+ # Represent a DBUS server interface and includes an associated thread
14
+ # in which to run the dbus methods.
11
15
  class DBus
12
16
  include Thread
17
+ include Events::Emitter
13
18
 
19
+ # This child class fulfils the DBus::Object interface
14
20
  class DBusObject < ::DBus::Object
15
- def initialize(main, servers)
21
+ def initialize(main, emitter)
16
22
  @main = main
17
- @servers = servers
23
+ @emitter = emitter
18
24
  super PATH
19
25
  end
20
26
 
@@ -22,19 +28,17 @@ class DBus
22
28
  dbus_method :stop do
23
29
  Catfriend.whisper "received shutdown request"
24
30
  @main.quit # this must be run from within method handler
25
- @servers.each { |s| s.disconnect }
31
+ @emitter.emit :disconnect
26
32
  end
27
33
  end
28
34
  end
29
35
 
30
- def initialize(servers = nil)
31
- @servers = servers
32
- end
33
-
36
+ # Start the DBus interface
34
37
  def init
35
38
  @bus = ::DBus::SessionBus.instance unless @bus
36
39
  end
37
40
 
41
+ # Attempt to shutdown another application listening on catfriend's address.
38
42
  def send_shutdown
39
43
  init
40
44
  service = @bus.service(SERVICE)
@@ -47,12 +51,14 @@ class DBus
47
51
  false
48
52
  end
49
53
 
54
+ # Call send_shutdown then start the DBus interface.
50
55
  def start_service
51
- object = DBusObject.new(@main, @servers)
56
+ object = DBusObject.new(@main, self)
52
57
  service = @bus.request_service(SERVICE)
53
58
  service.export object
54
59
  end
55
60
 
61
+ # Thread mixin interface: Run the DBus server in a virtual thread
56
62
  def run
57
63
  init
58
64
  if send_shutdown
@@ -1,9 +1,11 @@
1
1
  require 'libnotify'
2
- require 'catfriend/server'
3
- require 'catfriend/thread'
4
-
2
+ require 'events'
5
3
  require 'net/imap'
6
4
 
5
+ require_relative 'server'
6
+ require_relative 'thread'
7
+ require_relative 'net_imap_exchange_patch'
8
+
7
9
  # unless I do this I get random errors from Libnotify on startup 90% of the
8
10
  # time... this could be a bug in autoload or ruby 1.9 rather than libnotify
9
11
  module Libnotify
@@ -12,13 +14,12 @@ end
12
14
 
13
15
  module Catfriend
14
16
 
15
- require_relative 'net_imap_exchange_patch'
16
-
17
17
  # This class represents a thread capable of checking and creating
18
18
  # notifications for a single mailbox on a single IMAP server.
19
19
  class ImapServer
20
20
  include Thread
21
21
  include AccessorsFromHash
22
+ include Events::Emitter
22
23
 
23
24
  # Create new IMAP server with optional full configuration hash.
24
25
  # If the hash is not supplied at construction a further call must be
@@ -32,15 +33,9 @@ class ImapServer
32
33
  def configure args
33
34
  super args
34
35
 
35
- if not @user
36
- raise ConfigError, "imap user not set"
37
- end
38
- if not @host
39
- raise ConfigError, "imap host not set"
40
- end
41
- if not @password
42
- raise ConfigError, "imap password not set"
43
- end
36
+ raise ConfigError, "imap user not set" unless @user
37
+ raise ConfigError, "imap host not set" unless @host
38
+ raise ConfigError, "imap password not set" unless @password
44
39
  end
45
40
 
46
41
  # The id is a token which represents this server when displaying
@@ -50,18 +45,16 @@ class ImapServer
50
45
 
51
46
  # Raise an error related to this particular server.
52
47
  def error message
53
- # consider raising notification instead?
54
- puts "#{id}: #{message}"
48
+ emit :error, message
55
49
  end
56
50
 
57
51
  # ThreadMixin interface. This connects to the mailserver and then
58
- # runs #check_loop to do the e-mail checking if the connection
52
+ # runs #sleep_until_change to do the e-mail checking if the connection
59
53
  # succeeds.
60
54
  def run
61
55
  begin
62
56
  @notification =
63
- Libnotify.new :body => nil,
64
- :timeout => Catfriend.notification_timeout
57
+ Libnotify.new :body => nil, :timeout => Catfriend.notification_timeout
65
58
  @message_count = connect
66
59
  notify_message @message_count
67
60
  # :body => nil means summary only
@@ -71,25 +64,27 @@ class ImapServer
71
64
  error "no response to connect, try ssl"
72
65
  else
73
66
  loop {
74
- check_loop
67
+ sleep_until_change
75
68
  break if stopping?
76
69
  }
77
70
  end
78
71
  end
79
72
 
80
- # Continually waits for new e-mail raising notifications when new
81
- # e-mail arrives or when error conditions happen. This methods only exits
82
- # on an unrecoverable error.
83
- def check_loop
73
+ # Waits until an event occurs which could change the message count.
74
+ def sleep_until_change
84
75
  @imap.idle do |r|
85
- Catfriend.whisper "#{id}: #{r}"
86
- next if r.instance_of? Net::IMAP::ContinuationRequest
87
-
88
- if r.instance_of? Net::IMAP::UntaggedResponse
89
- case r.name
90
- when 'EXISTS', 'EXPUNGE'
91
- @imap.idle_done
76
+ begin
77
+ Catfriend.whisper "#{id}: #{r}"
78
+ next if r.instance_of? Net::IMAP::ContinuationRequest
79
+
80
+ if r.instance_of? Net::IMAP::UntaggedResponse
81
+ case r.name
82
+ when 'EXISTS', 'EXPUNGE'
83
+ @imap.idle_done
84
+ end
92
85
  end
86
+ rescue => e
87
+ error e.message
93
88
  end
94
89
  end
95
90
 
@@ -110,20 +105,25 @@ class ImapServer
110
105
  end
111
106
  end
112
107
 
108
+ # Update the associated notification with message and show it if it is not
109
+ # already displayed.
113
110
  def notify_message message
114
111
  @notification.update :summary => "#{id}: #{message}"
115
112
  Catfriend.whisper @notification.summary
116
113
  end
117
114
 
115
+ # Returns false until kill/disconnect have been called.
118
116
  def stopping?
119
117
  stopped? or @stopping
120
118
  end
121
119
 
120
+ # Disconnect from the imap server and stop the associated thread.
122
121
  def kill
123
122
  disconnect
124
123
  super
125
124
  end
126
125
 
126
+ # Ask IMAP server for count of unseen messages.
127
127
  def get_unseen_count
128
128
  begin
129
129
  # fetch raises an exception when the mailbox is empty
@@ -137,7 +137,7 @@ class ImapServer
137
137
  # Connect to the configured IMAP server and return message count.
138
138
  def connect
139
139
  args = nil
140
- if not @no_ssl
140
+ unless @no_ssl
141
141
  if @cert_file
142
142
  args = { :ssl => { :ca_file => @cert_file } }
143
143
  else
@@ -151,6 +151,9 @@ class ImapServer
151
151
  get_unseen_count
152
152
  end
153
153
 
154
+ # Reconnect to the server showing a "[reconnecting]" message. After
155
+ # reconnection then show the message count if it has changed from
156
+ # what was remembered before the disconnect.
154
157
  def reconnect
155
158
  notify_message "#{@message_count} [reconnecting]"
156
159
  new_count = connect
@@ -168,7 +171,7 @@ class ImapServer
168
171
  @imap.disconnect
169
172
  end
170
173
 
171
- private :connect, :reconnect, :check_loop, :run, :error, :notify_message
174
+ private :connect, :reconnect, :sleep_until_change, :run, :error, :notify_message
172
175
 
173
176
  attr_writer :host, :password, :id, :user, :no_ssl, :cert_file, :mailbox
174
177
  attr_accessor :work_account
@@ -1,5 +1,8 @@
1
1
  require 'net/imap'
2
2
 
3
+ # This patch is needed because microsoft exchange servers do not correctly implement
4
+ # the IMAP specification. Specifially Net::IMAP#status will not work without it.
5
+ #
3
6
  # source:
4
7
  # http://claudiofloreani.blogspot.co.uk/2012/01/monkeypatching-ruby-imap-class-to-build.html
5
8
  # thank you!
@@ -16,7 +16,6 @@ module Thread
16
16
  end
17
17
  end
18
18
 
19
-
20
19
  # Kill thread if it has started.
21
20
  def kill
22
21
  unless stopped?
metadata CHANGED
@@ -1,55 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: catfriend
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.17'
4
+ version: '0.18'
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Pike
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-09 00:00:00.000000000 Z
11
+ date: 2014-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: libnotify
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.7.1
20
- - - ~>
20
+ - - "~>"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '0.8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - '>='
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.7.1
30
- - - ~>
30
+ - - "~>"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '0.8'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: ruby-dbus
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - '>='
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.7'
40
- - - ~>
40
+ - - "~>"
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0.11'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - '>='
47
+ - - ">="
48
48
  - !ruby/object:Gem::Version
49
49
  version: '0.7'
50
- - - ~>
50
+ - - "~>"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '0.11'
53
+ - !ruby/object:Gem::Dependency
54
+ name: events
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.9'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.9'
53
67
  description: E-mail checker with libnotify desktop notifications.
54
68
  email:
55
69
  - catfriend@chilon.net
@@ -59,14 +73,14 @@ extensions: []
59
73
  extra_rdoc_files: []
60
74
  files:
61
75
  - LICENSE
76
+ - bin/catfriend
62
77
  - catfriend.example
63
- - lib/catfriend/server.rb
64
78
  - lib/catfriend/dbus.rb
65
- - lib/catfriend/thread.rb
66
- - lib/catfriend/net_imap_exchange_patch.rb
67
- - lib/catfriend/imap.rb
68
79
  - lib/catfriend/filetokenstack.rb
69
- - bin/catfriend
80
+ - lib/catfriend/imap.rb
81
+ - lib/catfriend/net_imap_exchange_patch.rb
82
+ - lib/catfriend/server.rb
83
+ - lib/catfriend/thread.rb
70
84
  homepage: https://github.com/nuisanceofcats/catfriend
71
85
  licenses:
72
86
  - Expat
@@ -77,17 +91,17 @@ require_paths:
77
91
  - lib
78
92
  required_ruby_version: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - '>='
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  required_rubygems_version: !ruby/object:Gem::Requirement
84
98
  requirements:
85
- - - '>='
99
+ - - ">="
86
100
  - !ruby/object:Gem::Version
87
101
  version: '1.3'
88
102
  requirements: []
89
103
  rubyforge_project:
90
- rubygems_version: 2.0.14
104
+ rubygems_version: 2.2.2
91
105
  signing_key:
92
106
  specification_version: 4
93
107
  summary: E-mail checker with desktop notifications.