1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2014 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "syncdialog.h"
25 #include "synccollectionwidget.h"
26 #include "actiondialog.h"
27 #include "devicepropertiesdialog.h"
28 #include "devicepropertieswidget.h"
29 #include "mpd-interface/song.h"
30 #include "mpd-interface/mpdconnection.h"
31 #include "models/mpdlibrarymodel.h"
32 #include "models/devicesmodel.h"
33 #include "support/messagebox.h"
34 #include "support/squeezedtextlabel.h"
35 #include "widgets/icons.h"
36 #include <QSplitter>
37 #include <QFileInfo>
38
39 struct SyncSong : public Song
40 {
SyncSongSyncSong41 SyncSong(const Song &o)
42 : Song(o) {
43 }
operator ==SyncSong44 bool operator==(const SyncSong &o) const {
45 return title==o.title && album==o.album && albumArtist()==o.albumArtist();
46 }
operator <SyncSong47 bool operator<(const SyncSong &o) const {
48 int compare=albumArtist().compare(o.albumArtist());
49
50 if (0!=compare) {
51 return compare<0;
52 }
53 compare=album.compare(o.album);
54 if (0!=compare) {
55 return compare<0;
56 }
57 return title.compare(o.title)<0;
58 }
59 };
60
qHash(const SyncSong & key)61 inline uint qHash(const SyncSong &key)
62 {
63 return qHash(key.albumArtist()+key.artist+key.title);
64 }
65
getDiffs(const QSet<Song> & s1,const QSet<Song> & s2,QSet<Song> & in1,QSet<Song> & in2)66 static void getDiffs(const QSet<Song> &s1, const QSet<Song> &s2, QSet<Song> &in1, QSet<Song> &in2)
67 {
68 QSet<SyncSong> a;
69 QSet<SyncSong> b;
70
71 for (const Song &s: s1) {
72 a.insert(s);
73 }
74
75 for (const Song &s: s2) {
76 b.insert(s);
77 }
78
79 QSet<SyncSong> r=a-b;
80
81 for (const Song &s: r) {
82 in1.insert(s);
83 }
84
85 r=b-a;
86
87 for (const Song &s: r) {
88 in2.insert(s);
89 }
90 }
91
92 static int iCount=0;
93
instanceCount()94 int SyncDialog::instanceCount()
95 {
96 return iCount;
97 }
98
SyncDialog(QWidget * parent)99 SyncDialog::SyncDialog(QWidget *parent)
100 : Dialog(parent, "SyncDialog", QSize(680, 680))
101 , state(State_Lists)
102 , currentDev(nullptr)
103 {
104 iCount++;
105
106 QWidget *mw=new QWidget(this);
107 QVBoxLayout *l=new QVBoxLayout(mw);
108 QSplitter *splitter=new QSplitter(mw);
109 libWidget=new SyncCollectionWidget(splitter, tr("Library:"));
110 devWidget=new SyncCollectionWidget(splitter, tr("Device:"));
111 statusLabel=new SqueezedTextLabel(this);
112 statusLabel->setText(tr("Loading all songs from library, please wait..."));
113 splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
114 NoteLabel *noteLabel=new NoteLabel(this);
115 noteLabel->setText(tr("<code>Library</code> lists only songs that are in your library, but not on the device. Likewise <code>Device</code> lists "
116 "songs that are only on the device.<br/>"
117 "Select songs from <code>Library</code> that you would like to copy to <code>Device</code>, "
118 "and select songs from <code>Device</code> that you would like to copy to <code>Library</code>. "
119 "Then press the <code>Synchronize</code> button."));
120 l->addWidget(splitter);
121 l->addWidget(statusLabel);
122 l->addWidget(noteLabel);
123 libWidget->setEnabled(false);
124 devWidget->setEnabled(false);
125 setMainWidget(mw);
126 setButtons(Cancel|Ok);
127 setButtonText(Ok, tr("Synchronize"));
128 enableButtonOk(false);
129 setAttribute(Qt::WA_DeleteOnClose);
130 setCaption(tr("Synchronize"));
131 connect(libWidget, SIGNAL(selectionChanged()), SLOT(selectionChanged()));
132 connect(devWidget, SIGNAL(selectionChanged()), SLOT(selectionChanged()));
133 connect(libWidget, SIGNAL(configure()), SLOT(configure()));
134 connect(devWidget, SIGNAL(configure()), SLOT(configure()));
135 libOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true);
136 }
137
~SyncDialog()138 SyncDialog::~SyncDialog()
139 {
140 iCount--;
141 }
142
sync(const QString & udi)143 void SyncDialog::sync(const QString &udi)
144 {
145 devUdi=udi;
146 connect(MpdLibraryModel::self(), SIGNAL(songListing(QList<Song>,double)), this, SLOT(librarySongs(QList<Song>,double)));
147 MpdLibraryModel::self()->listSongs();
148 show();
149 }
150
copy(const QList<Song> & songs)151 void SyncDialog::copy(const QList<Song> &songs)
152 {
153 Device *dev=getDevice();
154
155 if (!dev) {
156 return;
157 }
158
159 bool fromDev=sender()==devWidget;
160 ActionDialog *dlg=new ActionDialog(this);
161 connect(dlg, SIGNAL(completed()), SLOT(updateSongs()));
162 dlg->copy(fromDev ? dev->id() : QString(), fromDev ? QString() : dev->id(), songs);
163 }
164
updateSongs()165 void SyncDialog::updateSongs()
166 {
167 Device *dev=getDevice();
168
169 if (!dev) {
170 deleteLater();
171 hide();
172 return;
173 }
174
175 QSet<Song> devSongs=dev->allSongs(dev->options().fixVariousArtists);
176 QSet<Song> inDev;
177 QSet<Song> inLib;
178 getDiffs(devSongs, libSongs, inDev, inLib);
179
180 if (0==inDev.count() && 0==inLib.count()) {
181 MessageBox::information(isVisible() ? this : parentWidget(), tr("Device and library are in sync."));
182 deleteLater();
183 hide();
184 return;
185 }
186 devWidget->setSupportsAlbumArtistTag(dev->supportsAlbumArtistTag());
187 devWidget->update(inDev);
188 libWidget->update(inLib);
189 libWidget->setEnabled(true);
190 devWidget->setEnabled(true);
191 libSongs.clear();
192 }
193
librarySongs(const QList<Song> & songs,double pc)194 void SyncDialog::librarySongs(const QList<Song> &songs, double pc)
195 {
196 if (songs.isEmpty()) {
197 statusLabel->hide();
198 disconnect(MpdLibraryModel::self(), SIGNAL(songListing(QList<Song>,double)), this, SLOT(librarySongs(QList<Song>,double)));
199 updateSongs();
200 } else {
201 libSongs+=songs.toSet();
202 statusLabel->setText(tr("Loading all songs from library, please wait...%1%...").arg(pc));
203 }
204 }
205
selectionChanged()206 void SyncDialog::selectionChanged()
207 {
208 enableButtonOk(libWidget->numCheckedSongs() || devWidget->numCheckedSongs());
209 }
210
configure()211 void SyncDialog::configure()
212 {
213 if (libWidget==sender()) {
214 DevicePropertiesDialog *dlg=new DevicePropertiesDialog(this);
215 connect(dlg, SIGNAL(updatedSettings(const QString &, const DeviceOptions &)), SLOT(saveProperties(const QString &, const DeviceOptions &)));
216 dlg->setCaption(tr("Local Music Library Properties"));
217 dlg->show(MPDConnection::self()->getDetails().dir, libOptions, DevicePropertiesWidget::Prop_Basic|DevicePropertiesWidget::Prop_FileName);
218 } else {
219 Device *dev=getDevice();
220 if (dev) {
221 dev->configure(this);
222 }
223 }
224 }
225
saveProperties(const QString & path,const DeviceOptions & opts)226 void SyncDialog::saveProperties(const QString &path, const DeviceOptions &opts)
227 {
228 Q_UNUSED(path)
229 libOptions=opts;
230 libOptions.save(MPDConnectionDetails::configGroupName(MPDConnection::self()->getDetails().name), true, false);
231 }
232
slotButtonClicked(int button)233 void SyncDialog::slotButtonClicked(int button)
234 {
235 switch(button) {
236 case Ok: {
237 Device *dev=getDevice();
238 if (dev) {
239 setVisible(false);
240 ActionDialog *dlg=new ActionDialog(parentWidget());
241 QList<Song> songs=libWidget->checkedSongs();
242 QString devId;
243 devId=dev->id();
244 dlg->sync(devId, libWidget->checkedSongs(), devWidget->checkedSongs());
245 Dialog::slotButtonClicked(button);
246 }
247 break;
248 }
249 case Cancel:
250 MpdLibraryModel::self()->cancelListing();
251 Dialog::slotButtonClicked(button);
252 break;
253 default:
254 break;
255 }
256 }
257
getDevice()258 Device * SyncDialog::getDevice()
259 {
260 Device *dev=DevicesModel::self()->device(devUdi);
261 if (!dev) {
262 MessageBox::error(isVisible() ? this : parentWidget(), tr("Device has been removed!"));
263 return nullptr;
264 }
265
266 if (currentDev && dev!=currentDev) {
267 MessageBox::error(isVisible() ? this : parentWidget(), tr("Device has been changed?"));
268 return nullptr;
269 }
270
271 if (dev->isIdle()) {
272 return dev;
273 }
274
275 MessageBox::error(isVisible() ? this : parentWidget(), tr("Device is busy?"));
276 return nullptr;
277 }
278
279 #include "moc_syncdialog.cpp"
280