View Issue Details

IDProjectCategoryView StatusLast Update
0001011OpenMPT[All Projects] Generalpublic2018-11-11 15:49
ReporterSaga MusixAssigned To 
PrioritylowSeverityminorReproducibilityN/A
Status newResolutionopen 
Product Version 
Target VersionOpenMPT 1.?? (long term goals)Fixed in Version 
Summary0001011: Automatic update
Description

There are already automatic update notifications, but people are lazy, so in addition we should offer to show what's new and automatically install the new version. In case someone is skipping several versions, we might want to show the news for all skipped versions as well. Not the complete changelog of course, but just the most important items.

We will need a differently structured response from the server, and since picojson is already used for the Wine integration, using JSON for update information seems like a good idea. The update response could look something like this:

openmpt_versions = [
{
  "version":"1.28.01.00",
  "released":"2018-01-01",
  "announcement_url":"...",
  "download":
  {
    "win32":{url:"...","windows":"6.1","bits":32,"required_processor_features":{"sse2"}},
    "win32old":{url:"...","windows":"5.1","bits":32},
    "win64":{url:"...","windows":"6.1","bits":64},
    "win64old":{url:"...","windows":"5.1","bits":64}
  },
  "news":
  [
    "Fixed stuff",
    "Cool new feature"
  ]
},
(more skipped versions here, download links not needed)
]

The rationale for sending all download links to the client is that information about the user's operating system environment is sent to the server optionally. I think data avoidance is a goal worth striving for, so we should not force this information to be sent along.
This means that the client has to decide which version to download. Since support for some operating systems and processor combinations may cease to exist, we may also need to specify extra data the client should check for.
Suggested way of doing this:

  • Check if the current client configuration (e.g. "win32old") still exists and it meets the OS requirements specified by the server (e.g. 5.1). If version exists and minimum OS requirement is fulfilled, use this version.
  • Otherwise iterate through all other offered versions and check if any of them match the user's OS and current OpenMPT bitness (to avoid issues with plugin bitness). Suggest the user to switch to this new build variant.
  • If still no build variant is found, notify the user of this fact. Suggest visiting the OpenMPT website.

What else needs to be considered:

  • Is the user using an installer or zip version?
  • We need to decide if we need a new update checker entry point on the server or if we keep the old one. Since the update client already sends version information to the server, we can dynamically decide if we send old-style or new-style update information.
TagsNo tags attached.
Has the bug occurred in previous versions?
Tested code revision (in case you know it)

Relationships

related to 0000649 new Re-register MPT instantly 
related to 0001123 new Provide unified multi-arch installer 

Activities

manx

manx

2017-08-14 11:27

administrator   ~0003167

I'd prefer to use a new update URL. That way, the client can also send JSON data, which could easily be extended without breaking backwards compatibility in the future (which is a major hassle with the current way of doing things).

Other than that, I really like the proposal to send all build variants. That's exactly what I also had in mind.

Additionally, we should at least take a look at WinSparkle ( https://winsparkle.org/ ). I do not know if the sparkle specification would be enough for what OpenMPT needs, but even if it is not, and even if we do not use RSS/XML (like Sparkle), there might be good ideas in there to copy or mimic.

manx

manx

2017-09-22 15:10

administrator   ~0003230

Even though downloading the automated updater from a TLS host with a valid certificate is technically and in practice enough to ensure authenticity, we might want to also look into additionally signing the installer itself. Either with a proper code signing certificate, or with a custom pinned key.

Saga Musix

Saga Musix

2017-09-22 15:28

administrator   ~0003232

I have looked into code signing certificates several times in the past, and there used to be free certificates for open-source developers from Certum, but those are no longer free. While they are relatively cheap, they still require documents for verification that I would be hesistant to provide (see https://www.certum.eu/certum/cert,offer_en_open_source_cs.xml)

manx

manx

2017-09-22 15:38

administrator   ~0003233

Which is why I suggested as an alternative just additionally signing the updater with custom key even without an official certificate which would still guard against any attacks on the domain. In particular, especially for signing the updater there is actually no semantic requirement to rely on any common certificate authority infrastructure.
Signing the OpenMPT executable itself would indeed require an official code signing certificate, however that aspect is totally orthogonal to automatic updates.

manx

manx

2018-08-22 10:55

administrator  

manx

manx

2018-08-22 10:55

administrator   ~0003605

Attached a quick mockup of a suggested update settings dialog.

Changes:

  • Update frequency moved to a combo box as this is of less importance and should not consume as much screen real estate
  • Update URL is completely gone (can be retained as a hidden setting of course), and replaced by an update channel mechanism as is done for most other software. This additionally add a stable-next channel which would be HEAD of the current release branch.
  • Explicitly show what information will be sent to the server for statistics purposes
  • Add combo box to switch between notify/download/install

All but the last change can already be implemented without actually changing the update/installer mechanism itself.

manx

manx

2018-08-22 16:03

administrator  

updatedialog-v2.png (40,975 bytes)
updatedialog-v2.png (40,975 bytes)
manx

manx

2018-08-22 16:05

administrator  

update-statistics-v2.patch (34,400 bytes)
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10676)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 28
+#define VER_MINORMINOR 29
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10676)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now",IDC_BUTTON1,222,84,54,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -881,7 +884,12 @@
     0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10676)
+++ mptrack/resource.h	(working copy)
@@ -973,6 +973,13 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1267,9 +1274,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2511
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10676)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,9 +331,16 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
@@ -644,6 +651,45 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,29))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10676)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,9 +817,16 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateSendGUID;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10676)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "BuildVariants.h"
 #include "../common/version.h"
 #include "../common/misc_util.h"
+#include "../common/mptStringBuffer.h"
 #include "Mptrack.h"
 #include "TrackerSettings.h"
 // Setup dialog stuff
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -84,12 +86,28 @@
 
 
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +122,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			updateCheckPeriod = 0;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +143,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +150,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +168,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateSendGUID)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +202,104 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	if(mpt::Windows::IsWine())
+	{
+		mpt::Wine::VersionContext v;
+		j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
+		if(v.Version().IsValid())
+		{
+			j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
+			j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
+			j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
+		}
+		j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
+	}
+	j["System"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) != 0)
+	{
+		j["System"]["Memory"] = memoryStatus.ullTotalPhys / 1024 / 1024;
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["tsc"] = ((GetRealProcSupport() & PROCSUPPORT_TSC) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +307,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +392,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +412,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
+		context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
 		s_InstanceCount.fetch_sub(1);
 		MPT_ASSERT(s_InstanceCount.load() >= 0);
 		return;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +495,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK_UPDATEENABLED,         &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO1,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO2,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO3,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_BUTTON1,                     &CUpdateSetupDlg::OnCheckNow)
+	ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +513,82 @@
 }
 
 
+void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
+{
+	CDialog::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
+}
+
+
 BOOL CUpdateSetupDlg::OnInitDialog()
 {
 	CPropertyPage::OnInitDialog();
 
+	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
+
 	int radioID = 0;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
+	CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
+
+	int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
+	int ndx;
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("always"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 0);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 7);
+	if(periodDays >= 7)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 30);
+	if(periodDays >= 0)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("never"));
+	m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
+	if(periodDays < 0)
+	{		
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
 	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
 
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(_T("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:"));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +596,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
+
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,19 +657,42 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	/*
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+	*/
+
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel())) : -1;
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
+	TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
 	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
@@ -452,10 +712,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10676)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,11 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +53,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +60,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +107,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +122,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +141,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10676)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -147,7 +147,7 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
manx

manx

2018-09-12 13:46

administrator  

update-statistics-v3.patch (35,294 bytes)
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10769)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 31
+#define VER_MINORMINOR 32
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10769)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -881,7 +884,12 @@
     0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10769)
+++ mptrack/resource.h	(working copy)
@@ -974,6 +974,13 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1268,9 +1275,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2511
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10769)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,10 +331,17 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -644,6 +651,45 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,32))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10769)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,9 +817,16 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateSendGUID;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10769)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "BuildVariants.h"
 #include "../common/version.h"
 #include "../common/misc_util.h"
+#include "../common/mptStringBuffer.h"
 #include "Mptrack.h"
 #include "TrackerSettings.h"
 // Setup dialog stuff
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -84,12 +86,28 @@
 
 
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +122,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			return;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +143,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +150,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +168,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateSendGUID)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +202,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	if(mpt::Windows::IsWine())
+	{
+		mpt::Wine::VersionContext v;
+		j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
+		if(v.Version().IsValid())
+		{
+			j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
+			j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
+			j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
+		}
+		j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
+	}
+	j["System"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) != 0)
+	{
+		j["System"]["Memory"] = memoryStatus.ullTotalPhys / 1024 / 1024;
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +306,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +391,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +411,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
+		context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
 		s_InstanceCount.fetch_sub(1);
 		MPT_ASSERT(s_InstanceCount.load() >= 0);
 		return;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +494,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK_UPDATEENABLED,         &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO1,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO2,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO3,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_BUTTON1,                     &CUpdateSetupDlg::OnCheckNow)
+	ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +512,82 @@
 }
 
 
+void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
+{
+	CDialog::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
+}
+
+
 BOOL CUpdateSetupDlg::OnInitDialog()
 {
 	CPropertyPage::OnInitDialog();
 
+	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
+
 	int radioID = 0;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
+	CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
+
+	int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
+	int ndx;
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("always"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 0);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 7);
+	if(periodDays >= 7)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 30);
+	if(periodDays >= 0)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("never"));
+	m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
+	if(periodDays < 0)
+	{		
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
 	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
 
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(_T("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:"));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +595,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
+
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,19 +656,60 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+
+	GetDlgItem(IDC_STATIC_UDATECHANNEL)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO1)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO3)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATECHECK)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_COMBO_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_BUTTON1)->EnableWindow(status);
+	GetDlgItem(IDC_LASTUPDATE)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
 	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
@@ -452,10 +729,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10769)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,11 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +53,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +60,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +107,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +122,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +141,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10769)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -147,7 +147,7 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
manx

manx

2018-11-04 15:39

administrator  

update-statistics-v5.patch (43,422 bytes)
Index: common/mptOS.cpp
===================================================================
--- common/mptOS.cpp	(revision 10963)
+++ common/mptOS.cpp	(working copy)
@@ -589,6 +589,20 @@
 }
 
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host)
+{
+	std::vector<Architecture> result;
+	for(const auto & entry : hostArchitectureCanRun)
+	{
+		if(entry.Host == host)
+		{
+			result.push_back(entry.Process);
+		}
+	}
+	return result;
+}
+
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
@@ -629,6 +643,18 @@
 
 #endif // MPT_OS_WINDOWS
 
+uint64 GetSystemMemorySize()
+{
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) == 0)
+	{
+		return 0;
+	}
+	return memoryStatus.ullTotalPhys;
+}
+
 static bool SystemIsWine(bool allowDetection = true)
 {
 	#if MPT_OS_WINDOWS
Index: common/mptOS.h
===================================================================
--- common/mptOS.h	(revision 10963)
+++ common/mptOS.h	(working copy)
@@ -183,11 +183,15 @@
 
 EmulationLevel HostCanRun(Architecture host, Architecture process) noexcept;
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host);
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
 #if defined(MODPLUG_TRACKER)
 
+uint64 GetSystemMemorySize();
+
 void PreventWineDetection();
 
 bool IsOriginal();
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10963)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 37
+#define VER_MINORMINOR 38
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 10963)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -1071,8 +1071,25 @@
 		font.size = Clamp(Util::GetDPIy(m_pMainWnd->m_hWnd) / 96 - 1, 0, 9);
 		TrackerSettings::Instance().patternFont = font;
 		new WelcomeDlg(m_pMainWnd);
+
+		TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+
 	} else
 	{
+
+		// ask if user wants to contribute system statistics
+		if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
+		{
+			TrackerSettings::Instance().UpdateStatistics = (ConfirmAnswer::cnfYes == Reporting::Confirm(
+				MPT_USTRING("Do you want to contribute to OpenMPT by providing system statistics?\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING("\r\n")) + MPT_USTRING("\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::format(MPT_USTRING("This option was previously %1 on your system.\r\n"))(TrackerSettings::Instance().UpdateStatistics ? MPT_USTRING("enabled") : MPT_USTRING("disabled")),
+				false, !TrackerSettings::Instance().UpdateStatistics.Get()));
+			TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+		}
+
 		// Update check
 		CUpdateCheck::DoAutoUpdateCheck();
 
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10963)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -374,23 +377,26 @@
     PUSHBUTTON      "&Cancel",IDCANCEL,138,24,50,14
 END
 
-IDD_WECLOME DIALOGEX 0, 0, 256, 137
+IDD_WECLOME DIALOGEX 0, 0, 257, 261
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Welcome to OpenMPT!"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&OK",IDOK,198,116,50,14
+    DEFPUSHBUTTON   "&OK",IDOK,198,236,50,14
     LTEXT           "Please review the following settings before using this software:",IDC_STATIC,6,6,246,8
     CONTROL         "&Automatically check for new versions of OpenMPT",IDC_CHECK1,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,246,10
+    CONTROL         "Help OpenMPT development by providing basic s&tatistics",IDC_CHECK3,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,42,234,10
+    LTEXT           "Static",IDC_STATIC_WELCOME_STATISTICS,30,54,216,102
     CONTROL         "&Use a big font in the pattern editor",IDC_CHECK2,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,42,246,10
-    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,63,108,8
-    COMBOBOX        IDC_COMBO1,114,60,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,82,172,8
-    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,79,50,14
-    EDITTEXT        IDC_EDIT1,6,95,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
-    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,116,60,14
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,162,246,10
+    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,183,108,8
+    COMBOBOX        IDC_COMBO1,114,180,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,202,172,8
+    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,199,50,14
+    EDITTEXT        IDC_EDIT1,6,215,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,236,60,14
 END
 
 IDD_UPDATE DIALOGEX 0, 0, 196, 138
@@ -751,9 +757,9 @@
     IDD_WECLOME, DIALOG
     BEGIN
         LEFTMARGIN, 7
-        RIGHTMARGIN, 249
+        RIGHTMARGIN, 250
         TOPMARGIN, 7
-        BOTTOMMARGIN, 130
+        BOTTOMMARGIN, 254
     END
 
     IDD_UPDATE, DIALOG
@@ -931,7 +937,17 @@
     0, 100, 100, 0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+IDD_WECLOME AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
+
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10963)
+++ mptrack/resource.h	(working copy)
@@ -975,6 +975,14 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
+#define IDC_STATIC_WELCOME_STATISTICS   2511
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1269,9 +1277,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        544
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2512
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10963)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,10 +331,19 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateStatisticsConsentAsked(conf, MPT_USTRING("Update"), MPT_USTRING("StatistisConsentAsked"), false)
+	, UpdateStatistics(conf, MPT_USTRING("Update"), MPT_USTRING("Statistis"), false)
+	, UpdateSendGUID_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -644,6 +653,47 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,38))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10963)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,10 +817,19 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
-	Setting<bool> UpdateSendGUID;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
+	Setting<bool> UpdateStatisticsConsentAsked;
+	Setting<bool> UpdateStatistics;
+	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
 	Setting<CString> UpdateIgnoreVersion;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10963)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "BuildVariants.h"
 #include "../common/version.h"
 #include "../common/misc_util.h"
+#include "../common/mptStringBuffer.h"
 #include "Mptrack.h"
 #include "TrackerSettings.h"
 // Setup dialog stuff
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -83,13 +85,46 @@
 
 
 
+mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
+{
+	if(shortText)
+	{
+		return MPT_USTRING("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:");
+	} else
+	{
+		return MPT_USTRING("")
+			+ MPT_USTRING("When checking for updates, OpenMPT can additionally collect some basic statistical information.") + MPT_USTRING("\n")
+			+ MPT_USTRING("A randomized user ID is created and transmitted alongside the update check. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems.") + MPT_USTRING("\n")
+			+ MPT_USTRING("Without this statistical information, the OpenMPT developers would be blind with respect to what systems are used to run OpenMPT. This makes deciding where to focus development plain guesswork.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT collects the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, configured update check frequency of OpenMPT.")
+			;
+	}
+}
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +139,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			return;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +160,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +167,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +185,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +219,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	std::vector<mpt::Windows::Architecture> architectures = mpt::Windows::GetSupportedProcessArchitectures(mpt::Windows::GetHostArchitecture());
+	for(const auto & arch : architectures)
+	{
+		j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::CharsetUTF8, mpt::Windows::Name(arch))] = true;
+	}
+	j["System"]["Memory"] = mpt::Windows::GetSystemMemorySize() / 1024 / 1024;  // MB
+	if(mpt::Windows::IsWine())
+	{
+		mpt::Wine::VersionContext v;
+		j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
+		if(v.Version().IsValid())
+		{
+			j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
+			j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
+			j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
+		}
+		j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["lm"] = ((GetRealProcSupport() & PROCSUPPORT_LM) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +323,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +408,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +428,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
+		context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
 		s_InstanceCount.fetch_sub(1);
 		MPT_ASSERT(s_InstanceCount.load() >= 0);
 		return;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +511,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK_UPDATEENABLED,         &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO1,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO2,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO3,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_BUTTON1,                     &CUpdateSetupDlg::OnCheckNow)
+	ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +529,82 @@
 }
 
 
+void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
+{
+	CDialog::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
+}
+
+
 BOOL CUpdateSetupDlg::OnInitDialog()
 {
 	CPropertyPage::OnInitDialog();
 
+	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
+
 	int radioID = 0;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
-	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
+	CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
 
+	int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
+	int ndx;
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("always"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 0);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 7);
+	if(periodDays >= 7)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 30);
+	if(periodDays >= 0)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("never"));
+	m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
+	if(periodDays < 0)
+	{		
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +612,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
+
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,20 +673,61 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+
+	GetDlgItem(IDC_STATIC_UDATECHANNEL)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO1)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO3)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATECHECK)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_COMBO_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_BUTTON1)->EnableWindow(status);
+	GetDlgItem(IDC_LASTUPDATE)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
-	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
 }
@@ -452,10 +746,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10963)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,13 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetStatisticsUserInformation(bool shortText);
+
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +55,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +62,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +109,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +124,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +143,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10963)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -119,6 +119,8 @@
 	combo->SetItemDataPtr(combo->AddString(_T("FastTracker 2")), (void*)("US_mpt-ft2_classic"));
 
 	CheckDlgButton(IDC_CHECK1, BST_CHECKED);
+	CheckDlgButton(IDC_CHECK3, BST_UNCHECKED);
+	GetDlgItem(IDC_STATIC_WELCOME_STATISTICS)->SetWindowText(mpt::ToCString(mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING(" "))));
 	CheckDlgButton(IDC_CHECK2, (TrackerSettings::Instance().patternFont.Get().name == PATTERNFONT_LARGE) ? BST_CHECKED : BST_UNCHECKED);
 
 	ShowWindow(SW_SHOW);
@@ -147,7 +149,8 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK3) != BST_UNCHECKED);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
manx

manx

2018-11-09 09:12

administrator  

update-statistics-v7.patch (43,972 bytes)
Index: common/mptOS.cpp
===================================================================
--- common/mptOS.cpp	(revision 10965)
+++ common/mptOS.cpp	(working copy)
@@ -589,6 +589,20 @@
 }
 
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host)
+{
+	std::vector<Architecture> result;
+	for(const auto & entry : hostArchitectureCanRun)
+	{
+		if(entry.Host == host)
+		{
+			result.push_back(entry.Process);
+		}
+	}
+	return result;
+}
+
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
@@ -629,6 +643,18 @@
 
 #endif // MPT_OS_WINDOWS
 
+uint64 GetSystemMemorySize()
+{
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) == 0)
+	{
+		return 0;
+	}
+	return memoryStatus.ullTotalPhys;
+}
+
 static bool SystemIsWine(bool allowDetection = true)
 {
 	#if MPT_OS_WINDOWS
Index: common/mptOS.h
===================================================================
--- common/mptOS.h	(revision 10965)
+++ common/mptOS.h	(working copy)
@@ -183,11 +183,15 @@
 
 EmulationLevel HostCanRun(Architecture host, Architecture process) noexcept;
 
+std::vector<Architecture> GetSupportedProcessArchitectures(Architecture host);
+
 #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS
 
 
 #if defined(MODPLUG_TRACKER)
 
+uint64 GetSystemMemorySize();
+
 void PreventWineDetection();
 
 bool IsOriginal();
Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10965)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 38
+#define VER_MINORMINOR 39
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/Mptrack.cpp
===================================================================
--- mptrack/Mptrack.cpp	(revision 10965)
+++ mptrack/Mptrack.cpp	(working copy)
@@ -1071,8 +1071,25 @@
 		font.size = Clamp(Util::GetDPIy(m_pMainWnd->m_hWnd) / 96 - 1, 0, 9);
 		TrackerSettings::Instance().patternFont = font;
 		new WelcomeDlg(m_pMainWnd);
+
+		TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+
 	} else
 	{
+
+		// ask if user wants to contribute system statistics
+		if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
+		{
+			TrackerSettings::Instance().UpdateStatistics = (ConfirmAnswer::cnfYes == Reporting::Confirm(
+				MPT_USTRING("Do you want to contribute to OpenMPT by providing system statistics?\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING("\r\n")) + MPT_USTRING("\r\n") +
+				MPT_USTRING("\r\n") +
+				mpt::format(MPT_USTRING("This option was previously %1 on your system.\r\n"))(TrackerSettings::Instance().UpdateStatistics ? MPT_USTRING("enabled") : MPT_USTRING("disabled")),
+				false, !TrackerSettings::Instance().UpdateStatistics.Get()));
+			TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
+		}
+
 		// Update check
 		CUpdateCheck::DoAutoUpdateCheck();
 
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10965)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now...",IDC_BUTTON1,216,84,60,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -374,23 +377,26 @@
     PUSHBUTTON      "&Cancel",IDCANCEL,138,24,50,14
 END
 
-IDD_WECLOME DIALOGEX 0, 0, 256, 137
+IDD_WECLOME DIALOGEX 0, 0, 257, 261
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Welcome to OpenMPT!"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    DEFPUSHBUTTON   "&OK",IDOK,198,116,50,14
+    DEFPUSHBUTTON   "&OK",IDOK,198,236,50,14
     LTEXT           "Please review the following settings before using this software:",IDC_STATIC,6,6,246,8
     CONTROL         "&Automatically check for new versions of OpenMPT",IDC_CHECK1,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,246,10
+    CONTROL         "Help OpenMPT development by providing basic s&tatistics",IDC_CHECK3,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,42,234,10
+    LTEXT           "Static",IDC_STATIC_WELCOME_STATISTICS,30,54,216,102
     CONTROL         "&Use a big font in the pattern editor",IDC_CHECK2,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,42,246,10
-    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,63,108,8
-    COMBOBOX        IDC_COMBO1,114,60,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
-    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,82,172,8
-    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,79,50,14
-    EDITTEXT        IDC_EDIT1,6,95,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
-    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,116,60,14
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,162,246,10
+    LTEXT           "&Default keyboard scheme:",IDC_STATIC,6,183,108,8
+    COMBOBOX        IDC_COMBO1,114,180,132,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    LTEXT           "Scan for existing VST plugins in the following location:",IDC_STATIC,6,202,172,8
+    PUSHBUTTON      "&Scan",IDC_BUTTON2,198,199,50,14
+    EDITTEXT        IDC_EDIT1,6,215,240,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER
+    PUSHBUTTON      "&More Settings",IDC_BUTTON1,6,236,60,14
 END
 
 IDD_UPDATE DIALOGEX 0, 0, 196, 138
@@ -751,9 +757,9 @@
     IDD_WECLOME, DIALOG
     BEGIN
         LEFTMARGIN, 7
-        RIGHTMARGIN, 249
+        RIGHTMARGIN, 250
         TOPMARGIN, 7
-        BOTTOMMARGIN, 130
+        BOTTOMMARGIN, 254
     END
 
     IDD_UPDATE, DIALOG
@@ -931,7 +937,17 @@
     0, 100, 100, 0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+IDD_WECLOME AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
+
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10965)
+++ mptrack/resource.h	(working copy)
@@ -975,6 +975,14 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
+#define IDC_STATIC_WELCOME_STATISTICS   2511
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1269,9 +1277,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        544
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2512
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10965)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -332,10 +332,19 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
-	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
+	, UpdateStatisticsConsentAsked(conf, MPT_USTRING("Update"), MPT_USTRING("StatistisConsentAsked"), false)
+	, UpdateStatistics(conf, MPT_USTRING("Update"), MPT_USTRING("Statistis"), false)
+	, UpdateSendGUID_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), false)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
 	, UpdateIgnoreVersion(conf, MPT_USTRING("Update"), MPT_USTRING("IgnoreVersion"), _T(""))
@@ -645,6 +654,47 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,39))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10965)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -818,10 +818,19 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
-	Setting<bool> UpdateSendGUID;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
+	Setting<bool> UpdateStatisticsConsentAsked;
+	Setting<bool> UpdateStatistics;
+	Setting<bool> UpdateSendGUID_DEPRECATED;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
 	Setting<CString> UpdateIgnoreVersion;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10965)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "BuildVariants.h"
 #include "../common/version.h"
 #include "../common/misc_util.h"
+#include "../common/mptStringBuffer.h"
 #include "Mptrack.h"
 #include "TrackerSettings.h"
 // Setup dialog stuff
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -83,13 +85,46 @@
 
 
 
+mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
+{
+	if(shortText)
+	{
+		return MPT_USTRING("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:");
+	} else
+	{
+		return MPT_USTRING("")
+			+ MPT_USTRING("When checking for updates, OpenMPT can additionally collect some basic statistical information.") + MPT_USTRING(" ")
+			+ MPT_USTRING("A randomized user ID is created and transmitted alongside the update check. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer.") + MPT_USTRING(" ")
+			+ MPT_USTRING("OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems.") + MPT_USTRING(" ")
+			+ MPT_USTRING("Without this statistical information, the OpenMPT developers would be blind with respect to what systems are used to run OpenMPT. This makes deciding where to focus development plain guesswork.") + MPT_USTRING("\n")
+			+ MPT_USTRING("OpenMPT would collect the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, configured update check frequency of OpenMPT.")
+			;
+	}
+}
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,16 +139,25 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			return;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
-		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
+		const time_t lastCheck = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
+		// Check update interval. Note that we always check for updates when the system time had gone backwards (i.e. when the last update check supposedly happened in the future).
+		if(difftime(now, lastCheck) > 0.0)
 		{
-			return;
+			if(difftime(now, lastCheck) < static_cast<double>(updateCheckPeriod * 86400))
+			{
+				return;
+			}
 		}
 
 		// Never ran update checks before, so we notify the user of automatic update checks.
@@ -121,7 +165,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +172,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +190,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +224,103 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	std::vector<mpt::Windows::Architecture> architectures = mpt::Windows::GetSupportedProcessArchitectures(mpt::Windows::GetHostArchitecture());
+	for(const auto & arch : architectures)
+	{
+		j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::CharsetUTF8, mpt::Windows::Name(arch))] = true;
+	}
+	j["System"]["Memory"] = mpt::Windows::GetSystemMemorySize() / 1024 / 1024;  // MB
+	if(mpt::Windows::IsWine())
+	{
+		mpt::Wine::VersionContext v;
+		j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
+		if(v.Version().IsValid())
+		{
+			j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
+			j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
+			j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
+		}
+		j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Id"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Id"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Id"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["lm"] = ((GetRealProcSupport() & PROCSUPPORT_LM) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +328,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +413,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// �ncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +433,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
+		context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
 		s_InstanceCount.fetch_sub(1);
 		MPT_ASSERT(s_InstanceCount.load() >= 0);
 		return;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +516,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK_UPDATEENABLED,         &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO1,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO2,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO3,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_BUTTON1,                     &CUpdateSetupDlg::OnCheckNow)
+	ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +534,82 @@
 }
 
 
+void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
+{
+	CDialog::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
+}
+
+
 BOOL CUpdateSetupDlg::OnInitDialog()
 {
 	CPropertyPage::OnInitDialog();
 
+	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
+
 	int radioID = 0;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
-	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
+	CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
 
+	int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
+	int ndx;
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("always"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 0);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 7);
+	if(periodDays >= 7)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 30);
+	if(periodDays >= 0)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("never"));
+	m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
+	if(periodDays < 0)
+	{		
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +617,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
+
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,20 +678,61 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+
+	GetDlgItem(IDC_STATIC_UDATECHANNEL)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO1)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(status);
+	GetDlgItem(IDC_RADIO3)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATECHECK)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_COMBO_UPDATEFREQUENCY)->EnableWindow(status);
+	GetDlgItem(IDC_BUTTON1)->EnableWindow(status);
+	GetDlgItem(IDC_LASTUPDATE)->EnableWindow(status);
+
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACY)->EnableWindow(status);
+	GetDlgItem(IDC_CHECK1)->EnableWindow(status);
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
+	GetDlgItem(IDC_EDIT_STATISTICS)->EnableWindow(status);
+
+	// disabled features
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
-	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+	if(GetDlgItem(IDC_CHECK_UPDATEENABLED)->IsWindowEnabled() != FALSE)
+	{
+		TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	}
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
 }
@@ -452,10 +751,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10965)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,13 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetStatisticsUserInformation(bool shortText);
+
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +55,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +62,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +109,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +124,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,6 +143,7 @@
 	CUpdateSetupDlg();
 
 protected:
+	void DoDataExchange(CDataExchange *pDX) override;
 	BOOL OnInitDialog() override;
 	void OnOK() override;
 	BOOL OnSetActive() override;
@@ -117,13 +150,15 @@
 
 	void SettingChanged(const SettingPath &changedPath) override;
 
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10965)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -119,6 +119,8 @@
 	combo->SetItemDataPtr(combo->AddString(_T("FastTracker 2")), (void*)("US_mpt-ft2_classic"));
 
 	CheckDlgButton(IDC_CHECK1, BST_CHECKED);
+	CheckDlgButton(IDC_CHECK3, BST_UNCHECKED);
+	GetDlgItem(IDC_STATIC_WELCOME_STATISTICS)->SetWindowText(mpt::ToCString(mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), MPT_USTRING("\n"), MPT_USTRING(" "))));
 	CheckDlgButton(IDC_CHECK2, (TrackerSettings::Instance().patternFont.Get().name == PATTERNFONT_LARGE) ? BST_CHECKED : BST_UNCHECKED);
 
 	ShowWindow(SW_SHOW);
@@ -147,7 +149,8 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK3) != BST_UNCHECKED);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
manx

manx

2018-11-09 09:12

administrator   ~0003708

  • [Mod] Update Check: Rework update dialog. The explicit setting of the update URL is moved to hidden settings and instead a selection of the update channel is provided (i.e. -release or -development), which should be way less confusing to users.
  • [Fix] Update Check: Make sure that each user and/or installation is at least once asked prominently if they want to contribute statistics to OpenMPT. Change the default to not providing statistics for privacy reasons.
  • [Imp] Update Check: Make completely transparent what information is sent to OpenMPT as part of the update check and statistics data.
  • [Mod] Update Check: Add possibility to check for updates at each program startup.
  • [Imp] Update Check: In case the last update check supposedly happened in the future, we now always check for updates instead of comparing against the configured interval. Future dates had probably been caused by a wrong system clock, in which case we cannot depend on its value (neither before nor after) in order to determine a real world time interval. We always check in this case for security reasons in order to get updates to the users.
  • [New] Update Check: Add client side of new statistics gathering API. This uses a JSON REST interface. The old API is still supported and used, which enables us to convert the server part at a later time. We should implement some JSON/REST thingy just now in order to test it and make it work reliably for when we convert the update mechanism itself to JSON.
  • [New] Statistics: Add CPU type and flags, system memory, precise OS version, and supported process architectures to gathered statistics data.
manx

manx

2018-11-11 15:49

administrator   ~0003712

Patch committed as r10970.

Issue History

Date Modified Username Field Change
2017-08-14 10:24 Saga Musix New Issue
2017-08-14 10:25 Saga Musix Description Updated View Revisions
2017-08-14 11:27 manx Note Added: 0003167
2017-08-14 11:32 Saga Musix Description Updated View Revisions
2017-08-14 12:40 Saga Musix Description Updated View Revisions
2017-08-14 12:42 Saga Musix Description Updated View Revisions
2017-08-16 22:51 Saga Musix Description Updated View Revisions
2017-09-22 15:10 manx Note Added: 0003230
2017-09-22 15:28 Saga Musix Note Added: 0003232
2017-09-22 15:38 manx Note Added: 0003233
2018-05-27 06:22 manx Relationship added related to 0001123
2018-08-22 10:55 manx File Added: Screenshot at 2018-01-15 11:58:45.png
2018-08-22 10:55 manx Note Added: 0003605
2018-08-22 16:03 manx File Added: updatedialog-v2.png
2018-08-22 16:05 manx File Added: update-statistics-v2.patch
2018-09-12 06:12 manx Relationship added related to 0000649
2018-09-12 13:46 manx File Added: update-statistics-v3.patch
2018-11-04 15:39 manx File Added: update-statistics-v5.patch
2018-11-09 09:12 manx File Added: update-statistics-v7.patch
2018-11-09 09:12 manx Note Added: 0003708
2018-11-11 15:49 manx Note Added: 0003712