File: bibles.cpp

package info (click to toggle)
bibledit-cloud 5.1.036-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 250,636 kB
  • sloc: xml: 915,934; ansic: 261,349; cpp: 92,628; javascript: 32,542; sh: 4,915; makefile: 586; php: 69
file content (245 lines) | stat: -rw-r--r-- 10,328 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
 Copyright (©) 2003-2025 Teus Benschop.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <sync/bibles.h>
#include <filter/url.h>
#include <filter/roles.h>
#include <filter/string.h>
#include <filter/merge.h>
#include <tasks/logic.h>
#include <database/config/general.h>
#include <database/config/bible.h>
#include <database/logs.h>
#include <database/bibles.h>
#include <database/books.h>
#include <database/mail.h>
#include <database/modifications.h>
#include <database/git.h>
#include <client/logic.h>
#include <locale/translate.h>
#include <webserver/request.h>
#include <sync/logic.h>
#include <checksum/logic.h>
#include <access/bible.h>
#include <bb/logic.h>
#include <rss/logic.h>
#include <sendreceive/logic.h>


std::string sync_bibles_url ()
{
  return "sync/bibles";
}


std::string sync_bibles_receive_chapter (Webserver_Request& webserver_request, std::string & bible, int book, int chapter)
{
  // Convert the tags to plus signs, which the client had converted to tags,
  // for safekeeping the + signs during transit.
  std::string oldusfm = filter_url_tag_to_plus (webserver_request.post_get("o"));
  std::string newusfm = filter_url_tag_to_plus (webserver_request.post_get("n"));
  std::string checksum = webserver_request.post_get("s");

  
  const std::string& username = webserver_request.session_logic ()->get_username ();
  std::string bookname = database::books::get_english_from_id (static_cast<book_id>(book));
  
  
  // Check whether the user has write-access to the Bible book.
  if (!access_bible::book_write (webserver_request, username, bible, book)) {
    std::string message = "User " + username + " does not have write access to Bible " + bible;
    Database_Logs::log (message, roles::manager);
    // The Cloud will email the user with details about the issue.
    bible_logic::client_no_write_access_mail (bible, book, chapter, username, oldusfm, newusfm);
    // The Cloud returns the checksum so the client things the chapter was send off correcly,
    // and will not re-schedule this as a failure.
    return checksum;
  }
  
  
  // Check checksum.
  if (checksum != checksum_logic::get (oldusfm + newusfm)) {
    std::string message = "The received data is corrupted";
    Database_Logs::log (message, roles::manager);
    return message;
  }
  
  
  std::string serverusfm = database::bibles::get_chapter (bible, book, chapter);
  
  
  // There have been cases like this:
  // The Bibledit client sends USFM with double spaces in them to the Cloud.
  // Then the Cloud would add one one more space.
  // If this chapter was edited and sent multiple times,
  // the result would be that each time extra spaces kept being added.
  // Solution / fix to this:
  // Remove all the double spaces from the USFM to be saved.
  // (Keep the reference USFM as it is: Goal: Detect the differences.
  newusfm = filter::strings::collapse_whitespace (newusfm);
  

  // Gather data for recording the changes made by the user, for the change notifications.
  int old_id = database::bibles::get_chapter_id (bible, book, chapter);
  std::string old_text = serverusfm;
  std::string new_text = newusfm;
  
  
  // Server logs the change from the client.
  bible_logic::log_change (bible, book, chapter, newusfm, username, translate ("Received from client"), false);

  
  if (serverusfm == "") {
    // If the chapter on the server is still empty, then just store the client's version on the server, and that's it.
    bible_logic::store_chapter (bible, book, chapter, newusfm);
  } else if (newusfm != serverusfm) {
    // Do a merge in case the client sends USFM that differs from what's on the server.
    std::vector <Merge_Conflict> conflicts;
    std::string mergedusfm = filter_merge_run (oldusfm, newusfm, serverusfm, true, conflicts);
    // Update the server with the new chapter data.
    bible_logic::store_chapter (bible, book, chapter, mergedusfm);
    // Check on the merge.
    filter_merge_add_book_chapter (conflicts, book, chapter);
    bible_logic::client_receive_merge_mail (bible, book, chapter, username, oldusfm, newusfm, mergedusfm);
    // Log the merge in the journal, for possible trouble shooting.
    bible_logic::log_merge (username, bible, book, chapter, oldusfm, newusfm, serverusfm, mergedusfm);
  }
  

  // If text was saved, record it as a change entered by the user.
  int new_id = database::bibles::get_chapter_id (bible, book, chapter);
  if (new_id != old_id) {
    database::modifications::recordUserSave (username, bible, book, chapter, old_id, old_text, new_id, new_text);
#ifdef HAVE_CLOUD
    if (sendreceive_git_repository_linked (bible)) {
      database::git::store_chapter (username, bible, book, chapter, old_text, new_text);
    }
    rss_logic_schedule_update (username, bible, book, chapter, old_text, new_text);
#endif
  }


  // Done: Return the checksum to client.
  // Returning the checksum serves as a confirmation to the client that the Cloud properly received the chapter.
  // When the client works through a sub-standard network, this checksumming is getting very important.
  // Back in the time that the Cloud didn't return a checksum, there were cases of data loss on clients,
  // due to a poor network connection.
  return checksum;
}


std::string sync_bibles (Webserver_Request& webserver_request)
{
  Sync_Logic sync_logic (webserver_request);

  if (!sync_logic.security_okay ()) {
    // When the Cloud enforces https, inform the client to upgrade.
    webserver_request.response_code = 426;
    return std::string();
  }
  
  // Check on the credentials.
  if (!sync_logic.credentials_okay ()) return std::string();
  
  // 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.
  int action = filter::strings::convert_to_int (webserver_request.post_get("a"));
  std::string bible = webserver_request.post_get("b");
  int book = filter::strings::convert_to_int (webserver_request.post_get("bk"));
  int chapter = filter::strings::convert_to_int (webserver_request.post_get("c"));
  
  switch (action) {
    case Sync_Logic::bibles_get_total_checksum:
    {
      // The server reads the credentials from the client's user,
      // checks which Bibles this user has access to,
      // calculate the checksum of all chapters in those Bibles,
      // and returns this checksum to the client.
      const std::string& username = webserver_request.session_logic ()->get_username ();
      std::vector <std::string> bibles = access_bible::bibles (webserver_request, username);
      std::string server_checksum = checksum_logic::get_bibles (bibles);
      return server_checksum;
    }
    case Sync_Logic::bibles_get_bibles:
    {
      // The server reads the credentials from the client's user,
      // and responds with a list of Bibles this user has access to.
      const std::string& username = webserver_request.session_logic ()->get_username ();
      std::vector <std::string> bibles = access_bible::bibles (webserver_request, username);
      std::string checksum = checksum_logic::get (bibles);
      std::string s_bibles = filter::strings::implode (bibles, "\n");
      return checksum + "\n" + s_bibles;
    }
    case Sync_Logic::bibles_get_bible_checksum:
    {
      // The server responds with the checksum for the whole Bible.
      return checksum_logic::get_bible (bible);
    }
    case Sync_Logic::bibles_get_books:
    {
      // The server responds with a checksum and then the list of books in the Bible.
      std::vector <int> server_books = database::bibles::get_books (bible);
      std::vector <std::string> v_server_books;
      for (auto server_book : server_books) v_server_books.push_back (std::to_string (server_book));
      std::string s_server_books = filter::strings::implode (v_server_books, "\n");
      std::string checksum = checksum_logic::get (v_server_books);
      return checksum + "\n" + s_server_books;
    }
    case Sync_Logic::bibles_get_book_checksum:
    {
      // The server responds with the checksum of the whole book.
      return checksum_logic::get_book (bible, book);
    }
    case Sync_Logic::bibles_get_chapters:
    {
      // The server responds with the list of books in the Bible book.
      std::vector <int> server_chapters = database::bibles::get_chapters (bible, book);
      std::vector <std::string> v_server_chapters;
      for (auto & server_chapter : server_chapters) v_server_chapters.push_back (std::to_string (server_chapter));
      std::string s_server_chapters = filter::strings::implode (v_server_chapters, "\n");
      std::string checksum = checksum_logic::get (v_server_chapters);
      return checksum + "\n" + s_server_chapters;
    }
    case Sync_Logic::bibles_get_chapter_checksum:
    {
      // The server responds with the checksum of the whole chapter.
      return checksum_logic::get_chapter (bible, book, chapter);
    }
    case Sync_Logic::bibles_send_chapter:
    {
      return sync_bibles_receive_chapter (webserver_request, bible, book, chapter);
    }
    case Sync_Logic::bibles_get_chapter:
    {
      // The server responds with the USFM of the chapter, prefixed by a checksum.
      std::string usfm = filter::strings::trim (database::bibles::get_chapter (bible, book, chapter));
      std::string checksum = checksum_logic::get (usfm);
      return checksum + "\n" + usfm;
    }
    default: {};
  }
  
  // Bad request.
  // Delay a while to obstruct a flood of bad requests.
  std::this_thread::sleep_for (std::chrono::seconds (1));
  webserver_request.response_code = 400;
  return std::string();
}