3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2011, StatusNet, Inc.
6 * Offline backup queue handler
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * @category Offline backup
25 * @author Evan Prodromou <evan@status.net>
26 * @copyright 2011 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
32 // This check helps protect against security problems;
33 // your code file can't be executed directly from the web.
38 * Offline backup queue handler
42 * @author Evan Prodromou <evan@status.net>
43 * @copyright 2011 StatusNet, Inc.
44 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
45 * @link http://status.net/
48 class OfflineBackupQueueHandler extends QueueHandler
55 function handle($object)
59 $user = User::getKV($userId);
61 common_log(LOG_INFO, "Making backup file for user ".$user->nickname);
63 $fileName = $this->makeBackupFile($user);
65 common_log(LOG_INFO, "Notifying user ".$user->nickname . " of their new backup file.");
67 $this->notifyBackupFile($user, $fileName);
72 function makeBackupFile($user)
74 // XXX: this is pretty lose-y; try another way
76 $tmpdir = common_get_temp_dir() . '/offline-backup/' . $user->nickname . '/' . common_date_iso8601(common_sql_now());
78 common_log(LOG_INFO, 'Writing backup data to ' . $tmpdir . ' for ' . $user->nickname);
80 mkdir($tmpdir, 0700, true);
82 $this->dumpNotices($user, $tmpdir);
83 $this->dumpFaves($user, $tmpdir);
84 $this->dumpSubscriptions($user, $tmpdir);
85 $this->dumpSubscribers($user, $tmpdir);
86 $this->dumpGroups($user, $tmpdir);
88 $fileName = File::filename($user->getProfile(), "backup", "application/atom+xml");
89 $fullPath = File::path($fileName);
91 $this->makeActivityFeed($user, $tmpdir, $fullPath);
93 $this->delTree($tmpdir);
98 function notifyBackupFile($user, $fileName)
100 $fileUrl = File::url($fileName);
102 $body = sprintf(_m("The backup file you requested is ready for download.\n\n".
104 "Thanks for your time,\n",
107 common_config('site', 'name'));
109 $headers = _mail_prepare_headers('offlinebackup', $user->nickname, $user->nickname);
111 mail_to_user($user, _('Backup file ready for download'), $body, $headers);
114 function dumpNotices($user, $dir)
116 common_log(LOG_INFO, 'dumping notices by ' . $user->nickname . ' to directory ' . $dir);
118 $profile = $user->getProfile();
120 $stream = new ProfileNoticeStream($profile, $profile);
126 $notice = $stream->getNotices(($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
128 while ($notice->fetch()) {
130 $fname = $dir . '/'. common_date_iso8601($notice->created) . '-notice-' . $notice->id . '.atom';
131 $data = $notice->asAtomEntry(false, false, false, null);
132 common_log(LOG_INFO, 'dumping notice ' . $notice->id . ' to file ' . $fname);
133 file_put_contents($fname, $data);
135 } catch (Exception $e) {
136 common_log(LOG_ERR, "Error backing up notice " . $notice->id . ": " . $e->getMessage());
143 } while ($notice->N > NOTICES_PER_PAGE);
146 function dumpFaves($user, $dir)
148 common_log(LOG_INFO, 'dumping faves by ' . $user->nickname . ' to directory ' . $dir);
153 $fave = Fave::byProfile($user->id, ($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
155 while ($fave->fetch()) {
157 $fname = $dir . '/'. common_date_iso8601($fave->modified) . '-fave-' . $fave->notice_id . '.atom';
158 $act = $fave->asActivity();
159 $data = $act->asString(false, false, false);
160 common_log(LOG_INFO, 'dumping fave of ' . $fave->notice_id . ' to file ' . $fname);
161 file_put_contents($fname, $data);
163 } catch (Exception $e) {
164 common_log(LOG_ERR, "Error backing up fave of " . $fave->notice_id . ": " . $e->getMessage());
171 } while ($fave->N > NOTICES_PER_PAGE);
174 function dumpSubscriptions($user, $dir)
176 common_log(LOG_INFO, 'dumping subscriptions by ' . $user->nickname . ' to directory ' . $dir);
181 $sub = Subscription::bySubscriber($user->id, ($page-1)*PROFILES_PER_PAGE, PROFILES_PER_PAGE + 1);
183 while ($sub->fetch()) {
185 if ($sub->subscribed == $user->id) {
188 $fname = $dir . '/'. common_date_iso8601($sub->created) . '-subscription-' . $sub->subscribed . '.atom';
189 $act = $sub->asActivity();
190 $data = $act->asString(false, false, false);
191 common_log(LOG_INFO, 'dumping sub of ' . $sub->subscribed . ' to file ' . $fname);
192 file_put_contents($fname, $data);
194 } catch (Exception $e) {
195 common_log(LOG_ERR, "Error backing up subscription to " . $sub->subscribed . ": " . $e->getMessage());
202 } while ($sub->N > PROFILES_PER_PAGE);
205 function dumpSubscribers($user, $dir)
207 common_log(LOG_INFO, 'dumping subscribers to ' . $user->nickname . ' to directory ' . $dir);
212 $sub = Subscription::bySubscribed($user->id, ($page-1)*PROFILES_PER_PAGE, PROFILES_PER_PAGE + 1);
214 while ($sub->fetch()) {
216 if ($sub->subscriber == $user->id) {
219 $fname = $dir . '/'. common_date_iso8601($sub->created) . '-subscriber-' . $sub->subscriber . '.atom';
220 $act = $sub->asActivity();
221 $data = $act->asString(false, true, false);
222 common_log(LOG_INFO, 'dumping sub by ' . $sub->subscriber . ' to file ' . $fname);
223 file_put_contents($fname, $data);
225 } catch (Exception $e) {
226 common_log(LOG_ERR, "Error backing up subscription from " . $sub->subscriber . ": " . $e->getMessage());
233 } while ($sub->N > PROFILES_PER_PAGE);
236 function dumpGroups($user, $dir)
238 common_log(LOG_INFO, 'dumping memberships of ' . $user->nickname . ' to directory ' . $dir);
244 $mem = Group_member::byMember($user->id, ($page-1)*GROUPS_PER_PAGE, GROUPS_PER_PAGE + 1);
246 while ($mem->fetch()) {
248 $fname = $dir . '/'. common_date_iso8601($mem->created) . '-membership-' . $mem->group_id . '.atom';
249 $act = $mem->asActivity();
250 $data = $act->asString(false, false, false);
251 common_log(LOG_INFO, 'dumping membership in ' . $mem->group_id . ' to file ' . $fname);
252 file_put_contents($fname, $data);
254 } catch (Exception $e) {
255 common_log(LOG_ERR, "Error backing up membership in " . $mem->group_id . ": " . $e->getMessage());
262 } while ($mem->N > GROUPS_PER_PAGE);
265 function makeActivityFeed($user, $tmpdir, $fullPath)
267 $handle = fopen($fullPath, 'c');
269 $this->writeFeedHeader($user, $handle);
271 $objects = scandir($tmpdir);
275 foreach ($objects as $object) {
276 $objFull = $tmpdir . '/' . $object;
277 if (!is_dir($objFull)) {
278 $entry = file_get_contents($objFull);
279 fwrite($handle, $entry);
284 $this->writeFeedFooter($user, $handle);
288 function writeFeedHeader($user, $handle)
290 fwrite($handle, '<?xml version="1.0" encoding="UTF-8"?>');
291 fwrite($handle, "\n");
292 fwrite($handle, '<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">');
293 fwrite($handle, "\n");
295 $profile = $user->getProfile();
297 $author = $profile->asActivityObject();
299 $xs = new XMLStringer();
300 $author->outputTo($xs, 'author');
301 fwrite($handle, $xs->getString());
302 fwrite($handle, "\n");
305 function writeFeedFooter($user, $handle)
307 fwrite($handle, '</feed>');
310 function delTree($dir)
313 $objects = scandir($dir);
314 foreach ($objects as $object) {
315 if ($object != "." && $object != "..") {
316 if (filetype($dir."/".$object) == "dir") {
317 $this->delTree($dir."/".$object);
319 unlink($dir."/".$object);