Download analytis
This commit is contained in:
Vendored
+25
@@ -92,6 +92,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) downloadCounts",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [ "--downloads" ],
|
||||
"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",
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/bash
|
||||
./build/src/makeRelease --downloads
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
# Create a temporary file to store the data
|
||||
temp_file=$(mktemp)
|
||||
|
||||
# Create a temporary file to store the data
|
||||
temp_file=$(mktemp)
|
||||
|
||||
# Run get_counts and process its output
|
||||
./build/src/makeRelease --gplot-downloads | while read -r date count; do
|
||||
# Convert ISO8601 date to timestamp for gnuplot
|
||||
timestamp=$(date -d "$date" "+%s")
|
||||
echo "$timestamp $count" >> "$temp_file"
|
||||
done
|
||||
|
||||
# Create and execute the gnuplot script
|
||||
gnuplot -persist << EOF
|
||||
set terminal qt enhanced font "Piboto,12" size 800,600
|
||||
set xdata time
|
||||
set timefmt "%s"
|
||||
set format x "%Y-%m-%d"
|
||||
set xlabel ""
|
||||
set ylabel "Cumulative Downloads"
|
||||
set title "PiPedal Downloads" font "Piboto:Bold,16"
|
||||
set grid
|
||||
|
||||
# Rotate x-axis labels by 45 degrees
|
||||
set xtics rotate by 90 offset 0,-4
|
||||
|
||||
# Adjust the bottom margin to make room for rotated labels
|
||||
set bmargin 10
|
||||
|
||||
unset key
|
||||
|
||||
plot "$temp_file" using 1:2 with lines title "Cumulative downloads"
|
||||
|
||||
|
||||
pause mouse close
|
||||
EOF
|
||||
|
||||
|
||||
# Clean up the temporary file
|
||||
rm "$temp_file"
|
||||
|
||||
@@ -152,6 +152,7 @@ set (PIPEDAL_SOURCES
|
||||
UpdateResults.cpp UpdateResults.hpp
|
||||
UpdaterSecurity.hpp
|
||||
Updater.cpp Updater.hpp UpdaterStatus.hpp
|
||||
GithubResponseHeaders.hpp
|
||||
Lv2PluginChangeMonitor.cpp Lv2PluginChangeMonitor.hpp
|
||||
WebServerConfig.cpp WebServerConfig.hpp
|
||||
Locale.hpp Locale.cpp
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include "json.hpp"
|
||||
|
||||
#pragma once
|
||||
namespace pipedal
|
||||
{
|
||||
class GithubAsset
|
||||
{
|
||||
public:
|
||||
GithubAsset(json_variant &v);
|
||||
std::string name;
|
||||
std::string browser_download_url;
|
||||
std::string updated_at;
|
||||
};
|
||||
class GithubRelease
|
||||
{
|
||||
public:
|
||||
GithubRelease(json_variant &v);
|
||||
|
||||
const GithubAsset *GetDownloadForCurrentArchitecture() const;
|
||||
const GithubAsset *GetGpgKeyForAsset(const std::string &name) const;
|
||||
|
||||
bool draft = true;
|
||||
bool prerelease = true;
|
||||
std::string name;
|
||||
std::string url;
|
||||
std::string version;
|
||||
std::string body;
|
||||
std::vector<GithubAsset> assets;
|
||||
std::string published_at;
|
||||
};
|
||||
|
||||
|
||||
class GithubResponseHeaders
|
||||
{
|
||||
public:
|
||||
GithubResponseHeaders() {}
|
||||
GithubResponseHeaders(const std::filesystem::path path);
|
||||
int code_ = -1;
|
||||
std::chrono::system_clock::time_point date_;
|
||||
std::chrono::system_clock::time_point ratelimit_reset_;
|
||||
uint64_t ratelimit_limit_ = 60;
|
||||
uint64_t ratelimit_remaining_ = 60;
|
||||
uint64_t ratelimit_used_ = 0;
|
||||
std::string ratelimit_resource_;
|
||||
bool limit_exceeded() const { return code_ != 200 && ratelimit_limit_ != 0 && ratelimit_limit_ == ratelimit_used_; }
|
||||
void Load(const std::filesystem::path &filename);
|
||||
void Save(const std::filesystem::path &filename);
|
||||
DECLARE_JSON_MAP(GithubResponseHeaders);
|
||||
|
||||
private:
|
||||
static std::filesystem::path FILENAME;
|
||||
};
|
||||
}
|
||||
|
||||
+3
-52
@@ -48,29 +48,7 @@ namespace fs = std::filesystem;
|
||||
#undef TEST_UPDATE // do NOT leat this leak into a production build!
|
||||
#endif
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
class GithubResponseHeaders
|
||||
{
|
||||
public:
|
||||
GithubResponseHeaders() {}
|
||||
GithubResponseHeaders(const std::filesystem::path path);
|
||||
int code_ = -1;
|
||||
std::chrono::system_clock::time_point date_;
|
||||
std::chrono::system_clock::time_point ratelimit_reset_;
|
||||
uint64_t ratelimit_limit_ = 60;
|
||||
uint64_t ratelimit_remaining_ = 60;
|
||||
uint64_t ratelimit_used_ = 0;
|
||||
std::string ratelimit_resource_;
|
||||
bool limit_exceeded() const { return code_ != 200 && ratelimit_limit_ != 0 && ratelimit_limit_ == ratelimit_used_; }
|
||||
void Load(const std::filesystem::path &filename);
|
||||
void Save(const std::filesystem::path &filename);
|
||||
DECLARE_JSON_MAP(GithubResponseHeaders);
|
||||
|
||||
private:
|
||||
static std::filesystem::path FILENAME;
|
||||
};
|
||||
}
|
||||
#include "GithubResponseHeaders.hpp"
|
||||
|
||||
class pipedal::UpdaterImpl : public Updater
|
||||
{
|
||||
@@ -372,35 +350,6 @@ void UpdaterImpl::ThreadProc()
|
||||
}
|
||||
}
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
class GithubAsset
|
||||
{
|
||||
public:
|
||||
GithubAsset(json_variant &v);
|
||||
std::string name;
|
||||
std::string browser_download_url;
|
||||
std::string updated_at;
|
||||
};
|
||||
class GithubRelease
|
||||
{
|
||||
public:
|
||||
GithubRelease(json_variant &v);
|
||||
|
||||
const GithubAsset *GetDownloadForCurrentArchitecture() const;
|
||||
const GithubAsset *GetGpgKeyForAsset(const std::string &name) const;
|
||||
|
||||
bool draft = true;
|
||||
bool prerelease = true;
|
||||
std::string name;
|
||||
std::string url;
|
||||
std::string version;
|
||||
std::string body;
|
||||
std::vector<GithubAsset> assets;
|
||||
std::string published_at;
|
||||
};
|
||||
}
|
||||
|
||||
GithubAsset::GithubAsset(json_variant &v)
|
||||
{
|
||||
auto o = v.as_object();
|
||||
@@ -425,6 +374,8 @@ GithubRelease::GithubRelease(json_variant &v)
|
||||
this->published_at = o->at("published_at").as_string();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static std::vector<std::string> split(const std::string &s, char delimiter)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
+199
-2
@@ -10,8 +10,13 @@
|
||||
#include "UpdaterSecurity.hpp"
|
||||
#include "Updater.hpp"
|
||||
#include <fstream>
|
||||
#include "SysExec.hpp"
|
||||
#include "TemporaryFile.hpp"
|
||||
#include "json_variant.hpp"
|
||||
#include "GithubResponseHeaders.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string getVersionString()
|
||||
@@ -60,7 +65,6 @@ void SignPackage()
|
||||
throw std::runtime_error(SS("File does not exist: " << packagePath));
|
||||
}
|
||||
|
||||
|
||||
// sign the package.
|
||||
// gpg --armor --output "$filename".asc -b "$filename"
|
||||
cout << "--------------------------------------------------------------" << endl;
|
||||
@@ -172,12 +176,205 @@ void GenerateUpdateJson()
|
||||
UpdateStatus updateStatus = updater->GetReleaseGeneratorStatus();
|
||||
updateStatus.AddRelease(packageName);
|
||||
}
|
||||
|
||||
class DownloadCountAsset
|
||||
{
|
||||
public:
|
||||
DownloadCountAsset(const json_variant &v)
|
||||
{
|
||||
auto o = v.as_object();
|
||||
this->name = o->at("name").as_string();
|
||||
this->browser_download_url = o->at("browser_download_url").as_string();
|
||||
this->updated_at = o->at("updated_at").as_string();
|
||||
this->downloads = (uint64_t)(o->at("download_count").as_number());
|
||||
}
|
||||
std::string name;
|
||||
std::string browser_download_url;
|
||||
std::string updated_at;
|
||||
uint64_t downloads;
|
||||
};
|
||||
class DownloadCountRelease
|
||||
{
|
||||
public:
|
||||
DownloadCountRelease(const json_variant &v)
|
||||
{
|
||||
auto o = v.as_object();
|
||||
this->name = o->at("name").as_string();
|
||||
this->draft = o->at("draft").as_bool();
|
||||
this->prerelease = o->at("prerelease").as_bool();
|
||||
|
||||
auto assets = o->at("assets").as_array();
|
||||
for (size_t i = 0; i < assets->size(); ++i)
|
||||
{
|
||||
auto &el = assets->at(i);
|
||||
this->assets.push_back(DownloadCountAsset(el));
|
||||
}
|
||||
this->published_at = o->at("published_at").as_string();
|
||||
}
|
||||
std::string name;
|
||||
bool draft = false, prerelease = false;
|
||||
std::vector<DownloadCountAsset> assets;
|
||||
std::string published_at;
|
||||
};
|
||||
|
||||
static std::string timePointToISO8601(const std::chrono::system_clock::time_point &tp)
|
||||
{
|
||||
auto tt = std::chrono::system_clock::to_time_t(tp);
|
||||
std::tm tm = *std::gmtime(&tt);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm,"%Y-%m-%dT%H:%M:%S") << "Z";
|
||||
return ss.str();
|
||||
|
||||
}
|
||||
|
||||
void GetDownloadCounts(bool gplotDownloads)
|
||||
{
|
||||
// error handling and temporary file use is different enough that it justifies
|
||||
// not including this code in production code.
|
||||
|
||||
// const std::string responseOption = "-w \"%{response_code}\"";
|
||||
#ifdef WIN32
|
||||
responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%.
|
||||
#endif
|
||||
|
||||
TemporaryFile headerFile{"/tmp"};
|
||||
std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str());
|
||||
|
||||
auto result = sysExecForOutput("curl", args);
|
||||
if (result.exitCode != EXIT_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Github APIs not available.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// hard throttling for github.
|
||||
|
||||
GithubResponseHeaders githubHeaders{headerFile.Path()};
|
||||
|
||||
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);
|
||||
|
||||
throw std::runtime_error(SS("Github API rate limit exceeded. Try again at " << strTime));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.output.length() == 0)
|
||||
{
|
||||
throw std::runtime_error("No internet access.");
|
||||
}
|
||||
std::stringstream ss(result.output);
|
||||
json_reader reader(ss);
|
||||
json_variant vResult(reader);
|
||||
|
||||
if (vResult.is_object())
|
||||
{
|
||||
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<DownloadCountRelease> releases;
|
||||
for (size_t i = 0; i < vArray->size(); ++i)
|
||||
{
|
||||
auto &el = vArray->at(i);
|
||||
DownloadCountRelease release{el};
|
||||
if (!release.draft)
|
||||
{
|
||||
releases.push_back(std::move(release));
|
||||
}
|
||||
}
|
||||
if (gplotDownloads)
|
||||
{
|
||||
std::sort(
|
||||
releases.begin(),
|
||||
releases.end(),
|
||||
[](const DownloadCountRelease &left, const DownloadCountRelease &right)
|
||||
{
|
||||
return left.published_at < right.published_at; // date ascending
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < releases.size()-1; ++i)
|
||||
{
|
||||
releases[i].published_at = releases[i+1].published_at;
|
||||
}
|
||||
if (releases.size() >= 1)
|
||||
{
|
||||
releases[releases.size()-1].published_at = timePointToISO8601(std::chrono::system_clock::now());
|
||||
}
|
||||
uint64_t cumulativeCount = 0;
|
||||
for (const auto &release : releases)
|
||||
{
|
||||
for (const auto &asset : release.assets)
|
||||
{
|
||||
if (asset.name.ends_with(".deb"))
|
||||
{
|
||||
cumulativeCount += asset.downloads;
|
||||
}
|
||||
}
|
||||
cout << release.published_at << " " << cumulativeCount << endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::sort(
|
||||
releases.begin(),
|
||||
releases.end(),
|
||||
[](const DownloadCountRelease &left, const DownloadCountRelease &right)
|
||||
{
|
||||
return left.published_at > right.published_at; // latest date first.
|
||||
});
|
||||
for (const auto &release : releases)
|
||||
{
|
||||
cout << release.name << endl;
|
||||
for (const auto &asset : release.assets)
|
||||
{
|
||||
cout << " " << asset.name << ": " << asset.downloads << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
CommandLineParser commandLine;
|
||||
bool getDownloadCounts = false;
|
||||
bool gPlotDownloads = false;
|
||||
|
||||
try
|
||||
{
|
||||
SignPackage();
|
||||
commandLine.AddOption("--downloads", &getDownloadCounts);
|
||||
commandLine.AddOption("--gplot-downloads", &gPlotDownloads);
|
||||
commandLine.Parse(argc, (const char **)argv);
|
||||
if (commandLine.Arguments().size() != 0)
|
||||
{
|
||||
throw std::runtime_error("Invalid arguments.");
|
||||
}
|
||||
if (getDownloadCounts || gPlotDownloads)
|
||||
{
|
||||
GetDownloadCounts(gPlotDownloads);
|
||||
}
|
||||
else
|
||||
{
|
||||
SignPackage();
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user