| 1 |
/* |
| 2 |
* Copyright (C) 2009-2016 TSUBAKIMOTO Hiroya <z0rac@users.sourceforge.jp> |
| 3 |
* |
| 4 |
* This software comes with ABSOLUTELY NO WARRANTY; for details of |
| 5 |
* the license terms, see the LICENSE.txt file included with the program. |
| 6 |
*/ |
| 7 |
#include "define.h" |
| 8 |
#include "mailbox.h" |
| 9 |
#include "setting.h" |
| 10 |
#include "winsock.h" |
| 11 |
#include "win32.h" |
| 12 |
#include "window.h" |
| 13 |
#include <algorithm> |
| 14 |
#include <cassert> |
| 15 |
#include <imagehlp.h> |
| 16 |
#include <shlobj.h> |
| 17 |
#include <shlwapi.h> |
| 18 |
|
| 19 |
#ifdef _DEBUG |
| 20 |
#include <iostream> |
| 21 |
#define DBG(s) s |
| 22 |
#define LOG(s) (cout << win32::time(time(NULL)) << "|" << s) |
| 23 |
#else |
| 24 |
#define DBG(s) |
| 25 |
#define LOG(s) |
| 26 |
#endif |
| 27 |
|
| 28 |
extern window* mascot(); |
| 29 |
extern window* summary(const mailbox*); |
| 30 |
|
| 31 |
// model - main model |
| 32 |
namespace { |
| 33 |
class model : public window::timer { |
| 34 |
static model* _model; |
| 35 |
struct mbox : public mailbox { |
| 36 |
unsigned period; |
| 37 |
unsigned next; |
| 38 |
string sound; |
| 39 |
mbox* fetch; |
| 40 |
mbox(const string& name) |
| 41 |
: mailbox(name), period(0), next(0) {} |
| 42 |
}; |
| 43 |
mailbox* _mailboxes; |
| 44 |
void _release(); |
| 45 |
void wakeup(window& source) { fetch(source, false); } |
| 46 |
public: |
| 47 |
model(); |
| 48 |
~model() { _release(); } |
| 49 |
const mailbox* mailboxes() const { return _mailboxes; } |
| 50 |
void cache(); |
| 51 |
model& fetch(window& source, bool force = true); |
| 52 |
bool fetching() const { return _fetching != 0; } |
| 53 |
private: |
| 54 |
// the class to control fetching |
| 55 |
win32::mex _key; |
| 56 |
DWORD _last; |
| 57 |
unsigned _fetching; |
| 58 |
list<mailbox*> _fetched; |
| 59 |
int _summary; |
| 60 |
HANDLE _idle; |
| 61 |
void _done(mbox& mb); |
| 62 |
static void _thread(void* param); |
| 63 |
}; |
| 64 |
model* model::_model = NULL; |
| 65 |
} |
| 66 |
|
| 67 |
model::model() |
| 68 |
: _mailboxes(NULL), _last(GetTickCount()), _fetching(0), _summary(0), |
| 69 |
_idle(win32::valid(CreateEvent(NULL, TRUE, TRUE, NULL))) |
| 70 |
{ |
| 71 |
try { |
| 72 |
mailbox* last = NULL; |
| 73 |
list<string> mbs = setting::mailboxes(); |
| 74 |
for (list<string>::iterator p = mbs.begin(); p != mbs.end(); ++p) { |
| 75 |
string name = *p; |
| 76 |
LOG("Load mailbox [" << name << "]" << endl); |
| 77 |
setting s = setting::mailbox(name); |
| 78 |
mbox* mb = new mbox(name); |
| 79 |
unique_ptr<mbox> hold(mb); |
| 80 |
int ip, verify; |
| 81 |
s["ip"](ip = 0); |
| 82 |
s["verify"](verify = 3); |
| 83 |
mb->uripasswd(s["uri"], s.cipher("passwd")) |
| 84 |
.domain(ip == 4 ? AF_INET : ip == 6 ? AF_INET6 : AF_UNSPEC) |
| 85 |
.verify(verify); |
| 86 |
int period; |
| 87 |
s["period"](period = 15); |
| 88 |
s["sound"].sep(0)(mb->sound); |
| 89 |
mb->period = period > 0 ? period * 60000U : 0; |
| 90 |
list<string> cache = setting::cache(mb->uristr()); |
| 91 |
mb->ignore(cache); |
| 92 |
hold.release(); |
| 93 |
last = last ? last->next(mb) : (_mailboxes = mb); |
| 94 |
} |
| 95 |
setting::cacheclear(); |
| 96 |
setting::preferences()["summary"]()(_summary); |
| 97 |
} catch (...) { |
| 98 |
_release(); |
| 99 |
throw; |
| 100 |
} |
| 101 |
_model = this; |
| 102 |
} |
| 103 |
|
| 104 |
void |
| 105 |
model::_release() |
| 106 |
{ |
| 107 |
cache(); |
| 108 |
for (mailbox* p = _mailboxes; p;) { |
| 109 |
mailbox* next = p->next(); |
| 110 |
delete p; |
| 111 |
p = next; |
| 112 |
} |
| 113 |
CloseHandle(_idle); |
| 114 |
} |
| 115 |
|
| 116 |
void |
| 117 |
model::cache() |
| 118 |
{ |
| 119 |
WaitForSingleObject(_idle, INFINITE); |
| 120 |
for (mailbox* p = _mailboxes; p; p = p->next()) { |
| 121 |
setting::cache(p->uristr(), p->ignore()); |
| 122 |
} |
| 123 |
} |
| 124 |
|
| 125 |
model& |
| 126 |
model::fetch(window& source, bool force) |
| 127 |
{ |
| 128 |
win32::mex::trylock lock(_key); |
| 129 |
if (!lock) { |
| 130 |
source.settimer(*this, 1); // delay to fetch. |
| 131 |
return *this; |
| 132 |
} |
| 133 |
LOG("Fetch mails..." << endl); |
| 134 |
unsigned next = 0; |
| 135 |
mbox* fetch = NULL; |
| 136 |
DWORD elapse = GetTickCount() - _last; |
| 137 |
_last += elapse; |
| 138 |
for (mailbox* p = _mailboxes; p; p = p->next()) { |
| 139 |
mbox* mb = static_cast<mbox*>(p); |
| 140 |
if (!mb->period) continue; // No fetching by the setting. |
| 141 |
if (force || mb->next <= elapse) { |
| 142 |
unsigned late = force ? 0 : (elapse - mb->next) % mb->period; |
| 143 |
mb->next = mb->period - late; |
| 144 |
mb->fetch = fetch, fetch = mb; |
| 145 |
} else { |
| 146 |
mb->next -= elapse; |
| 147 |
} |
| 148 |
if (!next || next > mb->next) next = mb->next; |
| 149 |
} |
| 150 |
source.settimer(*this, next); |
| 151 |
for (; fetch; fetch = fetch->fetch) { |
| 152 |
list<mailbox*>::iterator end = _fetched.end(); |
| 153 |
if (find(_fetched.begin(), end, fetch) == end) { |
| 154 |
CloseHandle(win32::thread(_thread, (void*)fetch)); |
| 155 |
if (_fetching++ == 0) { |
| 156 |
ResetEvent(_idle); |
| 157 |
window::broadcast(WM_APP, 0, 0); |
| 158 |
} |
| 159 |
_fetched.push_front(fetch); |
| 160 |
} |
| 161 |
} |
| 162 |
return *this; |
| 163 |
} |
| 164 |
|
| 165 |
void |
| 166 |
model::_done(mbox& mb) |
| 167 |
{ |
| 168 |
if (mb.recent() > 0 && !mb.sound.empty()) { |
| 169 |
LOG("Sound: " << mb.sound << endl); |
| 170 |
const char* name = mb.sound.c_str(); |
| 171 |
PlaySound(name, NULL, SND_NODEFAULT | SND_NOWAIT | SND_ASYNC| |
| 172 |
(*PathFindExtension(name) ? SND_FILENAME : SND_ALIAS)); |
| 173 |
} |
| 174 |
|
| 175 |
win32::mex::lock lock(_key); |
| 176 |
if (--_fetching) return; |
| 177 |
|
| 178 |
LOG("Done all fetching." << endl); |
| 179 |
size_t recent = 0; |
| 180 |
size_t unseen = 0; |
| 181 |
for (const mailbox* p = _mailboxes; p; p = p->next()) { |
| 182 |
int n = p->recent(); |
| 183 |
if (n > 0) recent += n; |
| 184 |
unseen += p->mails().size(); |
| 185 |
} |
| 186 |
window::broadcast(WM_APP, MAKEWPARAM(recent, unseen), LPARAM(&_fetched)); |
| 187 |
if (recent && _summary) { |
| 188 |
list<mailbox*>::const_iterator p = _fetched.begin(); |
| 189 |
while (p != _fetched.end() && (*p)->recent() <= 0) ++p; |
| 190 |
if (p != _fetched.end()) { |
| 191 |
window::broadcast(WM_COMMAND, MAKEWPARAM(0, ID_MENU_SUMMARY), 0); |
| 192 |
} |
| 193 |
} |
| 194 |
_fetched.clear(); |
| 195 |
LOG("***** HEAP SIZE [" << win32::cheapsize() << ", " |
| 196 |
<< win32::heapsize() << "] *****" << endl); |
| 197 |
SetEvent(_idle); |
| 198 |
} |
| 199 |
|
| 200 |
void |
| 201 |
model::_thread(void* param) |
| 202 |
{ |
| 203 |
mbox& mb = *reinterpret_cast<mbox*>(param); |
| 204 |
LOG("Start thread [" << mb.name() << "]." << endl); |
| 205 |
try { |
| 206 |
mb.fetchmail(); |
| 207 |
} catch (const exception& DBG(e)) { |
| 208 |
LOG(e.what() << endl); |
| 209 |
} catch (...) { |
| 210 |
LOG("Unknown exception." << endl); |
| 211 |
} |
| 212 |
LOG("End thread [" << mb.name() << "]." << endl); |
| 213 |
_model->_done(mb); |
| 214 |
} |
| 215 |
|
| 216 |
namespace { // misc. functions |
| 217 |
bool appendix(const char* file, char* path) |
| 218 |
{ |
| 219 |
return GetModuleFileName(NULL, path, MAX_PATH) < MAX_PATH && |
| 220 |
PathRemoveFileSpec(path) && PathAppend(path, file) && |
| 221 |
PathFileExists(path); |
| 222 |
} |
| 223 |
|
| 224 |
bool shell(const string& cmd) |
| 225 |
{ |
| 226 |
HANDLE h = win32::shell(cmd, SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT); |
| 227 |
if (!h) return false; |
| 228 |
WaitForSingleObject(h, INFINITE); |
| 229 |
CloseHandle(h); |
| 230 |
return true; |
| 231 |
} |
| 232 |
|
| 233 |
bool rundll(const char* arg) |
| 234 |
{ |
| 235 |
char s[MAX_PATH]; |
| 236 |
if (!appendix("extend.dll", s)) return false; |
| 237 |
win32::dll extend(s); |
| 238 |
if (!extend) return false; |
| 239 |
typedef void (*settingdlg)(HWND, HINSTANCE, LPCSTR, int); |
| 240 |
settingdlg fun = settingdlg(extend("settingdlg", NULL)); |
| 241 |
if (!fun) return false; |
| 242 |
fun(NULL, win32::exe, arg, SW_NORMAL); |
| 243 |
return true; |
| 244 |
} |
| 245 |
} |
| 246 |
|
| 247 |
#if USE_REG |
| 248 |
/** repository - registory added some features. |
| 249 |
*/ |
| 250 |
#define REG_KEY "Software\\GNU\\" APP_NAME |
| 251 |
namespace { |
| 252 |
class repository : public registory { |
| 253 |
public: |
| 254 |
repository() : registory(REG_KEY) {} |
| 255 |
bool edit(); |
| 256 |
}; |
| 257 |
} |
| 258 |
|
| 259 |
bool |
| 260 |
repository::edit() |
| 261 |
{ |
| 262 |
LOG("Edit setting." << endl); |
| 263 |
|
| 264 |
struct event { |
| 265 |
HANDLE h; |
| 266 |
event() : h(win32::valid(CreateEvent(NULL, FALSE, FALSE, NULL))) {} |
| 267 |
~event() { CloseHandle(h); } |
| 268 |
} event; |
| 269 |
|
| 270 |
struct key { |
| 271 |
HKEY h; |
| 272 |
key(const char* key) |
| 273 |
{ if (RegOpenKeyEx(HKEY_CURRENT_USER, key, |
| 274 |
0, KEY_NOTIFY, &h) != ERROR_SUCCESS) throw win32::error(); } |
| 275 |
~key() { RegCloseKey(h); } |
| 276 |
} key(REG_KEY); |
| 277 |
|
| 278 |
if (RegNotifyChangeKeyValue(key.h, TRUE, |
| 279 |
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, |
| 280 |
event.h, TRUE) != ERROR_SUCCESS) throw win32::error(); |
| 281 |
return rundll(REG_KEY) && WaitForSingleObject(event.h, 0) == WAIT_OBJECT_0; |
| 282 |
} |
| 283 |
#else // !USE_REG |
| 284 |
/** repository - profile added some features. |
| 285 |
*/ |
| 286 |
#define INI_FILE APP_NAME ".ini" |
| 287 |
namespace { |
| 288 |
class repository : public profile { |
| 289 |
static string _path; |
| 290 |
static const char* _prepare(); |
| 291 |
public: |
| 292 |
repository() : profile(_prepare()) {} |
| 293 |
bool edit(); |
| 294 |
}; |
| 295 |
string repository::_path; |
| 296 |
} |
| 297 |
|
| 298 |
const char* |
| 299 |
repository::_prepare() |
| 300 |
{ |
| 301 |
char path[MAX_PATH]; |
| 302 |
if (appendix(INI_FILE, path) || |
| 303 |
(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, |
| 304 |
NULL, SHGFP_TYPE_CURRENT, path) == 0 && |
| 305 |
PathAppend(path, APP_NAME "\\" INI_FILE) && |
| 306 |
MakeSureDirectoryPathExists(path))) { |
| 307 |
LOG("Using the setting file: " << path << endl); |
| 308 |
_path = path; |
| 309 |
HANDLE h = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, |
| 310 |
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); |
| 311 |
if (h != INVALID_HANDLE_VALUE) CloseHandle(h); |
| 312 |
return _path.c_str(); |
| 313 |
} |
| 314 |
return NULL; |
| 315 |
} |
| 316 |
|
| 317 |
bool |
| 318 |
repository::edit() |
| 319 |
{ |
| 320 |
LOG("Edit setting." << endl); |
| 321 |
if (_path.empty()) return false; |
| 322 |
|
| 323 |
WritePrivateProfileString(NULL, NULL, NULL, _path.c_str()); // flush entries. |
| 324 |
HANDLE fh = CreateFile(_path.c_str(), GENERIC_READ, |
| 325 |
FILE_SHARE_READ | FILE_SHARE_WRITE, |
| 326 |
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
| 327 |
if (fh == INVALID_HANDLE_VALUE) throw win32::error(); |
| 328 |
|
| 329 |
FILETIME before = { 0 }; |
| 330 |
GetFileTime(fh, NULL, NULL, &before); |
| 331 |
FILETIME after = before; |
| 332 |
(rundll(_path.c_str()) || shell('"' + _path + '"')) && |
| 333 |
GetFileTime(fh, NULL, NULL, &after); |
| 334 |
CloseHandle(fh); |
| 335 |
|
| 336 |
return CompareFileTime(&before, &after) != 0; |
| 337 |
} |
| 338 |
#endif // !USE_REG |
| 339 |
|
| 340 |
namespace cmd { |
| 341 |
struct fetch : public window::command { |
| 342 |
model& _model; |
| 343 |
fetch(model& model) : window::command(-265), _model(model) {} |
| 344 |
void execute(window& source) { _model.fetch(source); } |
| 345 |
UINT state(window&) { return _model.fetching() ? MFS_DISABLED : 0; } |
| 346 |
}; |
| 347 |
|
| 348 |
struct summary : public window::command { |
| 349 |
model& _model; |
| 350 |
unique_ptr<window> _summary; |
| 351 |
summary(model& model) : window::command(-281), _model(model) {} |
| 352 |
void execute(window&) |
| 353 |
{ |
| 354 |
LOG("Open the summary window." << endl); |
| 355 |
_summary.reset(); // to save preferences |
| 356 |
_summary.reset(::summary(_model.mailboxes())); |
| 357 |
} |
| 358 |
UINT state(window&) { return _model.fetching() ? MFS_DISABLED : 0; } |
| 359 |
}; |
| 360 |
|
| 361 |
struct setting : public window::command { |
| 362 |
repository& _rep; |
| 363 |
DWORD _tid; |
| 364 |
HANDLE _thread; |
| 365 |
setting(repository& rep) : window::command(-274), |
| 366 |
_rep(rep), _tid(GetCurrentThreadId()), _thread(NULL) {} |
| 367 |
~setting() { _thread && WaitForSingleObject(_thread, INFINITE); } |
| 368 |
void execute(window&) { if (!busy()) _thread = win32::thread(edit, (void*)this); } |
| 369 |
UINT state(window&) { return busy() ? MFS_DISABLED : 0; } |
| 370 |
static void edit(void* param) |
| 371 |
{ |
| 372 |
setting& self = *reinterpret_cast<setting*>(param); |
| 373 |
if (self._rep.edit()) PostThreadMessage(self._tid, WM_QUIT, 1, 0); |
| 374 |
} |
| 375 |
bool busy() |
| 376 |
{ |
| 377 |
if (_thread && WaitForSingleObject(_thread, 0) == WAIT_OBJECT_0) { |
| 378 |
CloseHandle(_thread); |
| 379 |
_thread = NULL; |
| 380 |
} |
| 381 |
return _thread != NULL; |
| 382 |
} |
| 383 |
}; |
| 384 |
|
| 385 |
struct exit : public window::command { |
| 386 |
exit() : window::command(-28) {} |
| 387 |
void execute(window&) { LOG("Exit." << endl); PostQuitMessage(0); } |
| 388 |
}; |
| 389 |
|
| 390 |
struct logoff : public window::command { |
| 391 |
model& _model; |
| 392 |
logoff(model& model) : _model(model) {} |
| 393 |
void execute(window&) { _model.cache(); } |
| 394 |
}; |
| 395 |
} |
| 396 |
|
| 397 |
/* |
| 398 |
* WinMain - main function |
| 399 |
*/ |
| 400 |
#if defined(_DEBUG) && defined(_MSC_VER) |
| 401 |
int main() |
| 402 |
#else |
| 403 |
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) |
| 404 |
#endif |
| 405 |
{ |
| 406 |
try { |
| 407 |
static win32 befoo("befoo:79585F30-DD15-446C-B414-152D31324970"); |
| 408 |
static winsock winsock; |
| 409 |
static repository rep; |
| 410 |
int delay; |
| 411 |
setting::preferences()["delay"](delay = 0); |
| 412 |
for (int qc = 1; qc > 0; delay = 0) { |
| 413 |
unique_ptr<window> w(mascot()); |
| 414 |
unique_ptr<model> m(new model); |
| 415 |
w->addcmd(ID_MENU_FETCH, new cmd::fetch(*m)); |
| 416 |
w->addcmd(ID_MENU_SUMMARY, new cmd::summary(*m)); |
| 417 |
w->addcmd(ID_MENU_SETTINGS, new cmd::setting(rep)); |
| 418 |
w->addcmd(ID_MENU_EXIT, new cmd::exit); |
| 419 |
w->addcmd(ID_EVENT_LOGOFF, new cmd::logoff(*m)); |
| 420 |
w->settimer(*m, delay > 0 ? delay * 1000 : 1); |
| 421 |
qc = window::eventloop(); |
| 422 |
} |
| 423 |
return 0; |
| 424 |
} catch (...) {} |
| 425 |
return -1; |
| 426 |
} |