// Start library. // Can be called multiple times during the lifetime of the app void bibledit_start_library () { // Repeating start guard. if (bibledit_started) return; bibledit_started = true; // Setup server behaviour. #ifdef HAVE_CLIENT config_globals_client_prepared = true; #else config_globals_client_prepared = false; #endif if (config_logic_demo_enabled ()) { config_globals_open_installation = true; } // Ignore SIGPIPE signal on Linux: When the browser cancels the request, it won't kill Bibledit. // On Windows, this is not needed. #ifndef HAVE_WINDOWS signal (SIGPIPE, SIG_IGN); #endif // Set running flag. config_globals_webserver_running = true; // Whether the plain http server redirects to secure http. config_globals_enforce_https_browser = config_logic_enforce_https_browser (); config_globals_enforce_https_client = config_logic_enforce_https_client (); // Run the plain web server in a thread. config_globals_http_worker = new thread (http_server); // Run the secure web server in a thread. config_globals_https_worker = new thread (https_server); // Run the timers in a thread. config_globals_timer = new thread (timer_index); // Client should sync right after wake up. sendreceive_queue_startup (); }
void timer_index () { bool client = client_logic_client_enabled (); int previous_second = -1; int previous_minute = -1; int previous_fraction = -1; while (config_globals_running) { try { // Wait shortly. this_thread::sleep_for (chrono::milliseconds (100)); // Wait tilll the data structures have been initialized. if (!config_globals_data_initialized) continue; // The current time, localized. int seconds_since_epoch = filter_date_seconds_since_epoch (); int local_seconds = filter_date_local_seconds (seconds_since_epoch); int second = filter_date_numerical_second (local_seconds); int minute = filter_date_numerical_minute (local_seconds); int hour = filter_date_numerical_hour (local_seconds); int weekday = filter_date_numerical_week_day (local_seconds); // Run once per second. if (second == previous_second) continue; previous_second = second; // Every second: Deal with queued and/or active tasks. tasks_run_check (); // Run the part below every so many seconds. int fraction = second / 5; if (fraction != previous_fraction) { previous_fraction = fraction; } // Run the part below once per minute. if (minute == previous_minute) continue; previous_minute = minute; // Every minute send out queued email, except in client mode. if (!client) tasks_logic_queue (SENDEMAIL); // Check for new mail every five minutes. // Do not check more often with gmail else the account may be shut down. if ((!client) && ((minute % 5) == 0)) { tasks_logic_queue (RECEIVEEMAIL); } // At the nineth minute after every full hour rotate the journal. // The nine is chosen, because the journal rotation will summarize the send/receive messages. // In case send/receive happens every five minutes, it is expected that under normal circumstances // the whole process of sending/receivning will be over, so summarization can then be done. if (minute == 9) tasks_logic_queue (ROTATEJOURNAL); // Client sends/receives Bibles and Consultation. sendreceive_queue_sync (minute); // Sending and receiving Bibles to and from the git repository. // On a production website running on an inexpensive virtual private server // with 512 Mbyte of memory and a fast network connection, // sending and receiving two Bibles takes more than 15 minutes when there are many changes. bool sendreceive = ((hour == 0) && (minute == 5)); bool repeat = ((minute % 5) == 0); if (sendreceive || repeat) { sendreceive_queue_all (sendreceive); } // Deal with the changes in the Bible made per user. // Deal with notifications for the daily changes in the Bibles. // This takes a few minutes on a production machine with two Bibles and changes in several chapters. // It runs in a server configuration, not on a client. if (!client) { if ((hour == 0) && (minute == 20)) { changes_logic_start (); } } // Run the checks on the Bibles. // This takes 15 minutes on a production machine with two Bibles. if (!client) { if ((hour == 0) && (minute == 30)) { checks_logic_start_all (); } } // Database maintenance and trimming. // It takes a few minutes on a production machine. if ((hour == 0) && (minute == 50)) { tasks_logic_queue (MAINTAINDATABASE); } // Export the Bibles to the various output formats. // This may take an hour on a production machine. // This hour was in PHP. In C++ it is much faster. if (!client) { if ((hour == 1) && (minute == 10)) { Export_Logic::scheduleAll (); } } // Delete temporal files older than a few days. if ((hour == 2) && (minute == 0)) { tasks_logic_queue (CLEANTMPFILES); } // Re-index Bibles and notes. // Only update missing indexes. if ((hour == 2) && (minute == 0)) { Database_State::create (); Database_Config_General::setIndexBibles (true); tasks_logic_queue (REINDEXBIBLES); Database_Config_General::setIndexNotes (true); tasks_logic_queue (REINDEXNOTES); } // Actions for a demo installation. if (minute == 10) { if (config_logic_demo_enabled ()) { tasks_logic_queue (CLEANDEMO); } } // Sprint burndown. // It runs every hour in the Cloud. // The script itself determines what to do at which hour of the day or day of the week or day of the month. if (!client) { if (minute == 5) { tasks_logic_queue (SPRINTBURNDOWN); } } // Quit at midnight if flag is set. if (config_globals_quit_at_midnight) { if (hour == 0) { if (minute == 1) { if (!Database_Config_General::getJustStarted ()) { if (tasks_run_active_count ()) { Database_Logs::log ("Server is due to restart itself but does not because of active jobs"); } else { Database_Logs::log ("Server restarts itself"); exit (0); } } } // Clear flag in preparation of restart next minute. // This flag also has the purpose of ensuring the server restarts once during that minute, // rather than restarting repeatedly many times during that minute. if (minute == 0) { Database_Config_General::setJustStarted (false); } } } // Email notes statistics to the users. if (!client) { if ((hour == 3) && (minute == 0)) { tasks_logic_queue (NOTESSTATISTICS); } } // Update SWORD stuff once a week. if (weekday == 1) { // Refresh module list. if ((!client) && (hour == 3) && (minute == 5)) { tasks_logic_queue (REFRESHSWORDMODULES); } // Update installed SWORD modules, shortly after the module list has been refreshed. if ((!client) && (hour == 3) && (minute == 15)) { tasks_logic_queue (UPDATESWORDMODULES); } } // The Cloud updates the list of USFM resources once a week. if (weekday == 1) { if ((!client) && (hour == 3) && (minute == 10)) { tasks_logic_queue (LISTUSFMRESOURCES); } } } catch (exception & e) { Database_Logs::log (e.what ()); } catch (exception * e) { Database_Logs::log (e->what ()); } catch (...) { Database_Logs::log ("A general internal error occurred in the timers"); } } }
string bible_manage (void * webserver_request) { Webserver_Request * request = (Webserver_Request *) webserver_request; string page; Assets_Header header = Assets_Header (translate("Bibles"), webserver_request); header.addBreadCrumb (menu_logic_settings_menu (), menu_logic_settings_text ()); page = header.run (); Assets_View view; string success_message; string error_message; // New Bible handler. if (request->query.count ("new")) { Dialog_Entry dialog_entry = Dialog_Entry ("manage", translate("Please enter a name for the new empty Bible"), "", "new", ""); page += dialog_entry.run (); return page; } if (request->post.count ("new")) { string bible = request->post ["entry"]; vector <string> bibles = request->database_bibles ()->getBibles (); if (find (bibles.begin(), bibles.end(), bible) != bibles.end()) { error_message = translate("This Bible already exists"); } else { request->database_bibles ()->createBible (bible); // Check / grant access. if (!access_bible_write (request, bible)) { string me = request->session_logic ()->currentUser (); Database_Privileges::setBible (me, bible, true); } success_message = translate("The Bible was created"); // Creating a Bible removes any Sample Bible that might have been there. if (!config_logic_demo_enabled ()) { request->database_bibles ()->deleteBible (demo_sample_bible_name ()); search_logic_delete_bible (demo_sample_bible_name ()); } } } // Copy Bible handler. if (request->query.count ("copy")) { string copy = request->query["copy"]; Dialog_Entry dialog_entry = Dialog_Entry ("manage", translate("Please enter a name for where to copy the Bible to"), "", "", "A new Bible will be created with the given name, and the current Bible copied to it"); dialog_entry.add_query ("origin", copy); page += dialog_entry.run (); return page; } if (request->query.count ("origin")) { string origin = request->query["origin"]; if (request->post.count ("entry")) { string destination = request->post["entry"]; vector <string> bibles = request->database_bibles ()->getBibles (); if (find (bibles.begin(), bibles.end(), destination) != bibles.end()) { error_message = translate("Cannot copy the Bible because the destination Bible already exists."); } else { // User needs read access to the original. if (access_bible_read (request, origin)) { request->database_bibles ()->createBible (destination); vector <int> books = request->database_bibles ()->getBooks (origin); for (auto & book : books) { vector <int> chapters = request->database_bibles ()->getChapters (origin, book); for (auto & chapter : chapters) { string data = request->database_bibles ()->getChapter (origin, book, chapter); Bible_Logic::storeChapter (destination, book, chapter, data); } } success_message = translate("The Bible was copied."); // Check / grant access to destination Bible. if (!access_bible_write (request, destination)) { string me = request->session_logic ()->currentUser (); Database_Privileges::setBible (me, destination, true); } // Creating a Bible removes any Sample Bible that might have been there. if (!config_logic_demo_enabled ()) { request->database_bibles ()->deleteBible (demo_sample_bible_name ()); search_logic_delete_bible (demo_sample_bible_name ()); } } } } } // Delete Bible handler. if (request->query.count ("delete")) { string bible = request->query ["delete"]; string confirm = request->query ["confirm"]; if (confirm == "yes") { // User needs write access for delete operation. if (access_bible_write (request, bible)) { Bible_Logic::deleteBible (bible); string gitdirectory = filter_git_directory (bible); if (file_exists (gitdirectory)) { filter_url_rmdir (gitdirectory); } // Remove associated settings and privileges. Database_Privileges::removeBible (bible); Database_Config_Bible::remove (bible); } else { page += Assets_Page::error ("Insufficient privileges to complete action"); } } if (confirm == "") { Dialog_Yes dialog_yes = Dialog_Yes ("manage", translate("Would you like to delete this Bible?") + " (" + bible + ")"); dialog_yes.add_query ("delete", bible); page += dialog_yes.run (); return page; } } view.set_variable ("success_message", success_message); view.set_variable ("error_message", error_message); vector <string> bibles = access_bible_bibles (request); string bibleblock; for (auto & bible : bibles) { bibleblock.append ("<li><a href=\"settings?bible=" + bible + "\">" + bible + "</a></li>\n"); } view.set_variable ("bibleblock", bibleblock); if (!client_logic_client_enabled ()) view.enable_zone ("server"); page += view.render ("bible", "manage"); page += Assets_Page::footer (); return page; }