catfriend 0.17 → 0.18

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.
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.