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 ®istry = 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