]> code.delx.au - offlineimap/blob - offlineimap/init.py
Import md5 from hashlib if available.
[offlineimap] / offlineimap / init.py
1 # OfflineIMAP initialization code
2 # Copyright (C) 2002-2007 John Goerzen
3 # <jgoerzen@complete.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 import imaplib
20 from offlineimap import imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
21 from offlineimap.localeval import LocalEval
22 from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
23 from offlineimap.ui import UIBase
24 import re, os, os.path, offlineimap, sys
25 from offlineimap.CustomConfig import CustomConfigParser
26 from threading import *
27 import threading, socket
28 from getopt import getopt
29 import signal
30
31 try:
32 import fcntl
33 hasfcntl = 1
34 except:
35 hasfcntl = 0
36
37 lockfd = None
38
39 def lock(config, ui):
40 global lockfd, hasfcntl
41 if not hasfcntl:
42 return
43 lockfd = open(config.getmetadatadir() + "/lock", "w")
44 try:
45 fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
46 except IOError:
47 ui.locked()
48 ui.terminate(1)
49
50 def startup(versionno):
51 assert versionno == version.versionstr, "Revision of main program (%s) does not match that of library (%s). Please double-check your PYTHONPATH and installation locations." % (versionno, version.versionstr)
52 options = {}
53 if '--help' in sys.argv[1:]:
54 sys.stdout.write(version.getcmdhelp() + "\n")
55 sys.exit(0)
56
57 for optlist in getopt(sys.argv[1:], 'P:1oqa:c:d:l:u:hk:f:')[0]:
58 options[optlist[0]] = optlist[1]
59
60 if options.has_key('-h'):
61 sys.stdout.write(version.getcmdhelp())
62 sys.stdout.write("\n")
63 sys.exit(0)
64 configfilename = os.path.expanduser("~/.offlineimaprc")
65 if options.has_key('-c'):
66 configfilename = options['-c']
67 if options.has_key('-P'):
68 if not options.has_key('-1'):
69 sys.stderr.write("FATAL: profile mode REQUIRES -1\n")
70 sys.exit(100)
71 profiledir = options['-P']
72 os.mkdir(profiledir)
73 threadutil.setprofiledir(profiledir)
74 sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n")
75
76 config = CustomConfigParser()
77 if not os.path.exists(configfilename):
78 sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename)
79 sys.exit(1)
80
81 config.read(configfilename)
82
83 # override config values with option '-k'
84 for option in options.keys():
85 if option == '-k':
86 (key, value) = options['-k'].split('=', 1)
87 if ':' in key:
88 (secname, key) = key.split(':', 1)
89 section = secname.replace("_", " ")
90 else:
91 section = "general"
92 config.set(section, key, value)
93
94 ui = offlineimap.ui.detector.findUI(config, options.get('-u'))
95 UIBase.setglobalui(ui)
96
97 if options.has_key('-l'):
98 ui.setlogfd(open(options['-l'], 'wt'))
99
100 ui.init_banner()
101
102 if options.has_key('-d'):
103 for debugtype in options['-d'].split(','):
104 ui.add_debug(debugtype.strip())
105 if debugtype == 'imap':
106 imaplib.Debug = 5
107 if debugtype == 'thread':
108 threading._VERBOSE = 1
109
110 if options.has_key('-o'):
111 # FIXME: maybe need a better
112 for section in accounts.getaccountlist(config):
113 config.remove_option('Account ' + section, "autorefresh")
114
115 if options.has_key('-q'):
116 for section in accounts.getaccountlist(config):
117 config.set('Account ' + section, "quick", '-1')
118
119 if options.has_key('-f'):
120 foldernames = options['-f'].replace(" ", "").split(",")
121 folderfilter = "lambda f: f in %s" % foldernames
122 folderincludes = "[]"
123 for accountname in accounts.getaccountlist(config):
124 account_section = 'Account ' + accountname
125 remote_repo_section = 'Repository ' + \
126 config.get(account_section, 'remoterepository')
127 local_repo_section = 'Repository ' + \
128 config.get(account_section, 'localrepository')
129 for section in [remote_repo_section, local_repo_section]:
130 config.set(section, "folderfilter", folderfilter)
131 config.set(section, "folderincludes", folderincludes)
132
133 lock(config, ui)
134
135 def sigterm_handler(signum, frame):
136 # die immediately
137 ui.terminate(errormsg="terminating...")
138 signal.signal(signal.SIGTERM,sigterm_handler)
139
140 try:
141 pidfd = open(config.getmetadatadir() + "/pid", "w")
142 pidfd.write(str(os.getpid()) + "\n")
143 pidfd.close()
144 except:
145 pass
146
147 try:
148 if options.has_key('-l'):
149 sys.stderr = ui.logfile
150
151 socktimeout = config.getdefaultint("general", "socktimeout", 0)
152 if socktimeout > 0:
153 socket.setdefaulttimeout(socktimeout)
154
155 activeaccounts = config.get("general", "accounts")
156 if options.has_key('-a'):
157 activeaccounts = options['-a']
158 activeaccounts = activeaccounts.replace(" ", "")
159 activeaccounts = activeaccounts.split(",")
160 allaccounts = accounts.AccountHashGenerator(config)
161
162 syncaccounts = {}
163 for account in activeaccounts:
164 if account not in allaccounts:
165 if len(allaccounts) == 0:
166 errormsg = 'The account "%s" does not exist because no accounts are defined!'%account
167 else:
168 errormsg = 'The account "%s" does not exist. Valid accounts are:'%account
169 for name in allaccounts.keys():
170 errormsg += '\n%s'%name
171 ui.terminate(1, errortitle = 'Unknown Account "%s"'%account, errormsg = errormsg)
172 syncaccounts[account] = allaccounts[account]
173
174 server = None
175 remoterepos = None
176 localrepos = None
177
178 if options.has_key('-1'):
179 threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
180 else:
181 threadutil.initInstanceLimit("ACCOUNTLIMIT",
182 config.getdefaultint("general", "maxsyncaccounts", 1))
183
184 for reposname in config.getsectionlist('Repository'):
185 for instancename in ["FOLDER_" + reposname,
186 "MSGCOPY_" + reposname]:
187 if options.has_key('-1'):
188 threadutil.initInstanceLimit(instancename, 1)
189 else:
190 threadutil.initInstanceLimit(instancename,
191 config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
192 siglisteners = []
193 def sig_handler(signum, frame):
194 if signum == signal.SIGUSR1:
195 # tell each account to do a full sync asap
196 signum = (1,)
197 elif signum == signal.SIGHUP:
198 # tell each account to die asap
199 signum = (2,)
200 elif signum == signal.SIGUSR2:
201 # tell each account to do a full sync asap, then die
202 signum = (1, 2)
203 # one listener per account thread (up to maxsyncaccounts)
204 for listener in siglisteners:
205 for sig in signum:
206 listener.put_nowait(sig)
207 signal.signal(signal.SIGHUP,sig_handler)
208 signal.signal(signal.SIGUSR1,sig_handler)
209 signal.signal(signal.SIGUSR2,sig_handler)
210
211 threadutil.initexitnotify()
212 t = ExitNotifyThread(target=syncmaster.syncitall,
213 name='Sync Runner',
214 kwargs = {'accounts': syncaccounts,
215 'config': config,
216 'siglisteners': siglisteners})
217 t.setDaemon(1)
218 t.start()
219 except:
220 ui.mainException()
221
222 try:
223 threadutil.exitnotifymonitorloop(threadutil.threadexited)
224 except SystemExit:
225 raise
226 except:
227 ui.mainException() # Also expected to terminate.
228
229