catfriend 0.0 → 0.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 (4) hide show
  1. data/bin/catfriend +107 -0
  2. data/lib/imap.rb +86 -0
  3. data/lib/server.rb +24 -0
  4. metadata +21 -7
data/bin/catfriend CHANGED
@@ -1 +1,108 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ # Program for reading IMAP servers and creating desktop notifications when
4
+ # new e-mail arrives.
5
+ #
6
+ # Author:: James Pike (mailto:catfriend@chilon.net)
7
+ # Copyright:: Copyright (c) 2011 James Pike
8
+ # License:: MIT
9
+ module Catfriend
10
+
11
+ require 'imap'
12
+ require 'net/imap'
13
+
14
+ # xdg is optional. it outputs to stderr, so i redirect it for a while.
15
+ stderr_bak = $stderr.dup
16
+ $stderr.reopen '/dev/null', 'w'
17
+ begin
18
+ require 'xdg'
19
+ rescue LoadError ; end
20
+ $stderr = stderr_bak # restore stderr
21
+
22
+ # Reads a simple configuration format and returns an array of servers.
23
+ def self.parse_config
24
+ # xdg is optional
25
+ begin
26
+ config_file = XDG['CONFIG'].find 'catfriend'
27
+ rescue NameError ; end
28
+ config_file ||= "#{ENV['HOME']}/.config/catfriend"
29
+
30
+ # for location of certificate file
31
+ Dir.chdir File.dirname(config_file)
32
+
33
+ servers = []
34
+ current = {}
35
+ defaults = {}
36
+
37
+ File.foreach(config_file) do |line|
38
+ tokens = line.sub(/#.*/, '').scan(/\S+/)
39
+
40
+ # parse tokens so use ugly loop instead of functional loop
41
+ until tokens.empty?
42
+ field = tokens.shift
43
+
44
+ # obviously assigning it in a loop like this is slow but hey it's
45
+ # only run-once config and ruby people say DRY a lot.
46
+ shift_tokens = lambda do
47
+ if tokens.empty? then
48
+ raise ConfigError,
49
+ "field #{field} requires parameter"
50
+ end
51
+ return tokens.shift
52
+ end
53
+
54
+ case field
55
+ when "host","imap"
56
+ # host is deprecated
57
+ if not current.empty? then
58
+ servers << ImapServer.new(current)
59
+ current = {}
60
+ end
61
+ current[:host] = shift_tokens.call
62
+ when "notificationTimeout", "errorTimeout", "socketTimeout"
63
+ # convert from camelCase to camel_case
64
+ clean_field =
65
+ field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
66
+ defaults[clean_field] = shift_tokens.call
67
+ when "checkInterval"
68
+ shift_tokens.call # deprecated, ignore parameter
69
+ when "cert_file"
70
+ cert_file = shift_tokens.call
71
+ unless File.exists? cert_file
72
+ raise ConfigError,
73
+ "non-existant SSL certificate `#{cert_file}'" +
74
+ ", search path: #{File.dirname(config_file)}/"
75
+ end
76
+ current[:cert_file] = cert_file
77
+ when "mailbox", "id", "user", "password"
78
+ current[field] = shift_tokens.call
79
+ when "nossl"
80
+ current[:no_ssl] = true
81
+ else
82
+ raise ConfigError,
83
+ "invalid config parameter '#{field}': #{line}"
84
+ end
85
+ end
86
+ end
87
+
88
+ servers << ImapServer.new(current) unless current.empty?
89
+
90
+ servers
91
+ end
92
+
93
+ # Main interface to the application. Reads all servers from config then runs
94
+ # each one in a thread. The program exits when all threads encounter an
95
+ # unrecoverable error. Perhaps I should make it exit if any thread exits.
96
+ def self.main
97
+ begin
98
+ servers = parse_config
99
+ servers.each { |s| s.start }
100
+ servers.each { |s| s.join }
101
+ rescue ConfigError => e
102
+ puts "misconfiguration: " + e.message
103
+ end
104
+ end
105
+
106
+ end ########################### end module
107
+
108
+ Catfriend.main
data/lib/imap.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'server'
2
+
3
+ # This class represents a thread capable of checking and creating
4
+ # notifications for a single mailbox on a single IMAP server.
5
+ class ImapServer
6
+ include ThreadMixin
7
+ include AccessorsFromHash
8
+
9
+ # Create new IMAP server with optional full configuration hash.
10
+ # If the hash is not supplied at construction a further call must be
11
+ # made to #configure before #start is called to start the thread.
12
+ def initialize(args = nil)
13
+ configure args if args
14
+ end
15
+
16
+ # Configure all attributes based on hash then make sure this
17
+ # represents a total valid configuration.
18
+ def configure args
19
+ super args
20
+
21
+ if not @user
22
+ raise ConfigError, "imap user not set"
23
+ end
24
+ if not @host
25
+ raise ConfigError, "imap host not set"
26
+ end
27
+ if not @password
28
+ raise ConfigError, "imap password not set"
29
+ end
30
+
31
+ @id = @host unless @id
32
+ end
33
+
34
+ # The id is a token which represents this server when displaying
35
+ # notifications and is set to the host unless over-ridden by the
36
+ # configuration file
37
+ def id ; @id || @host ; end
38
+
39
+ # Raise an error related to this particular server.
40
+ def error message
41
+ # consider raising notification instead?
42
+ puts "#{id}: #{message}"
43
+ end
44
+
45
+ # ThreadMixin interface. This connects to the mailserver and then
46
+ # runs #check_loop to do the e-mail checking if the connection
47
+ # succeeds.
48
+ def run
49
+ # connect and go
50
+ begin
51
+ connect
52
+ rescue OpenSSL::SSL::SSLError
53
+ error "try providing ssl certificate"
54
+ rescue Net::IMAP::NoResponseError
55
+ error "no response to connect, try ssl"
56
+ else
57
+ check_loop
58
+ end
59
+ end
60
+
61
+ # Continually waits for new e-mail raising notifications when new
62
+ # e-mail arrives or when error conditions happen. This methods only exits
63
+ # on an unrecoverable error.
64
+ def check_loop
65
+ #
66
+ end
67
+
68
+ # Connect to the configured IMAP server.
69
+ def connect
70
+ args = nil
71
+ if not @no_ssl then
72
+ if @cert_file then
73
+ args = { :ssl => { :ca_file => @cert_file } }
74
+ else
75
+ args = { :ssl => true }
76
+ end
77
+ end
78
+ @imap = Net::IMAP.new(@host, args)
79
+ @imap.login(@user, @password)
80
+ puts @imap.select(@mailbox || "INBOX")
81
+ end
82
+
83
+ private :connect, :check_loop, :run, :error
84
+ attr_writer :host, :password, :id, :user, :no_ssl, :cert_file, :mailbox
85
+ end
86
+
data/lib/server.rb ADDED
@@ -0,0 +1,24 @@
1
+ # Mixin this module and define "run" for a simple runnable/joinable thread
2
+ module ThreadMixin
3
+ # Call to start a thread running via the start method.
4
+ def start ; @thread = Thread.new { run } ; end
5
+
6
+ # Join thread started with start.
7
+ def join ; @thread.join ; end
8
+ end
9
+
10
+ # Mixin to provide #configure which allows all instance variables with write
11
+ # accessors declared to be set from a hash.
12
+ module AccessorsFromHash
13
+ # Call this to tranfer the hash data to corresponding attributes. Any
14
+ # hash keys that do not have a corresponding write accessor in the
15
+ # class are silently ignored.
16
+ def configure args
17
+ args.each do |opt, val|
18
+ instance_variable_set("@#{opt}", val) if respond_to? "#{opt}="
19
+ end
20
+ end
21
+ end
22
+
23
+ # This class is used to signal the user made an error in their configuration.
24
+ class ConfigError < Exception ; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: catfriend
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.0'
4
+ version: '0.1'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-23 00:00:00.000000000 Z
12
+ date: 2011-12-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-libnotify
16
- requirement: &23635720 !ruby/object:Gem::Requirement
16
+ requirement: &9103360 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,8 +21,19 @@ dependencies:
21
21
  version: '0.5'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *23635720
25
- description: Based on libnotify, Does not punish the fearless.
24
+ version_requirements: *9103360
25
+ - !ruby/object:Gem::Dependency
26
+ name: xdg
27
+ requirement: &9102900 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *9102900
36
+ description: E-mail checker with libnotify desktop notifications.
26
37
  email:
27
38
  - catfriend@chilon.net
28
39
  executables:
@@ -31,9 +42,12 @@ extensions: []
31
42
  extra_rdoc_files: []
32
43
  files:
33
44
  - LICENSE
45
+ - lib/imap.rb
46
+ - lib/server.rb
34
47
  - bin/catfriend
35
- homepage: http://chilon.net/catfriend
36
- licenses: []
48
+ homepage: https://github.com/nuisanceofcats/catfriend
49
+ licenses:
50
+ - Expat
37
51
  post_install_message:
38
52
  rdoc_options: []
39
53
  require_paths: