1 /*
2    Vimpc
3    Copyright (C) 2013 Nathan Sweetman
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the 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,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18    command.cpp - ex mode input handling
19    */
20 
21 #include "command.hpp"
22 
23 #include <unistd.h>
24 
25 #include <algorithm>
26 #include <sstream>
27 
28 #ifdef HAVE_TAGLIB_H
29 #include <taglib/tag.h>
30 #include <taglib/taglib.h>
31 #include <taglib/fileref.h>
32 #endif
33 
34 #ifdef HAVE_TEST_H
35 #include <cppunit/extensions/TestFactoryRegistry.h>
36 #include <cppunit/TestResult.h>
37 #include <cppunit/TestResultCollector.h>
38 #include <cppunit/TestRunner.h>
39 #include <cppunit/TextOutputter.h>
40 #endif
41 
42 #include "algorithm.hpp"
43 #include "assert.hpp"
44 #include "buffers.hpp"
45 #include "regex.hpp"
46 #include "settings.hpp"
47 #include "tag.hpp"
48 #include "vimpc.hpp"
49 
50 #include "buffer/directory.hpp"
51 #include "buffer/list.hpp"
52 #include "buffer/outputs.hpp"
53 #include "buffer/playlist.hpp"
54 #include "mode/normal.hpp"
55 #include "window/console.hpp"
56 #include "window/debug.hpp"
57 #include "window/error.hpp"
58 #include "window/songwindow.hpp"
59 
60 using namespace Ui;
61 
62 static std::list<std::string>             Queue;
63 
64 static Atomic(bool)                       Running(true);
65 static Mutex                              QueueMutex;
66 static ConditionVariable                  Condition;
67 
68 
69 // COMMANDS
Command(Main::Vimpc * vimpc,Ui::Screen & screen,Mpc::Client & client,Mpc::ClientState & clientState,Main::Settings & settings,Ui::Search & search,Ui::Normal & normalMode)70 Command::Command(Main::Vimpc * vimpc, Ui::Screen & screen, Mpc::Client & client, Mpc::ClientState & clientState, Main::Settings & settings, Ui::Search & search, Ui::Normal & normalMode) :
71    InputMode           (screen),
72    Player              (screen, client, clientState, settings),
73    initTabCompletion_  (true),
74    forceCommand_       (false),
75    queueCommands_      (false),
76    connectAttempt_     (false),
77    count_              (0),
78    line_               (-1),
79    currentLine_        (-1),
80    aliasTable_         (),
81    commandTable_       (),
82    vimpc_              (vimpc),
83    search_             (search),
84    screen_             (screen),
85    client_             (client),
86    clientState_        (clientState),
87    settings_           (settings),
88    normalMode_         (normalMode)
89 #ifdef HAVE_TEST_H
90    ,testThread_        (Thread(&Command::TestExecutor, this))
91 #endif
92 {
93    // \todo find a away to add aliases to tab completion
94    // Command, RequiresConnection, SupportsRange, Function
95    AddCommand("!mpc",       true,  false, &Command::Mpc);
96    AddCommand("a",          true,  true,  &Command::Add);
97    AddCommand("add",        true,  true,  &Command::Add);
98    AddCommand("addall",     true,  false, &Command::AddAll);
99    AddCommand("alias",      false, false, &Command::Alias);
100    AddCommand("clear",      false, false, &Command::ClearScreen);
101    AddCommand("connect",    false, false, &Command::Connect);
102    AddCommand("consume",    true,  false, &Command::Consume);
103    AddCommand("crossfade",  true,  false, &Command::Crossfade);
104    AddCommand("d",          true,  true,  &Command::Delete);
105    AddCommand("delete",     true,  true,  &Command::Delete);
106    AddCommand("deleteall",  true,  false, &Command::DeleteAll);
107    AddCommand("disable",    true,  true,  &Command::Output<false>);
108    AddCommand("disconnect", true,  false, &Command::Disconnect);
109    AddCommand("echo",       false, false, &Command::Echo);
110    AddCommand("enable",     true,  true,  &Command::Output<true>);
111    AddCommand("error",      false, false, &Command::EchoError);
112    AddCommand("find",       true,  false, &Command::FindAny);
113    AddCommand("findalbum",  true,  false, &Command::FindAlbum);
114    AddCommand("findartist", true,  false, &Command::FindArtist);
115    AddCommand("findgenre",  true,  false, &Command::FindGenre);
116    AddCommand("findsong",   true,  false, &Command::FindSong);
117    AddCommand("move",       true,  true,  &Command::Move);
118    AddCommand("mute",       true,  false, &Command::Mute);
119    AddCommand("nohlsearch", false, false, &Command::NoHighlightSearch);
120    AddCommand("normal",     true,  false, &Command::Normal);
121    AddCommand("password",   true,  false, &Command::Password);
122    AddCommand("pause",      true,  false, &Command::Pause);
123    AddCommand("play",       true,  false,  &Command::Play);
124    AddCommand("q",          false, false, &Command::Quit);
125    AddCommand("qall",       false, false, &Command::QuitAll);
126    AddCommand("quit",       false, false, &Command::Quit);
127    AddCommand("quitall",    false, false, &Command::QuitAll);
128    AddCommand("random",     true,  false, &Command::Random);
129    AddCommand("reconnect",  false, false, &Command::Reconnect);
130    AddCommand("redraw",     false, false, &Command::Redraw);
131    AddCommand("repeat",     true,  false, &Command::Repeat);
132    AddCommand("set",        false, false, &Command::Set);
133    AddCommand("seek",       true,  false, &Command::SeekTo);
134    AddCommand("seek+",      true,  false, &Command::Seek<1>);
135    AddCommand("seek-",      true,  false, &Command::Seek<-1>);
136    AddCommand("single",     true,  false, &Command::Single);
137    AddCommand("shuffle",    true,  false, &Command::Shuffle);
138    AddCommand("sleep",      false, false, &Command::Sleep);
139 #ifdef TAG_SUPPORT
140    AddCommand("substitute", false, true,  &Command::Substitute);
141    AddCommand("s",          false, true,  &Command::Substitute);
142 #endif
143    AddCommand("swap",       true,  false,  &Command::Swap);
144    AddCommand("stop",       true,  false, &Command::Stop);
145    AddCommand("toggle",     true,  true,  &Command::ToggleOutput);
146    AddCommand("unalias",    false, false, &Command::Unalias);
147    AddCommand("volume",     true,  false,  &Command::Volume);
148 
149    AddCommand("map",        false, false, &Command::Map);
150    AddCommand("unmap",      false, false, &Command::Unmap);
151    AddCommand("wmap",       false, false, &Command::WindowMap);
152    AddCommand("wunmap",     false, false, &Command::WindowUnmap);
153    AddCommand("tmap",       false, false, &Command::TabMap);
154    AddCommand("tunmap",     false, false, &Command::TabUnmap);
155    AddCommand("tabmap",     false, false, &Command::TabMap);
156    AddCommand("tabunmap",   false, false, &Command::TabUnmap);
157 
158    AddCommand("tabfirst",   false, false, &Command::ChangeToWindow<First>);
159    AddCommand("tablast",    false, false, &Command::ChangeToWindow<Last>);
160    AddCommand("tabnext",    false, false, &Command::ChangeToWindow<Next>);
161    AddCommand("tabprevious",false, false, &Command::ChangeToWindow<Previous>);
162    AddCommand("tabclose",   false, false, &Command::HideWindow);
163    AddCommand("tabhide",    false, false, &Command::HideWindow);
164    AddCommand("tabmove",    false, false, &Command::MoveWindow);
165    AddCommand("tabrename",  false, false, &Command::RenameWindow);
166 
167    AddCommand("highlight",  false, false, &Command::SetColour);
168 
169    AddCommand("rescan",     true,  false, &Command::Rescan);
170    AddCommand("update",     true,  false, &Command::Update);
171 
172    AddCommand("next",       true,  false, &Command::SkipSong<Player::Next>);
173    AddCommand("previous",   true,  false, &Command::SkipSong<Player::Previous>);
174 
175    AddCommand("browse",      false, false, &Command::SetActiveAndVisible<Ui::Screen::Browse>);
176    AddCommand("console",     false, false, &Command::SetActiveAndVisible<Ui::Screen::Console>);
177    AddCommand("help",        true,  false, &Command::SetActiveAndVisible<Ui::Screen::Help>);
178    AddCommand("library",     true,  false, &Command::SetActiveAndVisible<Ui::Screen::Library>);
179    AddCommand("directory",   true,  false, &Command::SetActiveAndVisible<Ui::Screen::Directory>);
180    AddCommand("playlist",    true,  false, &Command::SetActiveAndVisible<Ui::Screen::Playlist>);
181    AddCommand("outputs",     true,  false, &Command::SetActiveAndVisible<Ui::Screen::Outputs>);
182    AddCommand("lists",       true,  false, &Command::SetActiveAndVisible<Ui::Screen::Lists>);
183    AddCommand("windowselect",false, false, &Command::SetActiveAndVisible<Ui::Screen::WindowSelect>);
184 
185    AddCommand("load",       true,  false, &Command::LoadPlaylist);
186    AddCommand("save",       true,  false, &Command::SavePlaylist);
187    AddCommand("e",          true,  false, &Command::LoadPlaylist);
188    AddCommand("edit",       true,  false, &Command::LoadPlaylist);
189    AddCommand("w",          true,  false, &Command::SavePlaylist);
190    AddCommand("write",      true,  false, &Command::SavePlaylist);
191    AddCommand("toplaylist", true,  false, &Command::ToPlaylist);
192 
193    // Local socket commands
194    AddCommand("localadd",   true,  true,  &Command::AddFile);
195 
196 #ifdef __DEBUG_PRINTS
197    AddCommand("debug-console",       false, false, &Command::SetActiveAndVisible<Ui::Screen::DebugConsole>);
198    AddCommand("debug-client-getmeta",true,  false, &Command::DebugClient<&Mpc::Client::GetAllMetaInformation>);
199 #endif
200 
201 #ifdef TEST_ENABLED
202    AddCommand("test-console",       false, false, &Command::SetActiveAndVisible<Ui::Screen::TestConsole>);
203    AddCommand("test",               false, false, &Command::Test);
204    AddCommand("test-screen",        false, false, &Command::TestScreen);
205    AddCommand("test-resize",        false, false, &Command::TestResize);
206    AddCommand("test-input-random",  true,  false, &Command::TestInputRandom);
207    AddCommand("test-input-seq",     true,  false, &Command::TestInputSequence);
208 #endif
209 
210    // Add all settings to command table to provide tab completion
211    for (auto setting : settings_.AvailableSettings())
212    {
213       settingsTable_.push_back("set " + setting);
214    }
215 
216    // Register for events
217    Main::Vimpc::EventHandler(Event::Connected, [this] (EventData const & Data) { ExecuteQueuedCommands(); });
218 }
219 
~Command()220 Command::~Command()
221 {
222    Running = false;
223 
224 #ifdef HAVE_TEST_H
225    testThread_.join();
226 #endif
227 }
228 
229 
Initialise(int input)230 void Command::Initialise(int input)
231 {
232    initTabCompletion_  = true;
233 
234    InputMode::Initialise(input);
235 }
236 
Handle(int const input)237 bool Command::Handle(int const input)
238 {
239    ResetTabCompletion(input);
240 
241    return InputMode::Handle(input);
242 }
243 
GenerateInputString(int input)244 void Command::GenerateInputString(int input)
245 {
246    if (input == '\t')
247    {
248       inputString_  = TabComplete(inputString_);
249       inputWString_ = stringtow(inputString_);
250    }
251 
252    InputMode::GenerateInputString(input);
253 }
254 
255 
AddCommand(std::string const & name,bool requiresConnection,bool hasRangeSupport,CommandFunction command)256 void Command::AddCommand(std::string const & name, bool requiresConnection, bool hasRangeSupport, CommandFunction command)
257 {
258    commandTable_[name]       = command;
259    requiresConnection_[name] = requiresConnection;
260    supportsRange_[name]      = hasRangeSupport;
261 }
262 
ExecuteCommand(std::string const & input)263 bool Command::ExecuteCommand(std::string const & input)
264 {
265    Regex::RE const blankCommand("^\\s*$");
266    Regex::RE const multipleCommands("^(\\s*([^;]+)\\s*;\\s*).*$");
267    Regex::RE const rangeSplit("(^\\d*),?(\\d*)$");
268 
269    std::string matchString, commandString;
270    std::string range, command, arguments;
271    uint32_t    count = 0;
272    uint32_t    line  = 0; //Actually stores line + 1, as 0 represents invalid
273 
274    SplitCommand(input, range, command, arguments);
275 
276    std::string fullCommand(command + " " + arguments);
277 
278    // Resolve a command from an alias
279    if (aliasTable_.find(command) != aliasTable_.end())
280    {
281       fullCommand = aliasTable_[command] + " " + arguments;
282    }
283 
284    // Determine whether a range or a line number was used of the form
285    // :x or :x,y this determines where the command should start
286    // and how many times it should run
287    if (range != "")
288    {
289       std::string rangeLine, rangeCount;
290       rangeSplit.Capture(range.c_str(), &rangeLine, &rangeCount);
291 
292       if ((Algorithm::isNumeric(rangeLine) == true) && (rangeLine != ""))
293       {
294          // Commands of the form :<number> go to that line instead
295          int32_t lineint = atoi(rangeLine.c_str());
296          line = (lineint > 1) ? lineint : 1;
297 
298          if ((Algorithm::isNumeric(rangeCount) == true) && (rangeCount != ""))
299          {
300             int32_t intcount = atoi(rangeCount.c_str());
301 
302             if (intcount >= lineint)
303             {
304                count = (intcount <= 0) ? 0 : static_cast<uint32_t>((intcount - lineint) + 1);
305             }
306             else
307             {
308                count = (intcount <= 0) ? 0 : static_cast<uint32_t>((lineint - intcount) + 1);
309                line = (intcount > 1) ? intcount : 1;
310             }
311          }
312       }
313    }
314 
315    // If there are multiple commands we need to run them all
316    // But in the case of alias we don't actually want to run the commands
317    if ((multipleCommands.Matches(fullCommand) == true) && (command != "alias"))
318    {
319       // There are multiple commands seperated by ';' we have to handle each one
320       while (multipleCommands.Capture(fullCommand, &matchString, &commandString) == true)
321       {
322          fullCommand = fullCommand.substr(matchString.size());
323 
324          if (blankCommand.Matches(commandString) == false)
325          {
326             ExecuteCommand(commandString);
327          }
328       }
329 
330       // The last command does not need a ';'
331       if (blankCommand.Matches(fullCommand) == false)
332       {
333          ExecuteCommand(fullCommand);
334       }
335    }
336    else if (aliasTable_.find(command) != aliasTable_.end())
337    {
338       // We are a single command alias, but this could actually be
339       // another alias, so ensure to look this up in the table to
340       // by calling this fucking again
341       ExecuteCommand(fullCommand);
342    }
343    else if (command != "")
344    {
345       // Just a normal command
346       Debug("Executing command :%u,%u %s %s", line, count, command.c_str(), arguments.c_str());
347       ExecuteCommand(line, count, command, arguments);
348    }
349    else if (line > 0)
350    {
351       screen_.ScrollTo(line - 1);
352 
353       if (count > 0)
354       {
355          screen_.ScrollTo(line + count - 2);
356       }
357    }
358 
359    return true;
360 }
361 
SetQueueCommands(bool enabled)362 void Command::SetQueueCommands(bool enabled)
363 {
364    queueCommands_ = enabled;
365 }
366 
367 
ExecuteQueuedCommands()368 void Command::ExecuteQueuedCommands()
369 {
370    for (auto command : commandQueue_)
371    {
372       Debug("Executing queued command :%u,%u %s %s", command.line, command.count, command.command.c_str(), command.arguments.c_str());
373       ExecuteCommand(command.line, command.count, command.command, command.arguments);
374    }
375 
376    commandQueue_.clear();
377 }
378 
RequiresConnection(std::string const & command)379 bool Command::RequiresConnection(std::string const & command)
380 {
381    return requiresConnection_[command];
382 }
383 
SupportsRange(std::string const & command)384 bool Command::SupportsRange(std::string const & command)
385 {
386    return supportsRange_[command];
387 }
388 
389 
Prompt() const390 char const * Command::Prompt() const
391 {
392    static char const CommandPrompt[] = ":";
393    return CommandPrompt;
394 }
395 
InputStringHandler(std::string input)396 bool Command::InputStringHandler(std::string input)
397 {
398    return ExecuteCommand(input);
399 }
400 
401 
ClearScreen(std::string const & arguments)402 void Command::ClearScreen(std::string const & arguments)
403 {
404    Player::ClearScreen();
405 }
406 
Pause(std::string const & arguments)407 void Command::Pause(std::string const & arguments)
408 {
409    Player::Pause();
410 }
411 
Play(std::string const & arguments)412 void Command::Play(std::string const & arguments)
413 {
414    std::vector<std::string> const args = SplitArguments(arguments);
415 
416    if (args.size() == 0)
417    {
418       if (screen_.GetActiveWindow() == Ui::Screen::Playlist)
419       {
420          int32_t line = screen_.Window(Ui::Screen::Playlist).CurrentLine();
421 
422          if (line >= 0)
423          {
424             Player::Play(line);
425          }
426       }
427       else
428       {
429          Player::Play(0);
430       }
431    }
432    else if (args.size() == 1)
433    {
434       int32_t SongId = atoi(args[0].c_str()) - 1;
435       SongId = (SongId == -1) ? 0 : SongId;
436 
437       if (SongId >= 0)
438       {
439          Player::Play(SongId);
440       }
441       else
442       {
443          ErrorString(ErrorNumber::InvalidParameter, "invalid song id");
444       }
445    }
446    else
447    {
448       ErrorString(ErrorNumber::InvalidParameter, "too many parameters");
449    }
450 }
451 
CheckConnected()452 bool Command::CheckConnected()
453 {
454    if (clientState_.Connected() == false)
455    {
456       ErrorString(ErrorNumber::ClientNoConnection);
457       return false;
458    }
459 
460    return true;
461 }
462 
Add(std::string const & arguments)463 void Command::Add(std::string const & arguments)
464 {
465    if (CheckConnected() == true)
466    {
467       screen_.Initialise(Ui::Screen::Playlist);
468 
469       if (arguments != "")
470       {
471          // Add based on a URI
472          client_.Add(arguments);
473       }
474       else
475       {
476          // Add according to current selection or range
477          screen_.ActiveWindow().AddLine(screen_.ActiveWindow().CurrentLine(), count_, false);
478       }
479 
480       client_.AddComplete();
481    }
482 }
483 
AddFile(std::string const & arguments)484 void Command::AddFile(std::string const & arguments)
485 {
486    if (arguments != "")
487    {
488       // Add a local filesystem song
489       Add("file://" + arguments);
490    }
491    else
492    {
493       Add("");
494    }
495 }
496 
AddAll(std::string const & arguments)497 void Command::AddAll(std::string const & arguments)
498 {
499    if (CheckConnected() == true)
500    {
501       screen_.Initialise(Ui::Screen::Playlist);
502       client_.AddAllSongs();
503       client_.AddComplete();
504    }
505 }
506 
Delete(std::string const & arguments)507 void Command::Delete(std::string const & arguments)
508 {
509    if (CheckConnected() == true)
510    {
511       screen_.Initialise(Ui::Screen::Playlist);
512 
513       std::vector<std::string> const args = SplitArguments(arguments);
514 
515       if (args.size() == 2)
516       {
517          // Delete a range of songs from pos1 to pos2
518          uint32_t pos1 = atoi(args[0].c_str()) - 1;
519          uint32_t pos2 = atoi(args[1].c_str()) - 1;
520 
521          client_.Delete(pos1, pos2 + 1);
522       }
523       else if (args.size() == 1)
524       {
525          // Delete the song at given position
526          client_.Delete(atoi(args[0].c_str()) - 1);
527       }
528       else
529       {
530          // Delete selected or range
531          screen_.ActiveWindow().DeleteLine(screen_.ActiveWindow().CurrentLine(), count_, false);
532       }
533    }
534 }
535 
DeleteAll(std::string const & arguments)536 void Command::DeleteAll(std::string const & arguments)
537 {
538    client_.Clear();
539    Main::Playlist().Clear();
540 }
541 
542 template <int Delta>
Seek(std::string const & arguments)543 void Command::Seek(std::string const & arguments)
544 {
545    uint32_t time = 0;
546    int min = 0, sec = 0;
547    size_t const pos = arguments.find_first_of(":");
548 
549    if (pos != std::string::npos)
550    {
551       std::string minutes = arguments.substr(0, pos);
552       std::string seconds = arguments.substr(pos + 1);
553       min = atoi(minutes.c_str());
554       sec = atoi(seconds.c_str());
555    }
556    else
557    {
558       sec = atoi(arguments.c_str());
559    }
560 
561    if (min < 0 || sec < 0)
562    {
563       ErrorString(ErrorNumber::InvalidParameter);
564    }
565    else
566    {
567       time = static_cast<uint32_t>( (min * 60) + sec );
568       Player::Seek(Delta * time);
569    }
570 }
571 
SeekTo(std::string const & arguments)572 void Command::SeekTo(std::string const & arguments)
573 {
574    uint32_t time = 0;
575    int min = 0, sec = 0;
576    size_t const pos = arguments.find_first_of(":");
577 
578    if (pos != std::string::npos)
579    {
580       std::string minutes = arguments.substr(0, pos);
581       std::string seconds = arguments.substr(pos + 1);
582       min = atoi(minutes.c_str()); // use int type so we can look for a
583                                    // negative seek value
584       sec = atoi(seconds.c_str());
585    }
586    else
587    {
588       sec = atoi(arguments.c_str());
589    }
590 
591    if (min < 0 || sec < 0)
592    {
593       ErrorString(ErrorNumber::InvalidParameter);
594    }
595    else
596    {
597       time = static_cast<uint32_t>( (min * 60) + sec );
598       Player::SeekTo(time);
599    }
600 }
601 
Quit(std::string const & arguments)602 void Command::Quit(std::string const & arguments)
603 {
604    if (settings_.Get(Setting::SingleQuit) == true)
605    {
606       QuitAll(arguments);
607    }
608    else
609    {
610       HideWindow(arguments);
611    }
612 }
613 
QuitAll(std::string const & arguments)614 void Command::QuitAll(std::string const & arguments)
615 {
616    if ((forceCommand_ == true) ||
617        (settings_.Get(Setting::StopOnQuit) == true))
618    {
619       Player::Stop();
620    }
621 
622    Player::Quit();
623 }
624 
Mute(std::string const & arguments)625 void Command::Mute(std::string const & arguments)
626 {
627    if (CheckConnected() == true)
628    {
629       if (arguments != "")
630       {
631          client_.SetMute((arguments.compare("on") == 0));
632       }
633    }
634 }
635 
Volume(std::string const & arguments)636 void Command::Volume(std::string const & arguments)
637 {
638    uint32_t const Vol = static_cast<uint32_t>(atoi(arguments.c_str()));
639 
640    if (Vol <= 100)
641    {
642       Player::Volume(Vol);
643    }
644    else
645    {
646       ErrorString(ErrorNumber::InvalidParameter);
647    }
648 }
649 
650 
ConnectionAttempt()651 bool Command::ConnectionAttempt()
652 {
653    return connectAttempt_;
654 }
655 
Connect(std::string const & arguments)656 void Command::Connect(std::string const & arguments)
657 {
658    // Ignore the connect command when starting up if -h/-p used
659    // on the command line
660    if (settings_.SkipConfigConnects() == false)
661    {
662       size_t   pos  = arguments.find_first_of(" ");
663       uint32_t port = 0;
664 
665       std::string hostname = arguments.substr(0, pos);
666 
667       if (pos != std::string::npos)
668       {
669          port = atoi(arguments.substr(pos + 1).c_str());
670       }
671 
672       client_.Connect(hostname, port);
673 
674       connectAttempt_ = true;
675    }
676 }
677 
Disconnect(std::string const & arguments)678 void Command::Disconnect(std::string const & arguments)
679 {
680    client_.Disconnect();
681 }
682 
Reconnect(std::string const & arguments)683 void Command::Reconnect(std::string const & arguments)
684 {
685    client_.Reconnect();
686 }
687 
Password(std::string const & password)688 void Command::Password(std::string const & password)
689 {
690    if (password == "")
691    {
692       screen_.PromptForPassword();
693    }
694    else
695    {
696       client_.Password(password.c_str());
697    }
698 }
699 
Echo(std::string const & echo)700 void Command::Echo(std::string const & echo)
701 {
702    Main::Console().Add(echo);
703 }
704 
EchoError(std::string const & arguments)705 void Command::EchoError(std::string const & arguments)
706 {
707    int32_t error = atoi(arguments.substr(0, arguments.find(" ")).c_str());
708    ErrorString(error, arguments.substr(arguments.find(" ") + 1));
709 }
710 
Sleep(std::string const & seconds)711 void Command::Sleep(std::string const & seconds)
712 {
713    vimpc_->ChangeMode('\n', "");
714    usleep(1000 * 1000 * atoi(seconds.c_str()));
715 }
716 
Substitute(std::string const & expression)717 void Command::Substitute(std::string const & expression)
718 {
719 #ifdef TAG_SUPPORT
720    typedef void (*EditFunction)(Mpc::Song *, std::string const &, char const *);
721    typedef std::map<std::string, EditFunction> OptionsMap;
722 
723    typedef std::string const & (Mpc::Song::*ReadFunction)() const;
724    typedef std::map<std::string, ReadFunction> InfoMap;
725 
726    static OptionsMap modifyFunctions;
727    static InfoMap    readFunctions;
728 
729    if (modifyFunctions.size() == 0)
730    {
731       modifyFunctions["a"] = &Mpc::Tag::SetArtist;
732       modifyFunctions["b"] = &Mpc::Tag::SetAlbum;
733       modifyFunctions["t"] = &Mpc::Tag::SetTitle;
734       modifyFunctions["n"] = &Mpc::Tag::SetTrack;
735 
736       readFunctions["a"] = &Mpc::Song::Artist;
737       readFunctions["b"] = &Mpc::Song::Album;
738       readFunctions["t"] = &Mpc::Song::Title;
739       readFunctions["n"] = &Mpc::Song::Track;
740    }
741 
742    std::string match, substitution, options;
743 
744    if (settings_.Get(Setting::LocalMusicDir) != "")
745    {
746       for (uint32_t i = 0; i < count_; ++i)
747       {
748          Mpc::Song * const song = screen_.GetSong(screen_.ActiveWindow().CurrentLine() + i);
749 
750          if (song != NULL)
751          {
752             std::string path = settings_.Get(Setting::LocalMusicDir) + "/" + song->URI();
753 
754             Regex::RE const split("^/([^/]*)/([^/]*)/([^/]*)\n?$");
755             split.Capture(expression.c_str(), &match, &substitution, &options);
756 
757             if (options.empty() == false)
758             {
759                InfoMap::iterator    it = readFunctions.find(options);
760                OptionsMap::iterator jt = modifyFunctions.find(options);
761 
762                if (it != readFunctions.end())
763                {
764                   ReadFunction readFunction = it->second;
765 
766                   if (match == "")
767                   {
768                      match = ".*";
769                   }
770 
771                   Regex::RE const check(match);
772                   std::string value = ((*song).*readFunction)();
773 
774                   if (check.Matches(value) == true)
775                   {
776                      if (jt != modifyFunctions.end())
777                      {
778                         check.Replace(substitution, value);
779                         EditFunction editFunction = jt->second;
780                         (*editFunction)(song, path, value.c_str());
781 
782                         if (settings_.Get(Setting::AutoUpdate) == true)
783                         {
784                            client_.Update(song->URI());
785                         }
786                      }
787                   }
788                }
789             }
790          }
791       }
792    }
793    else
794    {
795       ErrorString(ErrorNumber::NotSet, "local-music-dir");
796    }
797 #endif
798 }
799 
800 
801 template <bool ON>
Output(std::string const & arguments)802 void Command::Output(std::string const & arguments)
803 {
804    int32_t output = -1;
805    bool    rangeAllowed = false;
806 
807    screen_.Initialise(Ui::Screen::Outputs);
808 
809    if (arguments == "")
810    {
811       output = screen_.GetSelected(Ui::Screen::Outputs);
812       rangeAllowed = true;
813    }
814    else if (Algorithm::isNumeric(arguments.c_str()) == true)
815    {
816       output = atoi(arguments.c_str());
817    }
818    else
819    {
820       output = Player::FindOutput(arguments);
821    }
822 
823    if ((output < static_cast<int32_t>(Main::Outputs().Size())) && (output >= 0))
824    {
825       if (rangeAllowed == false)
826       {
827          client_.SetOutput(Main::Outputs().Get(output), (ON == true));
828       }
829       else
830       {
831          for (int i = 0; (i < static_cast<int>(count_)); ++i)
832          {
833             if ((output + i < static_cast<int32_t>(Main::Outputs().Size())) && (output + i >= 0))
834             {
835                client_.SetOutput(Main::Outputs().Get(output + i), (ON == true));
836             }
837          }
838       }
839    }
840    else
841    {
842       ErrorString(ErrorNumber::NoOutput);
843    }
844 }
845 
ToggleOutput(std::string const & arguments)846 void Command::ToggleOutput(std::string const & arguments)
847 {
848    int32_t output = -1;
849    bool    rangeAllowed = false;
850 
851    screen_.Initialise(Ui::Screen::Outputs);
852 
853    if (arguments == "")
854    {
855       // Toggle selected or range of outputs
856       output = screen_.GetSelected(Ui::Screen::Outputs);
857       rangeAllowed = true;
858    }
859    else if (Algorithm::isNumeric(arguments.c_str()) == true)
860    {
861       // Toggle a given output number
862       output = atoi(arguments.c_str());
863    }
864    else
865    {
866       // Toggle based on the output name
867       output = Player::FindOutput(arguments);
868    }
869 
870    if ((output < static_cast<int32_t>(Main::Outputs().Size())) && (output >= 0))
871    {
872       if (rangeAllowed == false)
873       {
874          Player::ToggleOutput(output);
875       }
876       else
877       {
878          for (uint32_t i = 0; (i < count_); ++i)
879          {
880             if (output + i < Main::Outputs().Size())
881             {
882                Player::ToggleOutput(output + i);
883             }
884          }
885       }
886    }
887    else
888    {
889       ErrorString(ErrorNumber::NoOutput);
890    }
891 }
892 
LoadPlaylist(std::string const & arguments)893 void Command::LoadPlaylist(std::string const & arguments)
894 {
895    if (arguments != "")
896    {
897       client_.LoadPlaylist(arguments);
898    }
899    else
900    {
901       ErrorString(ErrorNumber::NoParameter);
902    }
903 }
904 
SavePlaylist(std::string const & arguments)905 void Command::SavePlaylist(std::string const & arguments)
906 {
907    if (arguments != "")
908    {
909       client_.SavePlaylist(arguments);
910    }
911    else if (client_.HasLoadedPlaylist())
912    {
913       client_.SaveLoadedPlaylist();
914    }
915    else
916    {
917       ErrorString(ErrorNumber::NoPlaylistLoaded);
918    }
919 }
920 
ToPlaylist(std::string const & arguments)921 void Command::ToPlaylist(std::string const & arguments)
922 {
923    if (arguments != "")
924    {
925       screen_.ActiveWindow().Save(arguments);
926    }
927    else
928    {
929       ErrorString(ErrorNumber::NoParameter);
930    }
931 }
932 
Find(std::string const & arguments)933 void Command::Find(std::string const & arguments)
934 {
935    if (forceCommand_ == true)
936    {
937       client_.AddAllSearchResults();
938    }
939    else
940    {
941       client_.SearchResults(arguments);
942    }
943 }
944 
FindAny(std::string const & arguments)945 void Command::FindAny(std::string const & arguments)
946 {
947    client_.SearchAny(arguments);
948    Find("F:" + arguments);
949 }
950 
FindAlbum(std::string const & arguments)951 void Command::FindAlbum(std::string const & arguments)
952 {
953    client_.SearchAlbum(arguments);
954    Find("F:" + arguments);
955 }
956 
FindArtist(std::string const & arguments)957 void Command::FindArtist(std::string const & arguments)
958 {
959    client_.SearchArtist(arguments);
960    Find("F:" + arguments);
961 }
962 
FindGenre(std::string const & arguments)963 void Command::FindGenre(std::string const & arguments)
964 {
965    client_.SearchGenre(arguments);
966    Find("F:" + arguments);
967 }
968 
FindSong(std::string const & arguments)969 void Command::FindSong(std::string const & arguments)
970 {
971    client_.SearchSong(arguments);
972    Find("F:" + arguments);
973 }
974 
PrintMappings(std::string tabname)975 void Command::PrintMappings(std::string tabname)
976 {
977    Ui::Normal::MapNameTable mappings = normalMode_.Mappings();
978 
979    if (tabname != "")
980    {
981       mappings = normalMode_.WindowMappings(screen_.GetWindowFromName(tabname));
982    }
983 
984    if (mappings.size() > 0)
985    {
986       PagerWindow * const pager = screen_.GetPagerWindow();
987       pager->Clear();
988 
989       for (auto mapping : mappings)
990       {
991          pager->AddLine(mapping.first + "   " + mapping.second);
992       }
993 
994       screen_.ShowPagerWindow();
995    }
996    else
997    {
998       ErrorString(ErrorNumber::NoSuchMapping);
999    }
1000 }
1001 
1002 
Map(std::string const & arguments)1003 void Command::Map(std::string const & arguments)
1004 {
1005    if ((arguments.find(" ") != std::string::npos))
1006    {
1007       std::string const key     = arguments.substr(0, arguments.find(" "));
1008       std::string const mapping = arguments.substr(arguments.find(" ") + 1);
1009       normalMode_.Map(key, mapping);
1010    }
1011    else if (arguments == "")
1012    {
1013       PrintMappings();
1014    }
1015 }
1016 
Unmap(std::string const & arguments)1017 void Command::Unmap(std::string const & arguments)
1018 {
1019    normalMode_.Unmap(arguments);
1020 }
1021 
TabMap(std::string const & arguments)1022 void Command::TabMap(std::string const & arguments)
1023 {
1024    std::string range, tabname, args;
1025    SplitCommand(arguments, range, tabname, args);
1026 
1027    TabMap(tabname, args);
1028 }
1029 
TabMap(std::string const & tabname,std::string const & arguments)1030 void Command::TabMap(std::string const & tabname, std::string const & arguments)
1031 {
1032    std::vector<std::string> args = SplitArguments(arguments);
1033 
1034    if (args.size() == 2)
1035    {
1036       std::string const key     = args[0];
1037       std::string const mapping = args[1];
1038       normalMode_.WindowMap(screen_.GetWindowFromName(tabname), key, mapping);
1039    }
1040    else if (args.size() == 0)
1041    {
1042       PrintMappings(tabname);
1043    }
1044    else
1045    {
1046       Error(ErrorNumber::InvalidParameter, "Unexpected Arguments");
1047    }
1048 }
1049 
1050 
TabUnmap(std::string const & arguments)1051 void Command::TabUnmap(std::string const & arguments)
1052 {
1053    std::vector<std::string> args = SplitArguments(arguments);
1054 
1055    if (args.size() == 2)
1056    {
1057       std::string const window = args[0];
1058       std::string const key    = args[1];
1059       normalMode_.WindowUnmap(screen_.GetWindowFromName(window), key);
1060    }
1061 }
1062 
WindowMap(std::string const & arguments)1063 void Command::WindowMap(std::string const & arguments)
1064 {
1065    TabMap(screen_.GetNameFromWindow(screen_.GetActiveWindow()), arguments);
1066 }
1067 
WindowUnmap(std::string const & arguments)1068 void Command::WindowUnmap(std::string const & arguments)
1069 {
1070    normalMode_.WindowUnmap(screen_.GetActiveWindow(), arguments);
1071 }
1072 
Random(std::string const & arguments)1073 void Command::Random(std::string const & arguments)
1074 {
1075    if (arguments.empty() == false)
1076    {
1077       bool const value = (arguments.compare("on") == 0);
1078       Player::SetRandom(value);
1079    }
1080    else
1081    {
1082       Player::ToggleRandom();
1083    }
1084 }
1085 
Repeat(std::string const & arguments)1086 void Command::Repeat(std::string const & arguments)
1087 {
1088    if (arguments.empty() == false)
1089    {
1090       bool const value = (arguments.compare("on") == 0);
1091       Player::SetRepeat(value);
1092    }
1093    else
1094    {
1095       Player::ToggleRepeat();
1096    }
1097 }
1098 
Single(std::string const & arguments)1099 void Command::Single(std::string const & arguments)
1100 {
1101    if (arguments.empty() == false)
1102    {
1103       bool const value = (arguments.compare("on") == 0);
1104       Player::SetSingle(value);
1105    }
1106    else
1107    {
1108       Player::ToggleSingle();
1109    }
1110 }
1111 
Consume(std::string const & arguments)1112 void Command::Consume(std::string const & arguments)
1113 {
1114    if (arguments.empty() == false)
1115    {
1116       bool const value = (arguments.compare("on") == 0);
1117       Player::SetConsume(value);
1118    }
1119    else
1120    {
1121       Player::ToggleConsume();
1122    }
1123 }
1124 
Crossfade(std::string const & arguments)1125 void Command::Crossfade(std::string const & arguments)
1126 {
1127    if (arguments.empty() == false)
1128    {
1129       Player::SetCrossfade(static_cast<uint32_t>(atoi(arguments.c_str())));
1130    }
1131    else
1132    {
1133       Player::ToggleCrossfade();
1134    }
1135 }
1136 
Shuffle(std::string const & arguments)1137 void Command::Shuffle(std::string const & arguments)
1138 {
1139    Player::Shuffle();
1140 }
1141 
Move(std::string const & arguments)1142 void Command::Move(std::string const & arguments)
1143 {
1144    screen_.Initialise(Ui::Screen::Playlist);
1145 
1146    if ((arguments.find(" ") != std::string::npos))
1147    {
1148       int32_t position1 = atoi(arguments.substr(0, arguments.find(" ")).c_str());
1149       int32_t position2 = atoi(arguments.substr(arguments.find(" ") + 1).c_str());
1150 
1151       if (position1 > static_cast<int32_t>(screen_.ActiveWindow().BufferSize() - 1))
1152       {
1153          position1 = Main::Playlist().Size();
1154       }
1155       else if (position1 <= 1)
1156       {
1157          position1 = 1;
1158       }
1159 
1160       if (position2 > static_cast<int32_t>(screen_.ActiveWindow().BufferSize() - 1))
1161       {
1162          position2 = Main::Playlist().Size();
1163       }
1164       else if (position2 <= 1)
1165       {
1166          position2 = 1;
1167       }
1168 
1169       if ((Main::Playlist().Size() > 0) &&
1170           (position1 <= static_cast<int32_t>(Main::Playlist().Size())) &&
1171           (position2 <= static_cast<int32_t>(Main::Playlist().Size())))
1172       {
1173          client_.Move(position1 - 1, position2 - 1);
1174 
1175          Mpc::Song * song = Main::Playlist().Get(position1 - 1);
1176          Main::Playlist().Remove(position1 - 1, 1);
1177          Main::Playlist().Add(song, position2 - 1);
1178          screen_.Update();
1179       }
1180       else
1181       {
1182          // \TODO error here!
1183       }
1184    }
1185    else
1186    {
1187       Error(ErrorNumber::InvalidParameter, "Expected two arguments");
1188    }
1189 }
1190 
NoHighlightSearch(std::string const & arguments)1191 void Command::NoHighlightSearch(std::string const & arguments)
1192 {
1193    search_.SetHighlightSearch(false);
1194 }
1195 
Normal(std::string const & arguments)1196 void Command::Normal(std::string const & arguments)
1197 {
1198    vimpc_->ChangeMode('\n', "");
1199    normalMode_.Execute(arguments);
1200 }
1201 
Swap(std::string const & arguments)1202 void Command::Swap(std::string const & arguments)
1203 {
1204    screen_.Initialise(Ui::Screen::Playlist);
1205 
1206    if ((arguments.find(" ") != std::string::npos))
1207    {
1208       std::string const position1 = arguments.substr(0, arguments.find(" "));
1209       std::string const position2 = arguments.substr(arguments.find(" ") + 1);
1210       client_.Swap(atoi(position1.c_str()) - 1, atoi(position2.c_str()) - 1);
1211    }
1212    else
1213    {
1214       Error(ErrorNumber::InvalidParameter, "Expected two arguments");
1215    }
1216 }
1217 
Redraw(std::string const & arguments)1218 void Command::Redraw(std::string const & arguments)
1219 {
1220    Player::Redraw();
1221 }
1222 
Stop(std::string const & arguments)1223 void Command::Stop(std::string const & arguments)
1224 {
1225    Player::Stop();
1226 }
1227 
Rescan(std::string const & arguments)1228 void Command::Rescan(std::string const & arguments)
1229 {
1230    client_.Rescan(arguments);
1231 }
1232 
Update(std::string const & arguments)1233 void Command::Update(std::string const & arguments)
1234 {
1235    client_.Update(arguments);
1236 }
1237 
1238 
1239 //Implementation of skipping functions
1240 template <Ui::Player::Skip SKIP>
SkipSong(std::string const & arguments)1241 void Command::SkipSong(std::string const & arguments)
1242 {
1243    uint32_t count = atoi(arguments.c_str());
1244    count = (count == 0) ? 1 : count;
1245    Player::SkipSong(SKIP, count);
1246 }
1247 
1248 
1249 //Implementation of window change function
1250 template <Ui::Screen::MainWindow MAINWINDOW>
SetActiveAndVisible(std::string const & arguments)1251 void Command::SetActiveAndVisible(std::string const & arguments)
1252 {
1253    if (currentLine_ != -1)
1254    {
1255       screen_.ScrollTo(currentLine_);
1256    }
1257 
1258    screen_.SetActiveAndVisible(static_cast<int32_t>(MAINWINDOW));
1259 
1260    if (line_ != -1)
1261    {
1262       screen_.ScrollTo(line_);
1263    }
1264 }
1265 
1266 template <Command::Location LOCATION>
ChangeToWindow(std::string const & arguments)1267 void Command::ChangeToWindow(std::string const & arguments)
1268 {
1269    int32_t active = 0;
1270 
1271    switch (LOCATION)
1272    {
1273       case First:
1274          active = 0;
1275          break;
1276 
1277       case Next:
1278          active = ((screen_.GetActiveWindowIndex() + 1) %
1279                    screen_.VisibleWindows());
1280          break;
1281 
1282       case Previous:
1283          active = screen_.GetActiveWindowIndex() - 1;
1284          active = (active < 0) ? screen_.VisibleWindows() - 1 : active;
1285          break;
1286 
1287       case Last:
1288          active = screen_.VisibleWindows() - 1;
1289          break;
1290 
1291       case LocationCount:
1292       default:
1293          break;
1294    }
1295 
1296    screen_.SetActiveWindow(active);
1297 }
1298 
HideWindow(std::string const & arguments)1299 void Command::HideWindow(std::string const & arguments)
1300 {
1301    if (arguments == "")
1302    {
1303       screen_.SetVisible(screen_.GetActiveWindow(), false);
1304    }
1305    else
1306    {
1307       int32_t window = screen_.GetWindowFromName(arguments);
1308 
1309       if (window != Ui::Screen::Unknown)
1310       {
1311          screen_.SetVisible(window, false);
1312       }
1313       else
1314       {
1315          ErrorString(ErrorNumber::TabDoesNotExist, arguments);
1316       }
1317    }
1318 
1319    if (screen_.VisibleWindows() == 0)
1320    {
1321       QuitAll("");
1322    }
1323 }
1324 
MoveWindow(std::string const & arguments)1325 void Command::MoveWindow(std::string const & arguments)
1326 {
1327    screen_.MoveWindow(atoi(arguments.c_str()));
1328 }
1329 
RenameWindow(std::string const & arguments)1330 void Command::RenameWindow(std::string const & arguments)
1331 {
1332    if ((arguments.find(" ") != std::string::npos))
1333    {
1334       std::string oldname = arguments.substr(0, arguments.find(" "));
1335       std::string newname = arguments.substr(arguments.find(" ") + 1);
1336 
1337       int32_t id = screen_.GetWindowFromName(oldname);
1338 
1339       if (id != Ui::Screen::Unknown)
1340       {
1341          if (screen_.GetWindowFromName(newname) == Ui::Screen::Unknown)
1342          {
1343             screen_.Window(id).SetName(newname);
1344          }
1345          else
1346          {
1347             ErrorString(ErrorNumber::NameInUse, newname);
1348          }
1349       }
1350       else
1351       {
1352          ErrorString(ErrorNumber::TabDoesNotExist, oldname);
1353       }
1354    }
1355    else
1356    {
1357       if (screen_.GetWindowFromName(arguments) == Ui::Screen::Unknown)
1358       {
1359          screen_.ActiveWindow().SetName(arguments);
1360       }
1361       else
1362       {
1363          ErrorString(ErrorNumber::NameInUse, arguments);
1364       }
1365    }
1366 }
1367 
SetColour(std::string const & arguments)1368 void Command::SetColour(std::string const & arguments)
1369 {
1370    std::string range, property, args;
1371    SplitCommand(arguments, range, property, args);
1372 
1373    SetColour(property, arguments);
1374 }
1375 
SetColour(std::string const & property,std::string const & arguments)1376 void Command::SetColour(std::string const & property, std::string const & arguments)
1377 {
1378    std::vector<std::string> args = SplitArguments(arguments);
1379 
1380    if (args.size() == 3)
1381    {
1382       settings_.SetColour(property, args[1], args[2]);
1383    }
1384    else
1385    {
1386       ErrorString(ErrorNumber::NoParameter, arguments);
1387    }
1388 }
1389 
1390 template <Ui::Command::ClientFunction FUNC>
DebugClient(std::string const & arguments)1391 void Command::DebugClient(std::string const & arguments)
1392 {
1393    Ui::Command::ClientFunction func = FUNC;
1394    (client_.*func)();
1395 }
1396 
TestExecutor()1397 void Command::TestExecutor()
1398 {
1399    while (Running == true)
1400    {
1401       UniqueLock<Mutex> Lock(QueueMutex);
1402 
1403       if ((Queue.empty() == false) ||
1404           (ConditionWait(Condition, Lock, 250) != false))
1405       {
1406          if (Queue.empty() == false)
1407          {
1408             std::string arguments = Queue.front();
1409             Queue.pop_front();
1410             Lock.unlock();
1411 
1412 #ifdef HAVE_TEST_H
1413             Main::Tester::Instance().Vimpc->HandleUserEvents(false);
1414 
1415             CPPUNIT_NS::TestResult testresult;
1416             CPPUNIT_NS::TestResultCollector collectedresults;
1417             testresult.addListener(&collectedresults);
1418             CPPUNIT_NS::TestRunner testrunner;
1419 
1420             if ((arguments == "") || (arguments == "all"))
1421             {
1422                Main::TestConsole().Add("Running all tests...");
1423                testrunner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
1424             }
1425             else
1426             {
1427                Main::TestConsole().Add("Running test " + arguments + "...");
1428                CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry(arguments);
1429                testrunner.addTest(registry.makeTest());
1430             }
1431             testrunner.run(testresult);
1432 
1433             std::stringstream outStream;
1434             CPPUNIT_NS::TextOutputter textoutput(&collectedresults, outStream);
1435             textoutput.write();
1436 
1437             std::string output;
1438 
1439             while (!outStream.eof())
1440             {
1441                std::getline(outStream, output);
1442 
1443                if (output != "")
1444                {
1445                   EventData Data; Data.name = output;
1446                   Main::Vimpc::CreateEvent(Event::TestResult, Data);
1447                   Main::Vimpc::CreateEvent(Event::Repaint,   Data);
1448                }
1449             }
1450 
1451             Main::Tester::Instance().Vimpc->HandleUserEvents(true);
1452    #endif
1453          }
1454       }
1455    }
1456 }
1457 
Test(std::string const & arguments)1458 void Command::Test(std::string const & arguments)
1459 {
1460    UniqueLock<Mutex> Lock(QueueMutex);
1461    Queue.push_back(arguments);
1462    Condition.notify_all();
1463 }
1464 
TestInputRandom(std::string const & arguments)1465 void Command::TestInputRandom(std::string const & arguments)
1466 {
1467    int count = atoi(arguments.c_str());
1468    screen_.EnableRandomInput(count);
1469 }
1470 
TestInputSequence(std::string const & arguments)1471 void Command::TestInputSequence(std::string const & arguments)
1472 {
1473    for (int i = arguments.size() - 1; i >= 0; --i)
1474    {
1475       ungetch(arguments[i]);
1476    }
1477 }
1478 
TestScreen(std::string const & arguments)1479 void Command::TestScreen(std::string const & arguments)
1480 {
1481    screen_.ActiveWindow().ScrollTo(65535);
1482    screen_.ScrollTo(screen_.ActiveWindow().Playlist(0));
1483    screen_.ScrollTo(screen_.ActiveWindow().Current());
1484    screen_.ActiveWindow().ScrollTo(0);
1485    screen_.Invalidate(screen_.GetActiveWindow());
1486 }
1487 
TestResize(std::string const & arguments)1488 void Command::TestResize(std::string const & arguments)
1489 {
1490    std::vector<std::string> args = SplitArguments(arguments);
1491 
1492    if (args.size() == 2)
1493    {
1494       screen_.Resize(true, atoi(args[0].c_str()), atoi(args[1].c_str()));
1495    }
1496 }
1497 
1498 
ExecuteCommand(uint32_t line,uint32_t count,std::string command,std::string const & arguments)1499 bool Command::ExecuteCommand(uint32_t line, uint32_t count, std::string command, std::string const & arguments)
1500 {
1501    Regex::RE const forceCheck("^.*!$");
1502 
1503    forceCommand_ = (forceCheck.Matches(command));
1504 
1505    if (forceCommand_ == true)
1506    {
1507       command = command.substr(0, command.length() - 1);
1508    }
1509 
1510    // If we can't find the exact command, look for a unique command that starts
1511    // with the input command
1512    std::string commandToExecute  = command;
1513    bool        matchingCommand   = (commandTable_.find(commandToExecute) != commandTable_.end());
1514    uint32_t    validCommandCount = 0;
1515 
1516    if (matchingCommand == false)
1517    {
1518       for (auto cmd : commandTable_)
1519       {
1520          if (command.compare(cmd.first.substr(0, command.length())) == 0)
1521          {
1522             ++validCommandCount;
1523             commandToExecute = cmd.first;
1524          }
1525       }
1526 
1527       matchingCommand = (validCommandCount == 1);
1528    }
1529 
1530    // If we have found a command execute it, with \p arguments
1531    if (matchingCommand == true)
1532    {
1533       // This is a hack for ranges on the setactive command
1534       // so that we return the scroll for the window before the command
1535       // is executed and change it for the new one
1536       currentLine_ = -1;
1537       line_        = -1;
1538 
1539       // If a range was specified and supported scroll to the line
1540       // corresponding to the first part of the range
1541       // \TODO this may break if it is done when we are in visual mode
1542       if ((SupportsRange(commandToExecute) == true) && (line > 0))
1543       {
1544          currentLine_ = screen_.GetActiveSelected();
1545          line_        = line - 1;
1546          screen_.ScrollTo(line - 1);
1547       }
1548       else if (line > 0)
1549       {
1550          ErrorString(ErrorNumber::NoRangeAllowed, command);
1551          return true;
1552       }
1553 
1554       if ((RequiresConnection(commandToExecute) == false) || (queueCommands_ == false) || (clientState_.Connected() == true))
1555       {
1556          count_ = (count <= 0) ? 1 : count;
1557          CommandTable::const_iterator const it = commandTable_.find(commandToExecute);
1558          CommandFunction const commandFunction = it->second;
1559          (*this.*commandFunction)(arguments);
1560       }
1561       else if ((RequiresConnection(commandToExecute) == true) && ((queueCommands_ == true) && (clientState_.Connected() == false)))
1562       {
1563          CommandArgs commandArgs;
1564          commandArgs.line      = line;
1565          commandArgs.count     = count;
1566          commandArgs.command   = commandToExecute;
1567          commandArgs.arguments = arguments;
1568          commandQueue_.push_back(commandArgs);
1569       }
1570 
1571       currentLine_ = -1;
1572       line_        = -1;
1573 
1574       if ((SupportsRange(commandToExecute) == true) && (count > 0) && (line > 0))
1575       {
1576          screen_.ScrollTo(line + count - 2);
1577       }
1578    }
1579    else if (validCommandCount > 1)
1580    {
1581       ErrorString(ErrorNumber::CommandAmbiguous, command);
1582    }
1583    else
1584    {
1585       ErrorString(ErrorNumber::CommandNonexistant, command);
1586    }
1587 
1588    forceCommand_ = false;
1589    return true;
1590 }
1591 
SplitCommand(std::string const & input,std::string & range,std::string & command,std::string & arguments)1592 void Command::SplitCommand(std::string const & input, std::string & range, std::string & command, std::string & arguments)
1593 {
1594    Regex::RE const split("^([\\d,]*)?([^ /]*) ?(/?[^\n]*)\n?$");
1595    split.Capture(input.c_str(), &range, &command, &arguments);
1596 }
1597 
SplitArguments(std::string const & input,char delimeter)1598 std::vector<std::string> Command::SplitArguments(std::string const & input, char delimeter)
1599 {
1600    std::vector<std::string> arguments;
1601    std::string argument;
1602 
1603    std::stringstream stream(input);
1604 
1605    while (stream.eof() == false)
1606    {
1607       std::getline(stream, argument, delimeter);
1608 
1609       if (argument != "")
1610       {
1611          arguments.push_back(argument);
1612       }
1613    }
1614 
1615    return arguments;
1616 }
1617 
Set(std::string const & arguments)1618 void Command::Set(std::string const & arguments)
1619 {
1620    if (arguments != "")
1621    {
1622       settings_.Set(arguments);
1623    }
1624    else
1625    {
1626       PagerWindow * pager = screen_.GetPagerWindow();
1627       pager->Clear();
1628 
1629       for (auto setting : settings_.AvailableSettings())
1630       {
1631          if (((setting.size() < 2) || (setting.substr(0, 2) != "no")) &&
1632              (settings_.Get<bool>(setting) == true))
1633          {
1634             pager->AddLine(setting);
1635          }
1636          else if (settings_.Get<std::string>(setting) != "")
1637          {
1638             std::string const value = settings_.Get<std::string>(setting);
1639             pager->AddLine(setting + "=" + value);
1640          }
1641       }
1642 
1643       screen_.ShowPagerWindow();
1644    }
1645 }
1646 
Mpc(std::string const & arguments)1647 void Command::Mpc(std::string const & arguments)
1648 {
1649    static uint32_t const bufferSize = 512;
1650    char   buffer[bufferSize];
1651    char   port[8];
1652 
1653    // \todo redirect stderr results into the console window too
1654    snprintf(port, 8, "%u", clientState_.Port());
1655 
1656    // Ensure that we use the same mpd_host and port for mpc that
1657    // we are using but still allow the person running the command
1658    // to do -h and -p flags
1659    std::string const command("MPD_HOST=" + clientState_.Hostname() + " MPD_PORT=" + std::string(port) +
1660                              " mpc " + arguments + " 2>&1");
1661 
1662    Main::Console().Add("> mpc " + arguments);
1663 
1664    FILE * const mpcOutput = popen(command.c_str(), "r");
1665 
1666    if (mpcOutput != NULL)
1667    {
1668       while (fgets(buffer, bufferSize - 1, mpcOutput) != NULL)
1669       {
1670          Main::Console().Add(buffer);
1671       }
1672 
1673       pclose(mpcOutput);
1674    }
1675    else
1676    {
1677       ErrorString(ErrorNumber::ExternalProgramError, "mpc");
1678    }
1679 }
1680 
Alias(std::string const & input)1681 void Command::Alias(std::string const & input)
1682 {
1683    std::string range, command, arguments;
1684 
1685    SplitCommand(input, range, command, arguments);
1686 
1687    aliasTable_[command] = arguments;
1688 }
1689 
Unalias(std::string const & input)1690 void Command::Unalias(std::string const & input)
1691 {
1692    std::string range, command, arguments;
1693 
1694    SplitCommand(input, range, command, arguments);
1695 
1696    if (aliasTable_.find(command) != aliasTable_.end())
1697    {
1698       aliasTable_.erase(command);
1699    }
1700 }
1701 
ResetTabCompletion(int input)1702 void Command::ResetTabCompletion(int input)
1703 {
1704    if (input != '\t')
1705    {
1706       initTabCompletion_ = true;
1707    }
1708 }
1709 
1710 
TabComplete(std::string const & command)1711 std::string Command::TabComplete(std::string const & command)
1712 {
1713    static std::string tabStart(command);
1714 
1715    if (initTabCompletion_ == true)
1716    {
1717       tabStart = command;
1718       addTable_.clear();
1719       loadTable_.clear();
1720 
1721       screen_.Initialise(Ui::Screen::Directory);
1722       screen_.Initialise(Ui::Screen::Lists);
1723 
1724       for (uint32_t i = 0; i < Main::MpdLists().Size(); ++i)
1725       {
1726          loadTable_.push_back("load " + Main::MpdLists().Get(i).name_);
1727          loadTable_.push_back("edit " + Main::MpdLists().Get(i).name_);
1728       }
1729 
1730       std::vector<std::string> const & paths = Main::Directory().Paths();
1731 
1732       for (uint32_t i = 0; i < paths.size(); ++i)
1733       {
1734          addTable_.push_back("add " + paths[i]);
1735       }
1736    }
1737 
1738    if (tabStart.find("set ") == 0)
1739    {
1740       return TabComplete(tabStart, settingsTable_, TabCompletionMatch<std::string>(tabStart));
1741    }
1742    else if ((tabStart.find("load ") == 0) || (tabStart.find("edit ") == 0))
1743    {
1744       return TabComplete(tabStart, loadTable_, TabCompletionMatch<std::string>(tabStart));
1745    }
1746    else if (tabStart.find("add ") == 0)
1747    {
1748       return TabComplete(tabStart, addTable_, TabCompletionMatch<std::string>(tabStart));
1749    }
1750    else
1751    {
1752       return TabComplete(tabStart, commandTable_, TabCompletionMatch<CommandFunction>(tabStart));
1753    }
1754 }
1755 
1756 
1757 template <typename T, typename U>
TabComplete(std::string const & tabStart,T const & table,TabCompletionMatch<U> const & completor)1758 std::string Command::TabComplete(std::string const & tabStart, T const & table, TabCompletionMatch<U> const & completor)
1759 {
1760    static typename T::const_iterator tabIterator(table.begin());
1761 
1762    std::string result;
1763 
1764    if (initTabCompletion_ == true)
1765    {
1766       initTabCompletion_ = false;
1767       tabIterator        = table.begin();
1768    }
1769 
1770    tabIterator = find_if(tabIterator, table.end(), completor);
1771 
1772    if (tabIterator != table.end())
1773    {
1774       result = TabGetCompletion(tabIterator);
1775       ++tabIterator;
1776    }
1777    else
1778    {
1779       initTabCompletion_ = true;
1780       result             = tabStart;
1781    }
1782 
1783    return result;
1784 }
1785 
TabGetCompletion(CommandTable::const_iterator it)1786 std::string Command::TabGetCompletion(CommandTable::const_iterator it)
1787 {
1788    return it->first;
1789 }
1790 
TabGetCompletion(TabCompTable::const_iterator it)1791 std::string Command::TabGetCompletion(TabCompTable::const_iterator it)
1792 {
1793    return *it;
1794 }
1795 
1796 /* vim: set sw=3 ts=3: */
1797