Hotplug partition manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

530 lines
16 KiB

  1. /*
  2. * blk_tree.cpp
  3. * Copyright (C) 2021 Aitor Cuadrado Zubizarreta <aitor_czr@gnuinos.org>
  4. *
  5. * This program is free software: you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License as published by the
  7. * Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. * See the GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. * See the LICENSE file. *
  19. */
  20. #include "blk_tree.h"
  21. #include "hopman_ui.h"
  22. #include "fpipe.h"
  23. #include "eject_dialog.h"
  24. #include "svr.h"
  25. namespace Hopman {
  26. BlkTree::BlkTree(const Hopman::Annex& annex)
  27. : m_WorkerThread (nullptr),
  28. display_menu (false),
  29. m_shall_quit (true)
  30. {
  31. m_tooltip_window = new Gtk::Window(Gtk::WINDOW_POPUP);
  32. m_tooltip_window->set_default_size(0,0);
  33. set_tooltip_window(*m_tooltip_window);
  34. m_tooltip_window->add(m_Label);
  35. m_tooltip_window->show_all_children();
  36. get_selection()->set_mode( Gtk::SELECTION_SINGLE );
  37. set_hover_selection (true);
  38. set_sensitive (true);
  39. property_has_tooltip();
  40. add_events(Gdk::BUTTON_PRESS_MASK);
  41. #if GTK_MAJOR_VERSION == 3
  42. set_activate_on_single_click( true );
  43. #endif
  44. set_headers_visible (false);
  45. set_enable_tree_lines (true);
  46. //set_grid_lines(Gtk::TREE_VIEW_GRID_LINES_BOTH);
  47. m_refTreeModel = Gtk::TreeStore::create( m_Columns );
  48. set_model( m_refTreeModel );
  49. append_column( "DEVICE", m_Columns.device );
  50. append_column( "FSTYPE", m_Columns.fstype );
  51. append_column( "SIZE", m_Columns.size );
  52. append_column( "ICON", m_Columns.icon );
  53. append_column( "VENDOR", m_Columns.vendor );
  54. m_Menu = new cMenu(this);
  55. selected.clear();
  56. show_all_children();
  57. signal_query_tooltip().connect
  58. (
  59. sigc::mem_fun(*this, &BlkTree::on_query_tooltip),
  60. false
  61. );
  62. signal_button_press_event().connect
  63. (
  64. sigc::mem_fun(*this, &BlkTree::on_button_press_event),
  65. false
  66. );
  67. m_Dispatcher.connect
  68. (
  69. sigc::bind<const Hopman::Annex&>
  70. (
  71. sigc::mem_fun(*this, &BlkTree::get_info_from_worker_thread),
  72. annex
  73. )
  74. );
  75. // Start a new worker thread.
  76. if (!m_WorkerThread)
  77. {
  78. m_WorkerThread = new thread (
  79. [ this, annex ]
  80. {
  81. m_Worker.do_work(this, annex);
  82. }
  83. );
  84. }
  85. set_can_default();
  86. show_all_children();
  87. grab_focus();
  88. this->ejected_icon = annex.ejected_icon;
  89. this->socket = annex.socket;
  90. this->path_to_the_helper = annex.path_to_the_helper;
  91. }
  92. // Destructor
  93. BlkTree::~BlkTree()
  94. {
  95. delete m_tooltip_window;
  96. delete m_Menu;
  97. // Order the worker thread to stop and wait for it to stop.
  98. if (m_WorkerThread->joinable())
  99. {
  100. m_WorkerThread->detach();
  101. m_Worker.stop_work();
  102. if(m_WorkerThread) {
  103. delete m_WorkerThread;
  104. m_WorkerThread = nullptr;
  105. }
  106. }
  107. // Purge the contents
  108. pObj.clear();
  109. // Deallocate memory
  110. pObj.shrink_to_fit();
  111. }
  112. bool BlkTree::get_shall_quit()
  113. {
  114. return this->m_shall_quit;
  115. }
  116. string BlkTree::get_selected()
  117. {
  118. return selected;
  119. }
  120. void BlkTree::notify()
  121. {
  122. m_Dispatcher.emit();
  123. }
  124. void BlkTree::get_info_from_worker_thread(const Hopman::Annex& annex)
  125. {
  126. Hopman::Hopman_Ui *parent = dynamic_cast<Hopman::Hopman_Ui*>(this->get_toplevel());
  127. if (parent) {
  128. parent->resize(20,20);
  129. parent->m_Spinner.stop();
  130. parent->m_Spinner.hide();
  131. parent->m_Label.hide();
  132. }
  133. pObj.clear();
  134. pObj.begin();
  135. m_Worker.get_data( pObj );
  136. if(!pObj.size())
  137. return;
  138. /*
  139. // No devices?
  140. {
  141. Gtk::MessageDialog dialog(*parent, "No devices found.");
  142. dialog.run();
  143. gtk_main_quit();
  144. }
  145. */
  146. // Fill the tree
  147. m_refTreeModel->clear();
  148. Glib::RefPtr<Gdk::Pixbuf> warning_icon = Gdk::Pixbuf::create_from_file( annex.warning_icon );
  149. Glib::RefPtr<Gdk::Pixbuf> ejected_icon = Gdk::Pixbuf::create_from_file( annex.ejected_icon );
  150. Glib::RefPtr<Gdk::Pixbuf> eject_icon = Gdk::Pixbuf::create_from_file( annex.eject_icon );
  151. for(auto &u : pObj) {
  152. row = *(m_refTreeModel->append());
  153. row[m_Columns.device] = " " + u->get_devname();
  154. row[m_Columns.fstype] = " " + u->get_fstype();
  155. string str = u->get_size() + " G";
  156. int n = str.length();
  157. for(unsigned i=n; i<8; i++) str = " " + str;
  158. row[m_Columns.size] = str + " ";
  159. row[m_Columns.vendor] = " [ " + u->get_vendor() + " - " + u->get_model() + " ] ";
  160. bool mounted=false;
  161. int num_children = 0;
  162. for(auto &w : u->children) {
  163. childrow = *(m_refTreeModel->append(row.children()));
  164. childrow[m_Columns.device] = w->get_devname();
  165. childrow[m_Columns.fstype] = " " + w->get_fstype();
  166. string str = w->get_size() + " G";
  167. int n = str.length();
  168. for(unsigned i=n; i<8; i++) str = " " + str;
  169. childrow[m_Columns.size] = str + " ";
  170. if(w->get_mtpt().length()) {
  171. childrow[m_Columns.vendor] = " >> " + w->get_mtpt();
  172. childrow[m_Columns.icon] = eject_icon;
  173. mounted=true;
  174. }
  175. num_children++;
  176. }
  177. if(mounted) row[m_Columns.icon] = warning_icon;
  178. else {
  179. if(!u->get_removable()) row[m_Columns.icon] = ejected_icon;
  180. else row[m_Columns.icon] = eject_icon;
  181. }
  182. }
  183. // Set parent's gravity
  184. int x, y;
  185. parent->get_position(x, y);
  186. if(x > annex.x_screen/2) {
  187. if(y > annex.y_screen/2) parent->set_gravity(Gdk::GRAVITY_SOUTH_EAST);
  188. else parent->set_gravity(Gdk::GRAVITY_NORTH_EAST);
  189. } else {
  190. if(y > annex.y_screen/2) parent->set_gravity(Gdk::GRAVITY_SOUTH_WEST);
  191. else parent->set_gravity(Gdk::GRAVITY_NORTH_WEST);
  192. }
  193. /*
  194. while (Gtk::Main::events_pending ())
  195. Gtk::Main::iteration ();
  196. * */
  197. }
  198. // Signal Handler (single click)
  199. bool BlkTree::on_button_press_event(GdkEventButton* event)
  200. {
  201. //Call base class, to allow normal handling (requires: set_hover_selection( true ) );
  202. Gtk::TreeView::on_button_press_event(event);
  203. if(event->type == GDK_BUTTON_PRESS)
  204. {
  205. if(event->button == 1)
  206. return on_left_button_press_event();
  207. else if(event->button == 2)
  208. return on_middle_button_press_event(event);
  209. else if(event->button == 3)
  210. return on_right_button_press_event(event);
  211. }
  212. else if(event->type==GDK_2BUTTON_PRESS)
  213. return on_double_click_event(event);
  214. return false;
  215. }
  216. bool BlkTree::on_left_button_press_event()
  217. {
  218. if(!this->do_action) return false;
  219. this->do_action = false;
  220. Glib::RefPtr<Gtk::TreeView::Selection> refTreeSelection = get_selection();
  221. if(refTreeSelection)
  222. {
  223. Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
  224. if(iter && (*iter).parent())
  225. {
  226. string str = (*iter)[ m_Columns.device ];
  227. string cmd = "pumount " + str + " 2>&1";
  228. FILE *fp;
  229. char buf[1024];
  230. string e( "" );
  231. fp = popen( cmd.c_str(), "r" );
  232. if(!fp) {
  233. cout << "Popen error\n";
  234. exit( EXIT_FAILURE );
  235. }
  236. // There is a bug in pmount related to the replicated stdout error message
  237. string first_line( "" );
  238. while(fgets(buf, sizeof(buf), fp)) {
  239. if( !first_line.length() ) first_line = string(buf);
  240. e+=string(buf);
  241. }
  242. // Is it replicated? If so, split it half
  243. size_t pos=e.find(first_line, 2);
  244. if(pos != -1) {
  245. string str1=e.substr(0, pos);
  246. string str2=e.substr(pos);
  247. if(!str2.compare(str1)) e=str1;
  248. }
  249. int x = WEXITSTATUS(pclose(fp));
  250. if(x == 5) {
  251. Gtk::Window *parent = dynamic_cast<Gtk::Window*>(this->get_toplevel());
  252. Gtk::MessageDialog dialog(*parent, "This is an INFO MessageDialog");
  253. dialog.set_message( "[ERROR]: umount failed", false );
  254. dialog.set_secondary_text( e.c_str(), false );
  255. dialog.run();
  256. }
  257. /*
  258. // the weak_ptr has to be copied into a shared_ptr before usage
  259. if (auto p = BlkTree::getNode(str)->parent.lock()) {
  260. vector<string> children = p->get_children_mtpt_vector();
  261. int n=0;
  262. for (auto &u : children)
  263. if( u.length() ) n++;
  264. // Check whether n > 1 because the already removed partition still
  265. // figures as mounted in the vector
  266. if( n == 1 && !is_device_removable(p) ) {
  267. this_thread::sleep_for(chrono::milliseconds(500));
  268. Gtk::Window *parent = dynamic_cast<Gtk::Window*>(this->get_toplevel());
  269. Gtk::MessageDialog dialog(*parent, "This is an INFO MessageDialog");
  270. string msg = p->get_vendor() + " - " + p->get_model();
  271. dialog.set_message( msg.c_str(), false );
  272. dialog.set_secondary_text( "can be safely removed.", false );
  273. dialog.show();
  274. }
  275. } else {
  276. throw runtime_error( "weak pointer is expired\n" );
  277. }
  278. */
  279. }
  280. }
  281. return false;
  282. }
  283. bool BlkTree::on_double_click_event(GdkEventButton* event)
  284. {
  285. this->m_shall_quit = false;
  286. Hopman::EjectDialog *eject_dialog = new Hopman::EjectDialog( this->selected, this->ejected_icon, this->socket, this->path_to_the_helper );
  287. eject_dialog->run();
  288. delete eject_dialog;
  289. this->m_shall_quit = true;
  290. return true;
  291. }
  292. bool BlkTree::on_middle_button_press_event(GdkEventButton* event)
  293. {
  294. /* *
  295. *
  296. * */
  297. return true;
  298. }
  299. bool BlkTree::on_right_button_press_event(GdkEventButton* event)
  300. {
  301. if(display_menu) {
  302. m_Menu->display_menu(event, selected, is_partition_unmounted( selected ));
  303. return true;
  304. } else {
  305. return false;
  306. }
  307. }
  308. bool BlkTree::is_device_removable(shared_ptr<Hopman::BlkNode> node)
  309. {
  310. try {
  311. // Read the value in $SYSFS_PATH/removable
  312. string filename = node->get_pathname() + "/removable";
  313. unique_ptr<FILE, decltype(&fclose)> fp(::fopen(filename.c_str(), "r"), &fclose);
  314. char ch = (char)fgetc(fp.get());
  315. if (ch == '0')
  316. return false;
  317. else if (ch == '1')
  318. return true;
  319. }
  320. catch(exception const& e) {
  321. throw runtime_error("Sorry, there was an error opening the pipe\n");
  322. };
  323. }
  324. bool BlkTree::is_partition_unmounted(string partition)
  325. {
  326. bool res = true;
  327. string dev=partition;
  328. dev.pop_back();
  329. for(auto &u : pObj) {
  330. if(!dev.compare(u->get_devname()))
  331. for(auto &v : u->children) {
  332. if(!v->get_devname().compare(partition) && v->get_mtpt().length()) {
  333. res = false;
  334. }
  335. }
  336. }
  337. return res;
  338. }
  339. void BlkTree::on_mount()
  340. {
  341. FILE *fp;
  342. char buf[1024];
  343. string e( "" );
  344. string cmd = "pmount " + selected + " 2>&1";
  345. fp = popen( cmd.c_str(), "r" );
  346. if(!fp) {
  347. cout << "Popen error\n";
  348. exit( EXIT_FAILURE );
  349. }
  350. // There is a bug in pmount related to the replicated stdout error message
  351. string first_line( "" );
  352. while(fgets(buf, sizeof(buf), fp)) {
  353. if( !first_line.length() ) first_line = string(buf);
  354. e+=string(buf);
  355. }
  356. // Is it replicated? If so, split it half
  357. size_t pos=e.find(first_line, 2);
  358. if(pos != -1) {
  359. string str1=e.substr(0, pos);
  360. string str2=e.substr(pos);
  361. if(!str2.compare(str1)) e=str1;
  362. }
  363. if( WEXITSTATUS(pclose(fp)) != 255) {
  364. Gtk::Window *parent = dynamic_cast<Gtk::Window*>(this->get_toplevel());
  365. Gtk::MessageDialog dialog(*parent, "[ERROR]: pmount failed");
  366. dialog.set_secondary_text( e.c_str(), false );
  367. dialog.run();
  368. gtk_main_quit();
  369. }
  370. }
  371. // Signal Handler
  372. void BlkTree::on_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn *column)
  373. {
  374. if(column->get_title().compare("ICON")) return;
  375. else this->do_action = true;
  376. }
  377. void BlkTree::on_cursor_changed()
  378. {
  379. display_menu = false;
  380. selected.clear();
  381. Glib::RefPtr<Gtk::TreeView::Selection> refTreeSelection = get_selection();
  382. if(refTreeSelection)
  383. {
  384. Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
  385. if(iter) {
  386. string strLabel( "" );
  387. string temp( "" );
  388. string str=(*iter)[ m_Columns.device ];
  389. str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); /* Remove spaces in blanck */
  390. this->selected = str;
  391. auto node = BlkTree::getNode(this->selected);
  392. if( (*iter).parent() ) { /* child */
  393. // In the children, we'll specify parent's label too
  394. // Parent's weak_ptr has to be copied into a shared_ptr before usage
  395. if (auto p = node->parent.lock()) {
  396. display_menu = true;
  397. temp=p->get_label();
  398. if(temp.size()) temp+=" | ";
  399. } else {
  400. throw runtime_error( "weak pointer is expired\n" );
  401. }
  402. temp+=node->get_label();
  403. if(temp.size()) strLabel+=" LABEL=" + temp + " \n";
  404. strLabel+=" UUID=" + node->get_uuid() + " \n";
  405. strLabel+=" PARTUUID=" + node->get_partuuid() + " ";
  406. } else { /* parent */
  407. temp=node->get_label();
  408. if(temp.size()) strLabel+=" LABEL=" + temp + " \n";
  409. temp=node->get_uuid();
  410. if(temp.size()) strLabel+=" UUID=" + node->get_uuid() + " ";
  411. }
  412. m_Label.set_text( strLabel );
  413. }
  414. }
  415. }
  416. bool BlkTree::on_test_collapse_row (const Gtk::TreeModel::iterator& iter, const Gtk::TreeModel::Path& path)
  417. {
  418. Gtk::Window *parent = dynamic_cast<Gtk::Window*>(this->get_toplevel());
  419. if (parent)
  420. parent->resize(20,20);
  421. return Gtk::TreeView::on_test_collapse_row (iter, path);
  422. }
  423. bool BlkTree::on_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
  424. {
  425. if( m_Label.get_text().length() > 1) m_Label.show();
  426. m_tooltip_window->hide();
  427. m_tooltip_window->resize(1, 1);
  428. return true;
  429. }
  430. shared_ptr<Hopman::BlkNode> BlkTree::getNode( string dev )
  431. {
  432. string duplicate = dev;
  433. duplicate.pop_back();
  434. for(auto &p : pObj) {
  435. string d_name=p->get_devname();
  436. if( !dev.compare(d_name) )
  437. return p;
  438. else if( !duplicate.compare(d_name) )
  439. for(auto &w : p->children)
  440. if( !w->get_devname().compare(dev) )
  441. return w;
  442. }
  443. return nullptr;
  444. }
  445. } //namespace Hopman