1 /*
2 * gnote
3 *
4 * Copyright (C) 2010-2014,2016-2017,2019-2020 Aurimas Cernius
5 * Copyright (C) 2009 Hubert Figuiere
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #include <glibmm/i18n.h>
23 #include <glibmm/miscutils.h>
24
25 #include "debug.hpp"
26 #include "ignote.hpp"
27 #include "notemanagerbase.hpp"
28 #include "utils.hpp"
29 #include "trie.hpp"
30 #include "notebooks/notebookmanager.hpp"
31 #include "sharp/directory.hpp"
32 #include "sharp/files.hpp"
33 #include "sharp/string.hpp"
34 #include "sharp/uuid.hpp"
35
36
37 namespace gnote {
38
compare_dates(const NoteBase::Ptr & a,const NoteBase::Ptr & b)39 bool compare_dates(const NoteBase::Ptr & a, const NoteBase::Ptr & b)
40 {
41 return (std::static_pointer_cast<Note>(a)->change_date() > std::static_pointer_cast<Note>(b)->change_date());
42 }
43
44
45 class TrieController
46 {
47 public:
48 TrieController(NoteManagerBase &);
49 ~TrieController();
50
51 void add_note(const NoteBase::Ptr & note);
52 void update();
title_trie() const53 TrieTree<NoteBase::WeakPtr> *title_trie() const
54 {
55 return m_title_trie;
56 }
57 private:
58 void on_note_added(const NoteBase::Ptr & added);
59 void on_note_deleted (const NoteBase::Ptr & deleted);
60 void on_note_renamed(const NoteBase::Ptr & renamed, const Glib::ustring & old_title);
61
62 NoteManagerBase & m_manager;
63 TrieTree<NoteBase::WeakPtr> *m_title_trie;
64 };
65
66
67
sanitize_xml_content(const Glib::ustring & xml_content)68 Glib::ustring NoteManagerBase::sanitize_xml_content(const Glib::ustring & xml_content)
69 {
70 Glib::ustring::size_type pos = xml_content.find('\n');
71 int i = (pos == Glib::ustring::npos) ? -1 : pos;
72 Glib::ustring result(xml_content);
73
74 while(--i >= 0) {
75 if(xml_content[i] == '\r') {
76 continue;
77 }
78
79 if(std::isspace(result[i])) {
80 result.erase(i, 1);
81 }
82 else {
83 break;
84 }
85 }
86
87 return result;
88 }
89
90
NoteManagerBase(IGnote & g)91 NoteManagerBase::NoteManagerBase(IGnote & g)
92 : m_gnote(g)
93 , m_trie_controller(NULL)
94 {
95 }
96
~NoteManagerBase()97 NoteManagerBase::~NoteManagerBase()
98 {
99 if(m_trie_controller) {
100 delete m_trie_controller;
101 }
102 }
103
init(const Glib::ustring & directory,const Glib::ustring & backup_directory)104 bool NoteManagerBase::init(const Glib::ustring & directory, const Glib::ustring & backup_directory)
105 {
106 m_notes_dir = directory;
107 m_default_note_template_title = _("New Note Template");
108 m_backup_dir = backup_directory;
109 bool is_first_run = first_run();
110
111 const Glib::ustring old_note_dir = IGnote::old_note_dir();
112 const bool migration_needed = is_first_run && sharp::directory_exists(old_note_dir);
113 create_notes_dir();
114
115 if(migration_needed) {
116 try {
117 migrate_notes(old_note_dir);
118 }
119 catch(Glib::Exception & e) {
120 ERR_OUT("Migration failed! Exception: %s", e.what().c_str());
121 }
122 is_first_run = false;
123 }
124
125 m_trie_controller = create_trie_controller();
126 return is_first_run;
127 }
128
first_run() const129 bool NoteManagerBase::first_run() const
130 {
131 return !sharp::directory_exists(notes_dir());
132 }
133
134 // Create the notes directory if it doesn't exist yet.
create_notes_dir() const135 void NoteManagerBase::create_notes_dir() const
136 {
137 if(!sharp::directory_exists(notes_dir())) {
138 // First run. Create storage directory.
139 create_directory(notes_dir());
140 }
141 if(!sharp::directory_exists(m_backup_dir)) {
142 create_directory(m_backup_dir);
143 }
144 }
145
create_directory(const Glib::ustring & directory) const146 bool NoteManagerBase::create_directory(const Glib::ustring & directory) const
147 {
148 return g_mkdir_with_parents(directory.c_str(), S_IRWXU) == 0;
149 }
150
migrate_notes(const Glib::ustring &)151 void NoteManagerBase::migrate_notes(const Glib::ustring & /*old_note_dir*/)
152 {
153 }
154
155 // Create the TrieController. For overriding in test methods.
create_trie_controller()156 TrieController *NoteManagerBase::create_trie_controller()
157 {
158 return new TrieController(*this);
159 }
160
post_load()161 void NoteManagerBase::post_load()
162 {
163 std::sort(m_notes.begin(), m_notes.end(), compare_dates);
164
165 // Update the trie so addins can access it, if they want.
166 m_trie_controller->update ();
167 }
168
trie_max_length()169 size_t NoteManagerBase::trie_max_length()
170 {
171 return m_trie_controller->title_trie()->max_length();
172 }
173
find_trie_matches(const Glib::ustring & match)174 TrieHit<NoteBase::WeakPtr>::ListPtr NoteManagerBase::find_trie_matches(const Glib::ustring & match)
175 {
176 return m_trie_controller->title_trie()->find_matches(match);
177 }
178
get_notes_linking_to(const Glib::ustring & title) const179 NoteBase::List NoteManagerBase::get_notes_linking_to(const Glib::ustring & title) const
180 {
181 Glib::ustring tag = "<link:internal>" + utils::XmlEncoder::encode(title) + "</link:internal>";
182 NoteBase::List result;
183 for(const NoteBase::Ptr & note : m_notes) {
184 if(note->get_title() != title) {
185 if(note->get_complete_note_xml().find(tag) != Glib::ustring::npos) {
186 result.push_back(note);
187 }
188 }
189 }
190 return result;
191 }
192
add_note(NoteBase::Ptr note)193 void NoteManagerBase::add_note(NoteBase::Ptr note)
194 {
195 if(note) {
196 note->signal_renamed.connect(sigc::mem_fun(*this, &NoteManagerBase::on_note_rename));
197 note->signal_saved.connect(sigc::mem_fun(*this, &NoteManagerBase::on_note_save));
198 m_notes.push_back(std::move(note));
199 }
200 }
201
on_note_rename(const NoteBase::Ptr & note,const Glib::ustring & old_title)202 void NoteManagerBase::on_note_rename(const NoteBase::Ptr & note, const Glib::ustring & old_title)
203 {
204 signal_note_renamed(note, old_title);
205 std::sort(m_notes.begin(), m_notes.end(), compare_dates);
206 }
207
on_note_save(const NoteBase::Ptr & note)208 void NoteManagerBase::on_note_save (const NoteBase::Ptr & note)
209 {
210 signal_note_saved(note);
211 std::sort(m_notes.begin(), m_notes.end(), compare_dates);
212 }
213
find(const Glib::ustring & linked_title) const214 NoteBase::Ptr NoteManagerBase::find(const Glib::ustring & linked_title) const
215 {
216 for(const NoteBase::Ptr & note : m_notes) {
217 if(note->get_title().lowercase() == linked_title.lowercase()) {
218 return note;
219 }
220 }
221 return NoteBase::Ptr();
222 }
223
find_by_uri(const Glib::ustring & uri) const224 NoteBase::Ptr NoteManagerBase::find_by_uri(const Glib::ustring & uri) const
225 {
226 for(const NoteBase::Ptr & note : m_notes) {
227 if (note->uri() == uri) {
228 return note;
229 }
230 }
231 return NoteBase::Ptr();
232 }
233
create_note_from_template(const Glib::ustring & title,const NoteBase::Ptr & template_note)234 NoteBase::Ptr NoteManagerBase::create_note_from_template(const Glib::ustring & title, const NoteBase::Ptr & template_note)
235 {
236 return create_note_from_template(title, template_note, "");
237 }
238
create()239 NoteBase::Ptr NoteManagerBase::create()
240 {
241 return create_note("", "");
242 }
243
create(const Glib::ustring & title)244 NoteBase::Ptr NoteManagerBase::create(const Glib::ustring & title)
245 {
246 Glib::ustring body;
247 auto note_title = split_title_from_content(title, body);
248 return create_note(note_title, body);
249 }
250
create(const Glib::ustring & title,const Glib::ustring & xml_content)251 NoteBase::Ptr NoteManagerBase::create(const Glib::ustring & title, const Glib::ustring & xml_content)
252 {
253 return create_new_note(title, xml_content, "");
254 }
255
256 // Creates a new note with the given title and guid with body based on
257 // the template note.
create_note_from_template(const Glib::ustring & title,const NoteBase::Ptr & template_note,const Glib::ustring & guid)258 NoteBase::Ptr NoteManagerBase::create_note_from_template(const Glib::ustring & title,
259 const NoteBase::Ptr & template_note,
260 const Glib::ustring & guid)
261 {
262 Glib::ustring new_title(title);
263 Tag::Ptr template_save_title = tag_manager().get_or_create_system_tag(ITagManager::TEMPLATE_NOTE_SAVE_TITLE_SYSTEM_TAG);
264 if(template_note->contains_tag(template_save_title)) {
265 new_title = get_unique_name(template_note->get_title());
266 }
267
268 // Use the body from the template note
269 Glib::ustring xml_content = sharp::string_replace_first(template_note->xml_content(),
270 utils::XmlEncoder::encode(template_note->get_title()),
271 utils::XmlEncoder::encode(new_title));
272 xml_content = sanitize_xml_content(xml_content);
273
274 NoteBase::Ptr new_note = create_new_note(new_title, xml_content, guid);
275
276 // Copy template note's properties
277 Tag::Ptr template_save_size = tag_manager().get_or_create_system_tag(ITagManager::TEMPLATE_NOTE_SAVE_SIZE_SYSTEM_TAG);
278 if(template_note->data().has_extent() && template_note->contains_tag(template_save_size)) {
279 new_note->data().height() = template_note->data().height();
280 new_note->data().width() = template_note->data().width();
281 }
282
283 return new_note;
284 }
285
286 // Find a title that does not exist using basename
get_unique_name(const Glib::ustring & basename) const287 Glib::ustring NoteManagerBase::get_unique_name(const Glib::ustring & basename) const
288 {
289 int id = 1; // starting point
290 Glib::ustring title;
291 while(true) {
292 title = Glib::ustring::compose("%1 %2", basename, id++);
293 if(!find (title)) {
294 break;
295 }
296 }
297
298 return title;
299 }
300
create_note(Glib::ustring title,Glib::ustring body,const Glib::ustring & guid)301 NoteBase::Ptr NoteManagerBase::create_note(Glib::ustring title, Glib::ustring body, const Glib::ustring & guid)
302 {
303 if(title.empty()) {
304 title = get_unique_name(_("New Note"));
305 }
306
307 Glib::ustring content;
308 if(body.empty()) {
309 auto template_note = find_template_note();
310 if(template_note) {
311 return create_note_from_template(title, template_note, guid);
312 }
313
314 // Use a simple "Describe..." body and highlight
315 // it so it can be easily overwritten
316 content = get_note_template_content(title);
317 }
318 else {
319 content = get_note_content(title, body);
320 }
321
322 return create_new_note(title, content, guid);
323 }
324
325 // Create a new note with the specified Xml content
create_new_note(const Glib::ustring & title,const Glib::ustring & xml_content,const Glib::ustring & guid)326 NoteBase::Ptr NoteManagerBase::create_new_note(const Glib::ustring & title, const Glib::ustring & xml_content,
327 const Glib::ustring & guid)
328 {
329 if(title.empty())
330 throw sharp::Exception("Invalid title");
331
332 if(find(title))
333 throw sharp::Exception("A note with this title already exists: " + title);
334
335 Glib::ustring filename;
336 if(!guid.empty())
337 filename = make_new_file_name(guid);
338 else
339 filename = make_new_file_name();
340
341 NoteBase::Ptr new_note = note_create_new(title, filename);
342 if(new_note == 0) {
343 throw sharp::Exception("Failed to create new note");
344 }
345 new_note->set_xml_content(xml_content);
346 new_note->signal_renamed.connect(sigc::mem_fun(*this, &NoteManagerBase::on_note_rename));
347 new_note->signal_saved.connect(sigc::mem_fun(*this, &NoteManagerBase::on_note_save));
348
349 m_notes.push_back(new_note);
350
351 signal_note_added(new_note);
352
353 return new_note;
354 }
355
get_note_template_content(const Glib::ustring & title)356 Glib::ustring NoteManagerBase::get_note_template_content(const Glib::ustring & title)
357 {
358 return get_note_content(title, _("Describe your new note here."));
359 }
360
get_note_content(const Glib::ustring & title,const Glib::ustring & body)361 Glib::ustring NoteManagerBase::get_note_content(const Glib::ustring & title, const Glib::ustring & body)
362 {
363 return Glib::ustring::compose("<note-content>"
364 "<note-title>%1</note-title>\n\n"
365 "%2"
366 "</note-content>",
367 utils::XmlEncoder::encode(title),
368 utils::XmlEncoder::encode(body));
369 }
370
get_or_create_template_note()371 NoteBase::Ptr NoteManagerBase::get_or_create_template_note()
372 {
373 NoteBase::Ptr template_note = find_template_note();
374 if(!template_note) {
375 Glib::ustring title = m_default_note_template_title;
376 if(find(title)) {
377 title = get_unique_name(title);
378 }
379 template_note = create(title, get_note_template_content(title));
380 if(template_note == 0) {
381 throw sharp::Exception("Failed to create template note");
382 }
383
384 // Flag this as a template note
385 Tag::Ptr template_tag = tag_manager().get_or_create_system_tag(ITagManager::TEMPLATE_NOTE_SYSTEM_TAG);
386 template_note->add_tag(template_tag);
387
388 template_note->queue_save(CONTENT_CHANGED);
389 }
390
391 return template_note;
392 }
393
split_title_from_content(Glib::ustring title,Glib::ustring & body)394 Glib::ustring NoteManagerBase::split_title_from_content(Glib::ustring title, Glib::ustring & body)
395 {
396 body = "";
397
398 if(title.empty())
399 return "";
400
401 title = sharp::string_trim(title);
402 if(title.empty())
403 return "";
404
405 std::vector<Glib::ustring> lines;
406 sharp::string_split(lines, title, "\n\r");
407 if(lines.size() > 0) {
408 title = lines [0];
409 title = sharp::string_trim(title);
410 title = sharp::string_trim(title, ".,;");
411 if(title.empty())
412 return "";
413 }
414
415 if(lines.size() > 1)
416 body = lines [1];
417
418 return title;
419 }
420
make_new_file_name() const421 Glib::ustring NoteManagerBase::make_new_file_name() const
422 {
423 return make_new_file_name(sharp::uuid().string());
424 }
425
make_new_file_name(const Glib::ustring & guid) const426 Glib::ustring NoteManagerBase::make_new_file_name(const Glib::ustring & guid) const
427 {
428 return Glib::build_filename(notes_dir(), guid + ".note");
429 }
430
find_template_note() const431 NoteBase::Ptr NoteManagerBase::find_template_note() const
432 {
433 NoteBase::Ptr template_note;
434 Tag::Ptr template_tag = tag_manager().get_system_tag(ITagManager::TEMPLATE_NOTE_SYSTEM_TAG);
435 if(!template_tag) {
436 return template_note;
437 }
438 auto notes = template_tag->get_notes();
439 for(NoteBase *iter : notes) {
440 NoteBase::Ptr note = iter->shared_from_this();
441 if(!m_gnote.notebook_manager().get_notebook_from_note(note)) {
442 template_note = note;
443 break;
444 }
445 }
446
447 return template_note;
448 }
449
delete_note(const NoteBase::Ptr & note)450 void NoteManagerBase::delete_note(const NoteBase::Ptr & note)
451 {
452 if(sharp::file_exists(note->file_path())) {
453 if(!m_backup_dir.empty()) {
454 if(!sharp::directory_exists(m_backup_dir)) {
455 sharp::directory_create(m_backup_dir);
456 }
457 Glib::ustring backup_path
458 = Glib::build_filename(m_backup_dir, sharp::file_filename(note->file_path()));
459
460 if(sharp::file_exists(backup_path)) {
461 sharp::file_delete(backup_path);
462 }
463
464 sharp::file_move(note->file_path(), backup_path);
465 }
466 else {
467 sharp::file_delete(note->file_path());
468 }
469 }
470
471 for(auto iter = m_notes.begin(); iter != m_notes.end(); ++iter) {
472 if(*iter == note) {
473 m_notes.erase(iter);
474 break;
475 }
476 }
477 note->delete_note();
478
479 DBG_OUT("Deleting note '%s'.", note->get_title().c_str());
480
481 signal_note_deleted(note);
482 }
483
import_note(const Glib::ustring & file_path)484 NoteBase::Ptr NoteManagerBase::import_note(const Glib::ustring & file_path)
485 {
486 Glib::ustring dest_file = Glib::build_filename(notes_dir(),
487 sharp::file_filename(file_path));
488
489 if(sharp::file_exists(dest_file)) {
490 dest_file = make_new_file_name();
491 }
492 NoteBase::Ptr note;
493 try {
494 sharp::file_copy(file_path, dest_file);
495
496 // TODO: make sure the title IS unique.
497 note = note_load(dest_file);
498 add_note(note);
499 }
500 catch(...)
501 {
502 }
503 return note;
504 }
505
506
create_with_guid(const Glib::ustring & title,const Glib::ustring & guid)507 NoteBase::Ptr NoteManagerBase::create_with_guid(const Glib::ustring & title, const Glib::ustring & guid)
508 {
509 Glib::ustring body;
510 auto note_title = split_title_from_content(title, body);
511 return create_note(note_title, body, guid);
512 }
513
514
515
TrieController(NoteManagerBase & manager)516 TrieController::TrieController(NoteManagerBase & manager)
517 : m_manager(manager)
518 , m_title_trie(NULL)
519 {
520 m_manager.signal_note_deleted.connect(sigc::mem_fun(*this, &TrieController::on_note_deleted));
521 m_manager.signal_note_added.connect(sigc::mem_fun(*this, &TrieController::on_note_added));
522 m_manager.signal_note_renamed.connect(sigc::mem_fun(*this, &TrieController::on_note_renamed));
523
524 update();
525 }
526
~TrieController()527 TrieController::~TrieController()
528 {
529 delete m_title_trie;
530 }
531
on_note_added(const NoteBase::Ptr & note)532 void TrieController::on_note_added(const NoteBase::Ptr & note)
533 {
534 add_note(note);
535 }
536
on_note_deleted(const NoteBase::Ptr &)537 void TrieController::on_note_deleted(const NoteBase::Ptr &)
538 {
539 update();
540 }
541
on_note_renamed(const NoteBase::Ptr &,const Glib::ustring &)542 void TrieController::on_note_renamed(const NoteBase::Ptr &, const Glib::ustring &)
543 {
544 update();
545 }
546
add_note(const NoteBase::Ptr & note)547 void TrieController::add_note(const NoteBase::Ptr & note)
548 {
549 m_title_trie->add_keyword(note->get_title(), note);
550 m_title_trie->compute_failure_graph();
551 }
552
update()553 void TrieController::update()
554 {
555 if(m_title_trie) {
556 delete m_title_trie;
557 }
558 m_title_trie = new TrieTree<NoteBase::WeakPtr>(false /* !case_sensitive */);
559
560 for(const NoteBase::Ptr & note : m_manager.get_notes()) {
561 m_title_trie->add_keyword(note->get_title(), note);
562 }
563 m_title_trie->compute_failure_graph();
564 }
565
566
567 }
568
569