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) ver 0.19 (not yet released)
* protocol * protocol
- new commands "addtagid", "cleartagid" - new commands "addtagid", "cleartagid", "listfiles"
- "lsinfo" and "readcomments" allowed for remote files - "lsinfo" and "readcomments" allowed for remote files
- "listneighbors" lists file servers on the local network - "listneighbors" lists file servers on the local network
- "playlistadd" supports file:/// - "playlistadd" supports file:///

View File

@ -1600,6 +1600,31 @@ OK
</para> </para>
</listitem> </listitem>
</varlistentry> </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"> <varlistentry id="command_lsinfo">
<term> <term>
<cmdsynopsis> <cmdsynopsis>

View File

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

View File

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

View File

@ -107,6 +107,9 @@ static const struct command commands[] = {
{ "list", PERMISSION_READ, 1, -1, handle_list }, { "list", PERMISSION_READ, 1, -1, handle_list },
{ "listall", PERMISSION_READ, 0, 1, handle_listall }, { "listall", PERMISSION_READ, 0, 1, handle_listall },
{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, { "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 }, { "listmounts", PERMISSION_READ, 0, 0, handle_listmounts },
#endif #endif
#ifdef ENABLE_NEIGHBOR_PLUGINS #ifdef ENABLE_NEIGHBOR_PLUGINS

View File

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

View File

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

View File

@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#define __STDC_FORMAT_MACROS /* for PRIu64 */
#include "config.h" #include "config.h"
#include "FileCommands.hxx" #include "FileCommands.hxx"
#include "CommandError.hxx" #include "CommandError.hxx"
@ -33,9 +35,79 @@
#include "TagFile.hxx" #include "TagFile.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
#include "TimePrint.hxx"
#include "ls.hxx" #include "ls.hxx"
#include <assert.h> #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 gcc_pure
static bool static bool

View File

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

View File

@ -19,6 +19,8 @@
#include "config.h" #include "config.h"
#include "OtherCommands.hxx" #include "OtherCommands.hxx"
#include "FileCommands.hxx"
#include "StorageCommands.hxx"
#include "CommandError.hxx" #include "CommandError.hxx"
#include "db/Uri.hxx" #include "db/Uri.hxx"
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
@ -112,6 +114,41 @@ print_tag(TagType type, const char *value, void *ctx)
tag_print(client, type, value); 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 = { static constexpr tag_handler print_tag_handler = {
nullptr, nullptr,
print_tag, print_tag,

View File

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

View File

@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#define __STDC_FORMAT_MACROS /* for PRIu64 */
#include "config.h" #include "config.h"
#include "StorageCommands.hxx" #include "StorageCommands.hxx"
#include "CommandError.hxx" #include "CommandError.hxx"
@ -29,10 +31,103 @@
#include "Instance.hxx" #include "Instance.hxx"
#include "storage/Registry.hxx" #include "storage/Registry.hxx"
#include "storage/CompositeStorage.hxx" #include "storage/CompositeStorage.hxx"
#include "storage/FileInfo.hxx"
#include "db/plugins/simple/SimpleDatabasePlugin.hxx" #include "db/plugins/simple/SimpleDatabasePlugin.hxx"
#include "db/update/Service.hxx" #include "db/update/Service.hxx"
#include "TimePrint.hxx"
#include "Idle.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 static void
print_storage_uri(Client &client, const Storage &storage) print_storage_uri(Client &client, const Storage &storage)
{ {

View File

@ -23,6 +23,13 @@
#include "CommandResult.hxx" #include "CommandResult.hxx"
class Client; 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 CommandResult
handle_listmounts(Client &client, int argc, char *argv[]); handle_listmounts(Client &client, int argc, char *argv[]);

View File

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

View File

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