[FFmpeg-devel] [PATCH 3/4] lavf/ftp: implement directory listing callbacks
Lukasz Marek
lukasz.m.luki2 at gmail.com
Sat Jul 12 03:12:14 CEST 2014
Signed-off-by: Lukasz Marek <lukasz.m.luki2 at gmail.com>
---
libavformat/ftp.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 190 insertions(+), 7 deletions(-)
diff --git a/libavformat/ftp.c b/libavformat/ftp.c
index 66c0665..7f6a916 100644
--- a/libavformat/ftp.c
+++ b/libavformat/ftp.c
@@ -19,6 +19,8 @@
*/
#include "libavutil/avstring.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/avassert.h"
#include "avformat.h"
#include "internal.h"
#include "url.h"
@@ -27,12 +29,14 @@
#define CONTROL_BUFFER_SIZE 1024
#define CREDENTIALS_BUFFER_SIZE 128
+#define DIR_BUFFER_SIZE 4096
typedef enum {
UNKNOWN,
READY,
DOWNLOADING,
UPLOADING,
+ LISTING_DIR,
DISCONNECTED
} FTPState;
@@ -53,6 +57,11 @@ typedef struct {
const char *anonymous_password; /**< Password to be used for anonymous user. An email should be used. */
int write_seekable; /**< Control seekability, 0 = disable, 1 = enable. */
FTPState state; /**< State of data connection */
+ char *dir_buffer;
+ size_t dir_buffer_size;
+ size_t dir_buffer_offset;
+ AVIODirEntry *dir_entry;
+ int utf8;
} FTPContext;
#define OFFSET(x) offsetof(FTPContext, x)
@@ -438,6 +447,29 @@ static int ftp_restart(FTPContext *s, int64_t pos)
return 0;
}
+static int ftp_set_dir(FTPContext *s)
+{
+ static const int cwd_codes[] = {250, 0};
+ char command[MAX_URL_SIZE];
+
+ snprintf(command, sizeof(command), "CWD %s\r\n", s->path);
+ if (ftp_send_command(s, command, cwd_codes, NULL) != 250)
+ return AVERROR(EIO);
+ return 0;
+}
+
+static int ftp_list(FTPContext *s)
+{
+ static const char *command = "MLSD\r\n";
+ static const int mlsd_codes[] = {150, 0};
+
+ //TODO: Add fallback to LIST command
+ if (ftp_send_command(s, command, mlsd_codes, NULL) != 150)
+ return AVERROR(ENOSYS);
+ s->state = LISTING_DIR;
+ return 0;
+}
+
static int ftp_features(FTPContext *s)
{
static const char *feat_command = "FEAT\r\n";
@@ -447,8 +479,11 @@ static int ftp_features(FTPContext *s)
char *feat;
if (ftp_send_command(s, feat_command, feat_codes, &feat) == 211) {
- if (av_stristr(feat, "UTF8"))
- ftp_send_command(s, enable_utf8_command, opts_codes, NULL);
+ if (av_stristr(feat, "UTF8")) {
+ if (ftp_send_command(s, enable_utf8_command, opts_codes, NULL) == 200)
+ s->utf8 = 1;
+ }
+ av_free(feat);
}
return 0;
}
@@ -574,14 +609,12 @@ static int ftp_abort(URLContext *h)
return 0;
}
-static int ftp_open(URLContext *h, const char *url, int flags)
+static int ftp_connect(URLContext *h, const char *url)
{
char proto[10], path[MAX_URL_SIZE];
int err;
FTPContext *s = h->priv_data;
- av_dlog(h, "ftp protocol open\n");
-
s->state = DISCONNECTED;
s->filesize = -1;
s->position = 0;
@@ -597,12 +630,26 @@ static int ftp_open(URLContext *h, const char *url, int flags)
s->server_control_port = 21;
if ((err = ftp_connect_control_connection(h)) < 0)
- goto fail;
+ return err;
if ((err = ftp_current_dir(s)) < 0)
- goto fail;
+ return err;
+
av_strlcat(s->path, path, sizeof(s->path));
+ return 0;
+}
+
+static int ftp_open(URLContext *h, const char *url, int flags)
+{
+ FTPContext *s = h->priv_data;
+ int err;
+
+ av_dlog(h, "ftp protocol open\n");
+
+ if ((err = ftp_connect(h, url) < 0))
+ goto fail;
+
if (ftp_restart(s, 0) < 0) {
h->is_streamed = 1;
} else {
@@ -783,6 +830,139 @@ static int ftp_shutdown(URLContext *h, int flags)
return AVERROR(EIO);
}
+static int ftp_open_dir(URLContext *h)
+{
+ FTPContext *s = h->priv_data;
+ int ret;
+
+ s->dir_entry = av_mallocz(sizeof(AVIODirEntry));
+ if (!s->dir_entry)
+ return AVERROR(ENOMEM);
+
+ if ((ret = ftp_connect(h, h->filename)) < 0)
+ goto fail;
+ if ((ret = ftp_set_dir(s)) < 0)
+ goto fail;
+ if ((ret = ftp_connect_data_connection(h)) < 0)
+ goto fail;
+ if ((ret = ftp_list(s)) < 0)
+ goto fail;
+ s->dir_buffer = av_malloc(DIR_BUFFER_SIZE);
+ if (!s->dir_buffer) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ s->dir_buffer[0] = 0;
+ if (s->conn_data && s->state == LISTING_DIR)
+ return 0;
+ fail:
+ ffurl_closep(&s->conn_control);
+ ffurl_closep(&s->conn_data);
+ return ret;
+}
+
+static int64_t ftp_parse_date(const char *date)
+{
+ struct tm tv;
+ memset(&tv, 0, sizeof(struct tm));
+ av_small_strptime(date, "%Y%m%d%H%M%S", &tv);
+ return INT64_C(1000000) * av_timegm(&tv);
+}
+
+/**
+ * @return 0 on success, negative on error, positive on entry to discard.
+ */
+static int ftp_parse_entry(URLContext *h, char *mlsd)
+{
+ FTPContext *s = h->priv_data;
+ char *fact, *value;
+ av_dlog(NULL, "%s\n", mlsd);
+
+ av_assert0(s->dir_entry);
+ av_free(s->dir_entry->name);
+ memset(s->dir_entry, 0, sizeof(AVIODirEntry));
+ s->dir_entry->utf8 = s->utf8;
+
+ while(fact = av_strtok(mlsd, ";", &mlsd)) {
+ if (fact[0] == ' ') {
+ s->dir_entry->name = av_strdup(&fact[1]);
+ continue;
+ }
+ fact = av_strtok(fact, "=", &value);
+ if (!av_strcasecmp(fact, "type")) {
+ if (!av_strcasecmp(value, "cdir") || !av_strcasecmp(value, "pdir"))
+ return 1;
+ if (!av_strcasecmp(value, "dir"))
+ s->dir_entry->type = AVIO_ENTRY_DIRECTORY;
+ else if (!av_strcasecmp(value, "file"))
+ s->dir_entry->type = AVIO_ENTRY_FILE;
+ else if (!av_strcasecmp(value, "OS.unix=slink:"))
+ s->dir_entry->type = AVIO_ENTRY_SYMBOLIC_LINK;
+ } else if (!av_strcasecmp(fact, "modify")) {
+ s->dir_entry->modification_timestamp = ftp_parse_date(value);
+ } else if (!av_strcasecmp(fact, "UNIX.mode")) {
+ s->dir_entry->filemode = strtoumax(value, NULL, 8);
+ } else if (!av_strcasecmp(fact, "UNIX.uid") || !av_strcasecmp(fact, "UNIX.owner"))
+ s->dir_entry->user_id = strtoumax(value, NULL, 10);
+ else if (!av_strcasecmp(fact, "UNIX.gid") || !av_strcasecmp(fact, "UNIX.group"))
+ s->dir_entry->group_id = strtoumax(value, NULL, 10);
+ else if (!av_strcasecmp(fact, "size") || !av_strcasecmp(fact, "sizd"))
+ s->dir_entry->size = strtoll(value, NULL, 10);
+ }
+ return 0;
+}
+
+static int ftp_read_dir(URLContext *h, AVIODirEntry const **next)
+{
+ FTPContext *s = h->priv_data;
+ char *start, *found;
+ int ret, retried;
+
+ av_assert0(next);
+ *next = NULL;
+ do {
+ retried = 0;
+ start = s->dir_buffer + s->dir_buffer_offset;
+ while (!(found = strstr(start, "\n"))) {
+ if (retried)
+ return AVERROR(EIO);
+ s->dir_buffer_size -= s->dir_buffer_offset;
+ s->dir_buffer_offset = 0;
+ if (s->dir_buffer_size)
+ memmove(s->dir_buffer, start, s->dir_buffer_size);
+ ret = ffurl_read(s->conn_data, s->dir_buffer + s->dir_buffer_size, DIR_BUFFER_SIZE - (s->dir_buffer_size + 1));
+ if (ret <= 0)
+ return ret;
+ s->dir_buffer_size += ret;
+ s->dir_buffer[s->dir_buffer_size] = 0;
+ start = s->dir_buffer;
+ retried = 1;
+ }
+ s->dir_buffer_offset += (found + 1 - start);
+ found[0] = 0;
+ if (found > start && found[-1] == '\r')
+ found[-1] = 0;
+
+ ret = ftp_parse_entry(h, start);
+ if (ret < 0)
+ return ret;
+ } while (ret > 0);
+ *next = s->dir_entry;
+ return 0;
+}
+
+static void ftp_close_dir(URLContext *h)
+{
+ FTPContext *s = h->priv_data;
+ av_freep(&s->dir_buffer);
+ if (s->dir_entry) {
+ av_free(s->dir_entry->name);
+ av_freep(&s->dir_entry);
+ }
+ ffurl_closep(&s->conn_control);
+ ffurl_closep(&s->conn_data);
+}
+
URLProtocol ff_ftp_protocol = {
.name = "ftp",
.url_open = ftp_open,
@@ -794,5 +974,8 @@ URLProtocol ff_ftp_protocol = {
.url_shutdown = ftp_shutdown,
.priv_data_size = sizeof(FTPContext),
.priv_data_class = &ftp_context_class,
+ .url_open_dir = ftp_open_dir,
+ .url_read_dir = ftp_read_dir,
+ .url_close_dir = ftp_close_dir,
.flags = URL_PROTOCOL_FLAG_NETWORK,
};
--
1.9.1
More information about the ffmpeg-devel
mailing list