]> code.delx.au - offlineimap/blob - offlineimap/init.py
526478c86c91106676ffb5da3c145dcf55901a21
[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 from offlineimap import imaplib2, imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
20 from offlineimap.localeval import LocalEval
21 from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
22 from offlineimap.ui import UIBase
23 import re, os, os.path, offlineimap, sys
24 from offlineimap.CustomConfig import CustomConfigParser
25 from threading import *
26 import threading, socket
27 from getopt import getopt
28 import signal
29
30 try:
31 import fcntl
32 hasfcntl = 1
33 except:
34 hasfcntl = 0
35
36 lockfd = None
37
38 def lock(config, ui):
39 global lockfd, hasfcntl
40 if not hasfcntl:
41 return
42 lockfd = open(config.getmetadatadir() + "/lock", "w")
43 try:
44 fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
45 except IOError:
46 ui.locked()
47 ui.terminate(1)
48
49 def startup(versionno):
50 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)
51 options = {}
52 if '--help' in sys.argv[1:]:
53 sys.stdout.write(version.getcmdhelp() + "\n")
54 sys.exit(0)
55
56 for optlist in getopt(sys.argv[1:], 'P:1oqa:c:d:l:u:hk:f:')[0]:
57 options[optlist[0]] = optlist[1]
58
59 if options.has_key('-h'):
60 sys.stdout.write(version.getcmdhelp())
61 sys.stdout.write("\n")
62 sys.exit(0)
63 configfilename = os.path.expanduser("~/.offlineimaprc")
64 if options.has_key('-c'):
65 configfilename = options['-c']
66 if options.has_key('-P'):
67 if not options.has_key('-1'):
68 sys.stderr.write("FATAL: profile mode REQUIRES -1\n")
69 sys.exit(100)
70 profiledir = options['-P']
71 os.mkdir(profiledir)
72 threadutil.setprofiledir(profiledir)
73 sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n")
74
75 config = CustomConfigParser()
76 if not os.path.exists(configfilename):
77 sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename)
78 sys.exit(1)
79
80 config.read(configfilename)
81
82 # override config values with option '-k'
83 for option in options.keys():
84 if option == '-k':
85 (key, value) = options['-k'].split('=', 1)
86 if ':' in key:
87 (secname, key) = key.split(':', 1)
88 section = secname.replace("_", " ")
89 else:
90 section = "general"
91 config.set(section, key, value)
92
93 ui = offlineimap.ui.detector.findUI(config, options.get('-u'))
94 UIBase.setglobalui(ui)
95
96 if options.has_key('-l'):
97 ui.setlogfd(open(options['-l'], 'wt'))
98
99 ui.init_banner()
100
101 if options.has_key('-d'):
102 for debugtype in options['-d'].split(','):
103 ui.add_debug(debugtype.strip())
104 if debugtype == 'imap':
105 imaplib2.Debug = 5
106 if debugtype == 'thread':
107 threading._VERBOSE = 1
108
109 if options.has_key('-o'):
110 # FIXME: maybe need a better
111 for section in accounts.getaccountlist(config):
112 config.remove_option('Account ' + section, "autorefresh")
113
114 if options.has_key('-q'):
115 for section in accounts.getaccountlist(config):
116 config.set('Account ' + section, "quick", '-1')
117
118 if options.has_key('-f'):
119 foldernames = options['-f'].replace(" ", "").split(",")
120 folderfilter = "lambda f: f in %s" % foldernames
121 folderincludes = "[]"
122 for accountname in accounts.getaccountlist(config):
123 account_section = 'Account ' + accountname
124 remote_repo_section = 'Repository ' + \
125 config.get(account_section, 'remoterepository')
126 local_repo_section = 'Repository ' + \
127 config.get(account_section, 'localrepository')
128 for section in [remote_repo_section, local_repo_section]:
129 config.set(section, "folderfilter", folderfilter)
130 config.set(section, "folderincludes", folderincludes)
131
132 lock(config, ui)
133
134 def sigterm_handler(signum, frame):
135 # die immediately
136 ui.terminate(errormsg="terminating...")
137 signal.signal(signal.SIGTERM,sigterm_handler)
138
139 try:
140 pidfd = open(config.getmetadatadir() + "/pid", "w")
141 pidfd.write(str(os.getpid()) + "\n")
142 pidfd.close()
143 except:
144 pass
145
146 try:
147 if options.has_key('-l'):
148 sys.stderr = ui.logfile
149
150 socktimeout = config.getdefaultint("general", "socktimeout", 0)
151 if socktimeout > 0:
152 socket.setdefaulttimeout(socktimeout)
153
154 activeaccounts = config.get("general", "accounts")
155 if options.has_key('-a'):
156 activeaccounts = options['-a']
157 activeaccounts = activeaccounts.replace(" ", "")
158 activeaccounts = activeaccounts.split(",")
159 allaccounts = accounts.AccountHashGenerator(config)
160
161 syncaccounts = {}
162 for account in activeaccounts:
163 if account not in allaccounts:
164 if len(allaccounts) == 0:
165 errormsg = 'The account "%s" does not exist because no accounts are defined!'%account
166 else:
167 errormsg = 'The account "%s" does not exist. Valid accounts are:'%account
168 for name in allaccounts.keys():
169 errormsg += '\n%s'%name
170 ui.terminate(1, errortitle = 'Unknown Account "%s"'%account, errormsg = errormsg)
171 syncaccounts[account] = allaccounts[account]
172
173 server = None
174 remoterepos = None
175 localrepos = None
176
177 if options.has_key('-1'):
178 threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
179 else:
180 threadutil.initInstanceLimit("ACCOUNTLIMIT",
181 config.getdefaultint("general", "maxsyncaccounts", 1))
182
183 for reposname in config.getsectionlist('Repository'):
184 for instancename in ["FOLDER_" + reposname,
185 "MSGCOPY_" + reposname]:
186 if options.has_key('-1'):
187 threadutil.initInstanceLimit(instancename, 1)
188 else:
189 threadutil.initInstanceLimit(instancename,
190 config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
191 siglisteners = []
192 def sig_handler(signum, frame):
193 if signum == signal.SIGUSR1:
194 # tell each account to do a full sync asap
195 signum = (1,)
196 elif signum == signal.SIGHUP:
197 # tell each account to die asap
198 signum = (2,)
199 elif signum == signal.SIGUSR2:
200 # tell each account to do a full sync asap, then die
201 signum = (1, 2)
202 # one listener per account thread (up to maxsyncaccounts)
203 for listener in siglisteners:
204 for sig in signum:
205 listener.put_nowait(sig)
206 signal.signal(signal.SIGHUP,sig_handler)
207 signal.signal(signal.SIGUSR1,sig_handler)
208 signal.signal(signal.SIGUSR2,sig_handler)
209
210 threadutil.initexitnotify()
211 t = ExitNotifyThread(target=syncmaster.syncitall,
212 name='Sync Runner',
213 kwargs = {'accounts': syncaccounts,
214 'config': config,
215 'siglisteners': siglisteners})
216 t.setDaemon(1)
217 t.start()
218 except:
219 ui.mainException()
220
221 try:
222 threadutil.exitnotifymonitorloop(threadutil.threadexited)
223 except SystemExit:
224 raise
225 except:
226 ui.mainException() # Also expected to terminate.
227
228