schedulator: only do weekdays, not weekends.
[wvapps.git] / evolution / exchangeit-client.cc
blobbd75652600fa3a21570a8bcc971dbaa639d0937a
1 #include "common.h"
2 #include "exchangeit-client.h"
3 #include "folder-database.h"
4 #include "contact-folder.h"
5 #include "calendar-folder.h"
7 #include <camel/camel-service.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
11 ExchangeITClient::ExchangeITClient(CamelURL * url)
12 : _url("http://%s:%s@%s:%s/%s", url->user, url->passwd, url->host, (url->port) ? url->port : 80, SYNC_PATH),
13 _user(url->user)
15 // Create the directories we need
16 WvString dir("%s/.exchangeit", g_get_home_dir());
17 mkdir(dir, 0755);
18 mkdir(WvString("%s/commands", dir), 0755);
19 mkdir(WvString("%s/timestamps", dir), 0755);
21 _wantQuit = false;
22 _list.append(&_pool, false);
23 _requestCount = 0;
26 // Converts from an ExchangeIT folder name to an Evolution folder name. If this
27 // user has access to folders from other users, the other users' folders are
28 // placed in /Public Folders/username. The 'user' parameter returns the username
29 // of the folder owner (u1 if the ExchangeIT folder name was u1.Contacts)
30 WvString ExchangeITClient::decodeFolderName(WvStringParm serverFolderName, WvString & user)
32 WvString folderName("");
33 WvStringList strlist;
34 strlist.split(serverFolderName, ".");
36 user = web_unescape(strlist.popstr());
37 if(user != _user)
38 folderName.append("/Public Folders/%s", user);
40 while(!strlist.isempty())
41 folderName.append("/%s", web_unescape(strlist.popstr()));
43 if(folderName == "")
44 folderName = "/";
45 return folderName;
48 // Converts from an Evolution folder name to an ExchangeIT folder name. Opposite
49 // operation as above.
50 WvString ExchangeITClient::encodeFolderName(WvStringParm clientFolderName)
52 WvString folderName;
53 if(strncasecmp(clientFolderName, "/Public Folders/", 16) == 0)
54 folderName = clientFolderName + 16;
55 else
56 folderName = WvString("%s/%s", _user, clientFolderName + 1);
58 folderName = strreplace(folderName, " ", "%20"); // 0x20 == ' '
59 folderName = strreplace(folderName, ".", "%2e"); // 0x2e == '.'
60 folderName = strreplace(folderName, "/", ".");
61 return folderName;
64 // Converts from an ExchangeIT folder type to an Evolution folder type. If the
65 // type is unknown, we assume it's of type "mail".
66 WvString ExchangeITClient::decodeFolderType(WvStringParm serverFolderType)
68 WvString temp = web_unescape(serverFolderType);
69 if(temp == "IPF.Contact")
70 return "contacts";
71 if(temp == "IPF.Appointment")
72 return "calendar";
73 return "mail";
76 // Converts from an Evolution folder type to an ExchangeIT folder type. If the
77 // type is unknown, we assume it's of type "*" (which is assumed to be of type
78 // mail).
79 WvString ExchangeITClient::encodeFolderType(WvStringParm clientFolderType)
81 if(clientFolderType == "contacts") // 0x2e == '.'
82 return "IPF%2eContact";
83 if(clientFolderType == "calendar")
84 return "IPF%2eAppointment";
85 return "*";
88 // converts an urlencoded string to a stream of bytes. This is NOT the same as
89 // web_unescape because web_unescape puts the decoded bytes into a WvString
90 // which cannot handle embedded null characters.
91 void ExchangeITClient::unhexify(WvStringParm str, WvDynBuf & buffer)
93 // Super-cheezy but hey, it works
94 for(const char * ptr = str.cstr(); *ptr; ptr++)
96 if(*ptr == '%')
98 unsigned char c = 0;
99 if(ptr[1] >= '0' && ptr[1] <= '9')
100 c += ptr[1] - '0';
101 else
102 c += ptr[1] - 'a' + 10;
103 c *= 16;
104 if(ptr[2] >= '0' && ptr[2] <= '9')
105 c += ptr[2] - '0';
106 else
107 c += ptr[2] - 'a' + 10;
108 buffer.put(&c, 1);
109 ptr += 2;
111 else
112 buffer.put(ptr, 1);
116 // Actually sends a request to the ExchangeIT server. This sends an HTTP POST
117 // message to the server using WvHttpPool.
118 void ExchangeITClient::request(WvStringParm reqStr, WvStreamCallback cb, void * cbdata = 0)
120 WvStringStream * req = new WvStringStream(reqStr);
121 req->seteof();
123 WvStream * s = _pool.addurl(_url, "POST", "", req);
124 s->setcallback(cb, cbdata);
126 _list.append(s, true);
127 _list.append(req, true);
130 void ExchangeITClient::mainLoop()
132 // We run in a separate thread. How do we know the user clicked on the
133 // "cancel" button in the send/receive dialog? Well, camel sets up a pipe
134 // for us and sends us a single byte down that pipe when "cancel" is
135 // clicked. camel_operation_cancel_fd(...) returns the file descriptor for
136 // that pipe. See camel/camel-operation.h for details.
137 WvFDStream * quitMsgStream = new WvFDStream(camel_operation_cancel_fd(0));
138 quitMsgStream->setcallback(quitMessage, this);
139 _list.append(quitMsgStream, true);
141 request("Version: 1\r\n\r\n. LISTFOLDERS\r\n\r\n", ::queryResult, this);
142 _requestCount++;
144 // Our main loop! (yes, a useless comment but come on, the main loop
145 // deserves respect!)
146 _wantQuit = false;
147 while(!_wantQuit)
149 _list.select(-1);
150 _list.callback();
153 // No longer need to wait on this cancel file descriptor -- maybe we'll get
154 // a different one next time. Remove it from our stream list.
155 _list.unlink(quitMsgStream);
158 void ExchangeITClient::queryResult(WvStream & stream)
160 WvString line;
161 while((line = stream.getline(0)))
162 if(line == "")
164 if(_replyBuffer.count() > 0)
166 WvStringList strlist;
167 strlist.split(*_replyBuffer.first(), " ");
169 if(strlist.count() == 1)
170 continue;
171 strlist.popstr(); // Ignore the tag
172 WvString reply = strlist.popstr();
174 if(reply == "FOLDERS")
175 listFoldersReply();
176 else if(reply == "USERS")
177 listUsersReply();
178 else if(reply == "UPDATE")
179 itemUpdateReply();
180 else if(reply == "OK")
181 syncTimeReply();
183 _replyBuffer.zap();
186 else
187 _replyBuffer.append(new WvString(line), true);
190 void ExchangeITClient::syncTimeReply()
192 // When a sync time is received, a single folder has been completely synced.
193 // We have now received a full response to a SYNC request.
194 _requestCount--;
196 WvStringList strlist;
197 strlist.split(_replyBuffer.popstr(), " ");
199 WvString folderName = decodeFolderName(strlist.popstr());
200 strlist.popstr(); // skip "OK"
201 WvString syncTime(strlist.popstr());
203 FolderDB & fdb = FolderDB::instance();
204 Folder * f = fdb.find(folderName);
206 if(f != 0)
207 f->setSyncTime(atoi(syncTime.cstr()));
209 // If we're not waiting for any more replies, we're done this cycle.
210 if(_requestCount == 0)
211 _wantQuit = true;
214 // Server wants us to update an item in a folder we're trying to sync...
215 // This message does not complete a request.
216 void ExchangeITClient::itemUpdateReply()
218 WvStringList strlist;
219 strlist.split(_replyBuffer.popstr(), " ");
220 strlist.popstr(); // skip the tag
221 strlist.popstr(); // skip "UPDATE"
223 WvString folderName = decodeFolderName(strlist.popstr());
224 WvString folderType = decodeFolderType(strlist.popstr());
225 WvString itemID = strlist.popstr();
227 FolderDB & fdb = FolderDB::instance();
228 Folder * f = fdb.find(folderName);
230 if(f == 0)
231 return;
233 if(folderType == "calendar")
236 else if(folderType == "contacts")
238 WvDynBuf buffer;
239 unhexify(_replyBuffer.popstr(), buffer);
240 WvTnef tnef(buffer);
242 if(tnef.isVCard())
244 ContactFolder::Item i;
245 i.itemID = itemID;
246 i.vcard = tnef.getVCard();
247 f->updateItem(i);
252 // Response to a LISTFOLDERS request.
253 void ExchangeITClient::listFoldersReply()
255 _requestCount--;
256 _replyBuffer.popstr(); // Skip the reply line
258 WvStringList folderNameList;
259 FolderDB & fdb = FolderDB::instance();
260 WvStringList::Iter i(_replyBuffer);
262 // Go through all the folder names...
263 for(i.rewind(); i.next();)
265 WvStringList strlist;
266 strlist.split(*i.ptr(), " ");
268 // Get folder name, folder class, and ACL (we ignore ACL for now)
269 WvString path = decodeFolderName(strlist.popstr());
270 WvString type = decodeFolderType(strlist.popstr());
271 strlist.popstr(); // ACL being ignored
273 if(path == "/")
274 continue;
276 // Create the folder if it doesn't exist and put it in the folder
277 // database
278 if(path.len() != 0 && fdb.find(path) == 0)
280 Folder * f = fdb.insert(path, type);
281 if(f != 0)
282 f->create();
284 folderNameList.add(new WvString(path), true);
287 // Remove all the folders that we have but the server doesn't
288 FolderDB::Iter j(FolderDB::instance());
289 for(j.rewind(); j.next(); )
291 int removeState = 0;
292 WvStringList::Iter k(folderNameList);
293 for(k.rewind(); k.next(); )
294 if(strstr(k->cstr(), j->getPath().cstr()) == k->cstr())
296 removeState = 1;
297 if(*k == j->getPath())
298 removeState = 2;
299 break;
302 // The fallthroughs are intentional
303 switch(removeState)
305 case 0: // The server doesn't have this folder and we shouldn't either so we delete it
306 j->remove();
307 case 1: // The server doesn't have this folder (but has a child). We don't want to sync this one.
308 j.xunlink();
309 case 2:
310 break;
311 default:
312 assert(removeState >= 0 && removeState <= 2);
313 break;
317 // Here we generate the sync request for all the folders...
318 WvString syncRequest("Version: 1\r\n\r\n");
319 for(j.rewind(); j.next();)
321 syncRequest.append("%s SYNC %s %s %s\r\n\r\n", encodeFolderName(j->getPath()), j->getSyncTime(), encodeFolderName(j->getPath()), encodeFolderType(j->getType()));
322 _requestCount++;
324 request(syncRequest, ::queryResult, this);
326 // If we don't need to sync any folders, we're done.
327 if(_requestCount == 0)
328 _wantQuit = true;
331 // Response to a LISTUSERS request.
332 void ExchangeITClient::listUsersReply()
334 _requestCount--;
337 ExchangeITClient::~ExchangeITClient()
339 // Clear out our stream list
340 _list.zap();