You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
8.0 KiB
270 lines
8.0 KiB
/*****************************************************************************
|
|
* directory.cpp: Media library network directory
|
|
*****************************************************************************
|
|
* Copyright (C) 2018 VLC authors, VideoLAN and VideoLabs
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "directory.h"
|
|
#include "file.h"
|
|
#include "util.h"
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_url.h>
|
|
#include <vlc_input_item.h>
|
|
#include <vlc_fs.h>
|
|
#include <vlc_input.h>
|
|
#include <vlc_threads.h>
|
|
#include <vlc_cxx_helpers.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <medialibrary/filesystem/Errors.h>
|
|
#include <sys/stat.h>
|
|
#include <system_error>
|
|
#include <vector>
|
|
|
|
using InputItemPtr = vlc_shared_data_ptr_type(input_item_t,
|
|
input_item_Hold,
|
|
input_item_Release);
|
|
|
|
namespace vlc {
|
|
namespace medialibrary {
|
|
|
|
SDDirectory::SDDirectory(const std::string &mrl, SDFileSystemFactory &fs)
|
|
: m_mrl(mrl)
|
|
, m_fs(fs)
|
|
{
|
|
if ( *m_mrl.crbegin() != '/' )
|
|
m_mrl += '/';
|
|
}
|
|
|
|
const std::string &
|
|
SDDirectory::mrl() const
|
|
{
|
|
return m_mrl;
|
|
}
|
|
|
|
const std::vector<std::shared_ptr<IFile>> &
|
|
SDDirectory::files() const
|
|
{
|
|
if ( !m_read_done )
|
|
read();
|
|
return m_files;
|
|
}
|
|
|
|
const std::vector<std::shared_ptr<IDirectory>> &
|
|
SDDirectory::dirs() const
|
|
{
|
|
if ( !m_read_done )
|
|
read();
|
|
return m_dirs;
|
|
}
|
|
|
|
std::shared_ptr<IDevice>
|
|
SDDirectory::device() const
|
|
{
|
|
if ( !m_device )
|
|
m_device = m_fs.createDeviceFromMrl( mrl() );
|
|
return m_device;
|
|
}
|
|
|
|
std::shared_ptr<IFile> SDDirectory::file(const std::string& mrl) const
|
|
{
|
|
auto fs = files();
|
|
// Don't compare entire mrls, this might yield false negative when a
|
|
// device has multiple mountpoints.
|
|
auto fileName = utils::fileName( mrl );
|
|
auto it = std::find_if( cbegin( fs ), cend( fs ),
|
|
[&fileName]( const std::shared_ptr<fs::IFile> f ) {
|
|
return f->name() == fileName;
|
|
});
|
|
if ( it == cend( fs ) )
|
|
throw medialibrary::fs::errors::NotFound( mrl, m_mrl );
|
|
return *it;
|
|
}
|
|
|
|
bool SDDirectory::contains(const std::string& fileName) const
|
|
{
|
|
auto fs = files();
|
|
return std::find_if( cbegin( fs ), cend( fs ),
|
|
[&fileName]( const std::shared_ptr<fs::IFile> f ) {
|
|
return f->name() == fileName;
|
|
}) != cend( fs );
|
|
}
|
|
|
|
struct metadata_request {
|
|
vlc::threads::mutex lock;
|
|
vlc::threads::condition_variable cond;
|
|
/* results */
|
|
bool success;
|
|
bool probe;
|
|
std::vector<InputItemPtr> *children;
|
|
};
|
|
|
|
} /* namespace medialibrary */
|
|
} /* namespace vlc */
|
|
|
|
extern "C" {
|
|
|
|
static void onParserEnded( input_item_t *, int status, void *data )
|
|
{
|
|
auto req = static_cast<vlc::medialibrary::metadata_request*>( data );
|
|
|
|
vlc::threads::mutex_locker lock( req->lock );
|
|
req->success = status == VLC_SUCCESS;
|
|
req->probe = true;
|
|
req->cond.signal();
|
|
}
|
|
|
|
static void onParserSubtreeAdded( input_item_t *, input_item_node_t *subtree,
|
|
void *data )
|
|
{
|
|
auto req = static_cast<vlc::medialibrary::metadata_request*>( data );
|
|
|
|
for ( int i = 0; i < subtree->i_children; ++i )
|
|
{
|
|
input_item_node_t* child = subtree->pp_children[i];
|
|
/* this class assumes we always receive a flat list */
|
|
assert( child->i_children == 0 );
|
|
input_item_t* media = child->p_item;
|
|
req->children->emplace_back( media );
|
|
}
|
|
}
|
|
|
|
} /* extern C */
|
|
|
|
namespace vlc {
|
|
namespace medialibrary {
|
|
|
|
static bool request_metadata_sync( libvlc_int_t *libvlc, input_item_t *media,
|
|
std::vector<InputItemPtr> *out_children )
|
|
{
|
|
metadata_request req;
|
|
req.children = out_children;
|
|
req.probe = false;
|
|
auto deadline = vlc_tick_now() + VLC_TICK_FROM_SEC( 15 );
|
|
|
|
media->i_preparse_depth = 1;
|
|
|
|
static const input_item_parser_cbs_t cbs = {
|
|
onParserEnded,
|
|
onParserSubtreeAdded,
|
|
nullptr,
|
|
};
|
|
|
|
auto inputParser = vlc::wrap_cptr( input_item_Parse( media, VLC_OBJECT( libvlc ), &cbs, &req ),
|
|
&input_item_parser_id_Release );
|
|
|
|
if ( inputParser == nullptr )
|
|
return false;
|
|
|
|
vlc::threads::mutex_locker lock( req.lock );
|
|
while ( req.probe == false )
|
|
{
|
|
auto res = req.cond.timedwait( req.lock, deadline );
|
|
if ( res != 0 )
|
|
{
|
|
throw medialibrary::fs::errors::System(
|
|
ETIMEDOUT, "Failed to browse directory: Operation timed out" );
|
|
}
|
|
}
|
|
return req.success;
|
|
}
|
|
|
|
void
|
|
SDDirectory::read() const
|
|
{
|
|
auto media =
|
|
vlc::wrap_cptr( input_item_New( m_mrl.c_str(), m_mrl.c_str() ), &input_item_Release );
|
|
if ( !media )
|
|
throw std::bad_alloc();
|
|
|
|
std::vector<InputItemPtr> children;
|
|
|
|
input_item_AddOption( media.get(), "show-hiddenfiles", VLC_INPUT_OPTION_TRUSTED );
|
|
input_item_AddOption( media.get(), "ignore-filetypes=''", VLC_INPUT_OPTION_TRUSTED );
|
|
input_item_AddOption( media.get(), "sub-autodetect-fuzzy=2", VLC_INPUT_OPTION_TRUSTED );
|
|
auto status = request_metadata_sync( m_fs.libvlc(), media.get(), &children );
|
|
|
|
if ( status == false )
|
|
throw medialibrary::fs::errors::System(
|
|
EIO, "Failed to browse directory: Unknown error" );
|
|
|
|
for ( const InputItemPtr& m : children )
|
|
{
|
|
const char* mrl = m.get()->psz_uri;
|
|
enum input_item_type_e type = m->i_type;
|
|
if ( type == ITEM_TYPE_DIRECTORY )
|
|
{
|
|
m_dirs.push_back( std::make_shared<SDDirectory>( mrl, m_fs ) );
|
|
}
|
|
else if ( type == ITEM_TYPE_FILE )
|
|
{
|
|
addFile( mrl, IFile::LinkedFileType::None, {} );
|
|
for ( auto i = 0; i < m->i_slaves; ++i )
|
|
{
|
|
const auto* slave = m->pp_slaves[i];
|
|
const auto linked_type = slave->i_type == SLAVE_TYPE_GENERIC
|
|
? IFile::LinkedFileType::SoundTrack
|
|
: IFile::LinkedFileType::Subtitles;
|
|
|
|
addFile( slave->psz_uri, linked_type, mrl );
|
|
}
|
|
}
|
|
}
|
|
|
|
m_read_done = true;
|
|
}
|
|
|
|
void
|
|
SDDirectory::addFile(std::string mrl, IFile::LinkedFileType fType, std::string linkedFile) const
|
|
{
|
|
time_t lastModificationDate = 0;
|
|
int64_t fileSize = 0;
|
|
|
|
if ( m_fs.isNetworkFileSystem() == false )
|
|
{
|
|
const auto path = vlc::wrap_cptr( vlc_uri2path( mrl.c_str() ) );
|
|
struct stat stat;
|
|
|
|
if ( vlc_stat( path.get(), &stat ) != 0 )
|
|
{
|
|
if ( errno == EACCES )
|
|
return;
|
|
throw errors::System{ errno, "Failed to get file info" };
|
|
}
|
|
lastModificationDate = stat.st_mtime;
|
|
fileSize = stat.st_size;
|
|
}
|
|
|
|
if ( fType == IFile::LinkedFileType::None )
|
|
{
|
|
m_files.push_back(
|
|
std::make_shared<SDFile>( std::move( mrl ), fileSize, lastModificationDate ) );
|
|
}
|
|
else
|
|
{
|
|
m_files.push_back( std::make_shared<SDFile>(
|
|
std::move( mrl ), fType, std::move( linkedFile ), fileSize, lastModificationDate ) );
|
|
}
|
|
}
|
|
} /* namespace medialibrary */
|
|
} /* namespace vlc */
|
|
|