1 /* view_newest_files_op.cc
2  * This file belongs to Worker, a file manager for UN*X/X11.
3  * Copyright (C) 2016-2021 Ralf Hoffmann.
4  * You can contact me at: ralf@boomerangsworld.de
5  *   or http://www.boomerangsworld.de/worker
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 2 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, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "view_newest_files_op.hh"
23 #include "aguix/aguix.h"
24 #include "aguix/awindow.h"
25 #include "aguix/text.h"
26 #include "aguix/fieldlistview.h"
27 #include "aguix/button.h"
28 #include "aguix/textstorage.h"
29 #include "aguix/textview.h"
30 #include "listermode.h"
31 #include "nwc_fsentry.hh"
32 #include "nmspecialsourceext.hh"
33 #include "worker_locale.h"
34 #include "worker.h"
35 #include "get_files_thread.hh"
36 #include "nwc_path.hh"
37 #include "virtualdirmode.hh"
38 #include "wconfig.h"
39 
40 int ViewNewestFilesOp::s_vdir_number = 1;
41 
42 const char *ViewNewestFilesOp::name = "ViewNewestFilesOp";
43 
ViewNewestFilesOp()44 ViewNewestFilesOp::ViewNewestFilesOp() : FunctionProto()
45 {
46     m_past_days = 0;
47 }
48 
~ViewNewestFilesOp()49 ViewNewestFilesOp::~ViewNewestFilesOp()
50 {
51 }
52 
duplicate() const53 ViewNewestFilesOp *ViewNewestFilesOp::duplicate() const
54 {
55     ViewNewestFilesOp *ta = new ViewNewestFilesOp();
56     return ta;
57 }
58 
isName(const char * str)59 bool ViewNewestFilesOp::isName(const char *str)
60 {
61     if ( strcmp( str, name ) == 0 ) {
62         return true;
63     } else {
64         return false;
65     }
66 }
67 
getName()68 const char *ViewNewestFilesOp::getName()
69 {
70     return name;
71 }
72 
run(std::shared_ptr<WPUContext> wpu,ActionMessage * msg)73 int ViewNewestFilesOp::run( std::shared_ptr< WPUContext > wpu, ActionMessage *msg )
74 {
75     Lister *l1;
76     ListerMode *lm1;
77 
78     m_aguix = msg->getWorker()->getAGUIX();
79 
80     l1 = msg->getWorker()->getActiveLister();
81     if ( l1 == NULL ) {
82         return 1;
83     }
84 
85     msg->getWorker()->setWaitCursor();
86 
87     lm1 = l1->getActiveMode();
88 
89     std::string dirname;
90 
91     if ( lm1 ) {
92         dirname = lm1->getCurrentDirectory();
93     }
94 
95     if ( dirname.empty() ) {
96         msg->getWorker()->unsetWaitCursor();
97         return 0;
98     }
99 
100     std::unique_ptr< DirFilterSettings > dir_filter;
101     VirtualDirMode *vdm = dynamic_cast< VirtualDirMode *>( lm1 );
102     std::unique_ptr< NWC::Dir > use_dir;
103 
104     if ( vdm ) {
105         dir_filter = vdm->getDirFilterSettings();
106         use_dir = vdm->getCurrentDir();
107     } else {
108         use_dir = std::make_unique< NWC::Dir >( dirname );
109         use_dir->readDir( false );
110     }
111 
112     m_base_dir = dirname;
113     m_past_days = 0;
114 
115     std::unique_ptr< GetFilesThread > sth( new GetFilesThread( std::move( use_dir ) ) );
116 
117     time_t newest_mod_time = 0;
118     std::list< std::pair< std::string, time_t > > files;
119     time_t now = time( NULL );
120 
121     sth->setVisitCB( [ &newest_mod_time,
122                        &files,
123                        &dir_filter,
124                        &dirname,
125                        &now ]( NWC::File &file )
126                      {
127                          if ( ! dir_filter ||
128                               dir_filter->check( dirname, &file ) == true ) {
129 
130                              // ignore files newer than the current time
131                              if ( file.stat_lastmod() <= now ) {
132                                  files.push_back( std::make_pair( file.getFullname(),
133                                                                   file.stat_lastmod() ) );
134 
135                                  if ( file.stat_lastmod() > newest_mod_time ) {
136                                      newest_mod_time = file.stat_lastmod();
137                                  }
138                              }
139                          }
140                      }
141                      );
142     sth->setVisitEnterDirCB( [ &dir_filter,
143                                &dirname ]( NWC::Dir &dir )
144                              {
145                                  if ( ! dir_filter ||
146                                       dir_filter->check( dirname, &dir ) == true ) {
147                                      return true;
148                                  }
149 
150                                  return false;
151                              }
152                              );
153 
154     sth->start();
155 
156     openWindow();
157 
158     m_win->show();
159 
160     AGMessage *agmsg;
161     int endmode = 0;
162 
163     for ( ; endmode == 0 || endmode == 1; ) {
164         bool wait_some_time = false;
165 
166         agmsg = NULL;
167 
168         if ( sth && sth->running() ) {
169             agmsg = m_aguix->GetMessage( NULL );
170             wait_some_time = true;
171         } else {
172             if ( sth ) {
173                 sth->join();
174                 sth = NULL;
175 
176                 m_infotext->setText( catalog.getLocale( 1163 ) );
177 
178                 sortResults( files );
179 
180                 time_t l = calculateModLimit( newest_mod_time );
181 
182                 updateResults( files, l );
183             }
184 
185             if ( endmode == 1 ) {
186                 endmode = 2;
187             } else {
188                 agmsg = m_aguix->WaitMessage( NULL );
189             }
190         }
191         if ( agmsg != NULL ) {
192             switch ( agmsg->type ) {
193               case AG_CLOSEWINDOW:
194                   endmode = -1;
195                   if ( sth ) {
196                       sth->signalCancel();
197                   }
198                   break;
199               case AG_BUTTONCLICKED:
200                   if ( agmsg->button.button == m_okb && endmode == 0 ) {
201                       endmode = 1;
202 
203                       if ( sth ) {
204                           m_okb->setText( 0, catalog.getLocale( 1165 ) );
205                           m_okb->resize( m_okb->getMaximumWidth(),
206                                          m_okb->getHeight() );
207                       }
208                   } else if ( agmsg->button.button == m_closeb ) {
209                       endmode = -1;
210                       if ( sth ) {
211                           sth->signalCancel();
212                       }
213                   } else if ( agmsg->button.button == m_more_days_b && endmode == 0 ) {
214                       m_past_days++;
215                       updateTimeWindowText();
216 
217                       if ( ! sth ) {
218                           time_t l = calculateModLimit( newest_mod_time );
219 
220                           updateResults( files, l );
221                       }
222                   } else if ( agmsg->button.button == m_less_days_b && endmode == 0 ) {
223                       if ( m_past_days > 0 ) m_past_days--;
224                       updateTimeWindowText();
225 
226                       if ( ! sth ) {
227                           time_t l = calculateModLimit( newest_mod_time );
228 
229                           updateResults( files, l );
230                       }
231                   }
232                   break;
233               case AG_KEYPRESSED:
234                   if ( agmsg->key.key == XK_Return && endmode == 0 ) {
235                       endmode = 1;
236 
237                       if ( sth ) {
238                           m_okb->setText( 0, catalog.getLocale( 1165 ) );
239                           m_okb->resize( m_okb->getMaximumWidth(),
240                                          m_okb->getHeight() );
241                       }
242                   } else if ( agmsg->key.key == XK_Escape ) {
243                       endmode = -1;
244                       if ( sth ) {
245                           sth->signalCancel();
246                       }
247                   } else if ( agmsg->key.key == XK_Up && endmode == 0 ) {
248                       m_past_days++;
249                       updateTimeWindowText();
250 
251                       if ( ! sth ) {
252                           time_t l = calculateModLimit( newest_mod_time );
253 
254                           updateResults( files, l );
255                       }
256                   } else if ( agmsg->key.key == XK_Down && endmode == 0 ) {
257                       if ( m_past_days > 0 ) m_past_days--;
258                       updateTimeWindowText();
259 
260                       if ( ! sth ) {
261                           time_t l = calculateModLimit( newest_mod_time );
262 
263                           updateResults( files, l );
264                       }
265                   }
266                   break;
267             }
268         } else if ( wait_some_time ) {
269             waittime( 10 );
270         }
271 
272         m_aguix->ReplyMessage( agmsg );
273     }
274 
275     while ( sth && sth->running() ) {
276 	    waittime( 10 );
277     }
278     if ( sth ) {
279 	    sth->join();
280 	    sth = NULL;
281 
282         // just to be consistent with the panelize output
283         sortResults( files );
284     }
285 
286     if ( endmode > 0 ) {
287         panelizeResults( msg->getWorker(), files, newest_mod_time );
288     }
289 
290     m_win.reset();
291 
292     msg->getWorker()->unsetWaitCursor();
293     return 0;
294 }
295 
getDescription()296 const char *ViewNewestFilesOp::getDescription()
297 {
298     return catalog.getLocale( 1306 );
299 }
300 
openWindow()301 void ViewNewestFilesOp::openWindow()
302 {
303     m_win = std::unique_ptr<AWindow>( new AWindow( m_aguix,
304                                                    0, 0,
305                                                    500, 400,
306                                                    getDescription() ) );
307     m_win->create();
308 
309     AContainer *cont0 = m_win->setContainer( new AContainer( m_win.get(), 1, 5 ), true );
310     cont0->setMaxSpace( 5 );
311 
312     RefCount<AFontWidth> lencalc( new AFontWidth( m_aguix, NULL ) );
313     m_help_ts = std::unique_ptr< TextStorageString >( new TextStorageString( catalog.getLocale( 1168 ), lencalc ) );
314     TextView *help_tv = cont0->addWidget( new TextView( m_aguix,
315                                                         0, 0, 50, 80, "", *m_help_ts ),
316                                           0, 0, AContainer::CO_INCW );
317     help_tv->setLineWrap( true );
318     help_tv->maximizeYLines( 10, 500 );
319     help_tv->showFrame( false );
320     cont0->readLimits();
321     help_tv->show();
322     help_tv->setAcceptFocus( false );
323 
324     TextView::ColorDef tv_cd = help_tv->getColors();
325     tv_cd.setBackground( 0 );
326     tv_cd.setTextColor( 1 );
327     help_tv->setColors( tv_cd );
328 
329     m_infotext = cont0->addWidget( new Text( m_aguix, 0, 0, catalog.getLocale( 1164 ) ),
330                                    0, 1, AContainer::CO_INCW );
331 
332     m_lv = dynamic_cast<FieldListView*>( cont0->add( new FieldListView( m_aguix, 0, 0,
333                                                                         500, 200, 0 ),
334                                                      0, 2, AContainer::CO_MIN ) );
335     m_lv->setNrOfFields( 2 );
336     m_lv->setShowHeader( true );
337     m_lv->setFieldText( 0, catalog.getLocale( 176 ) );
338     m_lv->setFieldText( 1, catalog.getLocale( 163 ) );
339     m_lv->setHBarState( 2 );
340     m_lv->setVBarState( 2 );
341     m_lv->setGlobalFieldSpace( 5 );
342     m_lv->setDefaultColorMode( FieldListView::PRECOLOR_ONLYSELECT );
343 
344     AContainer *cont1 = cont0->add( new AContainer( m_win.get(), 4, 1 ), 0, 3 );
345     cont1->setBorderWidth( 0 );
346     cont1->setMinSpace( 5 );
347     cont1->setMaxSpace( 5 );
348 
349     cont1->add( new Text( m_aguix, 0, 0, catalog.getLocale( 1160 ) ),
350                 0, 0, AContainer::CO_FIX );
351     m_time_window_text = cont1->addWidget( new Text( m_aguix, 0, 0, "" ),
352                                            1, 0, AContainer::CO_INCW );
353     m_more_days_b = dynamic_cast<Button*>( cont1->add( new Button( m_aguix, 0, 0, catalog.getLocale( 1161 ), 0 ),
354                                                       2, 0, AContainer::CO_FIX ) );
355     m_less_days_b = dynamic_cast<Button*>( cont1->add( new Button( m_aguix, 0, 0, catalog.getLocale( 1162 ), 0 ),
356                                                       3, 0, AContainer::CO_FIX ) );
357 
358     m_more_days_b->setBubbleHelpText( catalog.getLocale( 1166 ) );
359     m_less_days_b->setBubbleHelpText( catalog.getLocale( 1167 ) );
360 
361     AContainer *cont2 = cont0->add( new AContainer( m_win.get(), 2, 1 ), 0, 4 );
362     cont2->setBorderWidth( 0 );
363     cont2->setMinSpace( 5 );
364     cont2->setMaxSpace( -1 );
365 
366     m_okb = dynamic_cast<Button*>( cont2->add( new Button( m_aguix, 0, 0,
367                                                            catalog.getLocale( 1034 ),
368                                                            0 ),
369                                                0, 0, AContainer::CO_FIX ) );
370     m_closeb = dynamic_cast<Button*>( cont2->add( new Button( m_aguix, 0, 0,
371                                                               catalog.getLocale( 8 ),
372                                                               0 ),
373                                                   1, 0, AContainer::CO_FIX ) );
374 
375     m_win->contMaximize( true );
376     m_win->setDoTabCycling( true );
377 
378     updateTimeWindowText();
379 }
380 
updateResults(const std::list<std::pair<std::string,time_t>> & files,time_t mod_limit)381 void ViewNewestFilesOp::updateResults( const std::list< std::pair< std::string, time_t > > &files,
382                                        time_t mod_limit )
383 {
384     m_lv->setSize( 0 );
385 
386     for ( auto p: files ) {
387         if ( p.second >= mod_limit ) {
388             int row = m_lv->addRow();
389 
390             std::string s = NWC::Path::get_extended_basename( m_base_dir, p.first );
391 
392             m_lv->setText( row, 0, s );
393 
394             s.clear();
395 
396             struct tm *timeptr = localtime( &p.second );
397             wconfig->writeDateToString( s, timeptr );
398 
399             m_lv->setText( row, 1, s );
400         }
401     }
402 
403     m_lv->redraw();
404     m_aguix->Flush();
405 }
406 
calculateModLimit(time_t newest_time)407 time_t ViewNewestFilesOp::calculateModLimit( time_t newest_time )
408 {
409     struct tm newest_date;
410 
411     localtime_r( &newest_time, &newest_date );
412 
413     newest_date.tm_sec = 0;
414     newest_date.tm_min = 0;
415     newest_date.tm_hour = 0;
416 
417     newest_date.tm_mday -= m_past_days;
418 
419     return mktime( &newest_date );
420 }
421 
panelizeResults(Worker * w,const std::list<std::pair<std::string,time_t>> & files,time_t newest_time)422 void ViewNewestFilesOp::panelizeResults( Worker *w,
423                                          const std::list< std::pair< std::string, time_t > > &files,
424                                          time_t newest_time )
425 {
426     Lister *l1;
427 
428     time_t l = calculateModLimit( newest_time );
429 
430     l1 = w->getActiveLister();
431     if ( l1 != NULL ) {
432         l1->switch2Mode( 0 );
433 
434         VirtualDirMode *vdm = dynamic_cast< VirtualDirMode* >( l1->getActiveMode() );
435         if ( vdm != NULL ) {
436             vdm->newTab();
437 
438             std::string name = AGUIXUtils::formatStringToString( "viewnewest%d", s_vdir_number++ );
439 
440             if ( s_vdir_number < 0 ) {
441                 // avoid negative and zero number
442                 s_vdir_number = 1;
443             }
444 
445             std::unique_ptr< NWC::Dir > d( new NWC::VirtualDir( name ) );
446 
447             const int rows = m_lv->getElements();
448             int row = 0;
449             bool some_selected = false;
450 
451             for ( row = 0; row < rows; row++ ) {
452                 if ( m_lv->getSelect( row ) ) {
453                     some_selected = true;
454                     break;
455                 }
456             }
457 
458             row = 0;
459             for ( auto p: files ) {
460                 if ( p.second >= l ) {
461 
462                     if ( ! some_selected ||
463                          m_lv->getSelect( row ) ) {
464                         NWC::FSEntry fse( p.first );
465                         if ( fse.entryExists() ) {
466                             d->add( fse );
467                         }
468                     }
469                     row++;
470                 }
471             }
472 
473             vdm->showDir( d );
474         }
475     }
476 }
477 
updateTimeWindowText()478 void ViewNewestFilesOp::updateTimeWindowText()
479 {
480     if ( m_past_days == 0 ) {
481         m_time_window_text->setText( catalog.getLocale( 1159 ) );
482     } else if ( m_past_days == 1 ) {
483         std::string t = AGUIXUtils::formatStringToString( catalog.getLocale( 1107 ), 1 );
484         m_time_window_text->setText( t.c_str() );
485     } else {
486         std::string t = AGUIXUtils::formatStringToString( catalog.getLocale( 1108 ), m_past_days );
487         m_time_window_text->setText( t.c_str() );
488     }
489 }
490 
sortResults(std::list<std::pair<std::string,time_t>> & files)491 void ViewNewestFilesOp::sortResults( std::list< std::pair< std::string, time_t > > &files )
492 {
493     files.sort( []( std::pair< std::string, time_t > &lhs,
494                     std::pair< std::string, time_t > &rhs ) -> bool {
495                     return lhs.first < rhs.first;
496                 });
497 }
498