2/27/2012 - 8:07 AM

Self-hosted Cydia repo download counter

Self-hosted Cydia repo download counter

CREATE TABLE `downloads` (
	`filename` text NOT NULL,
	`first_download` datetime NOT NULL,
	`last_download` datetime NOT NULL,
	`count` int(11) NOT NULL DEFAULT 1,
function errorout($code, $message) {
	// Makes dpkg know it's not a package
	header("Content-Type: text/plain");

	// Generate the HTTP error header
	header($_SERVER["SERVER_PROTOCOL"] . " $code $message");

	// Then print out the error.
	// Because the content type is plain text, escaping is not necessary.

$db = new mysqli("localhost", "myrepo", "mypassword"); // change parameters to what you use

if ($db->connect_error) {
	errorout(500, "Couldn't connect to the database. Please try your download later.");

	or errorout(500, "Couldn't set up the database. Please try your download later.");

$db->select_db($link, "myrepodb") // change myrepodb to your database name
	or errorout(500, "Couldn't open the database. Please try your download later.");

// Exit here if the filename hasn’t been provided.
if (!isset($_GET["filename"]) or empty($_GET["filename"])) {
	errorout(404, "The file you requested either doesn't exist or isn't allowed to be downloaded.");

// Store the filename in a variable, escaping it to be safe.
$file = $db->real_escape_string($_GET["filename"]);

// Determine the file path.
// Packages and Release are expected to be in the same folder as this script.
// Anything else is expected to be in a downloads/ folder.
if ($file == "Packages" or strpos($file, "Packages.") === 0
	or $file == "Release" or strpos($file, "Release.") === 0) {
	$path = $file;
} else {
	$path = "downloads/$file";

// The following code checks for attempts to read files that aren't debs.
// Not doing this lets attackers read PHP scripts and system files.
if (stristr($file, "..") or stristr($file, "/") or stristr($file, "\\") or !file_exists($file)) {
	errorout(404, "The file you requested either doesn't exist or isn't allowed to be downloaded.");

// Count the download in the database
// A log will be saved if something goes wrong.
try {
	// Check whether the package has already been downloaded.
	// We select an empty string as we don’t read the result, we just want to know if the row exists.
	$query = $db->query("SELECT '' FROM downloads WHERE filename='$file'");

	if (!$query or $query->num_rows == 0) {
		// If it doesn't exist, create the row
		$db->query("INSERT INTO downloads SET filename='$file', count=1, first_download=NOW(), last_download=NOW()");
	} else {
		// If it does, add one to the counter
		$db->query("UPDATE downloads SET count=count+1, last_download=NOW() WHERE filename='$file'");
} catch (Exception $e) {
	// Something went wrong, so save a log file and continue onto the download
	// (The document root is usually htdocs or public_html)
	file_put_contents(dirname($_SERVER["DOCUMENT_ROOT"]) . "/../cydiaerr.log", print_r($e, true));

// Finally, serve the user the file they requested
$filesize = filesize($path);

// If it's more than 16 bytes long, it's ok to serve it
if ($filesize > 16) {
	// Force browsers to download the file, not display it
	header("Content-Type: application/octet-stream");
	header("Content-Disposition: attachment; filename=\"$file\"");

	// Indicate how big the file is
	header("Content-Length: " . $filesize);

	// And finally, output the file!
	// It’s important to use readfile() as this buffers the output (reads and outputs small amounts at a time).
	// Reading the entire file into a string with file_get_contents() will use huge amounts of memory for bigger files.
} else {
	errorout(404, "The file you requested either doesn't exist or isn't allowed to be downloaded.");

How to add a download counter to a self-hosted Cydia repository

2018-04-22: This is pretty old, possibly broken on newer PHP, and the original blog post that accompanied it is lost to time. I’m keeping this here for posterity and you’re free to use it, but please consider other “download counter” scripts instead (doesn’t need to be Cydia-specific — they’re all the same thing).


  1. Click the download link at the top of the gist page and extract the zip.
  2. Open counter.php and .htaccess in your favorite editor (not Notepad).
  3. In counter.php, scroll down to the line that says $link = mysqli_connect( ... and change the parameters to the ones you use to connect to MySQL.
  4. Go down to the mysqli_select_db($link, "myrepodb") line and change myrepodb to the name of your database.
  5. Run the downloads.sql file in your MySQL database. In phpMyAdmin, you can use the Import tab to do this.
  6. Move your debs to a folder called downloads in your repo folder.
  7. Open the .htaccess file and replace repo with the path to the folder that your repo is in, relative to your document root (usually htdocs or public_html)
  8. Make 100,000% sure there is not a byte order mark at the start of counter.php by using the “save as UTF-8 without BOM” feature in your editor. In Notepad++ this is under the Encoding menu; in Sublime it’s under the File menu; in Visual Studio Code press Cmd/Ctrl-Shift-P and type in “encoding”.
  9. Upload and enjoy :)

Note this only collects the stats. It’s up to you to write something to download

# Use the Apache rewrite engine to redirect downloads to our script.
RewriteEngine On
RewriteBase /
RewriteRule ^repo/((Packages|Release)(.*)?)$ /repo/counter.php?filename=$1 [L,QSA]
RewriteRule ^repo/downloads/(.*)\.deb$ /repo/counter.php?filename=$1 [L,QSA]