]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - plugins/OStatus/classes/FeedSub.php
Woops, forgot auto_increment (comes with 'serial')
[quix0rs-gnu-social.git] / plugins / OStatus / classes / FeedSub.php
index b10509dae6c31806c7f182781a5c4bb75932161d..096baff202d2b6fb81f3fb60f23ac3c2e901e9c0 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
 /**
  * @package OStatusPlugin
  * @maintainer Brion Vibber <brion@status.net>
@@ -39,7 +43,6 @@ PuSH subscription flow:
         hub sends us updates via POST
 
 */
-
 class FeedDBException extends FeedSubException
 {
     public $obj;
@@ -56,7 +59,7 @@ class FeedDBException extends FeedSubException
  * Higher-level behavior building OStatus stuff on top is handled
  * under Ostatus_profile.
  */
-class FeedSub extends Memcached_DataObject
+class FeedSub extends Managed_DataObject
 {
     public $__table = 'feedsub';
 
@@ -75,97 +78,27 @@ class FeedSub extends Memcached_DataObject
     public $created;
     public $modified;
 
-    public /*static*/ function staticGet($k, $v=null)
-    {
-        return parent::staticGet(__CLASS__, $k, $v);
-    }
-
-    /**
-     * return table definition for DB_DataObject
-     *
-     * DB_DataObject needs to know something about the table to manipulate
-     * instances. This method provides all the DB_DataObject needs to know.
-     *
-     * @return array array of column definitions
-     */
-
-    function table()
-    {
-        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
-                     'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'huburi' =>  DB_DATAOBJECT_STR,
-                     'secret' => DB_DATAOBJECT_STR,
-                     'verify_token' => DB_DATAOBJECT_STR,
-                     'sub_state' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'last_update' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
-                     'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
-    }
-
-    static function schemaDef()
+    public static function schemaDef()
     {
-        return array(new ColumnDef('id', 'integer',
-                                   /*size*/ null,
-                                   /*nullable*/ false,
-                                   /*key*/ 'PRI',
-                                   /*default*/ null,
-                                   /*extra*/ null,
-                                   /*auto_increment*/ true),
-                     new ColumnDef('uri', 'varchar',
-                                   255, false, 'UNI'),
-                     new ColumnDef('huburi', 'text',
-                                   null, true),
-                     new ColumnDef('verify_token', 'text',
-                                   null, true),
-                     new ColumnDef('secret', 'text',
-                                   null, true),
-                     new ColumnDef('sub_state', "enum('subscribe','active','unsubscribe','inactive')",
-                                   null, false),
-                     new ColumnDef('sub_start', 'datetime',
-                                   null, true),
-                     new ColumnDef('sub_end', 'datetime',
-                                   null, true),
-                     new ColumnDef('last_update', 'datetime',
-                                   null, false),
-                     new ColumnDef('created', 'datetime',
-                                   null, false),
-                     new ColumnDef('modified', 'datetime',
-                                   null, false));
-    }
-
-    /**
-     * return key definitions for DB_DataObject
-     *
-     * DB_DataObject needs to know about keys that the table has; this function
-     * defines them.
-     *
-     * @return array key definitions
-     */
-
-    function keys()
-    {
-        return array_keys($this->keyTypes());
-    }
-
-    /**
-     * return key definitions for Memcached_DataObject
-     *
-     * Our caching system uses the same key definitions, but uses a different
-     * method to get them.
-     *
-     * @return array key definitions
-     */
-
-    function keyTypes()
-    {
-        return array('id' => 'K', 'uri' => 'U');
-    }
-
-    function sequenceKey()
-    {
-        return array('id', true, false);
+        return array(
+            'fields' => array(
+                'id' => array('type' => 'serial', 'not null' => true, 'description' => 'FeedSub local unique id'),
+                'uri' => array('type' => 'varchar', 'not null' => true, 'length' => 255, 'description' => 'FeedSub uri'),
+                'huburi' => array('type' => 'text', 'description' => 'FeedSub hub-uri'),
+                'verify_token' => array('type' => 'text', 'description' => 'FeedSub verify-token'),
+                'secret' => array('type' => 'text', 'description' => 'FeedSub stored secret'),
+                'sub_state' => array('type' => 'enum("subscribe","active","unsubscribe","inactive")', 'not null' => true, 'description' => 'subscription state'),
+                'sub_start' => array('type' => 'datetime', 'description' => 'subscription start'),
+                'sub_end' => array('type' => 'datetime', 'description' => 'subscription end'),
+                'last_update' => array('type' => 'datetime', 'not null' => true, 'description' => 'when this record was last updated'),
+                'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
+                'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
+            ),
+            'primary key' => array('id'),
+            'unique keys' => array(
+                'feedsub_uri_key' => array('uri'),
+            ),
+        );
     }
 
     /**
@@ -175,7 +108,7 @@ class FeedSub extends Memcached_DataObject
     public function localProfile()
     {
         if ($this->profile_id) {
-            return Profile::staticGet('id', $this->profile_id);
+            return Profile::getKV('id', $this->profile_id);
         }
         return null;
     }
@@ -187,7 +120,7 @@ class FeedSub extends Memcached_DataObject
     public function localGroup()
     {
         if ($this->group_id) {
-            return User_group::staticGet('id', $this->group_id);
+            return User_group::getKV('id', $this->group_id);
         }
         return null;
     }
@@ -199,7 +132,7 @@ class FeedSub extends Memcached_DataObject
      */
     public static function ensureFeed($feeduri)
     {
-        $current = self::staticGet('uri', $feeduri);
+        $current = self::getKV('uri', $feeduri);
         if ($current) {
             return $current;
         }
@@ -207,8 +140,8 @@ class FeedSub extends Memcached_DataObject
         $discover = new FeedDiscovery();
         $discover->discoverFromFeedURL($feeduri);
 
-        $huburi = $discover->getAtomLink('hub');
-        if (!$huburi) {
+        $huburi = $discover->getHubLink();
+        if (!$huburi && !common_config('feedsub', 'fallback_hub')) {
             throw new FeedSubNoHubException();
         }
 
@@ -241,11 +174,16 @@ class FeedSub extends Memcached_DataObject
             common_log(LOG_WARNING, "Attempting to (re)start PuSH subscription to $this->uri in unexpected state $this->sub_state");
         }
         if (empty($this->huburi)) {
-            if (common_config('feedsub', 'nohub')) {
+            if (common_config('feedsub', 'fallback_hub')) {
+                // No native hub on this feed?
+                // Use our fallback hub, which handles polling on our behalf.
+            } else if (common_config('feedsub', 'nohub')) {
                 // Fake it! We're just testing remote feeds w/o hubs.
+                // We'll never actually get updates in this mode.
                 return true;
             } else {
-                throw new ServerException("Attempting to start PuSH subscription for feed with no hub");
+                // TRANS: Server exception.
+                throw new ServerException(_m('Attempting to start PuSH subscription for feed with no hub.'));
             }
         }
 
@@ -255,6 +193,9 @@ class FeedSub extends Memcached_DataObject
     /**
      * Send a PuSH unsubscription request to the hub for this feed.
      * The hub will later send us a confirmation POST to /main/push/callback.
+     * Warning: this will cancel the subscription even if someone else in
+     * the system is using it. Most callers will want garbageCollect() instead,
+     * which confirms there's no uses left.
      *
      * @return bool true on success, false on failure
      * @throws ServerException if feed state is not valid
@@ -264,17 +205,49 @@ class FeedSub extends Memcached_DataObject
             common_log(LOG_WARNING, "Attempting to (re)end PuSH subscription to $this->uri in unexpected state $this->sub_state");
         }
         if (empty($this->huburi)) {
-            if (common_config('feedsub', 'nohub')) {
+            if (common_config('feedsub', 'fallback_hub')) {
+                // No native hub on this feed?
+                // Use our fallback hub, which handles polling on our behalf.
+            } else if (common_config('feedsub', 'nohub')) {
                 // Fake it! We're just testing remote feeds w/o hubs.
+                // We'll never actually get updates in this mode.
                 return true;
             } else {
-                throw new ServerException("Attempting to end PuSH subscription for feed with no hub");
+                // TRANS: Server exception.
+                throw new ServerException(_m('Attempting to end PuSH subscription for feed with no hub.'));
             }
         }
 
         return $this->doSubscribe('unsubscribe');
     }
 
+    /**
+     * Check if there are any active local uses of this feed, and if not then
+     * make sure it's inactive, unsubscribing if necessary.
+     *
+     * @return boolean true if the subscription is now inactive, false if still active.
+     */
+    public function garbageCollect()
+    {
+        if ($this->sub_state == '' || $this->sub_state == 'inactive') {
+            // No active PuSH subscription, we can just leave it be.
+            return true;
+        } else {
+            // PuSH subscription is either active or in an indeterminate state.
+            // Check if we're out of subscribers, and if so send an unsubscribe.
+            $count = 0;
+            Event::handle('FeedSubSubscriberCount', array($this, &$count));
+
+            if ($count) {
+                common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->uri);
+                return false;
+            } else {
+                common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->uri);
+                return $this->unsubscribe();
+            }
+        }
+    }
+
     protected function doSubscribe($mode)
     {
         $orig = clone($this);
@@ -296,7 +269,21 @@ class FeedSub extends Memcached_DataObject
                           'hub.secret' => $this->secret,
                           'hub.topic' => $this->uri);
             $client = new HTTPClient();
-            $response = $client->post($this->huburi, $headers, $post);
+            if ($this->huburi) {
+                $hub = $this->huburi;
+            } else {
+                if (common_config('feedsub', 'fallback_hub')) {
+                    $hub = common_config('feedsub', 'fallback_hub');
+                    if (common_config('feedsub', 'hub_user')) {
+                        $u = common_config('feedsub', 'hub_user');
+                        $p = common_config('feedsub', 'hub_pass');
+                        $client->setAuth($u, $p);
+                    }
+                } else {
+                    throw new FeedSubException('WTF?');
+                }
+            }
+            $response = $client->post($hub, $headers, $post);
             $status = $response->getStatus();
             if ($status == 202) {
                 common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
@@ -435,7 +422,15 @@ class FeedSub extends Memcached_DataObject
                 if ($their_hmac === $our_hmac) {
                     return true;
                 }
-                common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac");
+                if (common_config('feedsub', 'debug')) {
+                    $tempfile = tempnam(sys_get_temp_dir(), 'feedsub-receive');
+                    if ($tempfile) {
+                        file_put_contents($tempfile, $post);
+                    }
+                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed $this->uri on $this->huburi; saved to $tempfile");
+                } else {
+                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed $this->uri on $this->huburi");
+                }
             } else {
                 common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
             }
@@ -448,6 +443,4 @@ class FeedSub extends Memcached_DataObject
         }
         return false;
     }
-
 }
-