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>
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
),
15 // Create the directories we need
16 WvString
dir("%s/.exchangeit", g_get_home_dir());
18 mkdir(WvString("%s/commands", dir
), 0755);
19 mkdir(WvString("%s/timestamps", dir
), 0755);
22 _list
.append(&_pool
, false);
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("");
34 strlist
.split(serverFolderName
, ".");
36 user
= web_unescape(strlist
.popstr());
38 folderName
.append("/Public Folders/%s", user
);
40 while(!strlist
.isempty())
41 folderName
.append("/%s", web_unescape(strlist
.popstr()));
48 // Converts from an Evolution folder name to an ExchangeIT folder name. Opposite
49 // operation as above.
50 WvString
ExchangeITClient::encodeFolderName(WvStringParm clientFolderName
)
53 if(strncasecmp(clientFolderName
, "/Public Folders/", 16) == 0)
54 folderName
= clientFolderName
+ 16;
56 folderName
= WvString("%s/%s", _user
, clientFolderName
+ 1);
58 folderName
= strreplace(folderName
, " ", "%20"); // 0x20 == ' '
59 folderName
= strreplace(folderName
, ".", "%2e"); // 0x2e == '.'
60 folderName
= strreplace(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")
71 if(temp
== "IPF.Appointment")
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
79 WvString
ExchangeITClient::encodeFolderType(WvStringParm clientFolderType
)
81 if(clientFolderType
== "contacts") // 0x2e == '.'
82 return "IPF%2eContact";
83 if(clientFolderType
== "calendar")
84 return "IPF%2eAppointment";
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
++)
99 if(ptr
[1] >= '0' && ptr
[1] <= '9')
102 c
+= ptr
[1] - 'a' + 10;
104 if(ptr
[2] >= '0' && ptr
[2] <= '9')
107 c
+= ptr
[2] - 'a' + 10;
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
);
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);
144 // Our main loop! (yes, a useless comment but come on, the main loop
145 // deserves respect!)
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
)
161 while((line
= stream
.getline(0)))
164 if(_replyBuffer
.count() > 0)
166 WvStringList strlist
;
167 strlist
.split(*_replyBuffer
.first(), " ");
169 if(strlist
.count() == 1)
171 strlist
.popstr(); // Ignore the tag
172 WvString reply
= strlist
.popstr();
174 if(reply
== "FOLDERS")
176 else if(reply
== "USERS")
178 else if(reply
== "UPDATE")
180 else if(reply
== "OK")
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.
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
);
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)
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
);
233 if(folderType
== "calendar")
236 else if(folderType
== "contacts")
239 unhexify(_replyBuffer
.popstr(), buffer
);
244 ContactFolder::Item i
;
246 i
.vcard
= tnef
.getVCard();
252 // Response to a LISTFOLDERS request.
253 void ExchangeITClient::listFoldersReply()
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
276 // Create the folder if it doesn't exist and put it in the folder
278 if(path
.len() != 0 && fdb
.find(path
) == 0)
280 Folder
* f
= fdb
.insert(path
, type
);
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(); )
292 WvStringList::Iter
k(folderNameList
);
293 for(k
.rewind(); k
.next(); )
294 if(strstr(k
->cstr(), j
->getPath().cstr()) == k
->cstr())
297 if(*k
== j
->getPath())
302 // The fallthroughs are intentional
305 case 0: // The server doesn't have this folder and we shouldn't either so we delete it
307 case 1: // The server doesn't have this folder (but has a child). We don't want to sync this one.
312 assert(removeState
>= 0 && removeState
<= 2);
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()));
324 request(syncRequest
, ::queryResult
, this);
326 // If we don't need to sync any folders, we're done.
327 if(_requestCount
== 0)
331 // Response to a LISTUSERS request.
332 void ExchangeITClient::listUsersReply()
337 ExchangeITClient::~ExchangeITClient()
339 // Clear out our stream list