Update: Support for automatic updates

Use Github REST API to get info about latest release (version,
changelog, installer url).
This commit is contained in:
ge0rdi
2020-09-03 20:39:16 +02:00
parent b094ddc5f9
commit 4883d13950
6 changed files with 132 additions and 165 deletions

View File

@@ -14,6 +14,7 @@
#include "FNVHash.h"
#include "StringUtils.h"
#include "Translations.h"
#include "json.hpp"
#include <wininet.h>
#include <softpub.h>
@@ -141,30 +142,9 @@ LRESULT CProgressDlg::OnCancel( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&
static bool g_bCheckingVersion;
static DWORD GetTimeStamp( const wchar_t *fname )
{
HANDLE h=CreateFile(fname,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (h==INVALID_HANDLE_VALUE)
return 0;
DWORD res=0;
DWORD q;
IMAGE_DOS_HEADER header;
if (ReadFile(h,&header,sizeof(header),&q,NULL) && q==sizeof(header))
{
if (SetFilePointer(h,header.e_lfanew+8,NULL,FILE_BEGIN)!=INVALID_SET_FILE_POINTER)
{
if (!ReadFile(h,&res,4,&q,NULL) || q!=4)
res=0;
}
}
CloseHandle(h);
return res;
}
enum TDownloadResult
{
DOWNLOAD_OK,
DOWNLOAD_SAMETIME,
DOWNLOAD_CANCEL,
// errors
@@ -176,8 +156,7 @@ enum TDownloadResult
// Downloads a file
// filename - returns the name of the downloaded file
// timestamp - if not zero, it is compared to the timestamp of the file and returns DOWNLOAD_SAMETIME if the same (and buf will be empty)
static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf, CString *pFilename, DWORD timestamp, bool bAcceptCached, CProgressDlg *pProgress, TSettingsComponent component )
static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf, CString *pFilename, bool bAcceptCached, CProgressDlg *pProgress, TSettingsComponent component )
{
const wchar_t *compName=L"Open-Shell";
switch (component)
@@ -264,7 +243,7 @@ static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf,
if (fileSize==0)
pProgress->SetProgress(-1);
}
int CHUNK_SIZE=timestamp?1024:32768; // start with small chunk to verify the timestamp
int CHUNK_SIZE=32768;
DWORD size=0;
buf.reserve(fileSize+CHUNK_SIZE);
while (1)
@@ -286,25 +265,6 @@ static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf,
size+=dwSize;
if (pProgress && fileSize)
pProgress->SetProgress(size*100/fileSize);
if (timestamp && (size<sizeof(IMAGE_DOS_HEADER) || buf[0]!='M' || buf[1]!='Z'))
{
res=DOWNLOAD_FAIL;
break;
}
if (timestamp && size>=sizeof(IMAGE_DOS_HEADER))
{
DWORD pos=((IMAGE_DOS_HEADER*)&buf[0])->e_lfanew+8;
if (size>=pos+4)
{
if (timestamp==*(DWORD*)&buf[pos])
{
res=DOWNLOAD_SAMETIME;
break;
}
timestamp=0;
CHUNK_SIZE=32768;
}
}
}
buf.resize(size);
}
@@ -377,80 +337,17 @@ static DWORD WINAPI ThreadVersionCheck( void *param )
return 0;
}
DWORD curVersion=GetVersionEx(g_Instance);
regKey.SetDWORDValue(L"LastUpdateVersion",curVersion);
// download file
wchar_t fname[_MAX_PATH]=L"%ALLUSERSPROFILE%\\OpenShell";
DoEnvironmentSubst(fname,_countof(fname));
SHCreateDirectory(NULL,fname);
PathAppend(fname,L"update.ver");
bool res=false;
CString urlBase=LoadStringEx(IDS_VERSION_URL);
bool res = false;
VersionData data;
data.Clear();
if (data.Load(fname,false)==VersionData::LOAD_OK)
{
if (!data.altUrl.IsEmpty())
urlBase=data.altUrl;
WIN32_FILE_ATTRIBUTE_DATA attr;
if (GetFileAttributesEx(fname,GetFileExInfoStandard,&attr))
{
DWORD writeTime=(DWORD)(((((ULONGLONG)attr.ftLastWriteTime.dwHighDateTime)<<32)|attr.ftLastWriteTime.dwLowDateTime)/TIME_DIVISOR);
if (curTime>writeTime && (curTime-writeTime)<TIME_PRECISION)
{
res=true; // the file is valid and less than an hour old, don't download again
}
}
}
if (!res)
{
data.Clear();
CString url;
url.Format(L"%s%d.%d.%d.ver",urlBase,curVersion>>24,(curVersion>>16)&0xFF,curVersion&0xFFFF);
auto load = data.Load();
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG,L"URL: %s",url);
#endif
std::vector<char> buf;
TDownloadResult download=DownloadFile(url,buf,NULL,GetTimeStamp(fname),false,params.progress,params.component);
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG,L"Download result: %d",download);
#endif
if (download==DOWNLOAD_CANCEL)
{
g_bCheckingVersion=false;
return 2;
}
if (download<DOWNLOAD_FIRST_ERROR)
{
if (download==DOWNLOAD_SAMETIME || SaveFile(fname,buf)==0)
{
if (download==DOWNLOAD_SAMETIME)
{
HANDLE h=CreateFile(fname,GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (h!=INVALID_HANDLE_VALUE)
{
SetFileTime(h,NULL,NULL,(FILETIME*)&curTimeL);
CloseHandle(h);
}
}
if (params.progress)
{
params.progress->SetText(LoadStringEx(IDS_PROGRESS_VERIFY));
params.progress->SetProgress(-1);
}
VersionData::TLoadResult load=data.Load(fname,false);
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG,L"Load result: %d",load);
#endif
if (load==VersionData::LOAD_BAD_FILE)
DeleteFile(fname);
res=(load==VersionData::LOAD_OK);
}
}
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG, L"Load result: %d", load);
#endif
res = (load == VersionData::LOAD_OK);
}
curTime+=(rand()*TIME_PRECISION)/(RAND_MAX+1)-(TIME_PRECISION/2); // add between -30 and 30 minutes to randomize access
@@ -583,42 +480,19 @@ DWORD CheckForNewVersion( HWND owner, TSettingsComponent component, TVersionChec
}
else
{
DWORD buildTime=0;
{
// skip the update if the update component is not found
wchar_t path[_MAX_PATH];
GetModuleFileName(_AtlBaseModule.GetModuleInstance(),path,_countof(path));
PathRemoveFileSpec(path);
PathAppend(path,L"Update.exe");
WIN32_FILE_ATTRIBUTE_DATA attr;
if (!GetFileAttributesEx(path,GetFileExInfoStandard,&attr))
return 0;
buildTime=(DWORD)(((((ULONGLONG)attr.ftCreationTime.dwHighDateTime)<<32)|attr.ftCreationTime.dwLowDateTime)/TIME_DIVISOR); // in 0.01 hours
}
ULONGLONG curTimeL;
GetSystemTimeAsFileTime((FILETIME*)&curTimeL);
DWORD curTime=(DWORD)(curTimeL/TIME_DIVISOR); // in 0.01 hours
if (curTime-buildTime>24*365*TIME_PRECISION)
return 0; // the build is more than a year old, don't do automatic updates
CRegKey regKey;
if (regKey.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\OpenShell")!=ERROR_SUCCESS)
regKey.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\OpenShell");
DWORD lastVersion;
if (regKey.QueryDWORDValue(L"LastUpdateVersion",lastVersion)!=ERROR_SUCCESS)
lastVersion=0;
if (lastVersion==GetVersionEx(g_Instance))
{
DWORD lastTime;
if (regKey.QueryDWORDValue(L"LastUpdateTime",lastTime)!=ERROR_SUCCESS)
lastTime=0;
if ((int)(curTime-lastTime)<168*TIME_PRECISION)
return 0; // check weekly
}
DWORD lastTime;
if (regKey.QueryDWORDValue(L"LastUpdateTime",lastTime)!=ERROR_SUCCESS)
lastTime=0;
if ((int)(curTime-lastTime)<168*TIME_PRECISION)
return 0; // check weekly
// check the Update setting (uses the current value in the registry, not the one from memory
{
@@ -848,6 +722,94 @@ void VersionData::Swap( VersionData &data )
std::swap(languages,data.languages);
}
std::vector<char> DownloadUrl(const wchar_t* url)
{
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG, L"URL: %s", url);
#endif
std::vector<char> buffer;
TDownloadResult download = DownloadFile(url, buffer, nullptr, false, nullptr, COMPONENT_UPDATE);
#ifdef UPDATE_LOG
LogToFile(UPDATE_LOG, L"Download result: %d", download);
#endif
if (download != DOWNLOAD_OK)
buffer.clear();
return buffer;
}
using namespace nlohmann;
VersionData::TLoadResult VersionData::Load()
{
Clear();
auto buf = DownloadUrl(L"https://api.github.com/repos/Open-Shell/Open-Shell-Menu/releases/latest");
if (buf.empty())
return LOAD_ERROR;
try
{
auto data = json::parse(buf.begin(), buf.end());
// skip prerelease versions
if (data["prerelease"].get<bool>())
return LOAD_BAD_VERSION;
// get version from tag name
auto tag = data["tag_name"].get<std::string>();
if (tag.empty())
return LOAD_BAD_FILE;
int v1, v2, v3;
if (sscanf_s(tag.c_str(), "v%d.%d.%d", &v1, &v2, &v3) != 3)
return LOAD_BAD_FILE;
newVersion = (v1 << 24) | (v2 << 16) | v3;
// installer url
std::string url;
for (const auto& asset : data["assets"])
{
if (asset["name"].get<std::string>().find("OpenShellSetup") == 0)
{
url = asset["browser_download_url"].get<std::string>();
break;
}
}
if (url.empty())
return LOAD_BAD_FILE;
downloadUrl.Append(CA2T(url.c_str()));
// changelog
auto body = data["body"].get<std::string>();
if (!body.empty())
{
auto name = data["name"].get<std::string>();
if (!name.empty())
{
news.Append(CA2T(name.c_str()));
news.Append(L"\r\n\r\n");
}
news.Append(CA2T(body.c_str()));
news.Replace(L"\\n", L"\n");
news.Replace(L"\\r", L"\r");
}
return LOAD_OK;
}
catch (...)
{
return LOAD_BAD_FILE;
}
}
VersionData::TLoadResult VersionData::Load( const wchar_t *fname, bool bLoadFlags )
{
Clear();
@@ -937,7 +899,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
params.saveRes=0;
std::vector<char> buf;
params.downloadRes=DownloadFile(params.url,buf,params.fname.IsEmpty()?&params.fname:NULL,0,params.bAcceptCached,params.progress,params.component);
params.downloadRes=DownloadFile(params.url,buf,params.fname.IsEmpty()?&params.fname:NULL,params.bAcceptCached,params.progress,params.component);
if (params.downloadRes==DOWNLOAD_CANCEL || params.downloadRes>=DOWNLOAD_FIRST_ERROR)
return 0;
@@ -971,6 +933,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
return 0;
// validate signer
/*
if (params.signer)
{
if (params.progress)
@@ -982,7 +945,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
return 0;
}
}
*/
return 0;
}
@@ -1089,6 +1052,12 @@ DWORD DownloadNewVersion( HWND owner, TSettingsComponent component, const wchar_
params.bAcceptCached=true;
params.component=component;
{
const wchar_t* name = wcsrchr(url, '/');
if (name && name[1])
params.fname.Append(name+1);
}
HANDLE hThread=CreateThread(NULL,0,ThreadDownloadFile,&params,0,NULL);
while (1)

View File

@@ -31,19 +31,19 @@ struct LanguageVersionData
struct VersionData
{
bool bValid;
DWORD newVersion;
DWORD encodedLangVersion;
bool bValid = false;
DWORD newVersion = 0;
DWORD encodedLangVersion = 0;
CString downloadUrl;
CString downloadSigner;
CString news;
CString updateLink;
CString languageLink;
CString altUrl;
bool bNewVersion;
bool bIgnoreVersion;
bool bNewLanguage;
bool bIgnoreLanguage;
bool bNewVersion = false;
bool bIgnoreVersion = false;
bool bNewLanguage = false;
bool bIgnoreLanguage = false;
CString newLanguage;
std::vector<LanguageVersionData> languages;
@@ -59,6 +59,7 @@ struct VersionData
LOAD_BAD_FILE, // the file is corrupted
};
TLoadResult Load();
TLoadResult Load( const wchar_t *fname, bool bLoadFlags );
private:
void operator=( const VersionData& );

Binary file not shown.

View File

@@ -7323,8 +7323,9 @@ static void NewVersionCallback( VersionData &data )
wchar_t cmdLine[1024];
Sprintf(cmdLine,_countof(cmdLine),L"\"%s\" -popup",path);
STARTUPINFO startupInfo={sizeof(startupInfo)};
PROCESS_INFORMATION processInfo;
memset(&processInfo,0,sizeof(processInfo));
// don't display busy cursor as we are doing this on background
startupInfo.dwFlags=STARTF_FORCEOFFFEEDBACK;
PROCESS_INFORMATION processInfo{};
if (CreateProcess(path,cmdLine,NULL,NULL,TRUE,0,NULL,NULL,&startupInfo,&processInfo))
{
CloseHandle(processInfo.hThread);

View File

@@ -319,7 +319,7 @@ LRESULT CUpdateDlg::OnDontRemind( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL
LRESULT CUpdateDlg::OnWeb( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{
ShellExecute(m_hWnd,NULL,L"https://github.com/Open-Shell/Open-Shell-Menu",NULL,NULL,SW_SHOWNORMAL);
ShellExecute(m_hWnd,NULL,L"https://open-shell.github.io/Open-Shell-Menu/",NULL,NULL,SW_SHOWNORMAL);
return 0;
}
@@ -461,11 +461,7 @@ int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpstrC
{
INITCOMMONCONTROLSEX init={sizeof(init),ICC_STANDARD_CLASSES};
InitCommonControlsEx(&init);
/*
VersionData data;
data.Load(L"D:\\Work\\OpenShell\\Setup\\Final\\update_4.0.4.ver",false);
return 0;
*/
// prevent multiple instances from running on the same desktop
// the assumption is that multiple desktops for the same user will have different name (but may repeat across users)
wchar_t userName[256];

View File

@@ -123,22 +123,22 @@ END
// Dialog
//
IDD_UPDATE DIALOGEX 0, 0, 316, 181
IDD_UPDATE DIALOGEX 0, 0, 316, 183
STYLE DS_SETFONT | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION "Open-Shell Update"
FONT 9, "Segoe UI", 400, 0, 0x0
BEGIN
CONTROL "Automatically check for new versions",IDC_CHECKAUTOCHECK,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,129,10
PUSHBUTTON "Check now",IDC_BUTTONCHECKNOW,7,17,50,14
PUSHBUTTON "Check now",IDC_BUTTONCHECKNOW,7,19,50,14
LTEXT "message",IDC_STATICLATEST,7,33,302,10,SS_CENTERIMAGE
EDITTEXT IDC_EDITTEXT,7,45,302,97,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | NOT WS_VISIBLE | WS_VSCROLL
PUSHBUTTON "Download",IDC_BUTTONDOWNLOAD,7,144,50,14,NOT WS_VISIBLE
PUSHBUTTON "Download",IDC_BUTTONDOWNLOAD,7,146,50,14,NOT WS_VISIBLE
CONTROL "Don't remind me again about this version",IDC_CHECKDONT,
"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,61,144,141,14
CONTROL "<a>https://github.com/Open-Shell/Open-Shell-Menu</a>",IDC_LINKWEB,"SysLink",WS_TABSTOP,7,164,66,10,WS_EX_TRANSPARENT
DEFPUSHBUTTON "OK",IDOK,202,160,50,14
PUSHBUTTON "Cancel",IDCANCEL,259,160,50,14
"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,61,146,141,14
CONTROL "<a>Open-Shell-Menu</a>",IDC_LINKWEB,"SysLink",WS_TABSTOP,7,166,66,10,WS_EX_TRANSPARENT
DEFPUSHBUTTON "OK",IDOK,202,162,50,14
PUSHBUTTON "Cancel",IDCANCEL,259,162,50,14
END