Browse Source

Merge with master and resolve CI configure script conflict.

pull/2/head
gera 1 year ago
committed by Paul Licameli
parent
commit
0ab6aefe11
  1. 8
      CMakeLists.txt
  2. 1
      scripts/ci/configure.sh
  3. 5
      src/AudacityApp.cpp
  4. 5
      src/AudacityApp.h
  5. 16
      src/CMakeLists.txt
  6. 152
      src/update/UpdateDataParser.cpp
  7. 66
      src/update/UpdateDataParser.h
  8. 150
      src/update/UpdateManager.cpp
  9. 57
      src/update/UpdateManager.h
  10. 136
      src/update/UpdatePopupDialog.cpp
  11. 46
      src/update/UpdatePopupDialog.h
  12. 85
      src/update/VersionId.cpp
  13. 49
      src/update/VersionId.h
  14. 23
      src/update/VersionPatch.h

8
CMakeLists.txt

@ -191,6 +191,14 @@ cmake_dependent_option(
Off
)
cmake_dependent_option(
${_OPT}has_updates_check
"Has a builtin support for checking of updates"
On
"${_OPT}has_networking"
Off
)
# Determine 32-bit or 64-bit target
if( CMAKE_C_COMPILER_ID MATCHES "MSVC" AND CMAKE_VS_PLATFORM_NAME MATCHES "Win64|x64" )
set( IS_64BIT ON )

1
scripts/ci/configure.sh

@ -12,6 +12,7 @@ cmake_args=(
-G "${AUDACITY_CMAKE_GENERATOR}"
-D audacity_use_pch=no
-D audacity_has_networking=yes
-D audacity_has_updates_check=yes
-D CMAKE_BUILD_TYPE="${AUDACITY_BUILD_TYPE}"
-D CMAKE_INSTALL_PREFIX="${AUDACITY_INSTALL_PREFIX}"
)

5
src/AudacityApp.cpp

@ -111,6 +111,7 @@ It handles initialization and termination by subclassing wxApp.
#include "tracks/ui/Scrubbing.h"
#include "widgets/FileConfig.h"
#include "widgets/FileHistory.h"
#include "update/UpdateManager.h"
#ifdef HAS_NETWORKING
#include "NetworkManager.h"
@ -1489,6 +1490,10 @@ bool AudacityApp::InitPart2()
SplashDialog::DoHelpWelcome(*project);
}
#if defined(HAVE_UPDATES_CHECK)
mUpdateManager = std::make_unique<UpdateManager>(*project);
#endif
#ifdef USE_FFMPEG
FFmpegStartup();
#endif

5
src/AudacityApp.h

@ -33,6 +33,7 @@ class Importer;
class CommandHandler;
class AppCommandEvent;
class AudacityProject;
class UpdateManager;
class AudacityApp final : public wxApp {
public:
@ -113,6 +114,10 @@ class AudacityApp final : public wxApp {
std::unique_ptr<wxSocketServer> mIPCServ;
#endif
#if defined(HAVE_UPDATES_CHECK)
std::unique_ptr<UpdateManager> mUpdateManager;
#endif
public:
DECLARE_EVENT_TABLE()
};

16
src/CMakeLists.txt

@ -982,6 +982,19 @@ list( APPEND SOURCES
xml/XMLWriter.h
xml/audacityproject.dtd
# Update version
$<$<BOOL:${${_OPT}has_updates_check}>:
update/VersionId.h
update/VersionId.cpp
update/VersionPatch.h
update/UpdateDataParser.h
update/UpdateDataParser.cpp
update/UpdateManager.h
update/UpdateManager.cpp
update/UpdatePopupDialog.h
update/UpdatePopupDialog.cpp
>
Experimental.cmake
)
@ -1038,6 +1051,9 @@ list( APPEND DEFINES
__STDC_CONSTANT_MACROS
STRICT
>
$<$<BOOL:${USE_UPDATE_CHECK}>:
HAVE_UPDATES_CHECK
>
)
# If we have cmake 3.16 or higher, we can use precompiled headers, but

152
src/update/UpdateDataParser.cpp

@ -0,0 +1,152 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdateDataParser.cpp
@brief Declare a class that parse update server data format.
Anton Gerasimov
**********************************************************************/
#include "UpdateDataParser.h"
#include "xml/XMLFileReader.h"
UpdateDataParser::UpdateDataParser()
{}
UpdateDataParser::~UpdateDataParser()
{}
bool UpdateDataParser::Parse(const VersionPatch::UpdateDataFormat& updateData, VersionPatch* versionPatch)
{
XMLFileReader xmlReader;
mVersionPatch = versionPatch;
auto ok = xmlReader.ParseString(this, updateData);
mVersionPatch = nullptr;
return ok;
}
wxArrayString UpdateDataParser::splitChangelogSentences(const wxString& changelogContent)
{
wxArrayString changelogSentenceList;
size_t pos = 0;
std::string s(changelogContent.ToStdString());
std::string token;
const std::string delimiter(". ");
while ((pos = s.find(delimiter)) != std::string::npos)
{
token = s.substr(0, pos + 1);
changelogSentenceList.Add(token);
s.erase(0, pos + delimiter.length());
}
changelogSentenceList.Add(s);
return changelogSentenceList;
}
bool UpdateDataParser::HandleXMLTag(const wxChar* tag, const wxChar** attrs)
{
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kDescriptionTag]) == 0)
{
mXmlParsingState = XmlParsedTags::kDescriptionTag;
return true;
}
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kWindowsTag]) == 0)
{
if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS)
mXmlParsingState = XmlParsedTags::kOsTag;
return true;
}
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kMacosTag]) == 0)
{
if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_MAC)
mXmlParsingState = XmlParsedTags::kOsTag;
return true;
}
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kLinuxTag]) == 0)
{
if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_UNIX_LINUX)
mXmlParsingState = XmlParsedTags::kOsTag;
return true;
}
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kVersionTag]) == 0)
{
if (mXmlParsingState == XmlParsedTags::kOsTag)
mXmlParsingState = XmlParsedTags::kVersionTag;
return true;
}
if (wxStrcmp(tag, mXmlTagNames[XmlParsedTags::kLinkTag]) == 0)
{
if (mXmlParsingState == XmlParsedTags::kOsTag)
mXmlParsingState = XmlParsedTags::kLinkTag;
return true;
}
for (auto& xmlTag : mXmlTagNames)
{
if (wxStrcmp(tag, xmlTag.second) == 0)
return true;
}
return false;
}
void UpdateDataParser::HandleXMLEndTag(const wxChar* tag)
{
if (mXmlParsingState == XmlParsedTags::kDescriptionTag ||
mXmlParsingState == XmlParsedTags::kLinkTag)
mXmlParsingState = XmlParsedTags::kNotUsedTag;
if (mXmlParsingState == XmlParsedTags::kVersionTag)
mXmlParsingState = XmlParsedTags::kOsTag;
}
void UpdateDataParser::HandleXMLContent(const wxString& content)
{
if (mVersionPatch == nullptr)
return;
wxString trimedContent(content);
switch (mXmlParsingState)
{
case XmlParsedTags::kDescriptionTag:
trimedContent.Trim(true).Trim(false);
mVersionPatch->changelog = splitChangelogSentences(trimedContent);
break;
case XmlParsedTags::kVersionTag:
trimedContent.Trim(true).Trim(false);
mVersionPatch->version = VersionId::ParseFromString(trimedContent);
break;
case XmlParsedTags::kLinkTag:
trimedContent.Trim(true).Trim(false);
mVersionPatch->download = trimedContent;
break;
default:
break;
}
}
XMLTagHandler* UpdateDataParser::HandleXMLChild(const wxChar* tag)
{
for (auto& xmlTag : mXmlTagNames)
{
if (wxStrcmp(tag, xmlTag.second) == 0)
return this;
}
return NULL;
}

66
src/update/UpdateDataParser.h

@ -0,0 +1,66 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdateDataParser.h
@brief Declare a class that parse update server data format.
Anton Gerasimov
**********************************************************************/
#pragma once
#include "VersionPatch.h"
#include "xml/XMLTagHandler.h"
#include <wx/arrstr.h>
#include <map>
/// A class that parse update server data format.
class UpdateDataParser final : public XMLTagHandler
{
public:
UpdateDataParser();
~UpdateDataParser();
/// <summary>
/// Parsing from update data format to VersionPatch fields.
/// </summary>
/// <param name="updateData">Input data.</param>
/// <param name="versionPatch">Parsed output data.</param>
/// <returns>True if success.</returns>
bool Parse(const VersionPatch::UpdateDataFormat& updateData, VersionPatch* versionPatch);
private:
enum class XmlParsedTags : int {
kNotUsedTag,
kUpdateTag,
kDescriptionTag,
kOsTag,
kWindowsTag,
kMacosTag,
kLinuxTag,
kVersionTag,
kLinkTag
};
XmlParsedTags mXmlParsingState{ XmlParsedTags::kNotUsedTag };
std::map<XmlParsedTags, wxString> mXmlTagNames{
{ XmlParsedTags::kUpdateTag, wxT("Updates") },
{ XmlParsedTags::kDescriptionTag, wxT("Description") },
{ XmlParsedTags::kOsTag, wxT("OS") },
{ XmlParsedTags::kWindowsTag, wxT("Windows") },
{ XmlParsedTags::kMacosTag, wxT("Macos") },
{ XmlParsedTags::kLinuxTag, wxT("Linux") },
{ XmlParsedTags::kVersionTag, wxT("Version") },
{ XmlParsedTags::kLinkTag, wxT("Link") },
};
bool HandleXMLTag(const wxChar* tag, const wxChar** attrs) override;
void HandleXMLEndTag(const wxChar* tag) override;
void HandleXMLContent(const wxString& content) override;
XMLTagHandler* HandleXMLChild(const wxChar* tag) override;
wxArrayString splitChangelogSentences(const wxString& changelogContent);
VersionPatch* mVersionPatch{ nullptr };
};

150
src/update/UpdateManager.cpp

@ -0,0 +1,150 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdateManager.cpp
@brief Declare a class that managing of updates.
Anton Gerasimov
**********************************************************************/
#include "UpdateManager.h"
#include "UpdatePopupDialog.h"
#include "NetworkManager.h"
#include "IResponse.h"
#include "Request.h"
#include "widgets/AudacityMessageBox.h"
#include <wx/platinfo.h>
#include <wx/utils.h>
static const char* prefsUpdatePopupDialogShown = "/Update/UpdatePopupDialogShown";
static const char* prefsUpdateScheduledTime = "/Update/UpdateScheduledTime";
enum { ID_TIMER = wxID_HIGHEST + 1 };
BEGIN_EVENT_TABLE(UpdateManager, wxEvtHandler)
EVT_TIMER(ID_TIMER, UpdateManager::OnTimer)
END_EVENT_TABLE()
UpdateManager::UpdateManager(AudacityProject& project)
: mTrackingInterval(
std::chrono::milliseconds(std::chrono::hours(12)).count())
{
mParent = (wxWindow*)(&GetProjectFrame(project));
wxASSERT(mParent);
mTimer.SetOwner(this, ID_TIMER);
mTimer.StartOnce();
}
UpdateManager::~UpdateManager()
{
mTimer.Stop();
}
void UpdateManager::enableTracking(bool enable)
{
gPrefs->Write(prefsUpdatePopupDialogShown, enable);
gPrefs->Flush();
}
bool UpdateManager::isTrackingEnabled()
{
return gPrefs->ReadBool(prefsUpdatePopupDialogShown, true);
}
VersionPatch UpdateManager::getVersionPatch() const
{
return mVersionPatch;
}
void UpdateManager::getUpdates()
{
audacity::network_manager::Request request("https://updates.audacityteam.org/feed/latest.xml");
auto response = audacity::network_manager::NetworkManager::GetInstance().doGet(request);
response->setRequestFinishedCallback([response, this](audacity::network_manager::IResponse*) {
if (response->getError() != audacity::network_manager::NetworkError::NoError)
{
AudacityMessageBox(
XO("Unable to connect to Audacity update server."),
XO("Error checking for update"),
wxOK | wxCENTRE,
NULL);
return;
}
if (!mUpdateDataParser.Parse(response->readAll<VersionPatch::UpdateDataFormat>(), &mVersionPatch))
{
AudacityMessageBox(
XO("Update data was corrupted."),
XO("Error checking for update"),
wxOK | wxCENTRE,
NULL);
return;
}
if (mVersionPatch.version > CurrentBuildVersion())
{
mParent->CallAfter([this] {
UpdatePopupDialog dlg(mParent, this);
const int code = dlg.ShowModal();
if (code == wxID_YES)
{
if (!wxLaunchDefaultBrowser(mVersionPatch.download))
{
AudacityMessageBox(
XO("Can't open the Audacity download link."),
XO("Error downloading update"),
wxOK | wxCENTRE,
NULL);
return;
}
}
});
}
});
}
void UpdateManager::OnTimer(wxTimerEvent& WXUNUSED(event))
{
if (isTrackingEnabled() && isTimeToUpdate())
getUpdates();
mTimer.StartOnce(mTrackingInterval);
}
bool UpdateManager::isTimeToUpdate()
{
long long nextTrackingTime = std::stoll(
gPrefs->Read(prefsUpdateScheduledTime, "0").ToStdString());
// Get current time in milliseconds
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now());
auto currentTimeInMillisec = std::chrono::duration_cast<std::chrono::milliseconds>(
now_ms.time_since_epoch()).count();
// If next update time 0 or less then current time -> show update dialog,
// else this condition allow us to avoid from duplicating update notifications.
if (nextTrackingTime < currentTimeInMillisec)
{
nextTrackingTime = currentTimeInMillisec + mTrackingInterval;
gPrefs->Write(prefsUpdateScheduledTime,
wxString(std::to_string(nextTrackingTime)));
gPrefs->Flush();
return true;
}
return false;
}

57
src/update/UpdateManager.h

@ -0,0 +1,57 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdateManager.h
@brief Declare a class that managing of updates.
Anton Gerasimov
**********************************************************************/
#pragma once
#include "VersionId.h"
#include "VersionPatch.h"
#include "UpdateDataParser.h"
#include "Project.h"
#include "Prefs.h"
#include <wx/string.h>
#include <wx/event.h>
#include <wx/timer.h>
/// A class that managing of updates.
/**
Opt-in request and show update dialog by the scheduled time.
Have a built-in check that allows avoiding multiplying update notifications
when multiple Audacity windows are shown.
*/
class UpdateManager final : public wxEvtHandler
{
public:
UpdateManager(AudacityProject& project);
~UpdateManager();
void getUpdates();
void enableTracking(bool enable);
bool isTrackingEnabled();
VersionPatch getVersionPatch() const;
private:
UpdateDataParser mUpdateDataParser;
VersionPatch mVersionPatch;
wxWindow* mParent;
wxTimer mTimer;
const int mTrackingInterval;
void OnTimer(wxTimerEvent& event);
/// Scheduling update time for avoiding multiplying update notifications.
bool isTimeToUpdate();
public:
DECLARE_EVENT_TABLE()
};

136
src/update/UpdatePopupDialog.cpp

@ -0,0 +1,136 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdatePopupDialog.cpp
@brief Define a dialog for notifying users about new version available.
Anton Gerasimov
**********************************************************************/
#include "update/UpdatePopupDialog.h"
#include "ShuttleGui.h"
#include "widgets/HelpSystem.h"
#include <wx/debug.h>
#include <wx/sstream.h>
#include <wx/txtstrm.h>
enum { DontShowID = wxID_HIGHEST + 1 };
BEGIN_EVENT_TABLE (UpdatePopupDialog, wxDialogWrapper)
EVT_BUTTON (wxID_YES, UpdatePopupDialog::OnUpdate)
EVT_BUTTON (wxID_NO, UpdatePopupDialog::OnSkip)
EVT_CHECKBOX (DontShowID, UpdatePopupDialog::OnDontShow)
END_EVENT_TABLE()
IMPLEMENT_CLASS (UpdatePopupDialog, wxDialogWrapper)
UpdatePopupDialog::UpdatePopupDialog (wxWindow* parent, UpdateManager* updateManager)
: wxDialogWrapper (parent, -1, XO ("Update Audacity"),
wxDefaultPosition, wxDefaultSize,
wxCAPTION),
mUpdateManager (updateManager)
{
wxASSERT(mUpdateManager);
ShuttleGui S (this, eIsCreating);
S.SetBorder (5);
S.StartVerticalLay (wxEXPAND, 1);
{
S.AddWindow (AddHtmlContent (S.GetParent()));
S.StartHorizontalLay (wxEXPAND, 0);
{
S.SetBorder (5);
S.Id (DontShowID).AddCheckBox (
XO ("Don't show this again at start up"), !mUpdateManager->isTrackingEnabled());
S.Prop(1).AddSpace(1, 0, 1);
S.Id (wxID_NO).AddButton (XO ("Skip"));
S.Id (wxID_YES).AddButton (XO ("Install update"));
S.SetBorder (5);
}
S.EndHorizontalLay();
}
S.EndVerticalLay();
Layout();
Fit();
Center();
}
UpdatePopupDialog::~UpdatePopupDialog()
{
;
}
void UpdatePopupDialog::OnUpdate (wxCommandEvent&)
{
EndModal (wxID_YES);
}
void UpdatePopupDialog::OnSkip (wxCommandEvent&)
{
EndModal (wxID_NO);
}
void UpdatePopupDialog::OnDontShow (wxCommandEvent& event)
{
mUpdateManager->enableTracking(!event.IsChecked());
}
HtmlWindow* UpdatePopupDialog::AddHtmlContent (wxWindow* parent)
{
wxStringOutputStream o;
wxTextOutputStream informationStr (o);
static const auto title = XO("Audacity %s is available!")
.Format(mUpdateManager->getVersionPatch().version.getString());
informationStr
<< wxT("<html><body><h3>")
<< title.Translation()
<< wxT("</h3><h5>")
<< XO("Changelog")
<< wxT("</h5><p>");
informationStr << wxT("<ul>");
for (auto& logLine : mUpdateManager->getVersionPatch().changelog)
{
informationStr << wxT("<li>");
// We won't to translate downloaded text.
informationStr << logLine;
informationStr << wxT("</li>");
}
informationStr << wxT("</ul></p>");
informationStr << wxT("<p>");
informationStr << XO("<a href = \"https://github.com/audacity/audacity/releases\">Read more on GitHub</a>");
informationStr << wxT("</p>");
informationStr << wxT("</body></html>");
HtmlWindow* html = safenew LinkingHtmlWindow (parent, -1,
wxDefaultPosition,
wxSize (500, -1),
wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
html->SetBorders (20);
html->SetPage (o.GetString());
wxHtmlContainerCell* cell = html->GetInternalRepresentation();
cell->Layout (500);
const wxSize size = wxSize (500, cell->GetHeight());
html->SetMinSize (size);
html->SetMaxSize (size);
html->SetSize (size);
return html;
}

46
src/update/UpdatePopupDialog.h

@ -0,0 +1,46 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file UpdatePopupDialog.h
@brief Define a dialog for notifying users about new version available.
Anton Gerasimov
**********************************************************************/
#pragma once
#include "widgets/wxPanelWrapper.h"
#include "wx/string.h"
#include "Project.h"
#include "update/UpdateManager.h"
class HtmlWindow;
class wxWindow;
class AudacityProject;
class UpdateManager;
/// Show dialog window with update information for the user.
/**
Support user action behaviors as skip, download a new version,
and a checkbox that does not allow tracking version again.
*/
class UpdatePopupDialog final : public wxDialogWrapper
{
DECLARE_DYNAMIC_CLASS (AboutDialog)
public:
explicit UpdatePopupDialog (wxWindow* parent, UpdateManager *updateManager);
virtual ~UpdatePopupDialog();
void OnUpdate (wxCommandEvent& event);
void OnSkip (wxCommandEvent& event);
void OnDontShow (wxCommandEvent& event);
DECLARE_EVENT_TABLE()
private:
HtmlWindow* AddHtmlContent (wxWindow* parent);
UpdateManager* const mUpdateManager;
};

85
src/update/VersionId.cpp

@ -0,0 +1,85 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file VersionId.h
@brief Declare a class with version number manipulation.
Anton Gerasimov
**********************************************************************/
#include "VersionId.h"
VersionId::VersionId(int version, int release, int revision)
: mVersion(version),
mRelease(release),
mRevision(revision)
{}
wxString VersionId::MakeString(int version, int release, int revision)
{
return std::to_string(version)
+ "." + std::to_string(release)
+ "." + std::to_string(revision);
}
VersionId VersionId::ParseFromString(wxString& versionString)
{
auto versionStringParts = wxSplit(versionString, '.');
// If we have corrupted version string,
// then return the zero version number, that not allow us to update.
if (versionStringParts.size() != 3)
return VersionId{};
for (auto& v : versionStringParts)
{
if (v.empty() || !v.IsNumber())
return VersionId{};
}
return VersionId(
std::stoi(versionStringParts[0].ToStdString()),
std::stoi(versionStringParts[1].ToStdString()),
std::stoi(versionStringParts[2].ToStdString())
);
}
wxString VersionId::getString() const
{
return MakeString(mVersion, mRelease, mRevision);
}
bool VersionId::operator== (const VersionId& other)
{
return mVersion == other.mVersion &&
mRelease == other.mRelease &&
mRevision == other.mRevision;
}
bool VersionId::operator!= (const VersionId& other)
{
return !(*this == other);
}
bool VersionId::operator< (const VersionId& other)
{
if (mVersion < other.mVersion)
return true;
if (mRelease < other.mRelease &&
mVersion == other.mVersion)
return true;
if (mRevision < other.mRevision &&
mVersion == other.mVersion &&
mRelease == other.mRelease)
return true;
return false;
}
bool VersionId::operator> (const VersionId& other)
{
if (*this == other) return false;
return !(*this < other);
}

49
src/update/VersionId.h

@ -0,0 +1,49 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file VersionId.h
@brief Declare a class with version number manipulation.
Anton Gerasimov
**********************************************************************/
#pragma once
#include <wx/arrstr.h>
/// A class, that support base manipulation with version number.
/**
By default initialized by zero version number (Version = 0, Release = 0, Revision = 0),
that not allow us to update.
*/
class VersionId final
{
public:
/// Creates an zero version object.
VersionId() = default;
VersionId(int version, int release, int revision);
/// Creates version string like "1.2.3" by parameters.
static wxString MakeString(int version, int release, int revision);
/// Parse and return version object from version string like "1.2.3".
static VersionId ParseFromString(wxString& versionString);
/// Make string with version by MakeString() from instance values.
wxString getString() const;
bool operator== (const VersionId& other);
bool operator!= (const VersionId& other);
bool operator< (const VersionId& other);
bool operator> (const VersionId& other);
private:
int mVersion{ 0 };
int mRelease{ 0 };
int mRevision{ 0 };
};
/// Return version (VersionId) object with current Audacity build version.
static inline VersionId CurrentBuildVersion()
{
return VersionId{ AUDACITY_VERSION, AUDACITY_RELEASE, AUDACITY_REVISION };
}

23
src/update/VersionPatch.h

@ -0,0 +1,23 @@
/*!********************************************************************
Audacity: A Digital Audio Editor
@file VersionPatch.h
@brief Declare a structure that describes patch fields.
Anton Gerasimov
**********************************************************************/
#pragma once
#include "VersionId.h"
/// A structure that describes patch fields.
struct VersionPatch final
{
using UpdateDataFormat = std::string;
VersionPatch() = default;
VersionId version;
wxArrayString changelog;
wxString download;
};
Loading…
Cancel
Save