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 ""; }
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 ""; }
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 (); }
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 (); }
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 ""; }