catfriend 0.0 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
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: