root/src/kits/network/libnetservices/FileRequest.cpp
/*
 * Copyright 2013-2014 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Adrien Destugues, pulkomandy@pulkomandy.tk
 */


#include <assert.h>
#include <stdlib.h>

#include <Directory.h>
#include <File.h>
#include <FileRequest.h>
#include <NodeInfo.h>
#include <Path.h>

using namespace BPrivate::Network;


BFileRequest::BFileRequest(const BUrl& url, BDataIO* output,
        BUrlProtocolListener* listener, BUrlContext* context)
        :
        BUrlRequest(url, output, listener, context, "BUrlProtocol.File", "file"),
        fResult()
{
}


BFileRequest::~BFileRequest()
{
        status_t status = Stop();
        if (status == B_OK)
                wait_for_thread(fThreadId, &status);
}


const BUrlResult&
BFileRequest::Result() const
{
        return fResult;
}


status_t
BFileRequest::_ProtocolLoop()
{
        BNode node(fUrl.Path().String());

        if (node.IsSymLink()) {
                // Traverse the symlink and start over
                BEntry entry(fUrl.Path().String(), true);
                node = BNode(&entry);
        }

        ssize_t transferredSize = 0;
        if (node.IsFile()) {
                BFile file(fUrl.Path().String(), B_READ_ONLY);
                status_t error = file.InitCheck();
                if (error != B_OK)
                        return error;

                BNodeInfo info(&file);
                char mimeType[B_MIME_TYPE_LENGTH + 1];
                if (info.GetType(mimeType) != B_OK)
                        update_mime_info(fUrl.Path().String(), false, true, false);
                if (info.GetType(mimeType) == B_OK)
                        fResult.SetContentType(mimeType);

                // Send all notifications to listener, if any
                if (fListener != NULL)
                        fListener->ConnectionOpened(this);

                off_t size = 0;
                error = file.GetSize(&size);
                if (error != B_OK)
                        return error;
                fResult.SetLength(size);

                if (fListener != NULL)
                        fListener->HeadersReceived(this);

                if (fOutput != NULL) {
                        ssize_t chunkSize = 0;
                        char chunk[4096];
                        while (!fQuit) {
                                chunkSize = file.Read(chunk, sizeof(chunk));
                                if (chunkSize > 0) {
                                        size_t written = 0;
                                        error = fOutput->WriteExactly(chunk, chunkSize, &written);
                                        if (fListener != NULL && written > 0)
                                                fListener->BytesWritten(this, written);
                                        if (error != B_OK)
                                                return error;
                                        transferredSize += chunkSize;
                                        if (fListener != NULL)
                                                fListener->DownloadProgress(this, transferredSize,
                                                        size);
                                } else
                                        break;
                        }
                        if (fQuit)
                                return B_INTERRUPTED;
                        // Return error if we didn't transfer everything
                        if (transferredSize != size) {
                                if (chunkSize < 0)
                                        return (status_t)chunkSize;
                                else
                                        return B_IO_ERROR;
                        }
                }

                return B_OK;
        }

        node_ref ref;
        status_t error = node.GetNodeRef(&ref);

        // Stop here, and don't hit the assert below, if the file doesn't exist.
        if (error != B_OK)
                return error;

        assert(node.IsDirectory());
        BDirectory directory(&ref);

        fResult.SetContentType("application/x-ftp-directory; charset=utf-8");
                // This tells WebKit to use its FTP directory rendering code.

        if (fListener != NULL) {
                fListener->ConnectionOpened(this);
                fListener->HeadersReceived(this);
        }

        if (fOutput != NULL) {
                // Add a parent directory entry.
                size_t written = 0;
                error = fOutput->WriteExactly("+/,\t..\r\n", 8, &written);
                if (fListener != NULL && written > 0)
                        fListener->BytesWritten(this, written);
                if (error != B_OK)
                        return error;
                transferredSize += written;
                if (fListener != NULL)
                        fListener->DownloadProgress(this, transferredSize, 0);

                char name[B_FILE_NAME_LENGTH];
                BEntry entry;
                while (!fQuit && directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) {
                        // We read directories using the EPLF (Easily Parsed List Format)
                        // This happens to be one of the formats that WebKit can understand,
                        // and it is not too hard to parse or generate.
                        // http://tools.ietf.org/html/draft-bernstein-eplf-02
                        BString eplf("+");
                        if (entry.IsFile() || entry.IsSymLink()) {
                                eplf += "r,";
                                off_t fileSize;
                                if (entry.GetSize(&fileSize) == B_OK)
                                        eplf << "s" << fileSize << ",";
                        } else if (entry.IsDirectory())
                                eplf += "/,";

                        time_t modification;
                        if (entry.GetModificationTime(&modification) == B_OK)
                                eplf << "m" << modification << ",";

                        mode_t permissions;
                        if (entry.GetPermissions(&permissions) == B_OK)
                                eplf << "up" << BString().SetToFormat("%03o", permissions) << ",";

                        node_ref ref;
                        if (entry.GetNodeRef(&ref) == B_OK)
                                eplf << "i" << ref.device << "." << ref.node << ",";

                        entry.GetName(name);
                        eplf << "\t" << name << "\r\n";
                        size_t written = 0;
                        error = fOutput->WriteExactly(eplf.String(), eplf.Length(),
                                &written);
                        if (fListener != NULL && written > 0)
                                fListener->BytesWritten(this, written);
                        if (error != B_OK)
                                return error;
                        transferredSize += written;
                        if (fListener != NULL)
                                fListener->DownloadProgress(this, transferredSize, 0);
                }

                if (!fQuit)
                        fResult.SetLength(transferredSize);
        }

        return fQuit ? B_INTERRUPTED : B_OK;
}