]> code.delx.au - offlineimap/blob - offlineimap/init.py
Bug#502779: Sync accounts in order of general.accounts option
[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 if account not in syncaccounts:
172 syncaccounts.append(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