1 /*
2    Vimpc
3    Copyright (C) 2010 - 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    mpdclient.cpp - provides interaction with the music player daemon
19    */
20 
21 #include "clientstate.hpp"
22 
23 #include "mode/mode.hpp"
24 #include "mpdclient.hpp"
25 #include "assert.hpp"
26 #include "buffers.hpp"
27 #include "events.hpp"
28 #include "screen.hpp"
29 #include "settings.hpp"
30 #include "vimpc.hpp"
31 
32 using namespace Mpc;
33 
34 // Mpc::Client Implementation
ClientState(Main::Vimpc * vimpc,Main::Settings & settings,Ui::Screen & screen)35 ClientState::ClientState(Main::Vimpc * vimpc, Main::Settings & settings, Ui::Screen & screen) :
36    vimpc_                (vimpc),
37    settings_             (settings),
38    screen_               (screen),
39 
40    connected_            (false),
41    hostname_             (""),
42    port_                 (0),
43    timeSinceUpdate_      (0),
44    timeSinceSong_        (0),
45 
46    volume_               (-1),
47    mute_                 (false),
48    updating_             (false),
49    random_               (false),
50    repeat_               (false),
51    single_               (false),
52    consume_              (false),
53    crossfade_            (false),
54    running_              (true),
55    newSong_              (false),
56    scrollingStatus_      (false),
57    crossfadeTime_        (0),
58    elapsed_              (0),
59    titlePos_             (0),
60    waitTime_             (150),
61 
62    currentSong_          (NULL),
63    currentSongId_        (-1),
64    totalNumberOfSongs_   (0),
65    currentState_         ("Disconnected"),
66    lastTitleStr_         ("")
67 {
68    Main::Vimpc::EventHandler(Event::Connected, [this] (EventData const & Data)
69    {
70       this->connected_ = true;
71       DisplaySongInformation();
72    });
73 
74    Main::Vimpc::EventHandler(Event::Disconnected, [this] (EventData const & Data)
75    {
76       this->connected_          = false;
77       this->volume_             = -1;
78       this->mute_               = false;
79       this->updating_           = false;
80       this->random_             = false;
81       this->repeat_             = false;
82       this->single_             = false;
83       this->consume_            = false;
84       this->crossfade_          = false;
85       this->crossfadeTime_      = 0;
86       this->currentSongId_      = -1;
87       this->currentSongURI_     = "";
88       this->totalNumberOfSongs_ = 0;
89 
90       if (currentSong_ != NULL)
91       {
92          mpd_song_free(currentSong_);
93          currentSong_ = NULL;
94       }
95 
96       DisplaySongInformation();
97       EventData EData;
98       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
99    });
100 
101    Main::Vimpc::EventHandler(Event::ClearDatabase, [this] (EventData const & Data)
102    {
103       DisplaySongInformation();
104    });
105 
106    Main::Vimpc::EventHandler(Event::DisplaySongInfo, [this] (EventData const & Data)
107    {
108       DisplaySongInformation();
109    });
110 
111    Main::Vimpc::EventHandler(Event::ChangeHost, [this] (EventData const & Data)
112    {
113       this->hostname_ = Data.hostname;
114       this->port_     = Data.port;
115    });
116 
117    Main::Vimpc::EventHandler(Event::CurrentSongId, [this] (EventData const & Data)
118    {
119       this->currentSongId_ = Data.id;
120       DisplaySongInformation();
121 
122       Main::Vimpc::CreateEvent(Event::Repaint, Data);
123    });
124 
125    Main::Vimpc::EventHandler(Event::Elapsed, [this] (EventData const & Data)
126    {
127       this->elapsed_ = Data.value;
128       DisplaySongInformation();
129    });
130 
131    Main::Vimpc::EventHandler(Event::CurrentSong, [this] (EventData const & Data)
132    {
133       if (currentSong_ != NULL)
134       {
135          mpd_song_free(currentSong_);
136          currentSong_ = NULL;
137       }
138 
139       currentSong_    = Data.currentSong;
140       currentSongURI_ = (currentSong_ != NULL) ? mpd_song_get_uri(currentSong_) : "";
141       DisplaySongInformation();
142 
143       Main::Vimpc::CreateEvent(Event::Repaint, Data);
144    });
145 
146    Main::Vimpc::EventHandler(Event::Random, [this] (EventData const & Data)
147    {
148       this->random_ = Data.state;
149       EventData EData;
150       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
151    });
152 
153    Main::Vimpc::EventHandler(Event::Consume, [this] (EventData const & Data)
154    {
155       this->consume_ = Data.state;
156       EventData EData;
157       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
158    });
159 
160    Main::Vimpc::EventHandler(Event::Repeat, [this] (EventData const & Data)
161    {
162       this->repeat_ = Data.state;
163       EventData EData;
164       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
165    });
166 
167    Main::Vimpc::EventHandler(Event::Single, [this] (EventData const & Data)
168    {
169       this->single_ = Data.state;
170       EventData EData;
171       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
172    });
173 
174    Main::Vimpc::EventHandler(Event::Mute, [this] (EventData const & Data)
175    {
176       this->mute_ = Data.state;
177       EventData EData;
178       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
179    });
180 
181    Main::Vimpc::EventHandler(Event::Crossfade, [this] (EventData const & Data)
182    {
183       this->crossfade_ = Data.state;
184       EventData EData;
185       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
186    });
187 
188    Main::Vimpc::EventHandler(Event::CrossfadeTime, [this] (EventData const & Data)
189    { this->crossfadeTime_ = Data.value; });
190 
191    Main::Vimpc::EventHandler(Event::TotalSongCount, [this] (EventData const & Data)
192    {
193       this->totalNumberOfSongs_ = Data.count;
194       EventData EData;
195       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
196    });
197 
198    Main::Vimpc::EventHandler(Event::Update, [this] (EventData const & Data)
199    {
200       this->updating_ = true;
201       EventData EData;
202       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
203    });
204 
205    Main::Vimpc::EventHandler(Event::UpdateComplete, [this] (EventData const & Data)
206    {
207       this->updating_ = false;
208       EventData EData;
209       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
210    });
211 
212    Main::Vimpc::EventHandler(Event::Volume, [this] (EventData const & Data)
213    {
214       this->volume_ = Data.value;
215       EventData EData;
216       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
217    });
218 
219    Main::Vimpc::EventHandler(Event::CurrentState, [this] (EventData const & Data)
220    {
221       this->currentState_ = Data.clientstate;
222       EventData EData;
223       Main::Vimpc::CreateEvent(Event::StatusUpdate, EData);
224    });
225 
226    updateThread_ = std::thread([this]() {
227       while (this->running_)
228       {
229          std::this_thread::sleep_for(std::chrono::milliseconds(this->waitTime_));
230 
231          if (this->newSong_)
232          {
233             this->titlePos_ = 0;
234 
235             if (this->scrollingStatus_  == true)
236             {
237                this->waitTime_ = 2500;
238             }
239 
240             this->newSong_  = false;
241          }
242          else if (this->CurrentState() != "Stopped")
243          {
244             if (this->scrollingStatus_ == true)
245             {
246                this->titlePos_++;
247                EventData EData;
248                Main::Vimpc::CreateEvent(Event::DisplaySongInfo, EData);
249             }
250             this->waitTime_ = 150;
251          }
252          else
253          {
254             this->titlePos_ = 0;
255             this->waitTime_ = 150;
256          }
257       }
258    });
259 }
260 
~ClientState()261 ClientState::~ClientState()
262 {
263    running_ = false;
264    updateThread_.join();
265 }
266 
Hostname()267 std::string ClientState::Hostname()
268 {
269    return hostname_;
270 }
271 
Port()272 uint16_t ClientState::Port()
273 {
274    return port_;
275 }
276 
Connected() const277 bool ClientState::Connected() const
278 {
279    return connected_;
280 }
281 
IsSocketFile() const282 bool ClientState::IsSocketFile() const
283 {
284    return ((hostname_.length() > 1) && (hostname_[0] == '/'));
285 }
286 
Random() const287 bool ClientState::Random() const
288 {
289    return random_;
290 }
291 
Single() const292 bool ClientState::Single() const
293 {
294    return single_;
295 }
296 
Consume() const297 bool ClientState::Consume() const
298 {
299    return consume_;
300 }
301 
Repeat() const302 bool ClientState::Repeat() const
303 {
304    return repeat_;
305 }
306 
Crossfade() const307 int32_t ClientState::Crossfade() const
308 {
309    if (crossfade_ == true)
310    {
311       return crossfadeTime_;
312    }
313 
314    return 0;
315 }
316 
Volume() const317 int32_t ClientState::Volume() const
318 {
319    return volume_;
320 }
321 
322 
Mute() const323 bool ClientState::Mute() const
324 {
325    return mute_;
326 }
327 
IsUpdating() const328 bool ClientState::IsUpdating() const
329 {
330    return updating_;
331 }
332 
CurrentState() const333 std::string ClientState::CurrentState() const
334 {
335    return currentState_;
336 }
337 
GetCurrentSongURI() const338 std::string ClientState::GetCurrentSongURI() const
339 {
340    return currentSongURI_;
341 }
342 
343 
GetCurrentSongPos()344 int32_t ClientState::GetCurrentSongPos()
345 {
346    if (currentState_ != "Stopped")
347    {
348       return currentSongId_;
349    }
350    else
351    {
352       return -1;
353    }
354 }
355 
TotalNumberOfSongs()356 uint32_t ClientState::TotalNumberOfSongs()
357 {
358    return totalNumberOfSongs_;
359 }
360 
DisplaySongInformation()361 void ClientState::DisplaySongInformation()
362 {
363    static char durationStr[128];
364    static char titleStr[512];
365    static char statusStr[1536];
366 
367    screen_.HideCursor();
368 
369    if ((Connected() == true) && (CurrentState() != "Stopped"))
370    {
371       if (currentSong_ != NULL)
372       {
373          char const * const cartist  = mpd_song_get_tag(currentSong_, MPD_TAG_ARTIST, 0);
374          char const * const ctitle   = mpd_song_get_tag(currentSong_, MPD_TAG_TITLE, 0);
375          char const * const curi     = mpd_song_get_uri(currentSong_);
376          uint32_t     const duration = mpd_song_get_duration(currentSong_);
377          uint32_t     const elapsed  = elapsed_;
378          uint32_t     const remain   = (duration > elapsed) ? duration - elapsed : 0;
379          std::string  const artist   = (cartist != NULL) ? cartist : "Unknown";
380          std::string  const title    = (ctitle != NULL) ? ctitle : "";
381          std::string  const uri      = (curi != NULL) ? curi : "";
382 
383          if (title != "")
384          {
385             snprintf(titleStr, 512, "%s - %s", artist.c_str(), title.c_str());
386          }
387          else
388          {
389             snprintf(titleStr, 512, "%s", uri.c_str());
390          }
391 
392          if (settings_.Get(Setting::TimeRemaining) == false)
393          {
394             snprintf(durationStr, 127, " [%d:%.2d/%d:%.2d]",
395                      SecondsToMinutes(elapsed),  RemainingSeconds(elapsed),
396                      SecondsToMinutes(duration), RemainingSeconds(duration));
397          }
398          else
399          {
400             snprintf(durationStr, 127, " [-%d:%.2d/%d:%.2d]",
401                      SecondsToMinutes(remain),  RemainingSeconds(remain),
402                      SecondsToMinutes(duration), RemainingSeconds(duration));
403          }
404 
405          if ((strlen(titleStr) >= screen_.MaxColumns() - 7 - strlen(durationStr)) &&
406              (settings_.Get(Setting::ScrollStatus) == true))
407          {
408             snprintf(statusStr, 1536, "%s  |  %s", titleStr, titleStr);
409             scrollingStatus_ = true;
410          }
411          else
412          {
413             titlePos_ = 0;
414             scrollingStatus_ = false;
415             snprintf(statusStr, 512, "%s", titleStr);
416          }
417 
418          std::string const currentTitle = titleStr;
419 
420          if (lastTitleStr_ != currentTitle)
421          {
422             if (scrollingStatus_ == true)
423             {
424                newSong_ = true;
425             }
426 
427             titlePos_ = 0;
428          }
429 
430          lastTitleStr_ = currentTitle;
431 
432          screen_.SetStatusLine("[%5u] %s", GetCurrentSongPos() + 1, &statusStr[titlePos_]);
433 
434          screen_.MoveSetStatus(screen_.MaxColumns() - strlen(durationStr), "%s", durationStr);
435          screen_.SetProgress(static_cast<double>(elapsed) / duration);
436 
437          if (settings_.Get(Setting::ScrollStatus) == true)
438          {
439             titlePos_ %= (strlen(titleStr) + strlen("  |  "));
440          }
441       }
442       else
443       {
444          screen_.SetProgress(0);
445       }
446    }
447    else
448    {
449       screen_.SetStatusLine("%s","");
450       screen_.SetProgress(0);
451    }
452 
453    if (settings_.Get(Setting::ProgressBar) == true)
454    {
455       screen_.UpdateProgressWindow();
456    }
457 
458    if (screen_.PagerIsVisible() == false)
459    {
460       vimpc_->CurrentMode().Refresh();
461    }
462 }
463 
464 
TimeSinceUpdate()465 long ClientState::TimeSinceUpdate()
466 {
467    return timeSinceUpdate_;
468 }
469 
470 /* vim: set sw=3 ts=3: */
471