Work in progress: most of the infrastructure for running import via BG queues or CLI script is now in place (untested, no UI, needs tweaks & fixes)

This commit is contained in:
Brion Vibber 2010-09-23 16:40:22 -07:00
parent e8ad436a99
commit ae507b0485
4 changed files with 186 additions and 35 deletions

View File

@ -73,3 +73,45 @@ they do on Yammer; they will be linked instead.
File type and size limitations on attachments will be applied, so beware some File type and size limitations on attachments will be applied, so beware some
attachments may not make it through. attachments may not make it through.
Code structure
==============
Standalone classes
------------------
YammerRunner: encapsulates the iterative process of retrieving the various users,
groups, and messages via SN_YammerClient and saving them locally
via YammerImporter.
SN_YammerClient: encapsulates HTTP+OAuth interface to Yammer API, returns data
as straight decoded JSON object trees.
YammerImporter: encapsulates logic to pull information from the returned API data
and convert them to native StatusNet users, groups, and messages.
Web UI actions
-------------
YammeradminpanelAction: web panel for site administrator to initiate and monitor
the import process.
Command-line scripts
--------------------
yammer-import.php: CLI script to start a Yammer import run in one go.
Database objects
----------------
Yammer_state: data object storing YammerRunner's state between iterations.
Yammer_notice_stub: data object for temporary storage of fetched Yammer messages
between fetching them (reverse chron order) and saving them
to local messages (forward chron order).
Yammer_user,
Yammer_group,
Yammer_notice: data objects mapping original Yammer item IDs to their local copies.

View File

@ -36,11 +36,23 @@ class YammerQueueHandler extends QueueHandler
function handle($notice) function handle($notice)
{ {
$importer = new YammerImporter(); $runner = YammerRunner::init();
if ($importer->hasWork()) { if ($runner->hasWork()) {
return $importer->iterate(); if ($runner->iterate()) {
if ($runner->hasWork()) {
// More to do? Shove us back on the queue...
$qm = QueueManager::get();
$qm->enqueue('YammerImport', 'yammer');
}
return true;
} else {
// Something failed?
// @fixme should we be trying again here, or should we give warning?
return false;
}
} else { } else {
// We're done! // We're done!
common_log(LOG_INFO, "Yammer import has no work to do at this time; discarding.");
return true; return true;
} }
} }

View File

@ -33,29 +33,123 @@ class YammerRunner
private $client; private $client;
private $importer; private $importer;
function __construct() public static function init()
{ {
$state = Yammer_state::staticGet('id', 1); $state = Yammer_state::staticGet('id', 1);
if (!$state) { if (!$state) {
common_log(LOG_ERR, "No YammerImport state during import run. Should not happen!"); $state = new Yammer_state();
throw new ServerException('No YammerImport state during import run.'); $state->id = 1;
$state->state = 'init';
$state->insert();
} }
return new YammerRunner($state);
}
private function __construct($state)
{
$this->state = $state; $this->state = $state;
$this->client = new SN_YammerClient( $this->client = new SN_YammerClient(
common_config('yammer', 'consumer_key'), common_config('yammer', 'consumer_key'),
common_config('yammer', 'consumer_secret'), common_config('yammer', 'consumer_secret'),
$this->state->oauth_token, $this->state->oauth_token,
$this->state->oauth_secret); $this->state->oauth_secret);
$this->importer = new YammerImporter($client); $this->importer = new YammerImporter($client);
} }
/**
* Check which state we're in
*
* @return string
*/
public function state()
{
return $this->state->state;
}
/**
* Is the import done, finished, complete, finito?
*
* @return boolean
*/
public function isDone()
{
$workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
return ($this->state() == 'done');
}
/**
* Check if we have work to do in iterate().
*/
public function hasWork()
{
$workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
return in_array($this->state(), $workStates);
}
/**
* Start the authentication process! If all goes well, we'll get back a URL.
* Have the user visit that URL, log in on Yammer and verify the importer's
* permissions. They'll get back a verification code, which needs to be passed
* on to saveAuthToken().
*
* @return string URL
*/
public function requestAuth()
{
if ($this->state->state != 'init') {
throw ServerError("Cannot request Yammer auth; already there!");
}
$old = clone($this->state);
$this->state->state = 'requesting-auth';
$this->state->request_token = $client->requestToken();
$this->state->update($old);
return $this->client->authorizeUrl($this->state->request_token);
}
/**
* Now that the user's given us this verification code from Yammer, we can
* request a final OAuth token/secret pair which we can use to access the
* API.
*
* After success here, we'll be ready to move on and run through iterate()
* until the import is complete.
*
* @param string $verifier
* @return boolean success
*/
public function saveAuthToken($verifier)
{
if ($this->state->state != 'requesting-auth') {
throw ServerError("Cannot save auth token in Yammer import state {$this->state->state}");
}
$old = clone($this->state);
list($token, $secret) = $this->client->getAuthToken($verifier);
$this->state->verifier = '';
$this->state->oauth_token = $token;
$this->state->oauth_secret = $secret;
$this->state->update($old);
return true;
}
/**
* Once authentication is complete, we need to call iterate() a bunch of times
* until state() returns 'done'.
*
* @return boolean success
*/
public function iterate() public function iterate()
{ {
switch($state->state) switch($state->state)
{ {
case null: case 'init':
case 'requesting-auth': case 'requesting-auth':
// Neither of these should reach our background state! // Neither of these should reach our background state!
common_log(LOG_ERR, "Non-background YammerImport state '$state->state' during import run!"); common_log(LOG_ERR, "Non-background YammerImport state '$state->state' during import run!");

View File

@ -8,34 +8,37 @@ define('INSTALLDIR', dirname(dirname(dirname(dirname(__FILE__)))));
require INSTALLDIR . "/scripts/commandline.inc"; require INSTALLDIR . "/scripts/commandline.inc";
// temp stuff $runner = YammerRunner::init();
require 'yam-config.php';
$yam = new SN_YammerClient($consumerKey, $consumerSecret, $token, $tokenSecret);
$imp = new YammerImporter($yam);
// First, import all the users! switch ($runner->state())
// @fixme follow paging -- we only get 50 at a time {
$data = $yam->users(); case 'init':
foreach ($data as $item) { $url = $runner->requestAuth();
$user = $imp->importUser($item); echo "Log in to Yammer at the following URL and confirm permissions:\n";
echo "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)\n"; echo "\n";
} echo " $url\n";
echo "\n";
echo "Pass the resulting code back by running:\n"
echo "\n"
echo " php yammer-import.php --auth=####\n";
echo "\n";
break;
// Groups! case 'requesting-auth':
// @fixme follow paging -- we only get 20 at a time if (empty($options['auth'])) {
$data = $yam->groups(); echo "Please finish authenticating!\n";
foreach ($data as $item) { break;
$group = $imp->importGroup($item); }
echo "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)\n"; $runner->saveAuthToken($options['auth']);
} // Fall through...
// Messages! default:
// Process in reverse chron order... while (true) {
// @fixme follow paging -- we only get 20 at a time, and start at the most recent! echo "... {$runner->state->state}\n";
$data = $yam->messages(); if (!$runner->iterate()) {
$messages = $data['messages']; echo "... done.\n";
$messages = array_reverse($messages); break;
foreach ($messages as $item) { }
$notice = $imp->importNotice($item); }
echo "Imported Yammer notice " . $item['id'] . " as $notice->id\n"; break;
} }