command: add command "listfiles"

Lists files and directories.  Supports storage plugins.
This commit is contained in:
Max Kellermann 2014-02-28 19:02:23 +01:00
parent 797bbeabeb
commit 96afa8bd2b
15 changed files with 337 additions and 55 deletions

2
NEWS
View File

@ -1,6 +1,6 @@
ver 0.19 (not yet released)
* protocol
- new commands "addtagid", "cleartagid"
- new commands "addtagid", "cleartagid", "listfiles"
- "lsinfo" and "readcomments" allowed for remote files
- "listneighbors" lists file servers on the local network
- "playlistadd" supports file:///

View File

@ -1600,6 +1600,31 @@ OK
</para>
</listitem>
</varlistentry>
<varlistentry id="command_listfiles">
<term>
<cmdsynopsis>
<command>listfiles</command>
<arg><replaceable>URI</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Lists the contents of the directory
<varname>URI</varname>, including files are not
recognized by MPD. <varname>URI</varname> can be a path
relative to the music directory or an URI understood by
one of the storage plugins. The response contains at
least one line for each directory entry with the prefix
"file: " or "directory: ", and may be followed by file
attributes such as "Last-Modified" and "size".
</para>
<para>
For example, "smb://SERVER" returns a list of all shares
on the given SMB/CIFS server; "nfs://servername/path"
obtains a directory listing from the NFS server.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_lsinfo">
<term>
<cmdsynopsis>

View File

@ -30,44 +30,50 @@
#define SONG_FILE "file: "
static void
song_print_uri(Client &client, const char *uri)
song_print_uri(Client &client, const char *uri, bool base)
{
std::string allocated;
if (base) {
uri = PathTraitsUTF8::GetBase(uri);
} else {
#ifdef ENABLE_DATABASE
const Storage *storage = client.GetStorage();
if (storage != nullptr) {
const char *suffix = storage->MapToRelativeUTF8(uri);
if (suffix != nullptr)
uri = suffix;
}
const Storage *storage = client.GetStorage();
if (storage != nullptr) {
const char *suffix = storage->MapToRelativeUTF8(uri);
if (suffix != nullptr)
uri = suffix;
}
#endif
const std::string allocated = uri_remove_auth(uri);
if (!allocated.empty())
uri = allocated.c_str();
allocated = uri_remove_auth(uri);
if (!allocated.empty())
uri = allocated.c_str();
}
client_printf(client, "%s%s\n", SONG_FILE, uri);
}
void
song_print_uri(Client &client, const LightSong &song)
song_print_uri(Client &client, const LightSong &song, bool base)
{
if (song.directory != nullptr) {
if (!base && song.directory != nullptr) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
song.directory, song.uri);
} else
song_print_uri(client, song.uri);
song_print_uri(client, song.uri, base);
}
void
song_print_uri(Client &client, const DetachedSong &song)
song_print_uri(Client &client, const DetachedSong &song, bool base)
{
song_print_uri(client, song.GetURI());
song_print_uri(client, song.GetURI(), base);
}
void
song_print_info(Client &client, const LightSong &song)
song_print_info(Client &client, const LightSong &song, bool base)
{
song_print_uri(client, song);
song_print_uri(client, song, base);
if (song.end_ms > 0)
client_printf(client, "Range: %u.%03u-%u.%03u\n",
@ -87,9 +93,9 @@ song_print_info(Client &client, const LightSong &song)
}
void
song_print_info(Client &client, const DetachedSong &song)
song_print_info(Client &client, const DetachedSong &song, bool base)
{
song_print_uri(client, song);
song_print_uri(client, song, base);
const unsigned start_ms = song.GetStartMS();
const unsigned end_ms = song.GetEndMS();

View File

@ -25,15 +25,15 @@ class DetachedSong;
class Client;
void
song_print_info(Client &client, const DetachedSong &song);
song_print_info(Client &client, const DetachedSong &song, bool base=false);
void
song_print_info(Client &client, const LightSong &song);
song_print_info(Client &client, const LightSong &song, bool base=false);
void
song_print_uri(Client &client, const LightSong &song);
song_print_uri(Client &client, const LightSong &song, bool base=false);
void
song_print_uri(Client &client, const DetachedSong &song);
song_print_uri(Client &client, const DetachedSong &song, bool base=false);
#endif

View File

@ -107,6 +107,9 @@ static const struct command commands[] = {
{ "list", PERMISSION_READ, 1, -1, handle_list },
{ "listall", PERMISSION_READ, 0, 1, handle_listall },
{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
#endif
{ "listfiles", PERMISSION_READ, 0, 1, handle_listfiles },
#ifdef ENABLE_DATABASE
{ "listmounts", PERMISSION_READ, 0, 0, handle_listmounts },
#endif
#ifdef ENABLE_NEIGHBOR_PLUGINS

View File

@ -31,6 +31,18 @@
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
CommandResult
handle_listfiles_db(Client &client, const char *uri)
{
const DatabaseSelection selection(uri, false);
Error error;
if (!db_selection_print(client, selection, false, true, error))
return print_error(client, error);
return CommandResult::OK;
}
CommandResult
handle_lsinfo2(Client &client, int argc, char *argv[])
{
@ -42,7 +54,7 @@ handle_lsinfo2(Client &client, int argc, char *argv[])
const DatabaseSelection selection(uri, false);
Error error;
if (!db_selection_print(client, selection, true, error))
if (!db_selection_print(client, selection, true, false, error))
return print_error(client, error);
return CommandResult::OK;
@ -60,7 +72,7 @@ handle_match(Client &client, int argc, char *argv[], bool fold_case)
const DatabaseSelection selection("", true, &filter);
Error error;
return db_selection_print(client, selection, true, error)
return db_selection_print(client, selection, true, false, error)
? CommandResult::OK
: print_error(client, error);
}

View File

@ -24,6 +24,9 @@
class Client;
CommandResult
handle_listfiles_db(Client &client, const char *uri);
CommandResult
handle_lsinfo2(Client &client, int argc, char *argv[]);

View File

@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define __STDC_FORMAT_MACROS /* for PRIu64 */
#include "config.h"
#include "FileCommands.hxx"
#include "CommandError.hxx"
@ -33,9 +35,79 @@
#include "TagFile.hxx"
#include "storage/StorageInterface.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "TimePrint.hxx"
#include "ls.hxx"
#include <assert.h>
#include <sys/stat.h>
#include <inttypes.h> /* for PRIu64 */
gcc_pure
static bool
SkipNameFS(const char *name_fs)
{
return name_fs[0] == '.' &&
(name_fs[1] == 0 ||
(name_fs[1] == '.' && name_fs[2] == 0));
}
gcc_pure
static bool
skip_path(const char *name_fs)
{
return strchr(name_fs, '\n') != nullptr;
}
CommandResult
handle_listfiles_local(Client &client, const char *path_utf8)
{
const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
if (path_fs.IsNull()) {
command_error(client, ACK_ERROR_NO_EXIST,
"unsupported file name");
return CommandResult::ERROR;
}
Error error;
if (!client.AllowFile(path_fs, error))
return print_error(client, error);
DirectoryReader reader(path_fs);
if (reader.HasFailed()) {
error.FormatErrno("Failed to open '%s'", path_utf8);
return print_error(client, error);
}
while (reader.ReadEntry()) {
const Path name_fs = reader.GetEntry();
if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str()))
continue;
std::string name_utf8 = name_fs.ToUTF8();
if (name_utf8.empty())
continue;
const AllocatedPath full_fs =
AllocatedPath::Build(path_fs, name_fs);
struct stat st;
if (!StatFile(full_fs, st, false))
continue;
if (S_ISREG(st.st_mode)) {
client_printf(client, "file: %s\n"
"size: %" PRIu64 "\n",
name_utf8.c_str(),
uint64_t(st.st_size));
} else if (S_ISDIR(st.st_mode))
client_printf(client, "directory: %s\n",
name_utf8.c_str());
time_print(client, "Last-Modified", st.st_mtime);
}
return CommandResult::OK;
}
gcc_pure
static bool

View File

@ -24,6 +24,9 @@
class Client;
CommandResult
handle_listfiles_local(Client &client, const char *path_utf8);
CommandResult
handle_read_comments(Client &client, int argc, char *argv[]);

View File

@ -19,6 +19,8 @@
#include "config.h"
#include "OtherCommands.hxx"
#include "FileCommands.hxx"
#include "StorageCommands.hxx"
#include "CommandError.hxx"
#include "db/Uri.hxx"
#include "storage/StorageInterface.hxx"
@ -112,6 +114,41 @@ print_tag(TagType type, const char *value, void *ctx)
tag_print(client, type, value);
}
CommandResult
handle_listfiles(Client &client, int argc, char *argv[])
{
const char *const uri = argc == 2
? argv[1]
/* default is root directory */
: "";
if (memcmp(uri, "file:///", 8) == 0)
/* list local directory */
return handle_listfiles_local(client, uri + 7);
#ifdef ENABLE_DATABASE
if (uri_has_scheme(uri))
/* use storage plugin to list remote directory */
return handle_listfiles_storage(client, uri);
/* must be a path relative to the configured
music_directory */
if (client.partition.instance.storage != nullptr)
/* if we have a storage instance, obtain a list of
files from it */
return handle_listfiles_storage(client,
*client.partition.instance.storage,
uri);
/* fall back to entries from database if we have no storage */
return handle_listfiles_db(client, uri);
#else
command_error(client, ACK_ERROR_NO_EXIST, "No database");
return CommandResult::ERROR;
#endif
}
static constexpr tag_handler print_tag_handler = {
nullptr,
print_tag,

View File

@ -39,6 +39,9 @@ handle_kill(Client &client, int argc, char *argv[]);
CommandResult
handle_close(Client &client, int argc, char *argv[]);
CommandResult
handle_listfiles(Client &client, int argc, char *argv[]);
CommandResult
handle_lsinfo(Client &client, int argc, char *argv[]);

View File

@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define __STDC_FORMAT_MACROS /* for PRIu64 */
#include "config.h"
#include "StorageCommands.hxx"
#include "CommandError.hxx"
@ -29,10 +31,103 @@
#include "Instance.hxx"
#include "storage/Registry.hxx"
#include "storage/CompositeStorage.hxx"
#include "storage/FileInfo.hxx"
#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
#include "db/update/Service.hxx"
#include "TimePrint.hxx"
#include "Idle.hxx"
#include <inttypes.h> /* for PRIu64 */
gcc_pure
static bool
skip_path(const char *name_utf8)
{
return strchr(name_utf8, '\n') != nullptr;
}
static bool
handle_listfiles_storage(Client &client, StorageDirectoryReader &reader,
Error &error)
{
const char *name_utf8;
while ((name_utf8 = reader.Read()) != nullptr) {
if (skip_path(name_utf8))
continue;
FileInfo info;
if (!reader.GetInfo(false, info, error))
continue;
switch (info.type) {
case FileInfo::Type::OTHER:
/* ignore */
continue;
case FileInfo::Type::REGULAR:
client_printf(client, "file: %s\n"
"size: %" PRIu64 "\n",
name_utf8,
info.size);
break;
case FileInfo::Type::DIRECTORY:
client_printf(client, "directory: %s\n", name_utf8);
break;
}
if (info.mtime != 0)
time_print(client, "Last-Modified", info.mtime);
}
return true;
}
static bool
handle_listfiles_storage(Client &client, Storage &storage, const char *uri,
Error &error)
{
auto reader = storage.OpenDirectory(uri, error);
if (reader == nullptr)
return false;
bool success = handle_listfiles_storage(client, *reader, error);
delete reader;
return success;
}
CommandResult
handle_listfiles_storage(Client &client, Storage &storage, const char *uri)
{
Error error;
if (!handle_listfiles_storage(client, storage, uri, error))
return print_error(client, error);
return CommandResult::OK;
}
CommandResult
handle_listfiles_storage(Client &client, const char *uri)
{
Error error;
Storage *storage = CreateStorageURI(uri, error);
if (storage == nullptr) {
if (error.IsDefined())
return print_error(client, error);
command_error(client, ACK_ERROR_ARG,
"Unrecognized storage URI");
return CommandResult::ERROR;
}
bool success = handle_listfiles_storage(client, *storage, "", error);
delete storage;
if (!success)
return print_error(client, error);
return CommandResult::OK;
}
static void
print_storage_uri(Client &client, const Storage &storage)
{

View File

@ -23,6 +23,13 @@
#include "CommandResult.hxx"
class Client;
class Storage;
CommandResult
handle_listfiles_storage(Client &client, Storage &storage, const char *uri);
CommandResult
handle_listfiles_storage(Client &client, const char *uri);
CommandResult
handle_listmounts(Client &client, int argc, char *argv[]);

View File

@ -29,29 +29,39 @@
#include "LightDirectory.hxx"
#include "PlaylistInfo.hxx"
#include "Interface.hxx"
#include "fs/Traits.hxx"
#include <functional>
static void
PrintDirectoryURI(Client &client, const LightDirectory &directory)
static const char *
ApplyBaseFlag(const char *uri, bool base)
{
client_printf(client, "directory: %s\n", directory.GetPath());
if (base)
uri = PathTraitsUTF8::GetBase(uri);
return uri;
}
static void
PrintDirectoryURI(Client &client, bool base, const LightDirectory &directory)
{
client_printf(client, "directory: %s\n",
ApplyBaseFlag(directory.GetPath(), base));
}
static bool
PrintDirectoryBrief(Client &client, const LightDirectory &directory)
PrintDirectoryBrief(Client &client, bool base, const LightDirectory &directory)
{
if (!directory.IsRoot())
PrintDirectoryURI(client, directory);
PrintDirectoryURI(client, base, directory);
return true;
}
static bool
PrintDirectoryFull(Client &client, const LightDirectory &directory)
PrintDirectoryFull(Client &client, bool base, const LightDirectory &directory)
{
if (!directory.IsRoot()) {
PrintDirectoryURI(client, directory);
PrintDirectoryURI(client, base, directory);
if (directory.mtime > 0)
time_print(client, "Last-Modified", directory.mtime);
@ -61,23 +71,24 @@ PrintDirectoryFull(Client &client, const LightDirectory &directory)
}
static void
print_playlist_in_directory(Client &client,
print_playlist_in_directory(Client &client, bool base,
const char *directory,
const char *name_utf8)
{
if (directory == nullptr)
client_printf(client, "playlist: %s\n", name_utf8);
if (base || directory == nullptr)
client_printf(client, "playlist: %s\n",
ApplyBaseFlag(name_utf8, base));
else
client_printf(client, "playlist: %s/%s\n",
directory, name_utf8);
}
static void
print_playlist_in_directory(Client &client,
print_playlist_in_directory(Client &client, bool base,
const LightDirectory *directory,
const char *name_utf8)
{
if (directory == nullptr || directory->IsRoot())
if (base || directory == nullptr || directory->IsRoot())
client_printf(client, "playlist: %s\n", name_utf8);
else
client_printf(client, "playlist: %s/%s\n",
@ -85,44 +96,48 @@ print_playlist_in_directory(Client &client,
}
static bool
PrintSongBrief(Client &client, const LightSong &song)
PrintSongBrief(Client &client, bool base, const LightSong &song)
{
song_print_uri(client, song);
song_print_uri(client, song, base);
if (song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
print_playlist_in_directory(client, song.directory, song.uri);
print_playlist_in_directory(client, base,
song.directory, song.uri);
return true;
}
static bool
PrintSongFull(Client &client, const LightSong &song)
PrintSongFull(Client &client, bool base, const LightSong &song)
{
song_print_info(client, song);
song_print_info(client, song, base);
if (song.tag->has_playlist)
/* this song file has an embedded CUE sheet */
print_playlist_in_directory(client, song.directory, song.uri);
print_playlist_in_directory(client, base,
song.directory, song.uri);
return true;
}
static bool
PrintPlaylistBrief(Client &client,
PrintPlaylistBrief(Client &client, bool base,
const PlaylistInfo &playlist,
const LightDirectory &directory)
{
print_playlist_in_directory(client, &directory, playlist.name.c_str());
print_playlist_in_directory(client, base,
&directory, playlist.name.c_str());
return true;
}
static bool
PrintPlaylistFull(Client &client,
PrintPlaylistFull(Client &client, bool base,
const PlaylistInfo &playlist,
const LightDirectory &directory)
{
print_playlist_in_directory(client, &directory, playlist.name.c_str());
print_playlist_in_directory(client, base,
&directory, playlist.name.c_str());
if (playlist.mtime > 0)
time_print(client, "Last-Modified", playlist.mtime);
@ -132,7 +147,7 @@ PrintPlaylistFull(Client &client,
bool
db_selection_print(Client &client, const DatabaseSelection &selection,
bool full, Error &error)
bool full, bool base, Error &error)
{
const Database *db = client.GetDatabase(error);
if (db == nullptr)
@ -141,13 +156,13 @@ db_selection_print(Client &client, const DatabaseSelection &selection,
using namespace std::placeholders;
const auto d = selection.filter == nullptr
? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief,
std::ref(client), _1)
std::ref(client), base, _1)
: VisitDirectory();
const auto s = std::bind(full ? PrintSongFull : PrintSongBrief,
std::ref(client), _1);
std::ref(client), base, _1);
const auto p = selection.filter == nullptr
? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief,
std::ref(client), _1, _2)
std::ref(client), base, _1, _2)
: VisitPlaylist();
return db->Visit(selection, d, s, p, error);
@ -202,7 +217,7 @@ bool
printAllIn(Client &client, const char *uri_utf8, Error &error)
{
const DatabaseSelection selection(uri_utf8, true);
return db_selection_print(client, selection, false, error);
return db_selection_print(client, selection, false, false, error);
}
bool
@ -210,7 +225,7 @@ printInfoForAllIn(Client &client, const char *uri_utf8,
Error &error)
{
const DatabaseSelection selection(uri_utf8, true);
return db_selection_print(client, selection, true, error);
return db_selection_print(client, selection, true, false, error);
}
static bool

View File

@ -29,10 +29,11 @@ class Error;
/**
* @param full print attributes/tags
* @param base print only base name of songs/directories?
*/
bool
db_selection_print(Client &client, const DatabaseSelection &selection,
bool full, Error &error);
bool full, bool base, Error &error);
gcc_nonnull(2)
bool