/* Miranda IM Help Plugin Copyright (C) 2002 Richard Hughes, 2005-2007 H. Herkenrath This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (Help-License.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #define _WIN32_WINNT 0x0500 #define __RPCASYNC_H__ /* header shows warnings in VS6 */ #include #include #define MIRANDA_VER 0x0600 #include #include #include #include #include #include #include #include #include #include "m_help.h" #include "help.h" #include "unzip.h" #include "resource.h" #include "version.h" /* Lang Dlg */ static HANDLE hServiceShowLangDialog; /* Misc */ extern HINSTANCE hInst; static HANDLE hHookModulesLoaded,hHookPreShutdown; static HANDLE hNetlibUser; /************************* Download Update ****************************/ static BOOL FillUserAgent(char *pszUserAgent,int nSize) { char *pspace,szMirandaVer[32]; if(!CallService(MS_SYSTEM_GETVERSIONTEXT,SIZEOF(szMirandaVer),(LPARAM)szMirandaVer)) { pspace=strchr(szMirandaVer,' '); if(pspace) { *pspace++='\0'; mir_snprintf(pszUserAgent,nSize,"Miranda/%s (%s) HelpPlugin/%s",szMirandaVer,pspace,USERAGENT_VERSION); } else mir_snprintf(pszUserAgent,nSize,"Miranda/%s HelpPlugin/%s",szMirandaVer,USERAGENT_VERSION); return TRUE; } return FALSE; } static void EnsurePackHeader(HANDLE hFile,const char *pszFileVersionHeader,const char *pszTitle,const char *pszVersion) { DWORD dataSize,read; BYTE *pData,*pBuf; int headerLength,infoLength; /* file size */ dataSize=GetFileSize(hFile,NULL); if(dataSize==INVALID_FILE_SIZE) return; headerLength=lstrlenA(pszFileVersionHeader); pData=(BYTE*)mir_alloc(dataSize); infoLength=lstrlenA(pszTitle)+lstrlenA(pszVersion)+27; pBuf=(BYTE*)mir_alloc(infoLength); if(pData!=NULL && pBuf!=NULL) { /* compare header */ Netlib_Logf(hNetlibUser,"Ensure pack header"); SetFilePointer(hFile,0,NULL,FILE_BEGIN); read=0; if(ReadFile(hFile,pData,headerLength,&read,NULL)) { if(!strncmp(pszFileVersionHeader,(char*)pData,read)) { /* read all data */ read=0; if(ReadFile(hFile,pData,dataSize-headerLength,&read,NULL)) { dataSize=read; /* prepend header */ SetFilePointer(hFile,headerLength,NULL,FILE_BEGIN); wsprintfA((char*)pBuf,"\r\nX-FLName: %s\r\nX-Version: %s",pszTitle,pszVersion); /* buffer safe */ WriteFile(hFile,pBuf,infoLength-1,&read,NULL); /* write it back */ if(!WriteFile(hFile,pData,dataSize,&read,NULL)) Netlib_Logf(hNetlibUser,"Write pack failed (%u)",GetLastError()); } else Netlib_Logf(hNetlibUser,"Read pack failed (%u)",GetLastError()); } else Netlib_Logf(hNetlibUser,"Invalid pack file format"); } else Netlib_Logf(hNetlibUser,"Read pack failed (%u)",GetLastError()); } mir_free(pData); /* does NULL check */ mir_free(pBuf); /* does NULL check */ } static void CleanupFileName(TCHAR *pszFileName,const char *pszVersion) { TCHAR *p,*p2; int len; TCHAR szVersion[16]; #if defined(_UNICODE) szVersion[0]=_T('\0'); MultiByteToWideChar(CP_ACP,0,pszVersion,-1,szVersion,SIZEOF(szVersion)); #else lstrcpyn(szVersion,pszVersion,SIZEOF(szVersion)); #endif /* disallow version indicator in filename */ p=_tcsstr(pszFileName,szVersion); if(p!=NULL) { len=lstrlen(szVersion); p2=CharPrev(pszFileName,p); if(*p2=='v') p2=CharPrev(pszFileName,p2); if(p2==pszFileName || (*p2!='-' && *p2!='_')) p2=p; MoveMemory(p2,p+len,(lstrlen(p+len)+1)*sizeof(TCHAR)); } /* lower case */ CharLower(pszFileName); /* replace any '-' by '_' */ for(p=pszFileName;*p!='\0';p=CharNext(p)) { if(*p=='-') *p='_'; else if(*p==' ') *p='_'; } /* disallow digits */ for(p=pszFileName;*p!='\0';p=CharNext(p)) if(isdigit(*p)) { p2=CharPrev(pszFileName,p); if(*p2=='-' || *p2=='_') p=p2; *p='.'; /* handle together with dots */ break; } /* disallow any dots except extension */ p=_tcschr(pszFileName,'.'); p2=_tcsrchr(pszFileName,'.'); if(p!=NULL && p2!=NULL) MoveMemory(p,p2,(lstrlen(p2)+1)*sizeof(TCHAR)); } // pszFileName is allowed to be NULL, uses obtained file name in this case static BOOL DownloadPack(WORD id,const TCHAR *pszFilePattern,BOOL fEnabledPacks,const TCHAR *pszFileName,const char *pszFileVersionHeader,const char *pszTitle,const char *pszVersion) { TCHAR *pszFilePatternPrefix,*pszFilePatternExt; NETLIBHTTPREQUEST req,*resp; NETLIBHTTPHEADER headers[1]; char szUrl[64],szUserAgent[128]; BOOL fSuccess=FALSE; TCHAR *pszContentFileName,*pszExt; TCHAR szFileName[MAX_PATH]; HANDLE hFile; DWORD written; int i; #if defined(_UNICODE) TCHAR szContentDisp[MAX_PATH]; #endif /* split file pattern */ pszFilePatternPrefix=lstrcpy((TCHAR*)_alloca(lstrlen(pszFilePattern)+1),pszFilePattern); pszFilePatternExt=_tcschr(pszFilePatternPrefix,_T('*')); /* can't fail, static input */ *(pszFilePatternExt++)='\0'; ZeroMemory(&req,sizeof(req)); req.cbSize=sizeof(req); req.requestType=REQUEST_GET; mir_snprintf(szUrl,sizeof(szUrl),"http://addons.miranda-im.org/feed.php?dlfile=%u",id); req.szUrl=szUrl; req.flags=NLHRF_HTTP11|NLHRF_NODUMP; headers[0].szName="User-Agent"; headers[0].szValue=szUserAgent; if(FillUserAgent(szUserAgent,SIZEOF(szUserAgent))) req.headersCount=1; req.headers=headers; Netlib_Logf(hNetlibUser,"Download pack: %s (ver:%s)",szUrl,pszVersion); resp=(NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION,(WPARAM)hNetlibUser,(LPARAM)&req); if(resp!=NULL) { /* get content filename */ pszContentFileName=pszExt=NULL; for(i=0;iheadersCount;++i) if(!lstrcmpA(resp->headers[i].szName,"Content-Disposition")) { #if defined(_UNICODE) szContentDisp[0]=_T('\0'); MultiByteToWideChar(CP_ACP,0,resp->headers[i].szValue,-1,szContentDisp,SIZEOF(szContentDisp)); pszContentFileName=_tcsrchr(szContentDisp,_T('=')); pszExt=_tcsrchr(szContentDisp,_T('.')); #else pszContentFileName=_tcsrchr(resp->headers[i].szValue,_T('=')); pszExt=_tcsrchr(resp->headers[i].szValue,_T('.')); #endif break; } /* valid result? */ if(resp->resultCode==200 && pszContentFileName!=NULL && pszExt!=NULL && resp->dataLength) { ++pszContentFileName; /* txt-file, save directly */ if(!lstrcmpi(pszExt,pszFilePatternExt)) { Netlib_Logf(hNetlibUser,"txt-data ("TCHAR_STR_PARAM")",pszContentFileName); /* proper file name */ if(pszFileName!=NULL) pszContentFileName=(TCHAR*)pszFileName; else CleanupFileName(pszContentFileName,pszVersion); /* write file */ if(GetPackPath(szFileName,SIZEOF(szFileName),fEnabledPacks,pszContentFileName)) { hFile=CreateFile(szFileName,GENERIC_WRITE|GENERIC_READ,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0); if(hFile!=INVALID_HANDLE_VALUE) { Netlib_Logf(hNetlibUser,"Output: "TCHAR_STR_PARAM,szFileName); fSuccess=WriteFile(hFile,resp->pData,resp->dataLength,&written,NULL); if(fSuccess) EnsurePackHeader(hFile,pszFileVersionHeader,pszTitle,pszVersion); CloseHandle(hFile); /* remove duplicate */ if(fEnabledPacks && GetPackPath(szFileName,SIZEOF(szFileName),FALSE,pszContentFileName)) DeleteFile(szFileName); } if(!fSuccess) Netlib_Logf(hNetlibUser,"Write failed (%u)",GetLastError()); } else Netlib_Logf(hNetlibUser,"Write failed"); } /* zip-file, unzip txt-file in it */ else { HZIP hz; ZIPENTRY ze; int index=0; BOOL fFound=FALSE; /* open zip */ Netlib_Logf(hNetlibUser,"zip-data ("TCHAR_STR_PARAM")",pszContentFileName); hz=OpenZipMem(resp->pData,resp->dataLength,NULL); if(hz!=NULL) { Netlib_Logf(hNetlibUser,"Search "TCHAR_STR_PARAM" in zip",pszFilePattern); /* enum pack file in zip */ while(!FindZipItem(hz,pszFilePattern,TRUE,&index,&ze)) { /* skip some confusingly named files */ if(_tcsstr(ze.name,_T("readme"))==NULL && _tcsstr(ze.name,_T("history"))==NULL) { fFound=TRUE; Netlib_Logf(hNetlibUser,"Found "TCHAR_STR_PARAM" in zip",ze.name); /* proper file name */ if(pszFileName!=NULL) pszContentFileName=(TCHAR*)pszFileName; else CleanupFileName(pszContentFileName=ze.name,pszVersion); /* open file */ if(GetPackPath(szFileName,SIZEOF(szFileName),fEnabledPacks,pszContentFileName)) { hFile=CreateFile(szFileName,GENERIC_WRITE|GENERIC_READ,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0); if(hFile!=INVALID_HANDLE_VALUE) { Netlib_Logf(hNetlibUser,"Output: "TCHAR_STR_PARAM,szFileName); /* unzip */ fSuccess=!UnzipItemHandle(hz,ze.index,hFile); if(fSuccess) EnsurePackHeader(hFile,pszFileVersionHeader,pszTitle,pszVersion); else Netlib_Logf(hNetlibUser,"Invalid zip-item"); CloseHandle(hFile); /* remove duplicate */ if(fEnabledPacks && GetPackPath(szFileName,SIZEOF(szFileName),FALSE,pszContentFileName)) DeleteFile(szFileName); } else Netlib_Logf(hNetlibUser,"Write failed (%u)",GetLastError()); } else Netlib_Logf(hNetlibUser,"Write failed (%u)",GetLastError()); } ++index; /* skip current */ } CloseZip(hz); if(!fFound) Netlib_Logf(hNetlibUser,"No match found"); } else Netlib_Logf(hNetlibUser,"Invalid zip-data"); } } else Netlib_Logf(hNetlibUser,"No acceptable response from HTTP request"); CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp); } else Netlib_Logf(hNetlibUser,"No response from HTTP request"); return fSuccess; } /************************* Enum Updates *******************************/ #define CACHETIME 23*60*60 // refresh cache every 23 hours (seconds) typedef struct { char *pszTitle; WORD id; char *pszVersion; char *pszSubCategory; FILETIME ftFileDate; } UPDATE_INFO; typedef BOOL (CALLBACK *ENUM_UPDATES_CALLBACK)(UPDATE_INFO *upd,WORD nRemaining,WPARAM wParam,LPARAM lParam); // similar to strstr but allowes the searched buffer to contain zeros static BYTE* FindStrInBlobI(const BYTE *pData,const BYTE *pLast,const char *pszStr) { register BYTE *p; int i; for(p=(BYTE*)pData;p<=pLast;++p) { for(i=0;pszStr[i] && (p+i)<=pLast;++i) if(_tolower(*(p+i))!=_tolower(pszStr[i])) break; if(pszStr[i]=='\0') return (BYTE*)p; } return NULL; } // pszBeginTag is allowed to be NULL // returned value points into the buffer, returns an empty string on error static char* GetTagValue(const BYTE *pBlock,int blockLength,const char *pszBeginTag,const char *pszEndTag) { register BYTE *p,*pLast,*pVal; pLast=(BYTE*)pBlock+blockLength-1; /* begin tag */ if(pszBeginTag!=NULL) { pVal=FindStrInBlobI(pBlock,pLast,pszBeginTag); if(pVal==NULL) return ""; /* static */ pVal+=lstrlenA(pszBeginTag); } else pVal=(BYTE*)pBlock; /* end tag */ p=FindStrInBlobI(pBlock,pLast,pszEndTag); if(p==NULL) return ""; /* static */ *p='\0'; return (char*)pVal; } // pBlock and blockLength must be set to the previous block before call static BOOL GetNextBlock(const BYTE *pData,int dataLength,const char *pszBlockBeginTag,const char *pszBlockEndTag,BYTE **ppBlock,int *blockLength) { register BYTE *p,*pLast; pLast=(BYTE*)pData+dataLength-1; /* begin tag */ p=FindStrInBlobI((*ppBlock)+(*blockLength),pLast,pszBlockBeginTag); if(p==NULL) return FALSE; *ppBlock=p+lstrlenA(pszBlockBeginTag); /* end tag */ p=FindStrInBlobI(p,pLast,pszBlockEndTag); if(p==NULL) return FALSE; *blockLength=p-(*ppBlock); return TRUE; } // ppsz points just after first occurence of chr on return static WORD ParseWord(char **ppsz,int chr) { register char *p1,*p2; p1=strchr(*ppsz,chr); if(p1==NULL) return 0; *p1='\0'; p2=*ppsz; *ppsz=p1+1; return (WORD)atoi(p2); } static void EnumUpdates_Worker(BYTE *pData,int dataLength,BOOL *pbSuccess,BOOL fAllowRefetch,ENUM_UPDATES_CALLBACK callback,WPARAM wParam,LPARAM lParam) { UPDATE_INFO upd; WORD nRemaining; SYSTEMTIME st; BYTE *pBlock; int blockLength; char *pszUpdated; /* count items */ pBlock=pData; blockLength=0; for(nRemaining=0;GetNextBlock(pData,dataLength,"","",&pBlock,&blockLength);++nRemaining); if(fAllowRefetch) Netlib_Logf(hNetlibUser,"Data sets: %u",nRemaining); /* parse data */ pBlock=pData; blockLength=0; ZeroMemory(&st,sizeof(st)); /* init unused members */ for(;;) { if(!GetNextBlock(pData,dataLength,"","",&pBlock,&blockLength)) break; --nRemaining; ZeroMemory(&upd,sizeof(upd)); upd.pszTitle=GetTagValue(pBlock,blockLength,"",""); upd.id=(WORD)atoi(GetTagValue(pBlock,blockLength,"","")); if(!upd.id) continue; /* never happens */ upd.pszVersion=GetTagValue(pBlock,blockLength,"",""); upd.pszSubCategory=GetTagValue(pBlock,blockLength,"",""); /* parse time */ pszUpdated=GetTagValue(pBlock,blockLength,"",""); st.wYear=ParseWord(&pszUpdated,'-'); /* time is formated as follows: */ st.wMonth=ParseWord(&pszUpdated,'-'); /* yyyy-mm-dd hh:mm:ss */ st.wDay=ParseWord(&pszUpdated,' '); st.wHour=ParseWord(&pszUpdated,':'); st.wMinute=ParseWord(&pszUpdated,':'); st.wSecond=(WORD)atoi(pszUpdated); SystemTimeToFileTime(&st,&upd.ftFileDate); /* callback */ *pbSuccess=callback(&upd,nRemaining,wParam,lParam); if(!*pbSuccess) break; } } static BOOL EnumUpdates(ENUM_UPDATES_CALLBACK callback,const TCHAR *pszDataFile,BOOL fAllowRefetch,WPARAM wParam,LPARAM lParam) { TCHAR szCacheFile[MAX_PATH],*p; HANDLE hFile; BOOL fSuccess=FALSE; if(fAllowRefetch) Netlib_Logf(hNetlibUser,"Enumerate data sets"); /* read from cache */ if(GetTempPath(SIZEOF(szCacheFile)-lstrlen(pszDataFile),szCacheFile)) { BYTE *pData; int dataLength; DWORD read,age; FILETIME ftLastWrite,ft; /* ensure last subdirectory exists */ CreateDirectory(szCacheFile,NULL); /* file name */ p=_tcsrchr(szCacheFile,_T('\\')); if(p!=NULL && *p!=_T('\\')) lstrcat(p,_T("\\")); /* buffer safe */ lstrcat(szCacheFile,pszDataFile); /* buffer safe */ /* try to open file */ hFile=CreateFile(szCacheFile,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0); if(fAllowRefetch) Netlib_Logf(hNetlibUser,"Try to read from cache ("TCHAR_STR_PARAM")...",szCacheFile); if(hFile!=INVALID_HANDLE_VALUE) { /* analyze file age */ /* last-write file time resolution is acceptable * on file systems for our calculation */ GetSystemTimeAsFileTime(&ft); if(GetFileTime(hFile,NULL,NULL,&ftLastWrite)) { ft.dwHighDateTime-=ftLastWrite.dwHighDateTime; ft.dwLowDateTime-=ftLastWrite.dwLowDateTime; /* calc elapsed seconds */ age=(DWORD)((((ULONGLONG)ft.dwHighDateTime)<<32)/10000000)+(ft.dwLowDateTime/10000000); if(ageresultCode==200 && resp->dataLength) { /* keep cache */ hFile=CreateFile(szCacheFile,GENERIC_WRITE,FILE_SHARE_READ,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0); if(hFile!=INVALID_HANDLE_VALUE) { if(WriteFile(hFile,resp->pData,resp->dataLength,&written,NULL)) Netlib_Logf(hNetlibUser,"Cache output: "TCHAR_STR_PARAM,szCacheFile); CloseHandle(hFile); } else Netlib_Logf(hNetlibUser,"Cache output failed (%u)",GetLastError()); EnumUpdates_Worker((BYTE*)resp->pData,resp->dataLength,&fSuccess,fAllowRefetch,callback,wParam,lParam); } else Netlib_Logf(hNetlibUser,"No acceptable response from HTTP request"); CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp); } else Netlib_Logf(hNetlibUser,"No response from HTTP request"); } return fSuccess; } /************************* Lang Dlg ***********************************/ static HWND hwndLangDlg; #define UPDATEANIMFRAMES 20 #define M_STARTANIM (WM_APP+1) #define M_STOPANIM (WM_APP+2) #define M_FINISH (WM_APP+3) struct DownloadSelectionParams { HWND hwndDlg; HWND hwndCombo; BOOL fDownloadAll; }; static BOOL CALLBACK DownloadLatestEnumProc(UPDATE_INFO *upd,WORD nRemaining,WPARAM wParam,LPARAM lParam) { UPDATE_INFO *updLast=(UPDATE_INFO*)lParam; /* find latest */ if(!lstrcmpA(upd->pszSubCategory,updLast->pszSubCategory)) if(!updLast->id || CompareFileTime(&upd->ftFileDate,&updLast->ftFileDate)>0) CopyMemory(updLast,upd,sizeof(UPDATE_INFO)); /* download */ if(!nRemaining && updLast->id) if(!DownloadPack(updLast->id,_T("helppack_*.txt"),(BOOL)wParam,NULL,"Miranda Help Pack Version 1",updLast->pszTitle,updLast->pszVersion)) { updLast->id=0; if((BOOL)wParam) return FALSE; /* stop it */ } return TRUE; } static void DownloadSelectionThread(struct DownloadSelectionParams *arg) { UPDATE_INFO updLast; int index,sel,count; int fSuccess=0; SendMessage(arg->hwndDlg,M_STARTANIM,0,0); sel=SendMessage(arg->hwndCombo,CB_GETCURSEL,0,0); updLast.pszTitle=NULL; /* disable all */ CorrectPacks(_T("helppack_*.txt"),_T("helppack_english.txt"),TRUE); /* enum subcategories */ count=SendMessage(arg->hwndCombo,CB_GETCOUNT,0,0); for(index=0;indexfDownloadAll && sel!=index) continue; /* get subcategory */ ZeroMemory(&updLast,sizeof(UPDATE_INFO)); updLast.pszSubCategory=(char*)SendMessage(arg->hwndCombo,CB_GETITEMDATA,index,0); if(updLast.pszSubCategory==NULL || (int)updLast.pszSubCategory==CB_ERR) { if(!arg->fDownloadAll) fSuccess=-1; continue; } /* show status */ if(arg->fDownloadAll) SendMessage(arg->hwndCombo,CB_SETCURSEL,index,0); /* download latest */ EnumUpdates(DownloadLatestEnumProc,_T("category_localisation.xml"),FALSE,sel==index,(LPARAM)&updLast); if(updLast.id) fSuccess=1; if(!IsWindowVisible(arg->hwndDlg)) break; /* canceled? */ if(!arg->fDownloadAll) break; } if(arg->fDownloadAll) SendMessage(arg->hwndCombo,CB_SETCURSEL,sel,0); /* ensure one is enabled */ CorrectPacks(_T("helppack_*.txt"),_T("helppack_english.txt"),FALSE); ReloadLangOptList(); SendMessage(arg->hwndDlg,M_STOPANIM,0,0); PostMessage(arg->hwndDlg,M_FINISH,fSuccess,0); mir_free(arg); } struct PopulateComboBoxParams { HWND hwndDlg; HWND hwndCombo; HWND hwndOk; }; static BOOL CALLBACK PopulateComboBoxEnumProc(UPDATE_INFO *upd,WORD nRemaining,WPARAM wParam,LPARAM lParam) { struct PopulateComboBoxParams *arg=(struct PopulateComboBoxParams*)wParam; int index; char *pszParam; UNREFERENCED_PARAMETER(nRemaining); /* stop enum if canceled */ if(!IsWindowVisible(arg->hwndDlg)) return FALSE; /* skip 'Others' section */ if(!lstrcmpA(upd->pszSubCategory,"Others")) return TRUE; /* find out if already in list */ for(index=SendMessage(arg->hwndCombo,CB_GETCOUNT,0,0);index!=CB_ERR;--index) { pszParam=(char*)SendMessage(arg->hwndCombo,CB_GETITEMDATA,index,0); if((int)pszParam==CB_ERR) continue; if(!lstrcmpA(upd->pszSubCategory,pszParam)) return TRUE; /* already there */ } /* insert new item */ index=SendMessageA(arg->hwndCombo,CB_ADDSTRING,0,(LPARAM)Translate(upd->pszSubCategory)); if(index!=CB_ERR) { SendMessage(arg->hwndCombo,CB_SETITEMDATA,index,(LPARAM)mir_strdup(upd->pszSubCategory)); /* select current UI language */ if(strstr(upd->pszSubCategory,(char*)lParam)!=NULL) SendMessage(arg->hwndCombo,CB_SETCURSEL,index,0); } return TRUE; } static void PopulateComboBoxThread(struct PopulateComboBoxParams *arg) { char szCurrentLang[64]; /* get current UI language name */ SendMessage(arg->hwndDlg,M_STARTANIM,0,0); if(!GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SENGLANGUAGE,szCurrentLang,SIZEOF(szCurrentLang))) szCurrentLang[0]='\0'; /* enum all language sections */ EnumUpdates(PopulateComboBoxEnumProc,_T("category_localisation.xml"),TRUE,(WPARAM)arg,(LPARAM)szCurrentLang); /* enable dialog */ SendMessage(arg->hwndDlg,M_STOPANIM,0,0); EnableWindow(arg->hwndOk,TRUE); /* close dialog (if canceled) */ if(!IsWindowVisible(arg->hwndDlg)) PostMessage(arg->hwndDlg,M_FINISH,(WPARAM)-1,0); mir_free(arg); } static BOOL CALLBACK DownloadLangDlgProc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { HWND hwndCombo; /* init dialog */ hwndLangDlg=hwndDlg; TranslateDialogDefault(hwndDlg); SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)LoadSkinnedIcon(SKINICON_OTHER_MIRANDA)); /* init combo box */ hwndCombo=GetDlgItem(hwndDlg,IDC_LANGCOMBO); SendMessage(hwndCombo,CB_SETLOCALE,(LCID)CallService(MS_LANGPACK_GETLOCALE,0,0),0); /* for sort order */ SendMessage(hwndCombo,CB_SETEXTENDEDUI,TRUE,0); SendMessage(hwndCombo,CB_INITSTORAGE,32,32*16); /* speed up */ if(SendMessageA(hwndCombo,CB_ADDSTRING,0,(LPARAM)Translate("English (default)"))!=CB_ERR) { SendMessage(hwndCombo,CB_SETCURSEL,0,0); SendMessage(hwndCombo,CB_SETITEMDATA,0,(LPARAM)NULL); } /* enum all language sections */ { struct PopulateComboBoxParams *arg; arg=(struct PopulateComboBoxParams *)mir_alloc(sizeof(struct PopulateComboBoxParams)); if(arg!=NULL) { arg->hwndDlg=hwndDlg; arg->hwndCombo=hwndCombo; arg->hwndOk=GetDlgItem(hwndDlg,IDOK); if(mir_forkthread(PopulateComboBoxThread,(void*)arg)==(DWORD)-1) mir_free(arg); } } return TRUE; } case WM_DESTROY: { int index; char *pszParam; HWND hwnd; /* release icon */ hwndLangDlg=NULL; CallService(MS_SKIN2_RELEASEICON,(WPARAM)SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)NULL),0); /* free memory (WM_DELETEITEM does not fit) */ hwnd=GetDlgItem(hwndDlg,IDC_LANGCOMBO); for(index=SendMessage(hwnd,CB_GETCOUNT,0,0);index!=CB_ERR;--index) { pszParam=(char*)SendMessage(hwnd,CB_GETITEMDATA,index,0); if((int)pszParam==CB_ERR) continue; SendMessage(hwnd,CB_DELETESTRING,index,0); mir_free(pszParam); /* does NULL check */ } return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: /* disable */ EnableWindow(GetDlgItem(hwndDlg,IDOK),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_LANGCOMBO),FALSE); EnableWindow(GetDlgItem(hwndDlg,IDC_DOWNLOADALL),FALSE); /* download */ { struct DownloadSelectionParams *arg; arg=(struct DownloadSelectionParams*)mir_alloc(sizeof(struct DownloadSelectionParams)); arg->hwndDlg=hwndDlg; arg->hwndCombo=GetDlgItem(hwndDlg,IDC_LANGCOMBO); arg->fDownloadAll=IsDlgButtonChecked(hwndDlg,IDC_DOWNLOADALL); if(arg!=NULL && mir_forkthread(DownloadSelectionThread,arg)!=(DWORD)-1) return TRUE; else mir_free(arg); /* does NULL check */ } // else fall through case IDCANCEL: /* this is WM_CLOSE, too */ if(!IsWindowEnabled(GetDlgItem(hwndDlg,IDOK))) ShowWindow(hwndDlg,SW_HIDE); /* worker thread will handle this */ else PostMessage(hwndDlg,M_FINISH,(WPARAM)-1,0); return TRUE; } break; case M_FINISH: if(wParam!=-1 && IsWindowVisible(hwndDlg)) { MSGBOXPARAMS mbp; mbp.cbSize=sizeof(mbp); mbp.hwndOwner=hwndDlg; /* we get destroyed along with parent */ mbp.lpszCaption=TranslateT("Help Pack Download finished"); if((BOOL)wParam) { mbp.dwStyle=MB_ICONINFORMATION; mbp.lpszText=TranslateT("The download succeeded!"); } else { mbp.dwStyle=MB_ICONERROR; mbp.lpszText=TranslateT("The download failed!\n\nThe help pack could not be downloaded or extracted."); } mbp.dwStyle|=MB_OK; mbp.dwLanguageId=LANGIDFROMLCID((LCID)CallService(MS_LANGPACK_GETLOCALE,0,0)); MessageBoxIndirect(&mbp); } DestroyWindow(hwndDlg); /* we must be in window thread to do this */ return TRUE; case M_STARTANIM: SetTimer(hwndDlg,1,100,NULL); // fall through case WM_TIMER: { TCHAR str[128]; int updateAnimFrame; HWND hwnd; hwnd=GetDlgItem(hwndDlg,IDC_LOADING); if(msg==M_STARTANIM) updateAnimFrame=0; else updateAnimFrame=GetWindowLong(hwnd,GWL_USERDATA); mir_sntprintf(str,SIZEOF(str),_T("%.*s%s%.*s"),updateAnimFrame%10,_T("........."),TranslateT("downloading"),updateAnimFrame%10,_T(".........")); SetWindowText(hwnd,str); if(++updateAnimFrame==UPDATEANIMFRAMES) updateAnimFrame=0; SetWindowLong(hwnd,GWL_USERDATA,updateAnimFrame); if(msg==M_STARTANIM) ShowWindow(hwnd,SW_SHOW); return TRUE; } case WM_CTLCOLORSTATIC: switch(GetDlgCtrlID((HWND)lParam)) { case IDC_LOADING: { COLORREF textCol,bgCol,newCol; int ratio,updateAnimFrame; updateAnimFrame=GetWindowLong(GetDlgItem(hwndDlg,IDC_LOADING),GWL_USERDATA); textCol=GetSysColor(COLOR_BTNTEXT); bgCol=GetSysColor(COLOR_3DFACE); ratio=abs(UPDATEANIMFRAMES/2-updateAnimFrame)*510/UPDATEANIMFRAMES; newCol=RGB(GetRValue(bgCol)+(GetRValue(textCol)-GetRValue(bgCol))*ratio/256, GetGValue(bgCol)+(GetGValue(textCol)-GetGValue(bgCol))*ratio/256, GetBValue(bgCol)+(GetBValue(textCol)-GetBValue(bgCol))*ratio/256); SetTextColor((HDC)wParam,newCol); SetBkColor((HDC)wParam,GetSysColor(COLOR_3DFACE)); return (BOOL)GetSysColorBrush(COLOR_3DFACE); } } break; case M_STOPANIM: KillTimer(hwndDlg,1); ShowWindow(GetDlgItem(hwndDlg,IDC_LOADING),SW_HIDE); return TRUE; } return FALSE; } int ServiceShowLangDialog(WPARAM wParam,LPARAM lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); if(Miranda_Terminated()) return 2; /* after pre-shutdown */ if(hwndLangDlg!=NULL && IsWindowVisible(hwndLangDlg)) { SetForegroundWindow(hwndLangDlg); return 0; } /* we can't allow a parent here as our window would get destroyed alongside with it */ return !CreateDialog(hInst,MAKEINTRESOURCE(IDD_DOWNLOADLANG),NULL,DownloadLangDlgProc); } /************************* Notify Dlg *********************************/ static HWND hwndNotifyDlg; struct NotifyDlgParams { const TCHAR *pszLanguage; const FILETIME *ftCurrentVersion; const FILETIME *ftNewVersion; }; static BOOL CALLBACK UpdateNotifyDlgProc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { struct NotifyDlgParams *arg=(struct NotifyDlgParams*)lParam; SYSTEMTIME stFileDate; TCHAR szDate[128]; LCID locale; hwndNotifyDlg=hwndDlg; TranslateDialogDefault(hwndDlg); /* show update */ SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)LoadSkinnedIcon(SKINICON_OTHER_MIRANDA)); SetDlgItemText(hwndDlg,IDC_LANGUAGE,TranslateTS(arg->pszLanguage)); locale=(LCID)CallService(MS_LANGPACK_GETLOCALE,0,0); szDate[0]=_T('\0'); if(FileTimeToSystemTime(arg->ftCurrentVersion,&stFileDate)) GetDateFormat(locale,DATE_SHORTDATE,&stFileDate,NULL,szDate,SIZEOF(szDate)); SetDlgItemText(hwndDlg,IDC_CURRENTVERSION,szDate); szDate[0]=_T('\0'); if(FileTimeToSystemTime(arg->ftNewVersion,&stFileDate)) GetDateFormat(locale,DATE_SHORTDATE,&stFileDate,NULL,szDate,SIZEOF(szDate)); SetDlgItemText(hwndDlg,IDC_NEWVERSION,szDate); /* make labels use bold font */ { HFONT hFont; LOGFONT lf; hFont=(HFONT)SendDlgItemMessage(hwndDlg,IDC_NEWVERSION,WM_GETFONT,0,0); if(GetObject(hFont,sizeof(lf),&lf)) { lf.lfWeight=FW_BOLD; hFont=CreateFontIndirect(&lf); } else hFont=NULL; SendDlgItemMessage(hwndDlg,IDC_NEWVERSION,WM_SETFONT,(WPARAM)hFont,0); SendDlgItemMessage(hwndDlg,IDC_NEWVERSIONLABEL,WM_SETFONT,(WPARAM)hFont,0); SendDlgItemMessage(hwndDlg,IDC_LANGUAGE,WM_SETFONT,(WPARAM)hFont,0); SendDlgItemMessage(hwndDlg,IDC_LANGUAGELABEL,WM_SETFONT,(WPARAM)hFont,0); SetWindowLong(hwndDlg,DWL_USER,(LONG)hFont); } return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: case IDCANCEL: /* this is WM_CLOSE, too */ EndDialog(hwndDlg,LOWORD(wParam)); return TRUE; } break; case WM_DESTROY: hwndNotifyDlg=NULL; IcoLib_ReleaseIcon((HICON)SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)NULL)); return TRUE; case WM_NCDESTROY: { HFONT hFont; hFont=(HFONT)GetWindowLong(hwndDlg,DWL_USER); if(hFont!=NULL) DeleteObject(hFont); return TRUE; } } return FALSE; } /************************* Marked Versions ****************************/ static void SetVersionMarked(WORD id,const char *pszVersion,BOOL fMarked) { char szSetting[18]; wsprintfA(szSetting,"NoVerNotify_%u",id); if(fMarked) DBWriteContactSettingString(NULL,"HelpPlugin",szSetting,pszVersion); else DBDeleteContactSetting(NULL,"HelpPlugin",szSetting); } static BOOL IsVersionMarked(WORD id,const char *pszVersion) { DBVARIANT dbv; BOOL fMarked=FALSE; char szSetting[18]; wsprintfA(szSetting,"NoVerNotify_%u",id); if(!DBGetContactSettingString(NULL,"HelpPlugin",szSetting,&dbv)) { fMarked=!lstrcmpA(dbv.pszVal,pszVersion); mir_free(dbv.pszVal); } return fMarked; } /************************* Error Output ***************************/ static void MessageBoxIndirectFree(MSGBOXPARAMSA *mbp) { MessageBoxIndirectA(mbp); mir_free((char*)mbp->lpszCaption); /* does NULL check */ mir_free((char*)mbp->lpszText); /* does NULL check */ mir_free(mbp); } static void ShowInfoMessage(BYTE flags,const char *pszTitle,const char *pszTextFmt,...) { char szText[256]; /* max for systray */ MSGBOXPARAMSA *mbp; va_list va; va_start(va,pszTextFmt); mir_vsnprintf(szText,SIZEOF(szText),pszTextFmt,va); va_end(va); if(ServiceExists(MS_CLIST_SYSTRAY_NOTIFY)) { MIRANDASYSTRAYNOTIFY msn; msn.cbSize=sizeof(msn); msn.szProto=NULL; msn.szInfoTitle=(char*)pszTitle; msn.szInfo=(char*)szText; msn.uTimeout=30000; /* max timeout */ msn.dwInfoFlags=flags; if(!CallServiceSync(MS_CLIST_SYSTRAY_NOTIFY,0,(LPARAM)&msn)) return; /* success */ } mbp=(MSGBOXPARAMSA*)mir_calloc(sizeof(*mbp)); if(mbp==NULL) return; mbp->cbSize=sizeof(*mbp); mbp->lpszCaption=mir_strdup(pszTitle); mbp->lpszText=mir_strdup(szText); mbp->dwStyle=MB_OK|MB_SETFOREGROUND|MB_TASKMODAL; mbp->dwLanguageId=LANGIDFROMLCID((LCID)CallService(MS_LANGPACK_GETLOCALE,0,0)); switch(flags&NIIF_ICON_MASK) { case NIIF_INFO: mbp->dwStyle|=MB_ICONINFORMATION; break; case NIIF_WARNING: mbp->dwStyle|=MB_ICONWARNING; break; case NIIF_ERROR: mbp->dwStyle|=MB_ICONERROR; } mir_forkthread(MessageBoxIndirectFree,mbp); } /************************* Update Check ************************************/ #define MINCHECKTIME 60*60 // check no more than once an hour (seconds) #define REALCHECKTIME 24*60*60 // check once every 24 hours (seconds) #define FIRSTCHECKTIME 15 // first check 15 seconds after startup (seconds) static UINT idUpdateTimer; static DWORD idUpdateThread; static BOOL CALLBACK FindCurrentPackEnumProc(HELPPACK_INFO *pack,WPARAM wParam,LPARAM lParam) { UPDATE_INFO *upd=(UPDATE_INFO*)wParam; if(!lstrcmpiA(upd->pszTitle,pack->szFLName)) { CopyMemory((HELPPACK_INFO*)lParam,pack,sizeof(HELPPACK_INFO)); return FALSE; /* stop if found */ } return TRUE; } static BOOL CALLBACK FindLatestUpdEnumProc(UPDATE_INFO *upd,WORD nRemaining,WPARAM wParam,LPARAM lParam) { UPDATE_INFO *updFind=(UPDATE_INFO*)lParam; UNREFERENCED_PARAMETER(nRemaining); /* is there any later item? */ if(!lstrcmpiA(upd->pszTitle,updFind->pszTitle)) if(CompareFileTime(&upd->ftFileDate,&updFind->ftFileDate)>0) { *(BOOL*)wParam=TRUE; return FALSE; } return TRUE; } static BOOL CALLBACK UpdateCheckEnumProc(UPDATE_INFO *upd,WORD nRemaining,WPARAM wParam,LPARAM lParam) { HELPPACK_INFO pack; UNREFERENCED_PARAMETER(nRemaining); UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); /* search if installed */ pack.szFLName[0]='\0'; EnumPacks(FindCurrentPackEnumProc,_T("helppack_*.txt"),"Miranda Help Pack Version 1",(WPARAM)upd,(LPARAM)&pack); if(Miranda_Terminated()) return FALSE; if(pack.szVersion[0] && pack.szFLName[0]) { BOOL fHasNewerWithSameName; Netlib_Logf(hNetlibUser,TCHAR_STR_PARAM" (name:\"%s\", ver:%s)",pack.szFileName,pack.szFLName,pack.szVersion); /* skip earlier items with that same name (if any) */ fHasNewerWithSameName=FALSE; /* tentatively */ EnumUpdates(FindLatestUpdEnumProc,_T("category_localisation.xml"),FALSE,(WPARAM)&fHasNewerWithSameName,(LPARAM)upd); if(Miranda_Terminated()) return FALSE; if(fHasNewerWithSameName) Netlib_Logf(hNetlibUser,"Skip old duplicate (name:\"%s\", ver:%s, cat:\"%s\")",upd->pszTitle,upd->pszVersion,upd->pszSubCategory); /* cheap version check, allows for literals */ else if(lstrcmpiA(pack.szVersion,upd->pszVersion)) { Netlib_Logf(hNetlibUser,"New version found (name:\"%s\", ver:%s, cat:\"%s\")",upd->pszTitle,upd->pszVersion,upd->pszSubCategory); /* do not notify again when canceled once */ if(!IsVersionMarked(upd->id,upd->pszVersion)) { /* show notify dlg */ struct NotifyDlgParams arg; BOOL fSuccess=FALSE; arg.pszLanguage=pack.szLanguage; arg.ftCurrentVersion=&pack.ftFileDate; arg.ftNewVersion=&upd->ftFileDate; if(Miranda_Terminated()) return FALSE; if(DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_UPDATENOTIFY),0,UpdateNotifyDlgProc,(LPARAM)&arg)==IDOK) { fSuccess=DownloadPack(upd->id,_T("helppack_*.txt"),pack.flags&HPF_ENABLED,pack.szFileName,"Miranda Help Pack Version 1",upd->pszTitle,upd->pszVersion); if(fSuccess) ShowInfoMessage(NIIF_INFO,Translate("Help Pack Update succeeded"),Translate("The help pack \"%s\" has been sucessfully downloaded and installed."),TranslateTS(pack.szLanguage)); else ShowInfoMessage(NIIF_ERROR,Translate("Help Pack Update failed"),Translate("The help pack \"%s\" could not be downloaded or extracted."),TranslateTS(pack.szLanguage)); } /* delete/add marked setting */ SetVersionMarked(upd->id,upd->pszVersion,!fSuccess); } else Netlib_Logf(hNetlibUser,"Already notified"); } else Netlib_Logf(hNetlibUser,"Same version found (name:\"%s\", ver:%s, cat:\"%s\")",upd->pszTitle,upd->pszVersion,upd->pszSubCategory); } return !Miranda_Terminated(); } static void UpdateCheckThread(void *unused) { UNREFERENCED_PARAMETER(unused); /* only if any pack installed */ if(EnumPacks(NULL,_T("helppack_*.txt"),"Miranda Help Pack Version 1",0,0)) EnumUpdates(UpdateCheckEnumProc,_T("category_localisation.xml"),TRUE,0,0); else Netlib_Logf(hNetlibUser,"No "TCHAR_STR_PARAM" file present",_T("helppack_*.txt")); } static void CALLBACK UpdateCheckTimer(HWND hwnd,UINT msg,UINT idTimer,DWORD dwTime) { UNREFERENCED_PARAMETER(msg); UNREFERENCED_PARAMETER(dwTime); if(idUpdateThread==-1 && DBGetContactSettingByte(NULL,"HelpPlugin","EnableHelpUpdates",SETTING_ENABLEHELPUPDATES_DEFAULT)) { Netlib_Logf(hNetlibUser,"Running update check"); idUpdateThread=mir_forkthread(UpdateCheckThread,(void*)dwTime); } idUpdateTimer=SetTimer(hwnd,idTimer,1000*MINCHECKTIME,UpdateCheckTimer); } /************************* Misc ***************************************/ static int UpdateModulesLoaded(WPARAM wParam,LPARAM lParam) { NETLIBUSER nlu; UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); /* Shared */ /* netlib module is not available before this hook */ ZeroMemory(&nlu,sizeof(nlu)); nlu.cbSize=sizeof(nlu); nlu.flags=NUF_OUTGOING|NUF_HTTPCONNS; nlu.szSettingsModule="HelpPlugin"; nlu.szDescriptiveName=Translate("Help Pack Update HTTP connection"); hNetlibUser=(HANDLE)CallService(MS_NETLIB_REGISTERUSER,0,(LPARAM)&nlu); /* Update Check */ idUpdateThread=(DWORD)-1; idUpdateTimer=SetTimer(NULL,idUpdateTimer,1000*FIRSTCHECKTIME,UpdateCheckTimer); /* First Run */ // disabled temporarily // if(!DBGetContactSettingByte(NULL,"HelpPlugin","FirstRun",0)) // if(!ServiceShowLangDialog(0,0)) // DBWriteContactSettingByte(NULL,"HelpPlugin","FirstRun",1); return 0; } static int UpdatePreShutdown(WPARAM wParam,LPARAM lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); /* Notify Dlg (let the notify thread go unwind) */ if(hwndNotifyDlg!=NULL) SendMessage(hwndNotifyDlg,WM_CLOSE,0,0); /* Lang Dlg (stop any running download enum) */ if(hwndLangDlg!=NULL) SendMessage(hwndLangDlg,WM_CLOSE,0,0); /* Update Check (stop timer messages) */ if(idUpdateTimer) KillTimer(NULL,idUpdateTimer); return 0; } void InitUpdate(void) { /* Update Check */ idUpdateTimer=0; /* Notify Dlg */ hwndNotifyDlg=NULL; /* Lang Dlg */ hwndLangDlg=NULL; hServiceShowLangDialog=CreateServiceFunction(MS_HELP_SHOWLANGDIALOG,ServiceShowLangDialog); /* Misc */ hHookModulesLoaded=HookEvent(ME_SYSTEM_MODULESLOADED,UpdateModulesLoaded); hHookPreShutdown=HookEvent(ME_SYSTEM_PRESHUTDOWN,UpdatePreShutdown); } void UninitUpdate(void) { /* Misc */ UnhookEvent(hHookModulesLoaded); UnhookEvent(hHookPreShutdown); /* Lang Dlg */ DestroyServiceFunction(hServiceShowLangDialog); }