blob: fa8517c62527df9327b4f04f0be6fbb505353beb [file] [log] [blame]
Alexander Afanasyev026eaf32013-02-23 16:37:14 -08001/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2013 University of California, Los Angeles
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation;
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 * Author: Alexander Afanasyev <alexander.afanasyev@ucla.edu>
19 * Zhenkai Zhu <zhenkai@cs.ucla.edu>
20 */
21
22#include "state-server.h"
23#include "logging.h"
24#include <boost/make_shared.hpp>
25#include <utility>
26#include "task.h"
27#include "periodic-task.h"
28#include "simple-interval-generator.h"
29#include <boost/lexical_cast.hpp>
Alexander Afanasyeve1c95042013-02-27 01:02:36 -080030#include <boost/date_time/posix_time/posix_time.hpp>
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080031
32INIT_LOGGER ("StateServer");
33
34using namespace Ccnx;
35using namespace std;
36using namespace boost;
37
38StateServer::StateServer(CcnxWrapperPtr ccnx, ActionLogPtr actionLog,
39 const boost::filesystem::path &rootDir,
40 const Ccnx::Name &userName, const std::string &sharedFolderName,
41 const std::string &appName,
42 ObjectManager &objectManager,
43 int freshness/* = -1*/)
44 : m_ccnx(ccnx)
45 , m_actionLog(actionLog)
46 , m_objectManager (objectManager)
47 , m_rootDir(rootDir)
48 , m_freshness(freshness)
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080049 , m_executor (1)
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080050 , m_userName (userName)
51 , m_sharedFolderName (sharedFolderName)
52 , m_appName (appName)
53{
54 // may be later /localhost should be replaced with /%C1.M.S.localhost
55
56 // <PREFIX_INFO> = /localhost/<user's-device-name>/"chronoshare"/"info"
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080057 m_PREFIX_INFO = Name ("/localhost")(m_userName)("chronoshare")(m_sharedFolderName)("info");
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080058
59 // <PREFIX_CMD> = /localhost/<user's-device-name>/"chronoshare"/"cmd"
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080060 m_PREFIX_CMD = Name ("/localhost")(m_userName)("chronoshare")(m_sharedFolderName)("cmd");
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080061
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080062 m_executor.start ();
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080063
64 registerPrefixes ();
65}
66
67StateServer::~StateServer()
68{
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080069 m_executor.shutdown ();
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080070
71 deregisterPrefixes ();
72}
73
74void
75StateServer::registerPrefixes ()
76{
77 // currently supporting limited number of command.
78 // will be extended to support all planned commands later
79
80 // <PREFIX_INFO>/"actions"/"all"/<nonce>/<segment> get list of all actions
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080081 m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"), bind(&StateServer::info_actions_folder, this, _1));
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080082
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080083 // <PREFIX_INFO>/"filestate"/"all"/<nonce>/<segment>
Alexander Afanasyev94240b52013-02-27 11:57:29 -080084 m_ccnx->setInterestFilter (Name (m_PREFIX_INFO)("files")("folder"), bind(&StateServer::info_files_folder, this, _1));
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080085
86 // <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
87 m_ccnx->setInterestFilter (Name (m_PREFIX_CMD)("restore")("file"), bind(&StateServer::cmd_restore_file, this, _1));
88}
89
90void
91StateServer::deregisterPrefixes ()
92{
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080093 m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("actions")("folder"));
Alexander Afanasyev94240b52013-02-27 11:57:29 -080094 m_ccnx->clearInterestFilter (Name (m_PREFIX_INFO)("files")("folder"));
Alexander Afanasyev026eaf32013-02-23 16:37:14 -080095 m_ccnx->clearInterestFilter (Name (m_PREFIX_CMD) ("restore")("file"));
96}
97
Alexander Afanasyev95f9f552013-02-26 23:05:20 -080098// void
99// StateServer::info_actions_all (const Name &interest)
100// {
101// _LOG_DEBUG (">> info_actions_all: " << interest);
102// m_executor.execute (bind (&StateServer::info_actions_all_Execute, this, interest));
103// }
104
105// void
106// StateServer::info_actions_all_Execute (const Name &interest)
107// {
108// // <PREFIX_INFO>/"actions"/"all"/<nonce>/<offset> get list of all actions
109
110// try
111// {
112// int offset = interest.getCompFromBackAsInt (0);
113
114// // LookupActionsInFolderRecursively
115// /// @todo !!! add security checking
116// m_ccnx->publishData (interest, "FAIL: Not implemented", 1);
117// }
118// catch (Ccnx::NameException &ne)
119// {
120// // ignore any unexpected interests and errors
121// _LOG_ERROR (*boost::get_error_info<Ccnx::error_info_str>(ne));
122// }
123// }
124
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800125void
126StateServer::formatActionJson (json_spirit::Array &actions,
127 const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &action)
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800128{
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800129/*
130 * {
131 * "id": {
132 * "userName": "<NDN-NAME-OF-THE-USER>",
133 * "seqNo": "<SEQ_NO_OF_THE_ACTION>"
134 * },
135 * "timestamp": "<ACTION-TIMESTAMP>",
136 * "filename": "<FILENAME>",
137 *
138 * "action": "UPDATE | DELETE",
139 *
140 * // only if update
141 * "update": {
142 * "hash": "<FILE-HASH>",
143 * "timestamp": "<FILE-TIMESTAMP>",
144 * "chmod": "<FILE-MODE>",
145 * "segNum": "<NUMBER-OF-SEGMENTS (~file size)>"
146 * },
147 *
148 * // if parent_device_name is set
149 * "parentId": {
150 * "userName": "<NDN-NAME-OF-THE-USER>",
151 * "seqNo": "<SEQ_NO_OF_THE_ACTION>"
152 * }
153 * }
154 */
155
156 using namespace json_spirit;
157 using namespace boost::posix_time;
158
159 Object json;
160 Object id;
161
162 id.push_back (Pair ("userName", boost::lexical_cast<string> (name)));
163 id.push_back (Pair ("seqNo", seq_no));
164
165 json.push_back (Pair ("id", id));
166
Alexander Afanasyev46bd8062013-02-27 23:59:15 -0800167 json.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (action.timestamp ()))));
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800168 json.push_back (Pair ("filename", action.filename ()));
Alexander Afanasyevf63a5142013-02-28 02:21:42 -0800169 json.push_back (Pair ("version", action.version ()));
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800170 json.push_back (Pair ("action", (action.action () == 0) ? "UPDATE" : "DELETE"));
171
172 if (action.action () == 0)
173 {
174 Object update;
175 update.push_back (Pair ("hash", boost::lexical_cast<string> (Hash (action.file_hash ().c_str (), action.file_hash ().size ()))));
Alexander Afanasyev46bd8062013-02-27 23:59:15 -0800176 update.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (action.mtime ()))));
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800177
178 ostringstream chmod;
179 chmod << setbase (8) << setfill ('0') << setw (4) << action.mode ();
180 update.push_back (Pair ("chmod", chmod.str ()));
181
182 update.push_back (Pair ("segNum", action.seg_num ()));
183 json.push_back (Pair ("update", update));
184 }
185
186 if (action.has_parent_device_name ())
187 {
188 Object parentId;
189 Ccnx::Name parent_device_name (action.parent_device_name ().c_str (), action.parent_device_name ().size ());
190 id.push_back (Pair ("userName", boost::lexical_cast<string> (parent_device_name)));
191 id.push_back (Pair ("seqNo", action.parent_seq_no ()));
192
193 json.push_back (Pair ("parentId", parentId));
194 }
195
196 actions.push_back (json);
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800197}
198
199void
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800200StateServer::info_actions_folder (const Name &interest)
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800201{
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800202 if (interest.size () - m_PREFIX_INFO.size () != 4 &&
203 interest.size () - m_PREFIX_INFO.size () != 5)
204 {
205 _LOG_DEBUG ("Invalid interest: " << interest);
206 return;
207 }
208
209 _LOG_DEBUG (">> info_actions_all: " << interest);
210 m_executor.execute (bind (&StateServer::info_actions_folder_Execute, this, interest));
211}
212
213void
214StateServer::info_actions_folder_Execute (const Name &interest)
215{
216 // <PREFIX_INFO>/"actions"/"folder"/<folder>/<nonce>/<offset> get list of all actions
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800217
218 try
219 {
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800220 int offset = interest.getCompFromBackAsInt (0);
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800221
222 /// @todo !!! add security checking
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800223
224 string folder;
225 if (interest.size () - m_PREFIX_INFO.size () == 5)
226 folder = interest.getCompFromBackAsString (2);
227 else // == 4
228 folder = "";
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800229/*
230 * {
231 * "actions": [
232 * ...
233 * ],
234 *
235 * // only if there are more actions available
236 * "more": "<NDN-NAME-OF-NEXT-SEGMENT-OF-ACTION>"
237 * }
238 */
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800239
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800240 using namespace json_spirit;
241 Object json;
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800242
Alexander Afanasyeve1c95042013-02-27 01:02:36 -0800243 Array actions;
244 bool more = m_actionLog->LookupActionsInFolderRecursively
245 (boost::bind (StateServer::formatActionJson, boost::ref(actions), _1, _2, _3),
246 folder, offset*10, 10);
247
248 json.push_back (Pair ("actions", actions));
249
250 if (more)
251 {
252 Ccnx::Name more = Name (interest.getPartialName (0, interest.size () - 1))(offset + 1);
253 json.push_back (Pair ("more", lexical_cast<string> (more)));
254 }
255
256 ostringstream os;
257 write_stream (Value (json), os, pretty_print | raw_utf8);
258 m_ccnx->publishData (interest, os.str (), 1);
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800259 }
260 catch (Ccnx::NameException &ne)
261 {
262 // ignore any unexpected interests and errors
263 _LOG_ERROR (*boost::get_error_info<Ccnx::error_info_str>(ne));
264 }
265}
266
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800267void
268StateServer::formatFilestateJson (json_spirit::Array &files, const FileItem &file)
269{
270/**
271 * {
272 * "filestate": [
273 * {
274 * "filename": "<FILENAME>",
275 * "owner": {
276 * "userName": "<NDN-NAME-OF-THE-USER>",
277 * "seqNo": "<SEQ_NO_OF_THE_ACTION>"
278 * },
279 *
280 * "hash": "<FILE-HASH>",
281 * "timestamp": "<FILE-TIMESTAMP>",
282 * "chmod": "<FILE-MODE>",
283 * "segNum": "<NUMBER-OF-SEGMENTS (~file size)>"
284 * }, ...,
285 * ]
286 *
287 * // only if there are more actions available
288 * "more": "<NDN-NAME-OF-NEXT-SEGMENT-OF-FILESTATE>"
289 * }
290 */
291 using namespace json_spirit;
292 using namespace boost::posix_time;
293
294 Object json;
295
296 json.push_back (Pair ("filename", file.filename ()));
297 json.push_back (Pair ("version", file.version ()));
298 {
299 Object owner;
300 Ccnx::Name device_name (file.device_name ().c_str (), file.device_name ().size ());
301 owner.push_back (Pair ("userName", boost::lexical_cast<string> (device_name)));
302 owner.push_back (Pair ("seqNo", file.seq_no ()));
303
304 json.push_back (Pair ("owner", owner));
305 }
306
307 json.push_back (Pair ("hash", boost::lexical_cast<string> (Hash (file.file_hash ().c_str (), file.file_hash ().size ()))));
Alexander Afanasyev46bd8062013-02-27 23:59:15 -0800308 json.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (file.mtime ()))));
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800309
310 ostringstream chmod;
311 chmod << setbase (8) << setfill ('0') << setw (4) << file.mode ();
312 json.push_back (Pair ("chmod", chmod.str ()));
313
314 json.push_back (Pair ("segNum", file.seg_num ()));
315
316 files.push_back (json);
317}
318
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800319void debugFileState (const FileItem &file)
320{
321 std::cout << file.filename () << std::endl;
322}
323
324void
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800325StateServer::info_files_folder (const Ccnx::Name &interest)
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800326{
327 if (interest.size () - m_PREFIX_INFO.size () != 4 &&
328 interest.size () - m_PREFIX_INFO.size () != 5)
329 {
330 _LOG_DEBUG ("Invalid interest: " << interest << ", " << interest.size () - m_PREFIX_INFO.size ());
331 return;
332 }
333
334 _LOG_DEBUG (">> info_filestate_folder: " << interest);
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800335 m_executor.execute (bind (&StateServer::info_files_folder_Execute, this, interest));
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800336}
337
338
339void
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800340StateServer::info_files_folder_Execute (const Ccnx::Name &interest)
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800341{
342 // <PREFIX_INFO>/"filestate"/"folder"/<one-component-relative-folder-name>/<nonce>/<offset>
343 try
344 {
345 int offset = interest.getCompFromBackAsInt (0);
346
347 // /// @todo !!! add security checking
348
349 string folder;
350 if (interest.size () - m_PREFIX_INFO.size () == 5)
351 folder = interest.getCompFromBackAsString (2);
352 else // == 4
353 folder = "";
354
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800355/*
356 * {
357 * "files": [
358 * ...
359 * ],
360 *
361 * // only if there are more actions available
362 * "more": "<NDN-NAME-OF-NEXT-SEGMENT-OF-ACTION>"
363 * }
364 */
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800365
Alexander Afanasyev94240b52013-02-27 11:57:29 -0800366 using namespace json_spirit;
367 Object json;
368
369 Array files;
370 bool more = m_actionLog
371 ->GetFileState ()
372 ->LookupFilesInFolderRecursively
373 (boost::bind (StateServer::formatFilestateJson, boost::ref (files), _1),
374 folder, offset*10, 10);
375
376 json.push_back (Pair ("files", files));
377
378 if (more)
379 {
380 Ccnx::Name more = Name (interest.getPartialName (0, interest.size () - 1))(offset + 1);
381 json.push_back (Pair ("more", lexical_cast<string> (more)));
382 }
383
384 ostringstream os;
385 write_stream (Value (json), os, pretty_print | raw_utf8);
386 m_ccnx->publishData (interest, os.str (), 1);
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800387 }
388 catch (Ccnx::NameException &ne)
389 {
390 // ignore any unexpected interests and errors
391 _LOG_ERROR (*boost::get_error_info<Ccnx::error_info_str>(ne));
392 }
393}
394
395
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800396void
397StateServer::cmd_restore_file (const Ccnx::Name &interest)
398{
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800399 if (interest.size () - m_PREFIX_CMD.size () != 4 &&
400 interest.size () - m_PREFIX_CMD.size () != 5)
401 {
402 _LOG_DEBUG ("Invalid interest: " << interest);
403 return;
404 }
405
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800406 _LOG_DEBUG (">> cmd_restore_file: " << interest);
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800407 m_executor.execute (bind (&StateServer::cmd_restore_file_Execute, this, interest));
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800408}
409
410void
411StateServer::cmd_restore_file_Execute (const Ccnx::Name &interest)
412{
413 // <PREFIX_CMD>/"restore"/"file"/<one-component-relative-file-name>/<version>/<file-hash>
414
415 /// @todo !!! add security checking
416
417 try
418 {
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800419 FileItemPtr file;
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800420
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800421 if (interest.size () - m_PREFIX_CMD.size () == 5)
422 {
423 Hash hash (head(interest.getCompFromBack (0)), interest.getCompFromBack (0).size());
424 int64_t version = interest.getCompFromBackAsInt (1);
425 string filename = interest.getCompFromBackAsString (2); // should be safe even with full relative path
426
427 file = m_actionLog->LookupAction (filename, version, hash);
428 if (!file)
429 {
430 _LOG_ERROR ("Requested file is not found: [" << filename << "] version [" << version << "] hash [" << hash.shortHash () << "]");
431 }
432 }
433 else
434 {
435 int64_t version = interest.getCompFromBackAsInt (0);
436 string filename = interest.getCompFromBackAsString (1); // should be safe even with full relative path
437
438 file = m_actionLog->LookupAction (filename, version, Hash (0,0));
439 if (!file)
440 {
441 _LOG_ERROR ("Requested file is not found: [" << filename << "] version [" << version << "]");
442 }
443 }
444
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800445 if (!file)
446 {
447 m_ccnx->publishData (interest, "FAIL: Requested file is not found", 1);
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800448 return;
449 }
450
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800451 Hash hash = Hash (file->file_hash ().c_str (), file->file_hash ().size ());
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800452
453 ///////////////////
454 // now the magic //
455 ///////////////////
456
Alexander Afanasyev95f9f552013-02-26 23:05:20 -0800457 boost::filesystem::path filePath = m_rootDir / file->filename ();
Alexander Afanasyev026eaf32013-02-23 16:37:14 -0800458 Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
459
460 try
461 {
462 if (filesystem::exists (filePath) &&
463 filesystem::last_write_time (filePath) == file->mtime () &&
464 filesystem::status (filePath).permissions () == static_cast<filesystem::perms> (file->mode ()) &&
465 *Hash::FromFileContent (filePath) == hash)
466 {
467 m_ccnx->publishData (interest, "OK: File already exists", 1);
468 _LOG_DEBUG ("Asking to assemble a file, but file already exists on a filesystem");
469 return;
470 }
471 }
472 catch (filesystem::filesystem_error &error)
473 {
474 m_ccnx->publishData (interest, "FAIL: File operation failed", 1);
475 _LOG_ERROR ("File operations failed on [" << filePath << "] (ignoring)");
476 }
477
478 _LOG_TRACE ("Restoring file [" << filePath << "]");
479 if (m_objectManager.objectsToLocalFile (deviceName, hash, filePath))
480 {
481 last_write_time (filePath, file->mtime ());
482 permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
483 m_ccnx->publishData (interest, "OK", 1);
484 }
485 else
486 {
487 m_ccnx->publishData (interest, "FAIL: Unknown error while restoring file", 1);
488 }
489 }
490 catch (Ccnx::NameException &ne)
491 {
492 // ignore any unexpected interests and errors
493 _LOG_ERROR(*boost::get_error_info<Ccnx::error_info_str>(ne));
494 }
495}