qt/network*: Add the 'getItemsForIndexes' function
[vlc.git] / modules / gui / qt / network / networkmediamodel.cpp
blob603b6cf7bdd70a496a31fe6d598b3a726fa5b836
1 /*****************************************************************************
2 * Copyright (C) 2019 VLC authors and VideoLAN
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
17 *****************************************************************************/
19 #include "networkmediamodel.hpp"
21 #include "medialibrary/mlhelper.hpp"
23 #include "playlist/media.hpp"
24 #include "playlist/playlist_controller.hpp"
25 #include "util/qmlinputitem.hpp"
27 NetworkMediaModel::NetworkMediaModel( QObject* parent )
28 : QAbstractListModel( parent )
29 , m_preparseSem(1)
30 , m_ml( nullptr )
34 NetworkMediaModel::~NetworkMediaModel()
36 //this can only be aquired from UI thread
37 if (!m_preparseSem.tryAcquire())
39 auto libvlc = vlc_object_instance(m_ctx->getIntf());
40 vlc_media_tree_PreparseCancel( libvlc, this );
41 //wait for the callback call on cancel
42 m_preparseSem.acquire();
46 QVariant NetworkMediaModel::data( const QModelIndex& index, int role ) const
48 if (!m_ctx)
49 return {};
50 auto idx = index.row();
51 if ( idx < 0 || (size_t)idx >= m_items.size() )
52 return {};
53 const auto& item = m_items[idx];
54 switch ( role )
56 case NETWORK_NAME:
57 return item.name;
58 case NETWORK_MRL:
59 return item.mainMrl;
60 case NETWORK_INDEXED:
61 return item.indexed;
62 case NETWORK_CANINDEX:
63 return item.canBeIndexed;
64 case NETWORK_TYPE:
65 return item.type;
66 case NETWORK_PROTOCOL:
67 return item.protocol;
68 case NETWORK_TREE:
69 return QVariant::fromValue( item.tree );
70 case NETWORK_SOURCE:
71 return item.mediaSource->description;
72 case NETWORK_ARTWORK:
73 return item.artworkUrl;
74 default:
75 return {};
79 QHash<int, QByteArray> NetworkMediaModel::roleNames() const
81 return {
82 { NETWORK_NAME, "name" },
83 { NETWORK_MRL, "mrl" },
84 { NETWORK_INDEXED, "indexed" },
85 { NETWORK_CANINDEX, "can_index" },
86 { NETWORK_TYPE, "type" },
87 { NETWORK_PROTOCOL, "protocol" },
88 { NETWORK_TREE, "tree" },
89 { NETWORK_SOURCE, "source" },
90 { NETWORK_ARTWORK, "artwork" },
95 QMap<QString, QVariant> NetworkMediaModel::getDataAt(int idx)
97 QMap<QString, QVariant> dataDict;
98 QHash<int,QByteArray> roles = roleNames();
99 for (auto role: roles.keys()) {
100 dataDict[roles[role]] = data(index(idx), role);
102 return dataDict;
105 int NetworkMediaModel::rowCount(const QModelIndex& parent) const
107 if ( parent.isValid() )
108 return 0;
109 return getCount();
112 int NetworkMediaModel::getCount() const
114 assert( m_items.size() < INT32_MAX );
115 return static_cast<int>( m_items.size() );
118 Qt::ItemFlags NetworkMediaModel::flags( const QModelIndex& idx ) const
120 return QAbstractListModel::flags( idx ) | Qt::ItemIsEditable;
123 bool NetworkMediaModel::setData( const QModelIndex& idx, const QVariant& value, int role )
125 if (!m_ml)
126 return false;
128 if ( role != NETWORK_INDEXED )
129 return false;
130 auto enabled = value.toBool();
131 if ( m_items[idx.row()].indexed == enabled )
132 return false;
133 int res;
134 if ( enabled )
135 res = vlc_ml_add_folder( m_ml, qtu( m_items[idx.row()].mainMrl.toString( QUrl::FullyEncoded ) ) );
136 else
137 res = vlc_ml_remove_folder( m_ml, qtu( m_items[idx.row()].mainMrl.toString( QUrl::FullyEncoded ) ) );
138 m_items[idx.row()].indexed = enabled;
139 emit dataChanged(idx, idx, { NETWORK_INDEXED });
140 return res == VLC_SUCCESS;
144 void NetworkMediaModel::setIndexed(bool indexed)
146 if (indexed == m_indexed || !m_canBeIndexed)
147 return;
148 int res;
149 if ( indexed ) {
150 res = vlc_ml_add_folder( m_ml, qtu( m_url.toString( QUrl::FullyEncoded ) ) );
151 } else
152 res = vlc_ml_remove_folder( m_ml, qtu( m_url.toString( QUrl::FullyEncoded ) ) );
154 if (res == VLC_SUCCESS) {
155 m_indexed = indexed;
156 emit isIndexedChanged();
160 void NetworkMediaModel::setCtx(QmlMainContext* ctx)
162 if (ctx) {
163 m_ctx = ctx;
164 m_ml = vlc_ml_instance_get( m_ctx->getIntf() );
166 if (m_ctx && m_hasTree) {
167 initializeMediaSources();
169 emit ctxChanged();
172 void NetworkMediaModel::setTree(QVariant parentTree)
174 if (parentTree.canConvert<NetworkTreeItem>())
175 m_treeItem = parentTree.value<NetworkTreeItem>();
176 else
177 m_treeItem = NetworkTreeItem();
178 m_hasTree = true;
179 if (m_ctx && m_hasTree) {
180 initializeMediaSources();
182 emit treeChanged();
185 bool NetworkMediaModel::insertIntoPlaylist(const QModelIndexList &itemIdList, const ssize_t playlistIndex)
187 if (!(m_ctx && m_hasTree))
188 return false;
189 QVector<vlc::playlist::Media> medias;
190 medias.reserve( itemIdList.size() );
191 for ( const QModelIndex &id : itemIdList )
193 if ( !id.isValid() )
194 continue;
195 const int index = id.row();
196 if ( index < 0 || (size_t)index >= m_items.size() )
197 continue;
199 medias.append( vlc::playlist::Media {m_items[index].tree.media.get()} );
201 if (medias.isEmpty())
202 return false;
203 m_ctx->getIntf()->p_sys->p_mainPlaylistController->insert(playlistIndex, medias, false);
204 return true;
207 bool NetworkMediaModel::addToPlaylist(const int index)
209 if (!(m_ctx && m_hasTree))
210 return false;
211 if (index < 0 || (size_t)index >= m_items.size() )
212 return false;
213 auto item = m_items[index];
214 vlc::playlist::Media media{ item.tree.media.get() };
215 m_ctx->getIntf()->p_sys->p_mainPlaylistController->append( { media }, false);
216 return true;
219 bool NetworkMediaModel::addToPlaylist(const QVariantList &itemIdList)
221 bool ret = false;
222 for (const QVariant& varValue: itemIdList)
224 int index = -1;
226 if (varValue.canConvert<int>())
227 index = varValue.value<int>();
228 else if (varValue.canConvert<QModelIndex>())
229 index = varValue.value<QModelIndex>().row();
230 else
231 continue;
233 ret |= addToPlaylist(index);
235 return ret;
238 bool NetworkMediaModel::addToPlaylist(const QModelIndexList &itemIdList)
240 bool ret = false;
241 for (const QModelIndex& index: itemIdList)
243 if (!index.isValid())
244 continue;
245 ret |= addToPlaylist(index.row());
247 return ret;
250 bool NetworkMediaModel::addAndPlay(int index)
252 if (!(m_ctx && m_hasTree))
253 return false;
254 if (index < 0 || (size_t)index >= m_items.size() )
255 return false;
256 auto item = m_items[index];
257 vlc::playlist::Media media{ item.tree.media.get() };
258 m_ctx->getIntf()->p_sys->p_mainPlaylistController->append( { media }, true);
259 return true;
262 bool NetworkMediaModel::addAndPlay(const QVariantList& itemIdList)
264 bool ret = false;
265 for (const QVariant& varValue: itemIdList)
267 int index = -1;
269 if (varValue.canConvert<int>())
270 index = varValue.value<int>();
271 else if (varValue.canConvert<QModelIndex>())
272 index = varValue.value<QModelIndex>().row();
273 else
274 continue;
276 if (!ret)
277 ret |= addAndPlay(index);
278 else
279 ret |= addToPlaylist(index);
281 return ret;
284 bool NetworkMediaModel::addAndPlay(const QModelIndexList& itemIdList)
286 bool ret = false;
287 for (const QModelIndex& index: itemIdList)
289 if (!index.isValid())
290 continue;
292 if (!ret)
293 ret |= addAndPlay(index.row());
294 else
295 ret |= addToPlaylist(index.row());
297 return ret;
300 /* Q_INVOKABLE */
301 QVariantList NetworkMediaModel::getItemsForIndexes(const QModelIndexList & indexes) const
303 assert(m_ml);
305 QVariantList items;
307 for (const QModelIndex & modelIndex : indexes)
309 int index = modelIndex.row();
311 if (index < 0 || (size_t) index >= m_items.size())
312 continue;
314 const NetworkTreeItem & tree = m_items[index].tree;
316 QmlInputItem input(tree.media.get(), true);
318 items.append(QVariant::fromValue(input));
321 return items;
324 bool NetworkMediaModel::initializeMediaSources()
326 auto libvlc = vlc_object_instance(m_ctx->getIntf());
328 m_listener.reset();
329 if (!m_items.empty()) {
330 beginResetModel();
331 m_items.clear();
332 endResetModel();
333 emit countChanged();
336 if (!m_treeItem)
337 return false;
339 auto tree = m_treeItem.source->tree;
340 auto l = std::make_unique<NetworkSourceListener>( m_treeItem.source, this );
341 if ( l->listener == nullptr )
342 return false;
344 if (m_treeItem.media)
346 m_name = m_treeItem.media->psz_name;
347 emit nameChanged();
348 m_url = QUrl::fromEncoded( QByteArray{ m_treeItem.media->psz_uri }.append( '/' ) );
349 emit urlChanged();
350 m_type = static_cast<ItemType>(m_treeItem.media->i_type);
351 emit typeChanged();
352 m_canBeIndexed = canBeIndexed( m_url, m_type );
353 emit canBeIndexedChanged();
354 if ( !m_ml || vlc_ml_is_indexed( m_ml, QByteArray(m_treeItem.media->psz_uri).append('/').constData(), &m_indexed ) != VLC_SUCCESS ) {
355 m_indexed = false;
357 emit isIndexedChanged();
361 input_item_node_t* mediaNode = nullptr;
362 input_item_node_t* parent = nullptr;
363 vlc_media_tree_Lock(tree);
364 vlc_media_tree_PreparseCancel( libvlc, this );
365 std::vector<InputItemPtr> itemList;
366 m_path = {QVariant::fromValue(PathNode(m_treeItem, m_name))};
367 if (vlc_media_tree_Find( tree, m_treeItem.media.get(), &mediaNode, &parent))
369 itemList.reserve(mediaNode->i_children);
370 for (int i = 0; i < mediaNode->i_children; i++)
371 itemList.emplace_back(mediaNode->pp_children[i]->p_item);
373 while (parent && parent->p_item) {
374 m_path.push_front(QVariant::fromValue(PathNode(NetworkTreeItem(m_treeItem.source, parent->p_item), parent->p_item->psz_name)));
375 input_item_node_t *node = nullptr;
376 input_item_node_t *grandParent = nullptr;
377 if (!vlc_media_tree_Find( tree, parent->p_item, &node, &grandParent)) {
378 break;
380 parent = grandParent;
383 vlc_media_tree_Unlock(tree);
384 if (!itemList.empty())
385 refreshMediaList( m_treeItem.source, std::move( itemList ), true );
386 emit pathChanged();
389 m_preparseSem.acquire();
390 vlc_media_tree_Preparse( tree, libvlc, m_treeItem.media.get(), this );
391 m_parsingPending = true;
392 emit parsingPendingChanged(m_parsingPending);
394 m_listener = std::move( l );
396 return true;
399 void NetworkMediaModel::onItemCleared( MediaSourcePtr mediaSource, input_item_node_t* node)
401 InputItemPtr p_node { node->p_item };
402 QMetaObject::invokeMethod(this, [this, p_node = std::move(p_node), mediaSource = std::move(mediaSource)]() {
403 if (p_node != m_treeItem.media)
404 return;
405 input_item_node_t *res;
406 input_item_node_t *parent;
407 vlc_media_tree_Lock( m_treeItem.source->tree );
408 bool found = vlc_media_tree_Find( m_treeItem.source->tree, m_treeItem.media.get(),
409 &res, &parent );
410 vlc_media_tree_Unlock( m_treeItem.source->tree );
411 if (!found)
412 return;
414 std::vector<InputItemPtr> itemList;
415 itemList.reserve( static_cast<size_t>(res->i_children) );
416 for (int i = 0; i < res->i_children; i++)
417 itemList.emplace_back(res->pp_children[i]->p_item);
419 refreshMediaList( std::move( mediaSource ), std::move( itemList ), true );
420 }, Qt::QueuedConnection);
423 void NetworkMediaModel::onItemAdded( MediaSourcePtr mediaSource, input_item_node_t* parent,
424 input_item_node_t *const children[],
425 size_t count )
427 InputItemPtr p_parent { parent->p_item };
428 std::vector<InputItemPtr> itemList;
429 itemList.reserve( count );
430 for (size_t i = 0; i < count; i++)
431 itemList.emplace_back(children[i]->p_item);
433 QMetaObject::invokeMethod(this, [this, p_parent = std::move(p_parent), mediaSource = std::move(mediaSource), itemList=std::move(itemList)]() {
434 if ( p_parent == m_treeItem.media )
435 refreshMediaList( std::move( mediaSource ), std::move( itemList ), false );
436 }, Qt::QueuedConnection);
439 void NetworkMediaModel::onItemRemoved(MediaSourcePtr, input_item_node_t * node,
440 input_item_node_t *const children[],
441 size_t count )
443 std::vector<InputItemPtr> itemList;
444 itemList.reserve( count );
445 for ( auto i = 0u; i < count; ++i )
446 itemList.emplace_back( children[i]->p_item );
448 InputItemPtr p_node { node->p_item };
449 QMetaObject::invokeMethod(this, [this, p_node=std::move(p_node), itemList=std::move(itemList)]() {
450 if (p_node != m_treeItem.media)
451 return;
453 for (auto p_item : itemList)
455 QUrl itemUri = QUrl::fromEncoded(p_item->psz_uri);
456 auto it = std::find_if( begin( m_items ), end( m_items ), [&](const Item& i) {
457 return QString::compare( qfu(p_item->psz_name), i.name, Qt::CaseInsensitive ) == 0 &&
458 itemUri.scheme() == i.mainMrl.scheme();
460 if ( it == end( m_items ) )
461 continue;
463 auto mrlIt = std::find_if( begin( (*it).mrls ), end( (*it).mrls),
464 [itemUri]( const QUrl& mrl ) {
465 return mrl == itemUri;
467 if ( mrlIt == end( (*it).mrls ) )
468 continue;
469 (*it).mrls.erase( mrlIt );
470 if ( (*it).mrls.empty() == false )
471 continue;
472 auto idx = std::distance( begin( m_items ), it );
473 beginRemoveRows({}, idx, idx );
474 m_items.erase( it );
475 endRemoveRows();
476 emit countChanged();
478 }, Qt::QueuedConnection);
481 void NetworkMediaModel::onItemPreparseEnded(MediaSourcePtr, input_item_node_t* node, enum input_item_preparse_status )
483 m_preparseSem.release();
484 InputItemPtr p_node { node->p_item };
485 QMetaObject::invokeMethod(this, [this, p_node=std::move(p_node)]() {
486 if (p_node != m_treeItem.media)
487 return;
489 m_parsingPending = false;
490 emit parsingPendingChanged(false);
494 void NetworkMediaModel::refreshMediaList( MediaSourcePtr mediaSource,
495 std::vector<InputItemPtr> childrens,
496 bool clear )
498 std::vector<Item> items;
499 for ( auto it: childrens)
501 Item item;
502 item.name = it->psz_name;
503 item.protocol = "";
504 item.indexed = false;
505 item.type = static_cast<ItemType>(it->i_type);
506 item.mainMrl = (item.type == TYPE_DIRECTORY || item.type == TYPE_NODE) ?
507 QUrl::fromEncoded(QByteArray(it->psz_uri).append('/')) :
508 QUrl::fromEncoded(it->psz_uri);
510 item.canBeIndexed = canBeIndexed( item.mainMrl , item.type );
511 item.mediaSource = mediaSource;
513 char* artwork = input_item_GetArtworkURL(it.get());
514 if (artwork)
516 item.artworkUrl = QUrl::fromEncoded(artwork);
517 free(artwork);
520 if ( m_ml && item.canBeIndexed == true )
522 if ( vlc_ml_is_indexed( m_ml, qtu( item.mainMrl.toString( QUrl::FullyEncoded ) ),
523 &item.indexed ) != VLC_SUCCESS )
524 item.indexed = false;
526 item.tree = NetworkTreeItem( mediaSource, it.get() );
527 items.push_back( std::move( item ) );
529 if ( clear == true )
531 beginResetModel();
532 m_items.erase( begin( m_items ) , end( m_items ) );
534 else
535 beginInsertRows( {}, m_items.size(), m_items.size() + items.size() - 1 );
536 std::move( begin( items ), end( items ), std::back_inserter( m_items ) );
537 if ( clear == true )
538 endResetModel();
539 else
540 endInsertRows();
541 emit countChanged();
544 bool NetworkMediaModel::canBeIndexed(const QUrl& url , ItemType itemType )
546 return m_ml && static_cast<input_item_type_e>(itemType) != ITEM_TYPE_FILE && (url.scheme() == "smb" || url.scheme() == "ftp" || url.scheme() == "file");