Update mDNS service announcements when name changes.

This commit is contained in:
Robin Davies
2024-09-19 11:19:24 -04:00
parent 7ecc3de10a
commit 951a7a73de
18 changed files with 348 additions and 257 deletions
+26
View File
@@ -66,6 +66,32 @@
} }
] ]
}, },
{
"name": "(gdb) makeRelease",
"type": "cppdbg",
"request": "launch",
// Resolved by CMake Tools:
"program": "${command:cmake.launchTargetPath}",
"args": [ ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/../..",
"environment": [
{
"name": "PATH",
"value": "$PATH:${command:cmake.launchTargetDirectory}"
}
],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{ {
"name": "(gdb) pipedal_nm_p2pd", "name": "(gdb) pipedal_nm_p2pd",
"type": "cppdbg", "type": "cppdbg",
+2 -3
View File
@@ -4,7 +4,7 @@ project(pipedal
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
set (DISPLAY_VERSION "PiPedal v1.2.49-Release") set (DISPLAY_VERSION "PiPedal v1.2.49-Development")
set (PACKAGE_ARCHITECTURE "arm64") set (PACKAGE_ARCHITECTURE "arm64")
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
@@ -22,7 +22,6 @@ add_subdirectory("src")
# Replaced with hotspot. # Replaced with hotspot.
#add_subdirectory("NetworkManagerP2P") #add_subdirectory("NetworkManagerP2P")
# install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin # install (TARGETS pipedal_p2pd DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
# ) # )
@@ -56,7 +55,7 @@ install(
#) #)
install ( install (
FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/55-pipedal-networkmanager.rule DESTINATION /etc/polkit-1/rules.d/ FILES ${PROJECT_SOURCE_DIR}/src/polkit-1/rules/10-pipedal-networkmanager.rules DESTINATION /etc/polkit-1/rules.d/
) )
install ( install (
DIRECTORY ${REACT_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react DIRECTORY ${REACT_BUILD_DIRECTORY} DESTINATION /etc/pipedal/react
+2 -1
View File
@@ -4,5 +4,6 @@
"debug": true, "debug": true,
"max_upload_size": 536870912, "max_upload_size": 536870912,
"fakeAndroid": false, "fakeAndroid": false,
"ui_plugins": [] "ui_plugins": [],
"enable_auto_update": true
} }
+13 -9
View File
@@ -1,10 +1,14 @@
#!/bin/bash #!/bin/bash
# Sign the built .deb package with the private key used in the Auto-update process.
#sign a package with rerdavies@gmail.com's private key. #
cd build # Won't work for you, because you don't have MY private key.
for filename in *.deb; do #
echo gpg --armor --output "$filename".asc -b "$filename" # Auto-update requires that .deb files be distributed from the GitHub rerdavies/pipedal project.
# And auto-updates WILL update installs of your forked binary unless you take steps to prevent that.
gpg --armor --output "$filename".asc -b "$filename" #
done # If you are distributing a fork, you should see the file UpdaterSecurity.hpp for details on how to
cd .. # modify pipedal to perform updates from an alternate github site. Or you could modify your fork
# to NOT do auto-updating (probably best). The build doesn't currently have a procedure for
# knocking out Auto-Update from UpdaterSecurity.hpp. But I would happily accept a pull request
# to bring such a feature back into mainline if you do it.
build/src/makeRelease
+30 -37
View File
@@ -45,6 +45,12 @@ void AvahiService::Announce(
const std::string &mdnsName, const std::string &mdnsName,
bool addTestGroup) bool addTestGroup)
{ {
// We should update an existing group if the name doesn't change.
// but in practice, the name is the only thing that's going to change without restarting the server.
if (this->name && strcmp(name.c_str(),this->name) == 0)
{
return;
}
Unannounce(); Unannounce();
this->portNumber = portNumber; this->portNumber = portNumber;
@@ -162,7 +168,7 @@ void AvahiService::create_group(AvahiClient *c)
if ((ret = avahi_entry_group_add_service( if ((ret = avahi_entry_group_add_service(
group, group,
AVAHI_IF_UNSPEC, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.) AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0, (AvahiPublishFlags)0,
name, name,
PIPEDAL_SERVICE_TYPE, PIPEDAL_SERVICE_TYPE,
@@ -307,41 +313,32 @@ void AvahiService::Start()
while (true) while (true)
{ {
/* Allocate main loop object */ /* Allocate main loop object */
if (!(this->threadedPoll = avahi_threaded_poll_new())) if (!this->threadedPoll)
{ {
Lv2Log::error("Failed to create Avahi poll object."); if (!(this->threadedPoll = avahi_threaded_poll_new()))
goto fail; {
Lv2Log::error("Failed to create Avahi poll object.");
goto fail;
}
avahi_threaded_poll_start(threadedPoll);
} }
/* Allocate a new client */
client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error);
/* Check wether creating the client object succeeded */
if (!client) if (!client)
{ {
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error))); /* Allocate a new client */
goto fail; client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error);
/* Check wether creating the client object succeeded */
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
goto fail;
}
} }
avahi_threaded_poll_start(threadedPoll);
ret = 0; ret = 0;
return; return;
fail: fail:
/* Cleanup things */ Stop();
if (client)
{
avahi_client_free(client);
client = nullptr;
}
if (group)
{
avahi_entry_group_reset(group);
group = nullptr;
}
if (threadedPoll)
{
avahi_threaded_poll_free(threadedPoll);
threadedPoll = nullptr;
}
} }
return; return;
} }
@@ -349,27 +346,23 @@ void AvahiService::Start()
void AvahiService::Stop() void AvahiService::Stop()
{ {
if (threadedPoll) /* Cleanup things */
if (group)
{ {
avahi_threaded_poll_stop(threadedPoll); avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
} }
/* Cleanup things */
// client owns the group?
// if (group)
// {
// avahi_entry_group_reset(group);
// group = nullptr;
// }`
if (client) if (client)
{ {
avahi_client_free(client); avahi_client_free(client);
client = nullptr; client = nullptr;
} }
if (threadedPoll) if (threadedPoll)
{ {
avahi_threaded_poll_stop(threadedPoll);
avahi_threaded_poll_free(threadedPoll); avahi_threaded_poll_free(threadedPoll);
threadedPoll = nullptr; threadedPoll = nullptr;
} }
group = nullptr;
} }
-3
View File
@@ -43,7 +43,6 @@ namespace pipedal {
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false); int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false);
void Unannounce(); void Unannounce();
private: private:
void Start(); void Start();
void Stop(); void Stop();
@@ -56,8 +55,6 @@ namespace pipedal {
void create_group(AvahiClient *c); void create_group(AvahiClient *c);
void threadProc();
int clientErrno = 0; int clientErrno = 0;
int portNumber = -1; int portNumber = -1;
+32 -8
View File
@@ -301,6 +301,9 @@ add_executable(hotspotManagerTest
target_link_libraries(hotspotManagerTest PRIVATE ${PIPEDAL_LIBS}) target_link_libraries(hotspotManagerTest PRIVATE ${PIPEDAL_LIBS})
set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
add_executable(pipedaltest testMain.cpp add_executable(pipedaltest testMain.cpp
@@ -329,6 +332,8 @@ target_link_libraries(pipedaltest PRIVATE ${PIPEDAL_LIBS})
target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES} target_include_directories(pipedaltest PRIVATE ${PIPEDAL_INCLUDES}
) )
set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL true)
if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers. if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers.
@@ -339,6 +344,9 @@ if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action se
target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES} target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES}
) )
set_target_properties(profilePlugin PROPERTIES EXCLUDE_FROM_ALL true)
endif() endif()
add_executable(jsonTest add_executable(jsonTest
testMain.cpp testMain.cpp
@@ -356,7 +364,12 @@ target_link_libraries(jsonTest PRIVATE PiPedalCommon)
target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES} target_include_directories(jsonTest PRIVATE ${PIPEDAL_INCLUDES}
) )
set_target_properties(jsonTest PROPERTIES EXCLUDE_FROM_ALL true)
target_link_libraries(jsonTest PRIVATE pthread) target_link_libraries(jsonTest PRIVATE pthread)
add_test(NAME jsonTest COMMAND jsonTest) add_test(NAME jsonTest COMMAND jsonTest)
@@ -561,18 +574,18 @@ if(ENABLE_VST3)
) )
add_executable(vst3test Vst3test.cpp # add_executable(vst3test Vst3test.cpp
MemDebug.cpp # MemDebug.cpp
MemDebug.hpp # MemDebug.hpp
Vst3SdkRepro.cpp # Vst3SdkRepro.cpp
${VST3_FILES} # ${VST3_FILES}
#vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp # #vst3/Vst3PresetFile.hpp Vst3PresetFile.cpp
asan_options.cpp # asan_options.cpp
) # )
target_include_directories(vst3test PRIVATE ${PIPEDAL_INCLUDES} target_include_directories(vst3test PRIVATE ${PIPEDAL_INCLUDES}
) )
@@ -642,6 +655,17 @@ target_include_directories(capturepresets PRIVATE ${PIPEDAL_INCLUDES})
target_link_libraries(capturepresets ${PIPEDAL_LIBS}) target_link_libraries(capturepresets ${PIPEDAL_LIBS})
add_executable(makeRelease
makeReleaseMain.cpp
CommandLineParser.hpp
)
target_include_directories(makeRelease PRIVATE ${PIPEDAL_INCLUDES})
target_link_libraries(makeRelease ${PIPEDAL_LIBS})
add_executable(pipedal_update add_executable(pipedal_update
UpdateMain.cpp UpdateMain.cpp
+8 -30
View File
@@ -66,7 +66,6 @@ namespace pipedal::impl
{ {
Initial, Initial,
WaitingForNetworkManager, WaitingForNetworkManager,
Disabled,
Monitoring, Monitoring,
HotspotConnecting, HotspotConnecting,
HotspotConnected, HotspotConnected,
@@ -82,7 +81,6 @@ namespace pipedal::impl
void onDevicesChanged(); void onDevicesChanged();
void onDisconnect(); void onDisconnect();
void onReload(); void onReload();
void onDisableHotspot();
void DisableHotspot(); void DisableHotspot();
void EnableHotspot(); void EnableHotspot();
@@ -169,7 +167,7 @@ void HotspotManagerImpl::Open()
void HotspotManagerImpl::onClose() void HotspotManagerImpl::onClose()
{ {
onDisableHotspot(); StopHotspot();
ReleaseNetworkManager(); ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Closed"); Lv2Log::debug("HotspotManager: state=Closed");
@@ -201,14 +199,7 @@ void HotspotManagerImpl::onInitialize()
{ {
wifiConfigSettings.Load(); wifiConfigSettings.Load();
if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled()) onStartMonitoring();
{
onStartMonitoring();
}
else
{
onDisableHotspot();
}
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@@ -431,17 +422,6 @@ void HotspotManagerImpl::WaitForNetworkManager()
ReleaseNetworkManager(); ReleaseNetworkManager();
StartWaitForNetworkManagerTimer(); StartWaitForNetworkManagerTimer();
} }
void HotspotManagerImpl::onDisableHotspot()
{
if (this->state != State::Disabled)
{
ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Disabled");
SetState(State::Disabled);
Lv2Log::info("HotspotManager: Hotspot disabled.");
}
}
void HotspotManagerImpl::onReload() void HotspotManagerImpl::onReload()
{ {
if (closed) if (closed)
@@ -453,17 +433,12 @@ void HotspotManagerImpl::onReload()
switch (state) switch (state)
{ {
case State::Initial: case State::Initial:
case State::Error:
// ignore. // ignore.
return; return;
case State::Disabled: default:
if (wifiConfigSettings.valid_ && wifiConfigSettings.IsEnabled()) MaybeStartHotspot();
{
onStartMonitoring();
}
return; return;
default:
this->onDisableHotspot();
break;
} }
} }
@@ -726,10 +701,12 @@ static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessP
void HotspotManagerImpl::MaybeStartHotspot() void HotspotManagerImpl::MaybeStartHotspot()
{ {
if (this->state == State::Error) return; if (this->state == State::Error) return;
if (this->closed) return;
if (!wlanDevice || !wlanWirelessDevice) if (!wlanDevice || !wlanWirelessDevice)
{ {
// devices are transitioning. Do nothing. // devices are transitioning. Do nothing.
return;
} }
std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints(); std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints); std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
@@ -787,6 +764,7 @@ Connection::ptr HotspotManagerImpl::FindExistingConnection()
} }
} }
} }
return nullptr; return nullptr;
} }
+3 -1
View File
@@ -71,6 +71,7 @@ PiPedalModel::PiPedalModel()
atomConverter(pluginHost.GetMapFeature()) atomConverter(pluginHost.GetMapFeature())
{ {
this->updater = Updater::Create(); this->updater = Updater::Create();
this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus(); this->currentUpdateStatus = updater->GetCurrentStatus();
this->pedalboard = Pedalboard::MakeDefault(); this->pedalboard = Pedalboard::MakeDefault();
#if JACK_HOST #if JACK_HOST
@@ -980,6 +981,7 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
{ {
this->hotspotManager->Reload(); this->hotspotManager->Reload();
} }
this->UpdateDnsSd();
} }
#else #else
this->storage.SetWifiConfigSettings(wifiConfigSettings); this->storage.SetWifiConfigSettings(wifiConfigSettings);
@@ -1020,7 +1022,7 @@ static std::string GetP2pdName()
void PiPedalModel::UpdateDnsSd() void PiPedalModel::UpdateDnsSd()
{ {
avahiService.Unannounce(); // avahiService.Unannounce(); let Announce decide whether it wants to unannounce or update.
ServiceConfiguration deviceIdFile; ServiceConfiguration deviceIdFile;
deviceIdFile.Load(); deviceIdFile.Load();
+202 -147
View File
@@ -48,7 +48,8 @@ namespace fs = std::filesystem;
#undef TEST_UPDATE // do NOT leat this leak into a production build! #undef TEST_UPDATE // do NOT leat this leak into a production build!
#endif #endif
namespace pipedal { namespace pipedal
{
class GithubResponseHeaders class GithubResponseHeaders
{ {
public: public:
@@ -62,8 +63,8 @@ namespace pipedal {
uint64_t ratelimit_used_ = 0; uint64_t ratelimit_used_ = 0;
std::string ratelimit_resource_; std::string ratelimit_resource_;
bool limit_exceeded() const { return code_ != 200 && ratelimit_limit_ != 0 && ratelimit_limit_ == ratelimit_used_; } bool limit_exceeded() const { return code_ != 200 && ratelimit_limit_ != 0 && ratelimit_limit_ == ratelimit_used_; }
void Load(); void Load(const std::filesystem::path &filename);
void Save(); void Save(const std::filesystem::path &filename);
DECLARE_JSON_MAP(GithubResponseHeaders); DECLARE_JSON_MAP(GithubResponseHeaders);
private: private:
@@ -74,24 +75,37 @@ namespace pipedal {
class pipedal::UpdaterImpl : public Updater class pipedal::UpdaterImpl : public Updater
{ {
public: public:
UpdaterImpl(); UpdaterImpl(const std::filesystem::path &workingDirectory);
UpdaterImpl() : UpdaterImpl("/var/pipedal/updates") {}
virtual ~UpdaterImpl() noexcept; virtual ~UpdaterImpl() noexcept;
virtual void SetUpdateListener(UpdateListener &&listener) override; virtual void SetUpdateListener(UpdateListener &&listener) override;
virtual void Start() override;
virtual void Stop() override;
virtual void CheckNow() override; virtual void CheckNow() override;
virtual void Stop();
virtual UpdatePolicyT GetUpdatePolicy() override; virtual UpdatePolicyT GetUpdatePolicy() override;
virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) override; virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) override;
virtual void ForceUpdateCheck() override; virtual void ForceUpdateCheck() override;
virtual void DownloadUpdate(const std::string &url, std::filesystem::path *file, std::filesystem::path *signatureFile) override; virtual void DownloadUpdate(const std::string &url, std::filesystem::path *file, std::filesystem::path *signatureFile) override;
virtual UpdateStatus GetCurrentStatus() const override { return this->currentResult; } virtual UpdateStatus GetCurrentStatus() const override { return this->currentResult; }
virtual UpdateStatus GetReleaseGeneratorStatus() override;
private: private:
std::filesystem::path workingDirectory;
std::filesystem::path updateStatusCacheFile;
std::filesystem::path githubResponseHeaderFilename;
GithubResponseHeaders githubResponseHeaders; GithubResponseHeaders githubResponseHeaders;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
void SetCachedUpdateStatus(UpdateStatus &updateStatus);
UpdateStatus GetCachedUpdateStatus();
void RetryAfter(clock::duration delay); void RetryAfter(clock::duration delay);
template <typename REP, typename PERIOD> template <typename REP, typename PERIOD>
@@ -120,6 +134,7 @@ private:
int event_reader = -1; int event_reader = -1;
int event_writer = -1; int event_writer = -1;
void ThreadProc(); void ThreadProc();
UpdateStatus DoUpdate(bool forReleaseGenerator);
void CheckForUpdate(bool useCache); void CheckForUpdate(bool useCache);
UpdateListener listener; UpdateListener listener;
@@ -135,26 +150,27 @@ Updater::ptr Updater::Create()
{ {
return std::make_unique<UpdaterImpl>(); return std::make_unique<UpdaterImpl>();
} }
Updater::ptr Updater::Create(const std::filesystem::path &workingDirectory)
{
return std::make_unique<UpdaterImpl>(workingDirectory);
}
static constexpr uint64_t CLOSE_EVENT = 0; static constexpr uint64_t CLOSE_EVENT = 0;
static constexpr uint64_t CHECK_NOW_EVENT = 1; static constexpr uint64_t CHECK_NOW_EVENT = 1;
static constexpr uint64_t UNCACHED_CHECK_NOW_EVENT = 2; static constexpr uint64_t UNCACHED_CHECK_NOW_EVENT = 2;
static std::filesystem::path WORKING_DIRECTORY = "/var/pipedal/updates";
static std::filesystem::path UPDATE_STATUS_CACHE_FILE = WORKING_DIRECTORY / "updateStatus.json";
UpdaterImpl::clock::duration UpdaterImpl::updateRate = std::chrono::duration_cast<UpdaterImpl::clock::duration>(std::chrono::days(1)); UpdaterImpl::clock::duration UpdaterImpl::updateRate = std::chrono::duration_cast<UpdaterImpl::clock::duration>(std::chrono::days(1));
static std::chrono::system_clock::duration CACHE_DURATION = std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::minutes(30)); static std::chrono::system_clock::duration CACHE_DURATION = std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::minutes(30));
std::mutex cacheMutex; std::mutex cacheMutex;
static UpdateStatus GetCachedUpdateStatus() UpdateStatus UpdaterImpl::GetCachedUpdateStatus()
{ {
std::lock_guard lock{cacheMutex}; std::lock_guard lock{cacheMutex};
try try
{ {
if (std::filesystem::exists(UPDATE_STATUS_CACHE_FILE)) if (std::filesystem::exists(updateStatusCacheFile))
{ {
std::ifstream f{UPDATE_STATUS_CACHE_FILE}; std::ifstream f{updateStatusCacheFile};
if (f.is_open()) if (f.is_open())
{ {
json_reader reader(f); json_reader reader(f);
@@ -174,13 +190,13 @@ static UpdateStatus GetCachedUpdateStatus()
return UpdateStatus(); return UpdateStatus();
} }
static void SetCachedUpdateStatus(UpdateStatus &updateStatus) void UpdaterImpl::SetCachedUpdateStatus(UpdateStatus &updateStatus)
{ {
std::lock_guard lock{cacheMutex}; std::lock_guard lock{cacheMutex};
updateStatus.LastUpdateTime(std::chrono::system_clock::now()); updateStatus.LastUpdateTime(std::chrono::system_clock::now());
try try
{ {
pipedal::ofstream_synced f{UPDATE_STATUS_CACHE_FILE}; pipedal::ofstream_synced f{updateStatusCacheFile};
json_writer writer{f}; json_writer writer{f};
writer.write(updateStatus); writer.write(updateStatus);
} }
@@ -189,13 +205,22 @@ static void SetCachedUpdateStatus(UpdateStatus &updateStatus)
Lv2Log::error(SS("Unable to write cached UpdateStatus. " << e.what())); Lv2Log::error(SS("Unable to write cached UpdateStatus. " << e.what()));
} }
} }
UpdaterImpl::UpdaterImpl()
UpdaterImpl::UpdaterImpl(const std::filesystem::path &workingDirectory)
: workingDirectory(workingDirectory),
updateStatusCacheFile(workingDirectory / "updateStatus.json"),
githubResponseHeaderFilename(workingDirectory / "githubHeaders.json")
{ {
this->githubResponseHeaders.Load(); this->githubResponseHeaders.Load(githubResponseHeaderFilename);
cachedUpdateStatus = GetCachedUpdateStatus(); cachedUpdateStatus = GetCachedUpdateStatus();
this->updatePolicy = cachedUpdateStatus.UpdatePolicy(); this->updatePolicy = cachedUpdateStatus.UpdatePolicy();
currentResult = cachedUpdateStatus; currentResult = cachedUpdateStatus;
}
void UpdaterImpl::Start()
{
#if !ENABLE_AUTO_UPDATE
return;
#endif
int fds[2]; int fds[2];
int rc = pipe(fds); int rc = pipe(fds);
if (rc != 0) if (rc != 0)
@@ -225,6 +250,9 @@ void UpdaterImpl::Stop()
{ {
return; return;
} }
#if !ENABLE_AUTO_UPDATE
return;
#endif
stopped = true; stopped = true;
if (event_writer != -1) if (event_writer != -1)
@@ -563,8 +591,7 @@ JSON_MAP_REFERENCE(GithubResponseHeaders, ratelimit_used)
JSON_MAP_REFERENCE(GithubResponseHeaders, ratelimit_resource) JSON_MAP_REFERENCE(GithubResponseHeaders, ratelimit_resource)
JSON_MAP_END(); JSON_MAP_END();
std::filesystem::path GithubResponseHeaders::FILENAME = WORKING_DIRECTORY / "githubHeaders.json"; void GithubResponseHeaders::Load(const std::filesystem::path &FILENAME)
void GithubResponseHeaders::Load()
{ {
std::ifstream f(FILENAME); std::ifstream f(FILENAME);
if (f.is_open()) if (f.is_open())
@@ -581,9 +608,9 @@ void GithubResponseHeaders::Load()
} }
} }
} }
void GithubResponseHeaders::Save() void GithubResponseHeaders::Save(const std::filesystem::path &FILENAME)
{ {
std::filesystem::create_directories(WORKING_DIRECTORY); std::filesystem::create_directories(FILENAME.parent_path());
ofstream_synced f(FILENAME); ofstream_synced f(FILENAME);
if (!f.is_open()) if (!f.is_open())
{ {
@@ -622,7 +649,7 @@ GithubResponseHeaders::GithubResponseHeaders(const std::filesystem::path path)
auto trimPos = line.find_last_not_of("\r\n"); auto trimPos = line.find_last_not_of("\r\n");
if (trimPos != std::string::npos) if (trimPos != std::string::npos)
{ {
line = line.substr(0,trimPos + 1); line = line.substr(0, trimPos + 1);
} }
if (!f) if (!f)
@@ -675,6 +702,12 @@ GithubResponseHeaders::GithubResponseHeaders(const std::filesystem::path path)
} }
} }
UpdateStatus UpdaterImpl::GetReleaseGeneratorStatus()
{
UpdateStatus result = DoUpdate(true);
return result;
}
void UpdaterImpl::CheckForUpdate(bool useCache) void UpdaterImpl::CheckForUpdate(bool useCache)
{ {
UpdateStatus updateResult; UpdateStatus updateResult;
@@ -702,133 +735,11 @@ void UpdaterImpl::CheckForUpdate(bool useCache)
} }
} }
// set default next retry time. github failures may postpone even further.
Lv2Log::info("Checking for updates.");
// const std::string responseOption = "-w \"%{response_code}\"";
#ifdef WIN32
responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%.
#endif
TemporaryFile headerFile{WORKING_DIRECTORY};
std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str());
updateResult.errorMessage_ = "";
try try
{ {
auto result = sysExecForOutput("curl", args);
if (result.exitCode != EXIT_SUCCESS)
{
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(4)));
throw std::runtime_error("Server has no internet access.");
}
else
{
// hard throttling for github.
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(8)));
GithubResponseHeaders githubHeaders{headerFile.Path()}; updateResult = DoUpdate(false);
this->githubResponseHeaders = githubHeaders;
this->githubResponseHeaders.Save();
if (githubHeaders.code_ != 200)
{
if (
githubHeaders.ratelimit_limit_ != 0 &&
githubHeaders.ratelimit_limit_ == githubHeaders.ratelimit_used_)
{
std::time_t time = std::chrono::system_clock::to_time_t(githubHeaders.ratelimit_reset_);
std::string strTime = std::ctime(&time);
this->RetryAfter(githubHeaders.ratelimit_reset_ - githubHeaders.date_);
throw std::runtime_error(SS("Github API rate limit exceeded. Retrying at " << strTime));
}
}
if (result.output.length() == 0)
{
throw std::runtime_error("Server has no internet access.");
}
std::stringstream ss(result.output);
json_reader reader(ss);
json_variant vResult(reader);
if (vResult.is_object())
{
// an HTML error.
updateResult.isOnline_ = false;
auto o = vResult.as_object();
std::string message = "Unknown error.";
if (o->at("message").is_string())
{
message = o->at("message").as_string();
}
throw std::runtime_error(SS("Github Service error: " << message));
}
else if (!vResult.is_array())
{
throw std::runtime_error("Invalid file format error.");
}
else
{
json_variant::array_ptr vArray = vResult.as_array();
std::vector<GithubRelease> releases;
for (size_t i = 0; i < vArray->size(); ++i)
{
auto &el = vArray->at(i);
GithubRelease release{el};
if (!release.draft && release.GetDownloadForCurrentArchitecture() != nullptr)
{
if (release.name.find("Experimental") == std::string::npos) // experimental releases do not participate in auto-updates (not even for dev stream)
{
releases.push_back(std::move(release));
}
}
}
std::sort(
releases.begin(),
releases.end(),
[](const GithubRelease &left, const GithubRelease &right)
{
return left.published_at > right.published_at; // latest date first.
});
updateResult.releaseOnlyRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return !githubRelease.prerelease &&
githubRelease.name.find("Release") != std::string::npos;
});
updateResult.releaseOrBetaRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return !githubRelease.prerelease &&
(githubRelease.name.find("Release") != std::string::npos ||
githubRelease.name.find("Beta") != std::string::npos);
});
updateResult.devRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return true;
});
#ifdef TEST_UPDATE
updateResult.releaseOrBetaRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.41-Beta";
updateResult.devRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.39-Experimental";
updateResult.devRelease_.upgradeVersion_ = "1.2.39";
updateResult.devRelease_.updateAvailable_ = false;
#endif
updateResult.isValid_ = true;
updateResult.isOnline_ = true;
}
}
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@@ -851,6 +762,142 @@ void UpdaterImpl::CheckForUpdate(bool useCache)
} }
} }
} }
UpdateStatus UpdaterImpl::DoUpdate(bool forReleaseGenerator)
{
UpdateStatus updateResult;
if (forReleaseGenerator)
{
updateResult.currentVersion_ = "0.0.0";
}
// set default next retry time. github failures may postpone even further.
Lv2Log::info("Checking for updates.");
// const std::string responseOption = "-w \"%{response_code}\"";
#ifdef WIN32
responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%.
#endif
TemporaryFile headerFile{workingDirectory};
std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str());
updateResult.errorMessage_ = "";
auto result = sysExecForOutput("curl", args);
if (result.exitCode != EXIT_SUCCESS)
{
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(4)));
throw std::runtime_error("Server has no internet access.");
}
else
{
// hard throttling for github.
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(8)));
GithubResponseHeaders githubHeaders{headerFile.Path()};
this->githubResponseHeaders = githubHeaders;
this->githubResponseHeaders.Save(githubResponseHeaderFilename);
if (githubHeaders.code_ != 200)
{
if (
githubHeaders.ratelimit_limit_ != 0 &&
githubHeaders.ratelimit_limit_ == githubHeaders.ratelimit_used_)
{
std::time_t time = std::chrono::system_clock::to_time_t(githubHeaders.ratelimit_reset_);
std::string strTime = std::ctime(&time);
this->RetryAfter(githubHeaders.ratelimit_reset_ - githubHeaders.date_);
throw std::runtime_error(SS("Github API rate limit exceeded. Retrying at " << strTime));
}
}
if (result.output.length() == 0)
{
throw std::runtime_error("Server has no internet access.");
}
std::stringstream ss(result.output);
json_reader reader(ss);
json_variant vResult(reader);
if (vResult.is_object())
{
// an HTML error.
updateResult.isOnline_ = false;
auto o = vResult.as_object();
std::string message = "Unknown error.";
if (o->at("message").is_string())
{
message = o->at("message").as_string();
}
throw std::runtime_error(SS("Github Service error: " << message));
}
else if (!vResult.is_array())
{
throw std::runtime_error("Invalid file format error.");
}
else
{
json_variant::array_ptr vArray = vResult.as_array();
std::vector<GithubRelease> releases;
for (size_t i = 0; i < vArray->size(); ++i)
{
auto &el = vArray->at(i);
GithubRelease release{el};
if (!release.draft && release.GetDownloadForCurrentArchitecture() != nullptr)
{
if (release.name.find("Experimental") == std::string::npos) // experimental releases do not participate in auto-updates (not even for dev stream)
{
releases.push_back(std::move(release));
}
}
}
std::sort(
releases.begin(),
releases.end(),
[](const GithubRelease &left, const GithubRelease &right)
{
return left.published_at > right.published_at; // latest date first.
});
updateResult.releaseOnlyRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return !githubRelease.prerelease &&
githubRelease.name.find("Release") != std::string::npos;
});
updateResult.releaseOrBetaRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return !githubRelease.prerelease &&
(githubRelease.name.find("Release") != std::string::npos ||
githubRelease.name.find("Beta") != std::string::npos);
});
updateResult.devRelease_ = getUpdateRelease(
releases,
updateResult.currentVersion_,
[](const GithubRelease &githubRelease)
{
return true;
});
#ifdef TEST_UPDATE
updateResult.releaseOrBetaRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.41-Beta";
updateResult.devRelease_.upgradeVersionDisplayName_ = "PiPedal v1.2.39-Experimental";
updateResult.devRelease_.upgradeVersion_ = "1.2.39";
updateResult.devRelease_.updateAvailable_ = false;
#endif
updateResult.isValid_ = true;
updateResult.isOnline_ = true;
}
}
return updateResult;
}
bool UpdateRelease::operator==(const UpdateRelease &other) const bool UpdateRelease::operator==(const UpdateRelease &other) const
{ {
return (updateAvailable_ == other.updateAvailable_) && return (updateAvailable_ == other.updateAvailable_) &&
@@ -1216,7 +1263,7 @@ void UpdaterImpl::RetryAfter(clock::duration delay)
} }
void UpdaterImpl::SaveRetryTime(const std::chrono::system_clock::time_point &time) void UpdaterImpl::SaveRetryTime(const std::chrono::system_clock::time_point &time)
{ {
fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json"; fs::path retryTimePath = workingDirectory / "retryTime.json";
pipedal::ofstream_synced f{retryTimePath}; pipedal::ofstream_synced f{retryTimePath};
if (f.is_open()) if (f.is_open())
{ {
@@ -1229,7 +1276,7 @@ UpdaterImpl::clock::time_point UpdaterImpl::LoadRetryTime()
{ {
using namespace std::chrono; using namespace std::chrono;
fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json"; fs::path retryTimePath = workingDirectory / "retryTime.json";
std::ifstream f{retryTimePath}; std::ifstream f{retryTimePath};
if (f.is_open()) if (f.is_open())
{ {
@@ -1273,7 +1320,7 @@ void UpdaterImpl::DownloadUpdate(const std::string &url, std::filesystem::path *
{ {
throw std::runtime_error(SS("Invalid update url. Downloads from this address are not permitted: " << signatureUrl)); throw std::runtime_error(SS("Invalid update url. Downloads from this address are not permitted: " << signatureUrl));
} }
auto downloadDirectory = WORKING_DIRECTORY / "downloads"; auto downloadDirectory = workingDirectory / "downloads";
std::filesystem::create_directories(downloadDirectory); std::filesystem::create_directories(downloadDirectory);
auto downloadFilePath = downloadDirectory / filename; auto downloadFilePath = downloadDirectory / filename;
@@ -1336,6 +1383,14 @@ void UpdaterImpl::DownloadUpdate(const std::string &url, std::filesystem::path *
throw; throw;
} }
} }
void UpdateStatus::AddRelease(const std::string&packageName)
{
this->isValid_ = true;
this->errorMessage_ = "";
}
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
JSON_MAP_BEGIN(UpdateRelease) JSON_MAP_BEGIN(UpdateRelease)
+6 -2
View File
@@ -47,18 +47,22 @@ namespace pipedal
using ptr = std::unique_ptr<self>; using ptr = std::unique_ptr<self>;
static Updater::ptr Create(); static Updater::ptr Create();
static Updater::ptr Create(const std::filesystem::path & workingDirectory);
static void ValidateSignature(const std::filesystem::path&file, const std::filesystem::path&signatureFile); static void ValidateSignature(const std::filesystem::path&file, const std::filesystem::path&signatureFile);
using UpdateListener = std::function<void(const UpdateStatus &upateResult)>; using UpdateListener = std::function<void(const UpdateStatus &upateResult)>;
virtual void SetUpdateListener(UpdateListener &&listener) = 0; virtual void SetUpdateListener(UpdateListener &&listener) = 0;
virtual void CheckNow() = 0; virtual void Start() = 0;
virtual void Stop() = 0; virtual void Stop() = 0;
virtual void CheckNow() = 0;
virtual UpdatePolicyT GetUpdatePolicy() = 0; virtual UpdatePolicyT GetUpdatePolicy() = 0;
virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) = 0; virtual void SetUpdatePolicy(UpdatePolicyT updatePolicy) = 0;
virtual void ForceUpdateCheck() = 0; virtual void ForceUpdateCheck() = 0;
virtual void DownloadUpdate(const std::string &url, std::filesystem::path*file, std::filesystem::path*signatureFile) = 0; virtual void DownloadUpdate(const std::string &url, std::filesystem::path*file, std::filesystem::path*signatureFile) = 0;
UpdateStatus virtual GetCurrentStatus() const = 0; virtual UpdateStatus GetCurrentStatus() const = 0;
virtual UpdateStatus GetReleaseGeneratorStatus() = 0; // only used when generating releases. Absolutely no caching.
}; };
+5 -1
View File
@@ -24,6 +24,7 @@
#pragma once #pragma once
#include <string>
/* /*
PiPedal uses whitelisting and GPG signatures to verify that updates are valid. PiPedal uses whitelisting and GPG signatures to verify that updates are valid.
You must modify these defines appropriately if you want to host a fork. You must modify these defines appropriately if you want to host a fork.
@@ -32,6 +33,9 @@
installs published on github/rerdavies/pipedal. installs published on github/rerdavies/pipedal.
*/ */
#ifndef ENABLE_AUTO_UPDATE
#define ENABLE_AUTO_UPDATE 1
#endif
#ifndef GITHUB_PROJECT #ifndef GITHUB_PROJECT
#define GITHUB_PROJECT "rerdavies/pipedal" #define GITHUB_PROJECT "rerdavies/pipedal"
#endif #endif
@@ -43,7 +47,7 @@
#define UPDATE_GPG_ADDRESS "Robin Davies <rerdavies@gmail.com>" #define UPDATE_GPG_ADDRESS "Robin Davies <rerdavies@gmail.com>"
#endif #endif
#ifndef UPDATE_GPG_ADDRESS2 #ifndef UPDATE_GPG_ADDRESS2
#define UPDATE_GPG_ADDRESS2 "PiPedal Project <rerdavies@gmail.com>" #define UPDATE_GPG_ADDRESS2 "PiPedal Project <rerdavies@gmail.com>"
#endif #endif
+2
View File
@@ -92,6 +92,8 @@ namespace pipedal {
const UpdateRelease &DevRelease() const { return devRelease_; } const UpdateRelease &DevRelease() const { return devRelease_; }
bool operator==(const UpdateStatus &other) const; bool operator==(const UpdateStatus &other) const;
void AddRelease(const std::string&packageName); // Release Generator ONLY
DECLARE_JSON_MAP(UpdateStatus); DECLARE_JSON_MAP(UpdateStatus);
}; };
+2
View File
@@ -43,6 +43,8 @@ TEST_CASE( "updater test", "[updater]" ) {
++nCalls; ++nCalls;
} }
); );
updater->Start();
} }
REQUIRE(nCalls == 1); REQUIRE(nCalls == 1);
+5 -2
View File
@@ -29,6 +29,7 @@
#include "ZipFile.hpp" #include "ZipFile.hpp"
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "ofstream_synced.hpp" #include "ofstream_synced.hpp"
#include "UpdaterSecurity.hpp"
#define PRESET_EXTENSION ".piPreset" #define PRESET_EXTENSION ".piPreset"
@@ -524,8 +525,10 @@ public:
std::stringstream s; std::stringstream s;
s << "{ \"socket_server_port\": " << portNumber s << "{ \"socket_server_port\": " << portNumber
<< ", \"socket_server_address\": \"" << webSocketAddress << << ", \"socket_server_address\": \"" << webSocketAddress
"\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }"; << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize
<< ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true": "false")
<< " }";
return s.str(); return s.str();
} }
@@ -1,6 +0,0 @@
[Adding or changing system-wide NetworkManager connections]
Identity=unix-group:pipedal_d
Action=org.freedesktop.NetworkManager.settings.modify.system
ResultAny=yes
ResultInactive=yes
ResultActive=yes
@@ -1,5 +1,5 @@
// grant NetworkManager dbus permissions (no-prompts) to the pipedald systemd service. // grant NetworkManager dbus permissions (no-prompts) to the pipedald systemd service.
polpolkit.addRule(function(action, subject) { polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.NetworkManager.") == 0 && subject.isInGroup("pipedal_d")) { if (action.id.indexOf("org.freedesktop.NetworkManager.") == 0 && subject.isInGroup("pipedal_d")) {
return polkit.Result.YES; return polkit.Result.YES;
} }
+9 -6
View File
@@ -1,7 +1,11 @@
- X test reconnect sequence in client.
-X remove 10-pipedal-networkmanager.rules before testing installer.
- new signing procedure.
- verify that scanning doesn't cause overruns. - verify that scanning doesn't cause overruns.
- address change handlers in client (and browser?) - verify address change behaviour in client
- Keybaord escape for hotspotManager.
- verify stripping in installer. - verify stripping in installer.
- verify update with no keyring. - verify update with no keyring.
-verify clean removal of dhcpcd and nm_p2p2d -verify clean removal of dhcpcd and nm_p2p2d
@@ -14,12 +18,11 @@ Localisation: support non-UTF8 code pages.
- unicode commandline arguments. - unicode commandline arguments.
- review unicode filenames (this is probably ok) - review unicode filenames (this is probably ok)
- make app use the same theme settings as the website. (make the website use the same theme as the app)
- make app use the same theme settings as the website.
- BUG: gcs when we have an animated output control - BUG: gcs when we have an animated output control
- versioning. - versioning.
- do we want to take a second crack at establishin multi-p2p connections with the driver_param?