Skip to content

Commit 4883d13

Browse files
committed
Update: Support for automatic updates
Use Github REST API to get info about latest release (version, changelog, installer url).
1 parent b094ddc commit 4883d13

File tree

6 files changed

+132
-165
lines changed

6 files changed

+132
-165
lines changed

Src/Lib/DownloadHelper.cpp

Lines changed: 112 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "FNVHash.h"
1515
#include "StringUtils.h"
1616
#include "Translations.h"
17+
#include "json.hpp"
1718
#include <wininet.h>
1819
#include <softpub.h>
1920

@@ -141,30 +142,9 @@ LRESULT CProgressDlg::OnCancel( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&
141142

142143
static bool g_bCheckingVersion;
143144

144-
static DWORD GetTimeStamp( const wchar_t *fname )
145-
{
146-
HANDLE h=CreateFile(fname,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
147-
if (h==INVALID_HANDLE_VALUE)
148-
return 0;
149-
DWORD res=0;
150-
DWORD q;
151-
IMAGE_DOS_HEADER header;
152-
if (ReadFile(h,&header,sizeof(header),&q,NULL) && q==sizeof(header))
153-
{
154-
if (SetFilePointer(h,header.e_lfanew+8,NULL,FILE_BEGIN)!=INVALID_SET_FILE_POINTER)
155-
{
156-
if (!ReadFile(h,&res,4,&q,NULL) || q!=4)
157-
res=0;
158-
}
159-
}
160-
CloseHandle(h);
161-
return res;
162-
}
163-
164145
enum TDownloadResult
165146
{
166147
DOWNLOAD_OK,
167-
DOWNLOAD_SAMETIME,
168148
DOWNLOAD_CANCEL,
169149

170150
// errors
@@ -176,8 +156,7 @@ enum TDownloadResult
176156

177157
// Downloads a file
178158
// filename - returns the name of the downloaded file
179-
// 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)
180-
static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf, CString *pFilename, DWORD timestamp, bool bAcceptCached, CProgressDlg *pProgress, TSettingsComponent component )
159+
static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf, CString *pFilename, bool bAcceptCached, CProgressDlg *pProgress, TSettingsComponent component )
181160
{
182161
const wchar_t *compName=L"Open-Shell";
183162
switch (component)
@@ -264,7 +243,7 @@ static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf,
264243
if (fileSize==0)
265244
pProgress->SetProgress(-1);
266245
}
267-
int CHUNK_SIZE=timestamp?1024:32768; // start with small chunk to verify the timestamp
246+
int CHUNK_SIZE=32768;
268247
DWORD size=0;
269248
buf.reserve(fileSize+CHUNK_SIZE);
270249
while (1)
@@ -286,25 +265,6 @@ static TDownloadResult DownloadFile( const wchar_t *url, std::vector<char> &buf,
286265
size+=dwSize;
287266
if (pProgress && fileSize)
288267
pProgress->SetProgress(size*100/fileSize);
289-
if (timestamp && (size<sizeof(IMAGE_DOS_HEADER) || buf[0]!='M' || buf[1]!='Z'))
290-
{
291-
res=DOWNLOAD_FAIL;
292-
break;
293-
}
294-
if (timestamp && size>=sizeof(IMAGE_DOS_HEADER))
295-
{
296-
DWORD pos=((IMAGE_DOS_HEADER*)&buf[0])->e_lfanew+8;
297-
if (size>=pos+4)
298-
{
299-
if (timestamp==*(DWORD*)&buf[pos])
300-
{
301-
res=DOWNLOAD_SAMETIME;
302-
break;
303-
}
304-
timestamp=0;
305-
CHUNK_SIZE=32768;
306-
}
307-
}
308268
}
309269
buf.resize(size);
310270
}
@@ -377,80 +337,17 @@ static DWORD WINAPI ThreadVersionCheck( void *param )
377337
return 0;
378338
}
379339
DWORD curVersion=GetVersionEx(g_Instance);
380-
regKey.SetDWORDValue(L"LastUpdateVersion",curVersion);
381340

382-
// download file
383-
wchar_t fname[_MAX_PATH]=L"%ALLUSERSPROFILE%\\OpenShell";
384-
DoEnvironmentSubst(fname,_countof(fname));
385-
SHCreateDirectory(NULL,fname);
386-
PathAppend(fname,L"update.ver");
387-
388-
bool res=false;
389-
CString urlBase=LoadStringEx(IDS_VERSION_URL);
341+
bool res = false;
390342
VersionData data;
391-
data.Clear();
392-
if (data.Load(fname,false)==VersionData::LOAD_OK)
393-
{
394-
if (!data.altUrl.IsEmpty())
395-
urlBase=data.altUrl;
396-
WIN32_FILE_ATTRIBUTE_DATA attr;
397-
if (GetFileAttributesEx(fname,GetFileExInfoStandard,&attr))
398-
{
399-
DWORD writeTime=(DWORD)(((((ULONGLONG)attr.ftLastWriteTime.dwHighDateTime)<<32)|attr.ftLastWriteTime.dwLowDateTime)/TIME_DIVISOR);
400-
if (curTime>writeTime && (curTime-writeTime)<TIME_PRECISION)
401-
{
402-
res=true; // the file is valid and less than an hour old, don't download again
403-
}
404-
}
405-
}
406-
if (!res)
343+
407344
{
408-
data.Clear();
409-
CString url;
410-
url.Format(L"%s%d.%d.%d.ver",urlBase,curVersion>>24,(curVersion>>16)&0xFF,curVersion&0xFFFF);
411-
412-
#ifdef UPDATE_LOG
413-
LogToFile(UPDATE_LOG,L"URL: %s",url);
414-
#endif
415-
416-
std::vector<char> buf;
417-
TDownloadResult download=DownloadFile(url,buf,NULL,GetTimeStamp(fname),false,params.progress,params.component);
418-
#ifdef UPDATE_LOG
419-
LogToFile(UPDATE_LOG,L"Download result: %d",download);
420-
#endif
421-
if (download==DOWNLOAD_CANCEL)
422-
{
423-
g_bCheckingVersion=false;
424-
return 2;
425-
}
345+
auto load = data.Load();
426346

427-
if (download<DOWNLOAD_FIRST_ERROR)
428-
{
429-
if (download==DOWNLOAD_SAMETIME || SaveFile(fname,buf)==0)
430-
{
431-
if (download==DOWNLOAD_SAMETIME)
432-
{
433-
HANDLE h=CreateFile(fname,GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
434-
if (h!=INVALID_HANDLE_VALUE)
435-
{
436-
SetFileTime(h,NULL,NULL,(FILETIME*)&curTimeL);
437-
CloseHandle(h);
438-
}
439-
}
440-
if (params.progress)
441-
{
442-
params.progress->SetText(LoadStringEx(IDS_PROGRESS_VERIFY));
443-
params.progress->SetProgress(-1);
444-
}
445-
VersionData::TLoadResult load=data.Load(fname,false);
446-
#ifdef UPDATE_LOG
447-
LogToFile(UPDATE_LOG,L"Load result: %d",load);
448-
#endif
449-
if (load==VersionData::LOAD_BAD_FILE)
450-
DeleteFile(fname);
451-
res=(load==VersionData::LOAD_OK);
452-
}
453-
}
347+
#ifdef UPDATE_LOG
348+
LogToFile(UPDATE_LOG, L"Load result: %d", load);
349+
#endif
350+
res = (load == VersionData::LOAD_OK);
454351
}
455352

456353
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
583480
}
584481
else
585482
{
586-
DWORD buildTime=0;
587-
{
588-
// skip the update if the update component is not found
589-
wchar_t path[_MAX_PATH];
590-
GetModuleFileName(_AtlBaseModule.GetModuleInstance(),path,_countof(path));
591-
PathRemoveFileSpec(path);
592-
PathAppend(path,L"Update.exe");
593-
594-
WIN32_FILE_ATTRIBUTE_DATA attr;
595-
if (!GetFileAttributesEx(path,GetFileExInfoStandard,&attr))
596-
return 0;
597-
598-
buildTime=(DWORD)(((((ULONGLONG)attr.ftCreationTime.dwHighDateTime)<<32)|attr.ftCreationTime.dwLowDateTime)/TIME_DIVISOR); // in 0.01 hours
599-
}
600-
601483
ULONGLONG curTimeL;
602484
GetSystemTimeAsFileTime((FILETIME*)&curTimeL);
603485
DWORD curTime=(DWORD)(curTimeL/TIME_DIVISOR); // in 0.01 hours
604-
if (curTime-buildTime>24*365*TIME_PRECISION)
605-
return 0; // the build is more than a year old, don't do automatic updates
606486

607487
CRegKey regKey;
608488
if (regKey.Open(HKEY_CURRENT_USER,L"Software\\OpenShell\\OpenShell")!=ERROR_SUCCESS)
609489
regKey.Create(HKEY_CURRENT_USER,L"Software\\OpenShell\\OpenShell");
610490

611-
DWORD lastVersion;
612-
if (regKey.QueryDWORDValue(L"LastUpdateVersion",lastVersion)!=ERROR_SUCCESS)
613-
lastVersion=0;
614-
if (lastVersion==GetVersionEx(g_Instance))
615-
{
616-
DWORD lastTime;
617-
if (regKey.QueryDWORDValue(L"LastUpdateTime",lastTime)!=ERROR_SUCCESS)
618-
lastTime=0;
619-
if ((int)(curTime-lastTime)<168*TIME_PRECISION)
620-
return 0; // check weekly
621-
}
491+
DWORD lastTime;
492+
if (regKey.QueryDWORDValue(L"LastUpdateTime",lastTime)!=ERROR_SUCCESS)
493+
lastTime=0;
494+
if ((int)(curTime-lastTime)<168*TIME_PRECISION)
495+
return 0; // check weekly
622496

623497
// check the Update setting (uses the current value in the registry, not the one from memory
624498
{
@@ -848,6 +722,94 @@ void VersionData::Swap( VersionData &data )
848722
std::swap(languages,data.languages);
849723
}
850724

725+
std::vector<char> DownloadUrl(const wchar_t* url)
726+
{
727+
#ifdef UPDATE_LOG
728+
LogToFile(UPDATE_LOG, L"URL: %s", url);
729+
#endif
730+
731+
std::vector<char> buffer;
732+
TDownloadResult download = DownloadFile(url, buffer, nullptr, false, nullptr, COMPONENT_UPDATE);
733+
734+
#ifdef UPDATE_LOG
735+
LogToFile(UPDATE_LOG, L"Download result: %d", download);
736+
#endif
737+
738+
if (download != DOWNLOAD_OK)
739+
buffer.clear();
740+
741+
return buffer;
742+
}
743+
744+
using namespace nlohmann;
745+
746+
VersionData::TLoadResult VersionData::Load()
747+
{
748+
Clear();
749+
750+
auto buf = DownloadUrl(L"https://api.github.com/repos/Open-Shell/Open-Shell-Menu/releases/latest");
751+
if (buf.empty())
752+
return LOAD_ERROR;
753+
754+
try
755+
{
756+
auto data = json::parse(buf.begin(), buf.end());
757+
758+
// skip prerelease versions
759+
if (data["prerelease"].get<bool>())
760+
return LOAD_BAD_VERSION;
761+
762+
// get version from tag name
763+
auto tag = data["tag_name"].get<std::string>();
764+
if (tag.empty())
765+
return LOAD_BAD_FILE;
766+
767+
int v1, v2, v3;
768+
if (sscanf_s(tag.c_str(), "v%d.%d.%d", &v1, &v2, &v3) != 3)
769+
return LOAD_BAD_FILE;
770+
771+
newVersion = (v1 << 24) | (v2 << 16) | v3;
772+
773+
// installer url
774+
std::string url;
775+
for (const auto& asset : data["assets"])
776+
{
777+
if (asset["name"].get<std::string>().find("OpenShellSetup") == 0)
778+
{
779+
url = asset["browser_download_url"].get<std::string>();
780+
break;
781+
}
782+
}
783+
784+
if (url.empty())
785+
return LOAD_BAD_FILE;
786+
787+
downloadUrl.Append(CA2T(url.c_str()));
788+
789+
// changelog
790+
auto body = data["body"].get<std::string>();
791+
if (!body.empty())
792+
{
793+
auto name = data["name"].get<std::string>();
794+
if (!name.empty())
795+
{
796+
news.Append(CA2T(name.c_str()));
797+
news.Append(L"\r\n\r\n");
798+
}
799+
800+
news.Append(CA2T(body.c_str()));
801+
news.Replace(L"\\n", L"\n");
802+
news.Replace(L"\\r", L"\r");
803+
}
804+
805+
return LOAD_OK;
806+
}
807+
catch (...)
808+
{
809+
return LOAD_BAD_FILE;
810+
}
811+
}
812+
851813
VersionData::TLoadResult VersionData::Load( const wchar_t *fname, bool bLoadFlags )
852814
{
853815
Clear();
@@ -937,7 +899,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
937899
params.saveRes=0;
938900

939901
std::vector<char> buf;
940-
params.downloadRes=DownloadFile(params.url,buf,params.fname.IsEmpty()?&params.fname:NULL,0,params.bAcceptCached,params.progress,params.component);
902+
params.downloadRes=DownloadFile(params.url,buf,params.fname.IsEmpty()?&params.fname:NULL,params.bAcceptCached,params.progress,params.component);
941903
if (params.downloadRes==DOWNLOAD_CANCEL || params.downloadRes>=DOWNLOAD_FIRST_ERROR)
942904
return 0;
943905

@@ -971,6 +933,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
971933
return 0;
972934

973935
// validate signer
936+
/*
974937
if (params.signer)
975938
{
976939
if (params.progress)
@@ -982,7 +945,7 @@ static DWORD WINAPI ThreadDownloadFile( void *param )
982945
return 0;
983946
}
984947
}
985-
948+
*/
986949
return 0;
987950
}
988951

@@ -1089,6 +1052,12 @@ DWORD DownloadNewVersion( HWND owner, TSettingsComponent component, const wchar_
10891052
params.bAcceptCached=true;
10901053
params.component=component;
10911054

1055+
{
1056+
const wchar_t* name = wcsrchr(url, '/');
1057+
if (name && name[1])
1058+
params.fname.Append(name+1);
1059+
}
1060+
10921061
HANDLE hThread=CreateThread(NULL,0,ThreadDownloadFile,&params,0,NULL);
10931062

10941063
while (1)

Src/Lib/DownloadHelper.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ struct LanguageVersionData
3131

3232
struct VersionData
3333
{
34-
bool bValid;
35-
DWORD newVersion;
36-
DWORD encodedLangVersion;
34+
bool bValid = false;
35+
DWORD newVersion = 0;
36+
DWORD encodedLangVersion = 0;
3737
CString downloadUrl;
3838
CString downloadSigner;
3939
CString news;
4040
CString updateLink;
4141
CString languageLink;
4242
CString altUrl;
43-
bool bNewVersion;
44-
bool bIgnoreVersion;
45-
bool bNewLanguage;
46-
bool bIgnoreLanguage;
43+
bool bNewVersion = false;
44+
bool bIgnoreVersion = false;
45+
bool bNewLanguage = false;
46+
bool bIgnoreLanguage = false;
4747
CString newLanguage;
4848
std::vector<LanguageVersionData> languages;
4949

@@ -59,6 +59,7 @@ struct VersionData
5959
LOAD_BAD_FILE, // the file is corrupted
6060
};
6161

62+
TLoadResult Load();
6263
TLoadResult Load( const wchar_t *fname, bool bLoadFlags );
6364
private:
6465
void operator=( const VersionData& );

Src/Localization/English/en-US.csv

-60 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)