catfriend 0.0 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/catfriend +107 -0
- data/lib/imap.rb +86 -0
- data/lib/server.rb +24 -0
- 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.
|
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-
|
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: &
|
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: *
|
25
|
-
|
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:
|
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:
|