Example #1
0
string sync_changes (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  Sync_Logic sync_logic = Sync_Logic (webserver_request);
  Database_Modifications database_modifications;

  // Check on the credentials.
  if (!sync_logic.credentials_okay ()) return "";

  // Bail out if the change notifications are not now available to clients.
  if (!config_globals_change_notifications_available) {
    request->response_code = 503;
    return "";
  }
  
  // Client makes a prioritized server call: Record the client's IP address.
  sync_logic.prioritized_ip_address_record ();

  // Get the relevant parameters the client may have POSTed to us, the server.
  string user = hex2bin (request->post ["u"]);
  int action = convert_to_int (request->post ["a"]);
  int id = convert_to_int (request->post ["i"]);

  switch (action) {
    case Sync_Logic::changes_delete_modification:
    {
      // The server deletes the change notification.
      database_modifications.deleteNotification (id);
      Database_Logs::log ("Client deletes change notification from server: " + convert_to_string (id), Filter_Roles::translator ());
      request->database_config_user ()->setChangeNotificationsChecksum ("");
      return "";
    }
    case Sync_Logic::changes_get_checksum:
    {
      // The server responds with the possibly cached total checksum for the user's change notifications.
      string checksum = request->database_config_user ()->getChangeNotificationsChecksum ();
      if (checksum.empty ()) {
        checksum = Sync_Logic::changes_checksum (user);
        request->database_config_user ()->setChangeNotificationsChecksum (checksum);
      }
      return checksum;
    }
    case Sync_Logic::changes_get_identifiers:
    {
      // The server responds with the identifiers of all the user's change notifications.
      vector <int> ids = database_modifications.getNotificationIdentifiers (user, false);
      string response;
      for (auto & id : ids) {
        if (!response.empty ()) response.append ("\n");
        response.append (convert_to_string (id));
      }
      return response;
    }
    case Sync_Logic::changes_get_modification:
    {
      // The server responds with the relevant data of the requested modification.
      vector <string> lines;
      // category
      lines.push_back (database_modifications.getNotificationCategory (id));
      // bible
      lines.push_back (database_modifications.getNotificationBible (id));
      // book
      // chapter
      // verse
      Passage passage = database_modifications.getNotificationPassage (id);
      lines.push_back (convert_to_string (passage.book));
      lines.push_back (convert_to_string (passage.chapter));
      lines.push_back (passage.verse);
      // oldtext
      lines.push_back (database_modifications.getNotificationOldText (id));
      // modification
      lines.push_back (database_modifications.getNotificationModification (id));
      // newtext
      lines.push_back (database_modifications.getNotificationNewText (id));
      // Result.
      return filter_string_implode (lines, "\n");
    }
  }

  
  // Bad request.
  // Delay a while to obstruct a flood of bad requests.
  this_thread::sleep_for (chrono::seconds (1));
  request->response_code = 400;
  return "";
}
Example #2
0
string sync_settings (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  Sync_Logic sync_logic = Sync_Logic (webserver_request);

  // Check on the credentials.
  if (!sync_logic.credentials_okay ()) return "";
  
  // Client makes a prioritized server call: Record the client's IP address.
  sync_logic.prioritized_ip_address_record ();

  // Get the relevant parameters the client POSTed to us, the server.
  int action = convert_to_int (request->post ["a"]);
  string value = request->post ["v"];
  // The value can be all Bibles, or one Bible.
  string bible_s = request->post ["b"];

  switch (action) {
    case Sync_Logic::settings_get_total_checksum:
    {
      return sync_logic.settings_checksum (filter_string_explode (bible_s, '\n'));
    }
    case Sync_Logic::settings_send_workbench_urls:
    {
      request->database_config_user()->setWorkbenchURLs (value);
      return "";
    }
    case Sync_Logic::settings_get_workbench_urls:
    {
      return request->database_config_user()->getWorkbenchURLs ();
    }
    case Sync_Logic::settings_send_workbench_widths:
    {
      request->database_config_user()->setWorkbenchWidths (value);
      return "";
    }
    case Sync_Logic::settings_get_workbench_widths:
    {
      return request->database_config_user()->getWorkbenchWidths ();
    }
    case Sync_Logic::settings_send_workbench_heights:
    {
      request->database_config_user()->setWorkbenchHeights (value);
      return "";
    }
    case Sync_Logic::settings_get_workbench_heights:
    {
      return request->database_config_user()->getWorkbenchHeights ();
    }
    case Sync_Logic::settings_send_resources_organization:
    {
      vector <string> resources = filter_string_explode (value, '\n');
      request->database_config_user()->setActiveResources (resources);
      return "";
    }
    case Sync_Logic::settings_get_resources_organization:
    {
      vector <string> resources = request->database_config_user()->getActiveResources ();
      return filter_string_implode (resources, "\n");
    }
    case Sync_Logic::settings_get_bible_id:
    {
      return convert_to_string (request->database_bibles()->getID (bible_s));
    }
    case Sync_Logic::settings_get_bible_font:
    {
      return Database_Config_Bible::getTextFont (bible_s);
    }
  }

  // Bad request.
  // Delay a while to obstruct a flood of bad requests.
  this_thread::sleep_for (chrono::seconds (1));
  request->response_code = 400;
  return "";
}
Example #3
0
void sendreceive_usfmresources ()
{
  mutex_sendreceive_usfmresources.lock ();
  bool bail_out = sendreceive_usfmresources_running;
  mutex_sendreceive_usfmresources.unlock ();
  if (bail_out) return;
  mutex_sendreceive_usfmresources.lock ();
  sendreceive_usfmresources_running = true;
  mutex_sendreceive_usfmresources.unlock ();
  
  
  Database_UsfmResources database_usfmresources = Database_UsfmResources ();
  Webserver_Request request;
  Sync_Logic sync_logic = Sync_Logic (&request);

  
  Database_Logs::log (sendreceive_usfmresources_sendreceive_text (), Filter_Roles::translator ());
  
 
  string address = Database_Config_General::getServerAddress ();
  int port = Database_Config_General::getServerPort ();
  string url = client_logic_url (address, port, sync_usfmresources_url ());

  
  map <string, string> post;
  string error;
  string response;
  
  
  // Request the checksum of all USFM resources from the server.
  // Compare it with the local checksum.
  // If the two match: Ready.
  post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_total_checksum);
  response = sync_logic.post (post, url, error);
  if (!error.empty ()) {
    Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting total checksum: " + error, Filter_Roles::translator ());
    sendreceive_usfmresources_done ();
    return;
  }
  string checksum = Sync_Logic::usfm_resources_checksum ();
  if (response == checksum) {
    Database_Logs::log (sendreceive_usfmresources_up_to_date_text (), Filter_Roles::translator ());
    sendreceive_usfmresources_done ();
    return;
  }
  
  
  // Request a list of all USFM resources available on the server.
  post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_resources);
  response = sync_logic.post (post, url, error);
  if (!error.empty ()) {
    Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting resources: " + error, Filter_Roles::translator ());
    sendreceive_usfmresources_done ();
    return;
  }
  vector <string> server_resources = filter_string_explode (response, '\n');
  
  
  // Delete any resource on the local client but not on the server.
  vector <string> client_resources = database_usfmresources.getResources ();
  vector <string> resources = filter_string_array_diff (client_resources, server_resources);
  for (auto & resource : resources) {
    database_usfmresources.deleteResource (resource);
  }
  
  
  // Deal with each USFM resource individually.
  for (auto & resource : server_resources) {
    
    
    // Request the checksum of the resources from the server.
    // Compare it with the checksum of the local resource.
    // If they match: Go to the next resource.
    post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_resource_checksum);
    post ["r"] = resource;
    response = sync_logic.post (post, url, error);
    if (!error.empty ()) {
      Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting checksum of resource: " + error, Filter_Roles::translator ());
      sendreceive_usfmresources_done ();
      return;
    }
    checksum = Sync_Logic::usfm_resource_checksum (resource);
    if (response == checksum) {
      continue;
    }
    

    // Request a list of all books in the resource on the server.
    post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_books);
    post ["r"] = resource;
    response = sync_logic.post (post, url, error);
    if (!error.empty ()) {
      Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting books of resource: " + error, Filter_Roles::translator ());
      sendreceive_usfmresources_done ();
      return;
    }
    vector <int> server_books;
    vector <string> sbooks = filter_string_explode (response, '\n');
    for (auto & book : sbooks) server_books.push_back (convert_to_int (book));

    
    // Delete any books from the client that are not on the server.
    vector <int> client_books = database_usfmresources.getBooks (resource);
    vector <int> books = filter_string_array_diff (client_books, server_books);
    for (auto & book : books) {
      database_usfmresources.deleteBook (resource, book);
    }
    
    
    // Deal with each book individually.
    for (auto & book : server_books) {
      
      
      // Request checksum of this book,
      // compare it with the local checksum,
      // and skip the book if the checksums match.
      post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_book_checksum);
      post ["r"] = resource;
      post ["b"] = convert_to_string (book);
      response = sync_logic.post (post, url, error);
      if (!error.empty ()) {
        Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting checksum of resource book: " + error, Filter_Roles::translator ());
        sendreceive_usfmresources_done ();
        return;
      }
      checksum = Sync_Logic::usfm_resource_book_checksum (resource, book);
      if (response == checksum) {
        continue;
      }
      
      
      string bookname = Database_Books::getEnglishFromId (book);
      Database_Logs::log (sendreceive_usfmresources_text () + "Synchronizing " + resource + " " + bookname, Filter_Roles::translator ());
      
      
      // Retrieve a list of chapters in the $book from the server.
      post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_chapters);
      post ["r"] = resource,
      post ["b"] = convert_to_string (book);
      response = sync_logic.post (post, url, error);
      if (!error.empty ()) {
        Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting chapters of resource book: " + error, Filter_Roles::translator ());
        sendreceive_usfmresources_done ();
        return;
      }
      vector <int> server_chapters;
      vector <string> schapters = filter_string_explode (response, '\n');
      for (auto & chapter : schapters) server_chapters.push_back (convert_to_int (chapter));
      
      
      // Delete local chapters not found on the server.
      vector <int> client_chapters = database_usfmresources.getChapters (resource, book);
      vector <int> chapters = filter_string_array_diff (client_chapters, server_chapters);
      for (auto & chapter : chapters) {
        database_usfmresources.deleteChapter (resource, book, chapter);
      }
      
      
      // Go through each chapter individually.
      for (auto & chapter : server_chapters) {

        
        // Get the checksum of the chapter as it is on the server.
        post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_chapter_checksum);
        post ["r"] = resource;
        post ["b"] = convert_to_string (book);
        post ["c"] = convert_to_string (chapter);
        response = sync_logic.post (post, url, error);
        if (!error.empty ()) {
          Database_Logs::log (sendreceive_usfmresources_text () + "Failure getting checksum of resource chapter: " + error, Filter_Roles::translator ());
          sendreceive_usfmresources_done ();
          return;
        }
        checksum = Sync_Logic::usfm_resource_chapter_checksum (resource, book, chapter);
        if (response == checksum) {
          continue;
        }
        
        
        // Download the chapter from the server, and store it locally on the client.
        post ["a"] = convert_to_string (Sync_Logic::usfmresources_get_chapter);
        post ["r"] = resource;
        post ["b"] = convert_to_string (book);
        post ["c"] = convert_to_string (chapter);
        response = sync_logic.post (post, url, error);
        if (!error.empty ()) {
          Database_Logs::log (sendreceive_usfmresources_text () + "Failure downloading resource chapter: " + error, Filter_Roles::translator ());
          sendreceive_usfmresources_done ();
          return;
        }
        database_usfmresources.storeChapter (resource, book, chapter, response);
      }
    }
  }

  
  // Done.
  Database_Logs::log (sendreceive_usfmresources_text () + "Now up to date", Filter_Roles::translator ());
  sendreceive_usfmresources_done ();
}
Example #4
0
void sendreceive_changes ()
{
  if (sendreceive_changes_watchdog) {
    int time = filter_date_seconds_since_epoch ();
    if (time < (sendreceive_changes_watchdog + 900)) {
      Database_Logs::log (sendreceive_changes_text () + translate("Still busy"), Filter_Roles::translator ());
      return;
    }
    Database_Logs::log (sendreceive_changes_text () + translate("Watchdog timeout"), Filter_Roles::translator ());
  }
  sendreceive_changes_kick_watchdog ();
  config_globals_syncing_changes = true;

  
  Database_Logs::log (sendreceive_changes_sendreceive_text (), Filter_Roles::translator ());
  

  Webserver_Request request;
  Sync_Logic sync_logic = Sync_Logic (&request);
  Database_Modifications database_modifications;
  
  
  if (!database_modifications.healthy ()) {
    Database_Logs::log (sendreceive_changes_text () + translate("Recreate damaged modifications database"), Filter_Roles::translator ());
    database_modifications.erase ();
    database_modifications.create ();
  }
  
  
  string response = client_logic_connection_setup ();
  int iresponse = convert_to_int (response);
  if (iresponse < Filter_Roles::guest () || iresponse > Filter_Roles::admin ()) {
    Database_Logs::log (sendreceive_changes_text () + translate("Failure to initiate connection"), Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  
  
  // Set the correct user in the session: The sole user on the Client.
  vector <string> users = request.database_users ()->getUsers ();
  if (users.empty ()) {
    Database_Logs::log (translate("No user found"), Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  string user = users [0];
  request.session_logic ()->setUsername (user);
  string password = request.database_users ()->getmd5 (user);
  
  
  // The basic request to be POSTed to the server.
  // It contains the user's credentials.
  map <string, string> post;
  post ["u"] = bin2hex (user);
  post ["p"] = password;
  post ["l"] = convert_to_string (request.database_users ()->getUserLevel (user));
  
  
  // Error variables.
  string error;
  bool communication_errors = false;
  
  
  // Server URL to call.
  string address = Database_Config_General::getServerAddress ();
  int port = Database_Config_General::getServerPort ();
  string url = client_logic_url (address, port, sync_changes_url ());
  
  
  // Send the removed change notifications to the server.
  vector <int> ids = request.database_config_user ()->getRemovedChanges ();
  if (!ids.empty ()) Database_Logs::log (sendreceive_changes_text () + "Sending removed notifications: " + convert_to_string (ids.size()), Filter_Roles::translator ());
  for (auto & id : ids) {
    post ["a"] = convert_to_string (Sync_Logic::changes_delete_modification);
    post ["i"] = convert_to_string (id);
    response = sync_logic.post (post, url, error);
    if (!error.empty ()) {
      communication_errors = true;
      Database_Logs::log (sendreceive_changes_text () + "Failure sending removed notification: " + error, Filter_Roles::translator ());
    }
    else {
      request.database_config_user ()->removeRemovedChange (id);
    }
  }
  
  
  if (communication_errors) {
    Database_Logs::log (sendreceive_changes_text () + translate("Not downloading change notifications due to communication error"), Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  
  
  // Compare the total checksum for the change notifications for the active user on client and server.
  // Checksum is cached for future re-use.
  // Take actions based on that.
  string client_checksum = request.database_config_user ()->getChangeNotificationsChecksum ();
  if (client_checksum.empty ()) {
    client_checksum = Sync_Logic::changes_checksum (user);
    request.database_config_user ()->setChangeNotificationsChecksum (client_checksum);
  }
  string server_checksum;
  post ["a"] = convert_to_string (Sync_Logic::changes_get_checksum);
  response = sync_logic.post (post, url, error);
  if (!error.empty ()) {
    Database_Logs::log (sendreceive_changes_text () + "Failure receiving checksum: " + error, Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  server_checksum = response;
  if (client_checksum == server_checksum) {
    Database_Logs::log (sendreceive_changes_up_to_date_text (), Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  
  
  // Get all identifiers for the notifications on the server for the user.
  // Get the identifiers on the client.
  vector <int> client_identifiers = database_modifications.getNotificationIdentifiers (user, false);
  vector <int> server_identifiers;
  post ["a"] = convert_to_string (Sync_Logic::changes_get_identifiers);
  response = sync_logic.post (post, url, error);
  if (!error.empty ()) {
    Database_Logs::log (sendreceive_changes_text () + "Failure receiving identifiers: " + error, Filter_Roles::translator ());
    send_receive_changes_done ();
    return;
  }
  {
    vector <string> ids = filter_string_explode (response, '\n');
    for (auto & id : ids) server_identifiers.push_back (convert_to_int (id));
  }

  
  // Any identifiers on the client, but not on the server, remove them from the client.
  vector <int> remove_identifiers = filter_string_array_diff (client_identifiers, server_identifiers);
  for (auto & id : remove_identifiers) {
    database_modifications.deleteNotification (id);
    request.database_config_user ()->setChangeNotificationsChecksum ("");
    Database_Logs::log (sendreceive_changes_text () + "Removing notification: " + convert_to_string (id), Filter_Roles::translator ());
  }

  
  // Any identifiers on the server, but not on the client, download them from the server.
  vector <int> download_identifiers = filter_string_array_diff (server_identifiers, client_identifiers);
  for (auto & id : download_identifiers) {
    sendreceive_changes_kick_watchdog ();
    Database_Logs::log (sendreceive_changes_text () + "Downloading notification: " + convert_to_string (id), Filter_Roles::translator ());
    post ["a"] = convert_to_string (Sync_Logic::changes_get_modification);
    post ["i"] = convert_to_string (id);
    response = sync_logic.post (post, url, error);
    if (!error.empty ()) {
      Database_Logs::log (sendreceive_changes_text () + "Failure downloading notification: " + error, Filter_Roles::translator ());
    }
    else {
      // The server has put all bits together, one bit per line.
      vector <string> lines = filter_string_explode (response, '\n');
      string category;
      if (!lines.empty ()) {
        category = lines [0];
        lines.erase (lines.begin ());
      }
      string bible;
      if (!lines.empty ()) {
        bible = lines [0];
        lines.erase (lines.begin ());
      }
      int book = 0;
      if (!lines.empty ()) {
        book = convert_to_int (lines [0]);
        lines.erase (lines.begin ());
      }
      int chapter = 0;
      if (!lines.empty ()) {
        chapter = convert_to_int (lines [0]);
        lines.erase (lines.begin ());
      }
      int verse = 0;
      if (!lines.empty ()) {
        verse = convert_to_int (lines [0]);
        lines.erase (lines.begin ());
      }
      string oldtext;
      if (!lines.empty ()) {
        oldtext = lines [0];
        lines.erase (lines.begin ());
      }
      string modification;
      if (!lines.empty ()) {
        modification = lines [0];
        lines.erase (lines.begin ());
      }
      string newtext;
      if (!lines.empty ()) {
        newtext = lines [0];
        lines.erase (lines.begin ());
      }
      database_modifications.storeClientNotification (id, user, category, bible, book, chapter, verse, oldtext, modification, newtext);
      request.database_config_user ()->setChangeNotificationsChecksum ("");
    }
  }
  

  // Done.
  Database_Logs::log (sendreceive_changes_text () + "Ready", Filter_Roles::translator ());
  send_receive_changes_done ();
}
Example #5
0
string sync_files (void * webserver_request)
{
  Webserver_Request * request = (Webserver_Request *) webserver_request;
  Sync_Logic sync_logic = Sync_Logic (webserver_request);

  if (!sync_logic.security_okay ()) {
    // When the Cloud enforces https, inform the client to upgrade.
    request->response_code = 426;
    return "";
  }

  // If the client's IP address very recently made a prioritized server call,
  // then delay the current call.
  // This is the way to give priority to the other call:
  // Not clogging the client's internet connection.
  if (sync_logic.prioritized_ip_address_active ()) {
    this_thread::sleep_for (chrono::seconds (5));
  }
  
  if (request->post.empty ()) {
    request->post = request->query;
  }
  string user = hex2bin (request->post ["u"]);
  int action = convert_to_int (request->post ["a"]);
  int version = convert_to_int (request->post ["v"]);
  size_t d = convert_to_int (request->post ["d"]);
  string file = request->post ["f"];

  // For security reasons a client does not specify the directory of the file to be downloaded.
  // Rather it specifies the offset within the list of allowed directories for the version.
  vector <string> directories = Sync_Logic::files_get_directories (version, user);
  if (d >= directories.size ()) {
    request->response_code = 400;
    return "";
  }
  string directory = directories [d];
  
  if (action == Sync_Logic::files_total_checksum) {
    return convert_to_string (Sync_Logic::files_get_total_checksum (version, user));
  }
  
  else if (action == Sync_Logic::files_directory_checksum) {
    int checksum = Sync_Logic::files_get_directory_checksum (directory);
    return convert_to_string (checksum);
  }

  else if (action == Sync_Logic::files_directory_files) {
    vector <string> paths = Sync_Logic::files_get_files (directory);
    return filter_string_implode (paths, "\n");
  }

  else if (action == Sync_Logic::files_file_checksum) {
    int checksum = Sync_Logic::files_get_file_checksum (directory, file);
    return convert_to_string (checksum);
  }
  
  else if (action == Sync_Logic::files_file_download) {
    // This triggers the correct mime type.
    request->get = "file.download";
    // Return the file's contents.
    string path = filter_url_create_root_path (directory, file);
    return filter_url_file_get_contents (path);
  }
  
  // Bad request. Delay flood of bad requests.
  this_thread::sleep_for (chrono::seconds (1));
  request->response_code = 400;
  return "";
}