From 198e150ba8e882e92be4d66581d4dd088fb081f0 Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Mon, 25 Mar 2019 21:51:32 +0000
Subject: [PATCH] Archive inboxes after 5 days of delivery failures

---
 config/dbstructure.config.php            | 16 +++++-
 database.sql                             | 30 ++++++++----
 src/Protocol/ActivityPub/Transmitter.php | 20 +++++++-
 src/Util/HTTPSignature.php               | 62 +++++++++++++++++++++++-
 4 files changed, 115 insertions(+), 13 deletions(-)

diff --git a/config/dbstructure.config.php b/config/dbstructure.config.php
index f03132adda..b2f451b04e 100644
--- a/config/dbstructure.config.php
+++ b/config/dbstructure.config.php
@@ -34,7 +34,7 @@
 use Friendica\Database\DBA;
 
 if (!defined('DB_UPDATE_VERSION')) {
-	define('DB_UPDATE_VERSION', 1304);
+	define('DB_UPDATE_VERSION', 1305);
 }
 
 return [
@@ -529,6 +529,20 @@ return [
 			"hook_file_function" => ["UNIQUE", "hook", "file", "function"],
 		]
 	],
+	"inbox-status" => [
+		"comment" => "Status of ActivityPub inboxes",
+		"fields" => [
+			"url" => ["type" => "varbinary(255)", "not null" => "1", "primary" => "1", "comment" => "URL of the inbox"],
+			"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation date of this entry"],
+			"success" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last successful delivery"],
+			"failure" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last failed delivery"],
+			"previous" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Previous delivery date"],
+			"archive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Is the inbox archived?"]
+		],
+		"indexes" => [
+			"PRIMARY" => ["url"]
+		]
+	],
 	"intro" => [
 		"comment" => "",
 		"fields" => [
diff --git a/database.sql b/database.sql
index 10a428ffff..5b415b65d0 100644
--- a/database.sql
+++ b/database.sql
@@ -1,6 +1,6 @@
 -- ------------------------------------------
--- Friendica 2019.03-dev (The Tazmans Flax-lily)
--- DB_UPDATE_VERSION 1300
+-- Friendica 2019.03 (Dalmatian Bellflower)
+-- DB_UPDATE_VERSION 1305
 -- ------------------------------------------
 
 
@@ -470,6 +470,19 @@ CREATE TABLE IF NOT EXISTS `hook` (
 	 UNIQUE INDEX `hook_file_function` (`hook`,`file`,`function`)
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='addon hook registry';
 
+--
+-- TABLE inbox-status
+--
+CREATE TABLE IF NOT EXISTS `inbox-status` (
+	`url` varbinary(255) NOT NULL COMMENT 'URL of the inbox',
+	`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date of this entry',
+	`success` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful delivery',
+	`failure` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed delivery',
+	`previous` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Previous delivery date',
+	`archive` boolean NOT NULL DEFAULT '0' COMMENT 'Is the inbox archived?',
+	 PRIMARY KEY(`url`)
+) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Status of ActivityPub inboxes';
+
 --
 -- TABLE intro
 --
@@ -879,7 +892,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
 	`deny_gid` mediumtext COMMENT 'Access Control - list of denied groups',
 	`backend-class` tinytext COMMENT 'Storage backend class',
 	`backend-ref` text COMMENT 'Storage backend data reference',
-	`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'edited timestamp',
+	`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
 	 PRIMARY KEY(`id`),
 	 INDEX `contactid` (`contact-id`),
 	 INDEX `uid_contactid` (`uid`,`contact-id`),
@@ -1270,13 +1283,12 @@ CREATE TABLE IF NOT EXISTS `workerqueue` (
 	`retrial` tinyint NOT NULL DEFAULT 0 COMMENT 'Retrial counter',
 	`done` boolean NOT NULL DEFAULT '0' COMMENT 'Marked 1 when the task was done - will be deleted later',
 	 PRIMARY KEY(`id`),
-	 INDEX `pid` (`pid`),
-	 INDEX `parameter` (`parameter`(64)),
-	 INDEX `priority_created_next_try` (`priority`,`created`,`next_try`),
-	 INDEX `done_priority_executed_next_try` (`done`,`priority`,`executed`,`next_try`),
-	 INDEX `done_executed_next_try` (`done`,`executed`,`next_try`),
+	 INDEX `done_parameter` (`done`,`parameter`(64)),
+	 INDEX `done_executed` (`done`,`executed`),
+	 INDEX `done_priority_created` (`done`,`priority`,`created`),
 	 INDEX `done_priority_next_try` (`done`,`priority`,`next_try`),
-	 INDEX `done_next_try` (`done`,`next_try`)
+	 INDEX `done_pid_next_try` (`done`,`pid`,`next_try`),
+	 INDEX `done_pid_priority_created` (`done`,`pid`,`priority`,`created`)
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries';
 
 --
diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php
index ce23e6db20..eb1da09b3a 100644
--- a/src/Protocol/ActivityPub/Transmitter.php
+++ b/src/Protocol/ActivityPub/Transmitter.php
@@ -465,6 +465,18 @@ class Transmitter
 		return $receivers;
 	}
 
+	/**
+	 * Check if an inbox is archived
+	 *
+	 * @param string $url Inbox url
+	 *
+	 * @return boolean "true" if inbox is archived
+	 */
+	private static function archivedInbox($url)
+	{
+		return DBA::exists('inbox-status', ['url' => $url, 'archive' => true]);
+	}
+
 	/**
 	 * Fetches a list of inboxes of followers of a given user
 	 *
@@ -506,7 +518,9 @@ class Transmitter
 				} else {
 					$target = $profile['sharedinbox'];
 				}
-				$inboxes[$target] = $target;
+				if (!self::archivedInbox($target)) {
+					$inboxes[$target] = $target;
+				}
 			}
 		}
 		DBA::close($contacts);
@@ -563,7 +577,9 @@ class Transmitter
 						} else {
 							$target = $profile['sharedinbox'];
 						}
-						$inboxes[$target] = $target;
+						if (!self::archivedInbox($target)) {
+							$inboxes[$target] = $target;
+						}
 					}
 				}
 			}
diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php
index d5e1732c08..e002d5981c 100644
--- a/src/Util/HTTPSignature.php
+++ b/src/Util/HTTPSignature.php
@@ -5,6 +5,7 @@
  */
 namespace Friendica\Util;
 
+use Friendica\Database\DBA;
 use Friendica\Core\Config;
 use Friendica\Core\Logger;
 use Friendica\Model\User;
@@ -314,7 +315,66 @@ class HTTPSignature
 
 		Logger::log('Transmit to ' . $target . ' returned ' . $return_code, Logger::DEBUG);
 
-		return ($return_code >= 200) && ($return_code <= 299);
+		$success = ($return_code >= 200) && ($return_code <= 299);
+
+		self::setInboxStatus($target, $success);
+
+		return $success;
+	}
+
+	/**
+	 * @brief Set the delivery status for a given inbox
+	 *
+	 * @param string  $url     The URL of the inbox
+	 * @param boolean $success Transmission status
+	 */
+	static private function setInboxStatus($url, $success)
+	{
+		$now = DateTimeFormat::utcNow();
+
+		$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
+		if (!DBA::isResult($status)) {
+			DBA::insert('inbox-status', ['url' => $url, 'created' => $now]);
+			$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
+		}
+
+		if ($success) {
+			$fields = ['success' => $now];
+		} else {
+			$fields = ['failure' => $now];
+		}
+
+		if ($status['failure'] > DBA::NULL_DATETIME) {
+			$new_previous_stamp = strtotime($status['failure']);
+			$old_previous_stamp = strtotime($status['previous']);
+
+			// Only set "previous" with at least one day difference.
+			// We use this to assure to not accidentally archive too soon.
+			if (($new_previous_stamp - $old_previous_stamp) >= 86400) {
+				$fields['previous'] = $status['failure'];
+			}
+		}
+
+		if (!$success) {
+			if ($status['success'] <= DBA::NULL_DATETIME) {
+				$stamp1 = strtotime($status['created']);
+			} else {
+				$stamp1 = strtotime($status['success']);
+			}
+
+			$stamp2 = strtotime($now);
+			$previous_stamp = strtotime($status['previous']);
+
+			// Archive the inbox when there had been failures for five days.
+			// Additionally ensure that at least one previous attempt has to be in between.
+			if ((($stamp2 - $stamp1) >= 86400 * 5) && ($previous_stamp > $stamp1)) {
+				$fields['archive'] = true;
+			}
+		} else {
+			$fields['archive'] = false;
+		}
+
+		DBA::update('inbox-status', $fields, ['url' => $url]);
 	}
 
 	/**