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