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
)
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
50 auto idx
= index
.row();
51 if ( idx
< 0 || (size_t)idx
>= m_items
.size() )
53 const auto& item
= m_items
[idx
];
62 case NETWORK_CANINDEX
:
63 return item
.canBeIndexed
;
66 case NETWORK_PROTOCOL
:
69 return QVariant::fromValue( item
.tree
);
71 return item
.mediaSource
->description
;
73 return item
.artworkUrl
;
79 QHash
<int, QByteArray
> NetworkMediaModel::roleNames() const
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
);
105 int NetworkMediaModel::rowCount(const QModelIndex
& parent
) const
107 if ( parent
.isValid() )
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
)
128 if ( role
!= NETWORK_INDEXED
)
130 auto enabled
= value
.toBool();
131 if ( m_items
[idx
.row()].indexed
== enabled
)
135 res
= vlc_ml_add_folder( m_ml
, qtu( m_items
[idx
.row()].mainMrl
.toString( QUrl::FullyEncoded
) ) );
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
)
150 res
= vlc_ml_add_folder( m_ml
, qtu( m_url
.toString( QUrl::FullyEncoded
) ) );
152 res
= vlc_ml_remove_folder( m_ml
, qtu( m_url
.toString( QUrl::FullyEncoded
) ) );
154 if (res
== VLC_SUCCESS
) {
156 emit
isIndexedChanged();
160 void NetworkMediaModel::setCtx(QmlMainContext
* ctx
)
164 m_ml
= vlc_ml_instance_get( m_ctx
->getIntf() );
166 if (m_ctx
&& m_hasTree
) {
167 initializeMediaSources();
172 void NetworkMediaModel::setTree(QVariant parentTree
)
174 if (parentTree
.canConvert
<NetworkTreeItem
>())
175 m_treeItem
= parentTree
.value
<NetworkTreeItem
>();
177 m_treeItem
= NetworkTreeItem();
179 if (m_ctx
&& m_hasTree
) {
180 initializeMediaSources();
185 bool NetworkMediaModel::insertIntoPlaylist(const QModelIndexList
&itemIdList
, const ssize_t playlistIndex
)
187 if (!(m_ctx
&& m_hasTree
))
189 QVector
<vlc::playlist::Media
> medias
;
190 medias
.reserve( itemIdList
.size() );
191 for ( const QModelIndex
&id
: itemIdList
)
195 const int index
= id
.row();
196 if ( index
< 0 || (size_t)index
>= m_items
.size() )
199 medias
.append( vlc::playlist::Media
{m_items
[index
].tree
.media
.get()} );
201 if (medias
.isEmpty())
203 m_ctx
->getIntf()->p_sys
->p_mainPlaylistController
->insert(playlistIndex
, medias
, false);
207 bool NetworkMediaModel::addToPlaylist(const int index
)
209 if (!(m_ctx
&& m_hasTree
))
211 if (index
< 0 || (size_t)index
>= m_items
.size() )
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);
219 bool NetworkMediaModel::addToPlaylist(const QVariantList
&itemIdList
)
222 for (const QVariant
& varValue
: itemIdList
)
226 if (varValue
.canConvert
<int>())
227 index
= varValue
.value
<int>();
228 else if (varValue
.canConvert
<QModelIndex
>())
229 index
= varValue
.value
<QModelIndex
>().row();
233 ret
|= addToPlaylist(index
);
238 bool NetworkMediaModel::addToPlaylist(const QModelIndexList
&itemIdList
)
241 for (const QModelIndex
& index
: itemIdList
)
243 if (!index
.isValid())
245 ret
|= addToPlaylist(index
.row());
250 bool NetworkMediaModel::addAndPlay(int index
)
252 if (!(m_ctx
&& m_hasTree
))
254 if (index
< 0 || (size_t)index
>= m_items
.size() )
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);
262 bool NetworkMediaModel::addAndPlay(const QVariantList
& itemIdList
)
265 for (const QVariant
& varValue
: itemIdList
)
269 if (varValue
.canConvert
<int>())
270 index
= varValue
.value
<int>();
271 else if (varValue
.canConvert
<QModelIndex
>())
272 index
= varValue
.value
<QModelIndex
>().row();
277 ret
|= addAndPlay(index
);
279 ret
|= addToPlaylist(index
);
284 bool NetworkMediaModel::addAndPlay(const QModelIndexList
& itemIdList
)
287 for (const QModelIndex
& index
: itemIdList
)
289 if (!index
.isValid())
293 ret
|= addAndPlay(index
.row());
295 ret
|= addToPlaylist(index
.row());
301 QVariantList
NetworkMediaModel::getItemsForIndexes(const QModelIndexList
& indexes
) const
307 for (const QModelIndex
& modelIndex
: indexes
)
309 int index
= modelIndex
.row();
311 if (index
< 0 || (size_t) index
>= m_items
.size())
314 const NetworkTreeItem
& tree
= m_items
[index
].tree
;
316 QmlInputItem
input(tree
.media
.get(), true);
318 items
.append(QVariant::fromValue(input
));
324 bool NetworkMediaModel::initializeMediaSources()
326 auto libvlc
= vlc_object_instance(m_ctx
->getIntf());
329 if (!m_items
.empty()) {
339 auto tree
= m_treeItem
.source
->tree
;
340 auto l
= std::make_unique
<NetworkSourceListener
>( m_treeItem
.source
, this );
341 if ( l
->listener
== nullptr )
344 if (m_treeItem
.media
)
346 m_name
= m_treeItem
.media
->psz_name
;
348 m_url
= QUrl::fromEncoded( QByteArray
{ m_treeItem
.media
->psz_uri
}.append( '/' ) );
350 m_type
= static_cast<ItemType
>(m_treeItem
.media
->i_type
);
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
) {
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
)) {
380 parent
= grandParent
;
383 vlc_media_tree_Unlock(tree
);
384 if (!itemList
.empty())
385 refreshMediaList( m_treeItem
.source
, std::move( itemList
), true );
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
);
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
)
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(),
410 vlc_media_tree_Unlock( m_treeItem
.source
->tree
);
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
[],
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
[],
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
)
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
) )
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
) )
469 (*it
).mrls
.erase( mrlIt
);
470 if ( (*it
).mrls
.empty() == false )
472 auto idx
= std::distance( begin( m_items
), it
);
473 beginRemoveRows({}, idx
, idx
);
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
)
489 m_parsingPending
= false;
490 emit
parsingPendingChanged(false);
494 void NetworkMediaModel::refreshMediaList( MediaSourcePtr mediaSource
,
495 std::vector
<InputItemPtr
> childrens
,
498 std::vector
<Item
> items
;
499 for ( auto it
: childrens
)
502 item
.name
= it
->psz_name
;
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());
516 item
.artworkUrl
= QUrl::fromEncoded(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
) );
532 m_items
.erase( begin( m_items
) , end( m_items
) );
535 beginInsertRows( {}, m_items
.size(), m_items
.size() + items
.size() - 1 );
536 std::move( begin( items
), end( items
), std::back_inserter( m_items
) );
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");