]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/YammerImport/lib/yammerrunner.php
Made YammerImport more robust against errors; can now pause/resume/reset the import...
[quix0rs-gnu-social.git] / plugins / YammerImport / lib / yammerrunner.php
1 <?php
2 /*
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('STATUSNET')) {
21     exit(1);
22 }
23
24 /**
25  * State machine for running through Yammer import.
26  *
27  * @package YammerImportPlugin
28  * @author Brion Vibber <brion@status.net>
29  */
30 class YammerRunner
31 {
32     private $state;
33     private $client;
34     private $importer;
35
36     /**
37      * Normalize our singleton state and give us a YammerRunner object to play with!
38      *
39      * @return YammerRunner
40      */
41     public static function init()
42     {
43         $state = Yammer_state::staticGet('id', 1);
44         if (!$state) {
45             $state = self::initState();
46         }
47         return new YammerRunner($state);
48     }
49
50     private static function initState()
51     {
52         $state = new Yammer_state();
53         $state->id = 1;
54         $state->state = 'init';
55         $state->created = common_sql_now();
56         $state->modified = common_sql_now();
57         $state->insert();
58         return $state;
59     }
60
61     private function __construct($state)
62     {
63         $this->state = $state;
64
65         $this->client = new SN_YammerClient(
66             common_config('yammer', 'consumer_key'),
67             common_config('yammer', 'consumer_secret'),
68             $this->state->oauth_token,
69             $this->state->oauth_secret);
70
71         $this->importer = new YammerImporter($this->client);
72     }
73
74     /**
75      * Check which state we're in
76      *
77      * @return string
78      */
79     public function state()
80     {
81         return $this->state->state;
82     }
83
84     /**
85      * Is the import done, finished, complete, finito?
86      *
87      * @return boolean
88      */
89     public function isDone()
90     {
91         $workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
92         return ($this->state() == 'done');
93     }
94
95     /**
96      * Check if we have work to do in iterate().
97      *
98      * @return boolean
99      */
100     public function hasWork()
101     {
102         $workStates = array('import-users', 'import-groups', 'fetch-messages', 'save-messages');
103         return in_array($this->state(), $workStates);
104     }
105
106     /**
107      * Blow away any current state!
108      */
109     public function reset()
110     {
111         $this->state->delete();
112         $this->state = self::initState();
113     }
114
115     /**
116      * Start the authentication process! If all goes well, we'll get back a URL.
117      * Have the user visit that URL, log in on Yammer and verify the importer's
118      * permissions. They'll get back a verification code, which needs to be passed
119      * on to saveAuthToken().
120      *
121      * @return string URL
122      */
123     public function requestAuth()
124     {
125         if ($this->state->state != 'init') {
126             throw new ServerException("Cannot request Yammer auth; already there!");
127         }
128
129         $data = $this->client->requestToken();
130
131         $old = clone($this->state);
132         $this->state->state = 'requesting-auth';
133         $this->state->oauth_token = $data['oauth_token'];
134         $this->state->oauth_secret = $data['oauth_token_secret'];
135         $this->state->modified = common_sql_now();
136         $this->state->update($old);
137
138         return $this->getAuthUrl();
139     }
140
141     /**
142      * When already in requesting-auth state, grab the URL to send the user to
143      * to complete OAuth setup.
144      *
145      * @return string URL
146      */
147     function getAuthUrl()
148     {
149         if ($this->state() == 'requesting-auth') {
150             return $this->client->authorizeUrl($this->state->oauth_token);
151         } else {
152             throw new ServerException('Cannot get Yammer auth URL when not in requesting-auth state!');
153         }
154     }
155
156     /**
157      * Now that the user's given us this verification code from Yammer, we can
158      * request a final OAuth token/secret pair which we can use to access the
159      * API.
160      *
161      * After success here, we'll be ready to move on and run through iterate()
162      * until the import is complete.
163      *
164      * @param string $verifier
165      * @return boolean success
166      */
167     public function saveAuthToken($verifier)
168     {
169         if ($this->state->state != 'requesting-auth') {
170             throw new ServerException("Cannot save auth token in Yammer import state {$this->state->state}");
171         }
172
173         $data = $this->client->accessToken($verifier);
174
175         $old = clone($this->state);
176         $this->state->state = 'import-users';
177         $this->state->oauth_token = $data['oauth_token'];
178         $this->state->oauth_secret = $data['oauth_token_secret'];
179         $this->state->modified = common_sql_now();
180         $this->state->update($old);
181
182         return true;
183     }
184
185     /**
186      * Once authentication is complete, we need to call iterate() a bunch of times
187      * until state() returns 'done'.
188      *
189      * @return boolean success
190      */
191     public function iterate()
192     {
193         switch($this->state())
194         {
195             case 'init':
196             case 'requesting-auth':
197                 // Neither of these should reach our background state!
198                 common_log(LOG_ERR, "Non-background YammerImport state '$state->state' during import run!");
199                 return false;
200             case 'import-users':
201                 return $this->iterateUsers();
202             case 'import-groups':
203                 return $this->iterateGroups();
204             case 'fetch-messages':
205                 return $this->iterateFetchMessages();
206             case 'save-messages':
207                 return $this->iterateSaveMessages();
208             default:
209                 common_log(LOG_ERR, "Invalid YammerImport state '$state->state' during import run!");
210                 return false;
211         }
212     }
213
214     /**
215      * Trundle through one 'page' return of up to 50 user accounts retrieved
216      * from the Yammer API, importing them as we go.
217      *
218      * When we run out of users, move on to groups.
219      *
220      * @return boolean success
221      */
222     private function iterateUsers()
223     {
224         $old = clone($this->state);
225
226         $page = intval($this->state->users_page) + 1;
227         $data = $this->client->users(array('page' => $page));
228
229         if (count($data) == 0) {
230             common_log(LOG_INFO, "Finished importing Yammer users; moving on to groups.");
231             $this->state->state = 'import-groups';
232         } else {
233             foreach ($data as $item) {
234                 $user = $this->importer->importUser($item);
235                 common_log(LOG_INFO, "Imported Yammer user " . $item['id'] . " as $user->nickname ($user->id)");
236             }
237             $this->state->users_page = $page;
238         }
239         $this->state->modified = common_sql_now();
240         $this->state->update($old);
241         return true;
242     }
243
244     /**
245      * Trundle through one 'page' return of up to 20 user groups retrieved
246      * from the Yammer API, importing them as we go.
247      *
248      * When we run out of groups, move on to messages.
249      *
250      * @return boolean success
251      */
252     private function iterateGroups()
253     {
254         $old = clone($this->state);
255
256         $page = intval($this->state->groups_page) + 1;
257         $data = $this->client->groups(array('page' => $page));
258
259         if (count($data) == 0) {
260             common_log(LOG_INFO, "Finished importing Yammer groups; moving on to messages.");
261             $this->state->state = 'fetch-messages';
262         } else {
263             foreach ($data as $item) {
264                 $group = $this->importer->importGroup($item);
265                 common_log(LOG_INFO, "Imported Yammer group " . $item['id'] . " as $group->nickname ($group->id)");
266             }
267             $this->state->groups_page = $page;
268         }
269         $this->state->modified = common_sql_now();
270         $this->state->update($old);
271         return true;
272     }
273
274     /**
275      * Trundle through one 'page' return of up to 20 public messages retrieved
276      * from the Yammer API, saving them to our stub table for future import in
277      * correct chronological order.
278      *
279      * When we run out of messages to fetch, move on to saving the messages.
280      *
281      * @return boolean success
282      */
283     private function iterateFetchMessages()
284     {
285         $old = clone($this->state);
286
287         $oldest = intval($this->state->messages_oldest);
288         if ($oldest) {
289             $params = array('older_than' => $oldest);
290         } else {
291             $params = array();
292         }
293         $data = $this->client->messages($params);
294         $messages = $data['messages'];
295
296         if (count($messages) == 0) {
297             common_log(LOG_INFO, "Finished fetching Yammer messages; moving on to save messages.");
298             $this->state->state = 'save-messages';
299         } else {
300             foreach ($messages as $item) {
301                 $stub = Yammer_notice_stub::staticGet($item['id']);
302                 if (!$stub) {
303                     Yammer_notice_stub::record($item['id'], $item);
304                 }
305                 $oldest = $item['id'];
306             }
307             $this->state->messages_oldest = $oldest;
308         }
309         $this->state->modified = common_sql_now();
310         $this->state->update($old);
311         return true;
312     }
313
314     private function iterateSaveMessages()
315     {
316         $old = clone($this->state);
317
318         $newest = intval($this->state->messages_newest);
319
320         $stub = new Yammer_notice_stub();
321         if ($newest) {
322             $stub->whereAdd('id > ' . $newest);
323         }
324         $stub->limit(20);
325         $stub->orderBy('id');
326         $stub->find();
327         
328         if ($stub->N == 0) {
329             common_log(LOG_INFO, "Finished saving Yammer messages; import complete!");
330             $this->state->state = 'done';
331         } else {
332             while ($stub->fetch()) {
333                 $item = $stub->getData();
334                 $notice = $this->importer->importNotice($item);
335                 common_log(LOG_INFO, "Imported Yammer notice " . $item['id'] . " as $notice->id");
336                 $newest = $item['id'];
337             }
338             $this->state->messages_newest = $newest;
339         }
340         $this->state->modified = common_sql_now();
341         $this->state->update($old);
342         return true;
343     }
344
345     /**
346      * Count the number of Yammer users we've mapped into our system!
347      *
348      * @return int
349      */
350     public function countUsers()
351     {
352         $map = new Yammer_user();
353         return $map->count();
354     }
355
356
357     /**
358      * Count the number of Yammer groups we've mapped into our system!
359      *
360      * @return int
361      */
362     public function countGroups()
363     {
364         $map = new Yammer_group();
365         return $map->count();
366     }
367
368
369     /**
370      * Count the number of Yammer notices we've pulled down for pending import...
371      *
372      * @return int
373      */
374     public function countFetchedNotices()
375     {
376         $map = new Yammer_notice_stub();
377         return $map->count();
378     }
379
380
381     /**
382      * Count the number of Yammer notices we've mapped into our system!
383      *
384      * @return int
385      */
386     public function countSavedNotices()
387     {
388         $map = new Yammer_notice();
389         return $map->count();
390     }
391
392     /**
393      * Start running import work in the background queues...
394      */
395     public function startBackgroundImport()
396     {
397         $qm = QueueManager::get();
398         $qm->enqueue('YammerImport', 'yammer');
399     }
400
401     /**
402      * Record an error condition from a background run, which we should
403      * display in progress state for the admin.
404      * 
405      * @param string $msg 
406      */
407     public function recordError($msg)
408     {
409         // HACK HACK HACK
410         try {
411             $temp = new Yammer_state();
412             $temp->query('ROLLBACK');
413         } catch (Exception $e) {
414             common_log(LOG_ERR, 'Exception while confirming rollback while recording error: ' . $e->getMessage());
415         }
416         $old = clone($this->state);
417         $this->state->last_error = $msg;
418         $this->state->update($old);
419     }
420
421     /**
422      * Clear the error state.
423      */
424     public function clearError()
425     {
426         $this->recordError('');
427     }
428
429     /**
430      * Get the last recorded background error message, if any.
431      * 
432      * @return string
433      */
434     public function lastError()
435     {
436         return $this->state->last_error;
437     }
438 }