/**
 * @file file.cpp
 * @brief Classes for transferring files
 *
 * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
 *
 * This file is part of the MEGA SDK - Client Access Engine.
 *
 * Applications using the MEGA API must present a valid application key
 * and comply with the the rules set forth in the Terms of Service.
 *
 * The MEGA SDK 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.
 *
 * @copyright Simplified (2-clause) BSD License.
 *
 * You should have received a copy of the license along with this
 * program.
 */

#include "mega/file.h"

#include "mega/command.h"
#include "mega/heartbeats.h"
#include "mega/logging.h"
#include "mega/megaapp.h"
#include "mega/megaclient.h"
#include "mega/sync.h"
#include "mega/transfer.h"
#include "mega/transferslot.h"

namespace mega {

mutex File::localname_mutex;

File::File()
    :mCollisionResolution(CollisionResolution::RenameNewWithN)
{
    transfer = NULL;
    chatauth = NULL;
    hprivate = true;
    hforeign = false;
    syncxfer = false;
    fromInsycShare = false;
    temporaryfile = false;
    tag = 0;
}

File::~File()
{
    // if transfer currently running, stop
    if (transfer)
    {
        transfer->client->stopxfer(this, nullptr);
    }
    delete [] chatauth;
}

LocalPath File::getLocalname() const
{
    lock_guard<mutex> g(localname_mutex);
    return localname_multithreaded;
}

void File::setLocalname(const LocalPath& ln)
{
    lock_guard<mutex> g(localname_mutex);
    localname_multithreaded = ln;
}

bool File::serialize(string *d) const
{
    char type = char(transfer->type);
    d->append((const char*)&type, sizeof(type));

    if (!FileFingerprint::serialize(d))
    {
        LOG_err << "Error serializing File: Unable to serialize FileFingerprint";
        return false;
    }

    unsigned short ll;
    bool flag;

    ll = (unsigned short)name.size();
    d->append((char*)&ll, sizeof(ll));
    d->append(name.data(), ll);

    auto tmpstr = getLocalname().serialize();
    ll = (unsigned short)tmpstr.size();
    d->append((char*)&ll, sizeof(ll));
    d->append(tmpstr.data(), ll);

    ll = (unsigned short)targetuser.size();
    d->append((char*)&ll, sizeof(ll));
    d->append(targetuser.data(), ll);

    ll = (unsigned short)privauth.size();
    d->append((char*)&ll, sizeof(ll));
    d->append(privauth.data(), ll);

    ll = (unsigned short)pubauth.size();
    d->append((char*)&ll, sizeof(ll));
    d->append(pubauth.data(), ll);

    d->append((const char*)&h, sizeof(h));
    d->append((const char*)filekey, sizeof(filekey));

    flag = hprivate;
    d->append((const char*)&flag, sizeof(flag));

    flag = hforeign;
    d->append((const char*)&flag, sizeof(flag));

    flag = syncxfer;
    d->append((const char*)&flag, sizeof(flag));

    flag = temporaryfile;
    d->append((const char*)&flag, sizeof(flag));

    char hasChatAuth = (chatauth && chatauth[0]) ? 1 : 0;
    d->append((char *)&hasChatAuth, 1);

    d->append((char*)&mCollisionResolution, 1);

    bool pathSerializedAsLocalPath = true;
    d->append(reinterpret_cast<const char*>(&pathSerializedAsLocalPath), sizeof(bool));

    bool hasPitag = mPitag.has_value();
    d->append(reinterpret_cast<const char*>(&hasPitag), sizeof(bool));

    d->append("\0\0\0\0\0", 6);

    if (hasChatAuth)
    {
        ll = (unsigned short) strlen(chatauth);
        d->append((char*)&ll, sizeof(ll));
        d->append(chatauth, ll);
    }

    if (hasPitag)
    {
        const auto pitagStr = pitagToString(*mPitag);
        ll = static_cast<unsigned short>(pitagStr.size());
        d->append((char*)&ll, sizeof(ll));
        d->append(pitagStr.data(), ll);
    }

    return true;
}

File *File::unserialize(string *d)
{
    if (!d->size())
    {
        LOG_err << "Error unserializing File: Empty string";
        return NULL;
    }

    d->erase(0, 1);

    const char* ptr = d->data();
    const char* end = ptr + d->size();

    auto fp = FileFingerprint::unserialize(ptr, end);
    if (!fp)
    {
        LOG_err << "Error unserializing File: Unable to unserialize FileFingerprint";
        return NULL;
    }

    if (ptr + sizeof(unsigned short) > end)
    {
        LOG_err << "File unserialization failed - serialized string too short";
        return NULL;
    }

    // read name
    unsigned short namelen = MemAccess::get<unsigned short>(ptr);
    if (ptr + namelen + sizeof(unsigned short) > end)
    {
        LOG_err << "File unserialization failed - name too long";
        return NULL;
    }
    ptr += sizeof(namelen);
    const char *name = ptr;
    ptr += namelen;

    // read localname
    unsigned short localnamelen = MemAccess::get<unsigned short>(ptr);
    if (ptr + localnamelen + sizeof(unsigned short) > end)
    {
        LOG_err << "File unserialization failed - localname too long";
        return NULL;
    }
    ptr += sizeof(localnamelen);
    const char *localname = ptr;
    ptr += localnamelen;

    // read targetuser
    unsigned short targetuserlen = MemAccess::get<unsigned short>(ptr);
    if (ptr + targetuserlen + sizeof(unsigned short) > end)
    {
        LOG_err << "File unserialization failed - targetuser too long";
        return NULL;
    }
    ptr += sizeof(targetuserlen);
    const char *targetuser = ptr;
    ptr += targetuserlen;

    // read private auth
    unsigned short privauthlen = MemAccess::get<unsigned short>(ptr);
    if (ptr + privauthlen + sizeof(unsigned short) > end)
    {
        LOG_err << "File unserialization failed - private auth too long";
        return NULL;
    }
    ptr += sizeof(privauthlen);
    const char *privauth = ptr;
    ptr += privauthlen;

    unsigned short pubauthlen = MemAccess::get<unsigned short>(ptr);
    ptr += sizeof(pubauthlen);
    if (ptr + pubauthlen 
            + sizeof(handle) 
            + FILENODEKEYLENGTH 
            + sizeof(bool)      //hprivate
            + sizeof(bool)      //hforeign
            + sizeof(bool)      //syncxfer
            + sizeof(bool)      //temporaryfile
            + sizeof(char)      //hasChatAuth
            + sizeof(uint8_t)   //collisionResolution
            + 8                 //8 '0'
            > end)
    {
        LOG_err << "File unserialization failed - public auth too long";
        return NULL;
    }
    const char *pubauth = ptr;
    ptr += pubauthlen;

    File *file = new File();
    *(FileFingerprint *)file = *fp;
    fp.reset();

    file->name.assign(name, namelen);
    file->targetuser.assign(targetuser, targetuserlen);
    file->privauth.assign(privauth, privauthlen);
    file->pubauth.assign(pubauth, pubauthlen);

    file->h.set6byte(MemAccess::get<handle>(ptr));
    ptr += sizeof(handle);

    memcpy(file->filekey, ptr, FILENODEKEYLENGTH);
    ptr += FILENODEKEYLENGTH;

    file->hprivate = MemAccess::get<bool>(ptr);
    ptr += sizeof(bool);

    file->hforeign = MemAccess::get<bool>(ptr);
    ptr += sizeof(bool);

    file->syncxfer = MemAccess::get<bool>(ptr);
    ptr += sizeof(bool);

    file->temporaryfile = MemAccess::get<bool>(ptr);
    ptr += sizeof(bool);

    char hasChatAuth = MemAccess::get<char>(ptr);
    ptr += sizeof(char);

    uint8_t collisionResolutionUint8 = MemAccess::get<uint8_t>(ptr);
    ptr += sizeof(uint8_t);
    if (collisionResolutionUint8 < static_cast<uint8_t>(CollisionResolution::Begin) || collisionResolutionUint8 >= static_cast<uint8_t>(CollisionResolution::End))
    {
        LOG_err << "File unserialization failed - collision resolution " << collisionResolutionUint8 << " not valid";
        delete file;
        return NULL;
    }
    file->setCollisionResolution(static_cast<CollisionResolution>(collisionResolutionUint8));

    bool pathSerializedAsLocalPath = MemAccess::get<bool>(ptr);
    ptr += sizeof(bool);

    if (pathSerializedAsLocalPath)
    {
        auto localPath = LocalPath::unserialize(std::string(localname, localnamelen));
        if (!localPath.has_value())
        {
            LOG_err << "File unserialization failed - invalid LocalPath";
            delete file;
            return NULL;
        }
        file->setLocalname(localPath.value());
    }
    else
    {
        file->setLocalname(
            LocalPath::fromPlatformEncodedAbsolute(std::string(localname, localnamelen)));
    }

    char hasPitag = MemAccess::get<char>(ptr);
    ptr += sizeof(char);

    if (memcmp(ptr, "\0\0\0\0\0", 6))
    {
        LOG_err << "File unserialization failed - invalid version";
        delete file;
        return NULL;
    }
    ptr += 6;

    if (hasChatAuth)
    {
        if (ptr + sizeof(unsigned short) <= end)
        {
            unsigned short chatauthlen = MemAccess::get<unsigned short>(ptr);
            ptr += sizeof(chatauthlen);

            if (!chatauthlen || ptr + chatauthlen > end)
            {
                LOG_err << "File unserialization failed - incorrect size of chat auth";
                delete file;
                return NULL;
            }

            file->chatauth = new char[chatauthlen + 1];
            memcpy(file->chatauth, ptr, chatauthlen);
            file->chatauth[chatauthlen] = '\0';
            ptr += chatauthlen;
        }
        else
        {
            LOG_err << "File unserialization failed - chat auth not found";
            delete file;
            return NULL;
        }
    }

    if (hasPitag)
    {
        unsigned short pitagLength = MemAccess::get<unsigned short>(ptr);
        if (ptr + sizeof(unsigned short) + pitagLength > end)
        {
            LOG_err << "File unserialization failed - pitag too long";
            return nullptr;
        }

        ptr += sizeof(pitagLength);
        const char* pitag = ptr;
        auto pitagOpt{pitagFromString(std::string(pitag, pitagLength))};
        if (!pitagOpt.has_value())
        {
            LOG_err << "File unserialization failed - incorrect pitag";
            return nullptr;
        }

        file->setPitag(pitagOpt.value());
        ptr += pitagLength;
    }

    d->erase(0, static_cast<size_t>(ptr - d->data()));
    return file;
}

bool File::isFuseTransfer() const
{
    return false;
}

void File::logicalPath(LocalPath logicalPath)
{
    std::lock_guard<std::mutex> guard(localname_mutex);

    mLogicalPath = std::move(logicalPath);
}

LocalPath File::logicalPath() const
{
    std::lock_guard<std::mutex> guard(localname_mutex);

    if (mLogicalPath.empty())
        return localname_multithreaded;

    return mLogicalPath;
}

void File::prepare(FileSystemAccess&)
{
    transfer->localfilename = getLocalname();
    assert(transfer->localfilename.isAbsolute());
}

void File::start()
{
}

void File::progress()
{
}

void File::completed(Transfer* t, putsource_t source)
{
    assert(!transfer || t == transfer);
    assert(source == PUTNODES_APP);  // derived class for sync doesn't use this code path

    if (t->type == PUT)
    {
        sendPutnodesOfUpload(t->client,
                             t->uploadhandle,
                             "",
                             *t->ultoken,
                             t->filekey,
                             source,
                             NodeHandle(),
                             nullptr,
                             nullptr,
                             false);
    }
}

void File::sendPutnodesOfUpload(MegaClient* client,
                                UploadHandle fileAttrMatchHandle,
                                std::string&& fileAttr,
                                const UploadToken& ultoken,
                                const FileNodeKey& newFileKey,
                                putsource_t source,
                                NodeHandle ovHandle,
                                CommandPutNodes::Completion&& completion,
                                const m_time_t* overrideMtime,
                                bool canChangeVault,
                                std::optional<Pitag> pitag)
{
    vector<NewNode> newnodes(1);
    NewNode* newnode = &newnodes[0];

    // build new node
    newnode->source = NEW_UPLOAD;
    newnode->canChangeVault = canChangeVault;

    // upload handle required to retrieve/include pending file attributes
    // or file attribute value if it is not empty
    newnode->uploadhandle = fileAttrMatchHandle;
    newnode->fileattributes = std::move(fileAttr);

    // reference to uploaded file
    newnode->uploadtoken = ultoken;

    // file's crypto key
    static_assert(sizeof(newFileKey) == FILENODEKEYLENGTH,
                  "File completed: filekey size doesn't match with FILENODEKEYLENGTH");
    newnode->nodekey.assign((char*)&newFileKey, FILENODEKEYLENGTH);
    newnode->type = FILENODE;
    newnode->parenthandle = UNDEF;

    AttrMap attrs;
    MegaClient::honorPreviousVersionAttrs(previousNode.get(), attrs);

    // store filename
    attrs.map['n'] = name;

    // store fingerprint
    auto oldMtime = mtime;
    if (overrideMtime) mtime = *overrideMtime;
    serializefingerprint(&attrs.map['c']);
    if (overrideMtime) mtime = oldMtime;

    string tattrstring;

    attrs.getjson(&tattrstring);

    newnode->attrstring.reset(new string);
    MegaClient::makeattr(
        client->getRecycledTemporaryTransferCipher(newFileKey.bytes.data(), FILENODE),
        newnode->attrstring,
        tattrstring.c_str());

    if (targetuser.size())
    {
        // drop file into targetuser's inbox (obsolete feature, kept for sending logs to helpdesk)
        client->putnodes(targetuser.c_str(), std::move(newnodes), tag, std::move(completion));
    }
    else
    {
        NodeHandle th = h;
        std::shared_ptr<Node> parentNode = client->nodeByHandle(th);

        if (syncxfer)
        {
            newnode->ovhandle = ovHandle;
        }
        else if (mVersioningOption != NoVersioning)  // todo: resolve clash with mVersioningOption vs fixNameConflicts
        {
            // for manual upload, let the API apply the `ov` according to the global versions_disabled flag.
            // with versions on, the API will make the ov the first version of this new node
            // with versions off, the API will permanently delete `ov`, replacing it with this (and attaching the ov's old versions)
            if (auto ovNode = client->getovnode(parentNode.get(), &name))
            {
                newnode->ovhandle = ovNode->nodeHandle();
            }
        }

        if (pitag.has_value() && pitag->target == PitagTarget::NotApplicable)
        {
            const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching(
                                                           [](const Node& node)
                                                           {
                                                               return node.inshare != nullptr;
                                                           });
            pitag->target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive;
        }

        client->queueCommand(new CommandPutNodes(client,
                                                 th,
                                                 NULL,
                                                 mVersioningOption,
                                                 std::move(newnodes),
                                                 tag,
                                                 source,
                                                 nullptr,
                                                 std::move(completion),
                                                 canChangeVault,
                                                 {}, // customerIpPort
                                                 pitag));
    }
}

void File::sendPutnodesToCloneNode(MegaClient* client,
                                   Node* nodeToClone,
                                   putsource_t source,
                                   NodeHandle ovHandle,
                                   CommandPutNodes::Completion&& completion,
                                   bool canChangeVault,
                                   Pitag pitag)
{
    vector<NewNode> newnodes(1);
    NewNode* newnode = &newnodes[0];

    // build new node
    newnode->source = NEW_NODE;
    newnode->canChangeVault = canChangeVault;
    newnode->nodehandle = nodeToClone->nodehandle;

    // file's crypto key
    newnode->nodekey = nodeToClone->nodekey();
    assert(newnode->nodekey.size() == FILENODEKEYLENGTH);

    // copy attrs
    AttrMap tmpAttrs;
    tmpAttrs.map = nodeToClone->attrs.map;

    // Serialize fsNode fp, overriding nodeToClone’s attrs.
    serializefingerprint(&tmpAttrs.map['c']);

    attr_map::iterator it = tmpAttrs.map.find(AttrMap::string2nameid("rr"));
    if (it != tmpAttrs.map.end())
    {
        LOG_debug << "Removing rr attribute for clone";
        tmpAttrs.map.erase(it);
    }
    newnode->type = FILENODE;
    newnode->parenthandle = UNDEF;

    // store filename
    tmpAttrs.map['n'] = name;

    string tattrstring;
    tmpAttrs.getjson(&tattrstring);

    newnode->attrstring.reset(new string);
    MegaClient::makeattr(client->getRecycledTemporaryTransferCipher((byte*)newnode->nodekey.data(), FILENODE),
                         newnode->attrstring, tattrstring.c_str());

    if (targetuser.size())
    {
        // drop file into targetuser's inbox (obsolete feature, kept for sending logs to helpdesk)
        client->putnodes(targetuser.c_str(), std::move(newnodes), tag, std::move(completion));
    }
    else
    {
        NodeHandle th = h;
        assert(syncxfer);
        newnode->ovhandle = ovHandle;
        if (pitag.target == PitagTarget::NotApplicable)
        {
            const std::shared_ptr<Node> parentNode = client->nodeByHandle(th);
            const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching(
                                                           [](const Node& node)
                                                           {
                                                               return node.inshare != nullptr;
                                                           });
            pitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive;
        }

        client->queueCommand(new CommandPutNodes(client,
                                                 th,
                                                 NULL,
                                                 UseLocalVersioningFlag,
                                                 std::move(newnodes),
                                                 tag,
                                                 source,
                                                 nullptr,
                                                 std::move(completion),
                                                 canChangeVault,
                                                 {}, // customerIpPort
                                                 pitag));
    }
}

void File::terminated(error)
{

}

// do not retry crypto errors or administrative takedowns
bool File::failed(error e, MegaClient*)
{
    if (e == API_EKEY)
    {
        return false; // mac error; do not retry
    }

    return // Non fatal errors, up to FILE_MAX_RETRIES retries
        ((e != API_EBLOCKED && e != API_ENOENT && e != API_EINTERNAL && e != API_EACCESS &&
          e != API_ETOOMANY && transfer->failcount < FILE_MAX_RETRIES)
         // I/O errors up to FILE_IO_MAX_RETRIES retries
         && !((e == API_EREAD || e == API_EWRITE) && transfer->failcount > FILE_IO_MAX_RETRIES))
        // Retry sync transfers up to FILE_SYNC_MAX_RETRIES times for erros that doesn't have a
        // specific management to prevent immediate retries triggered by the sync engine
        || (syncxfer && e != API_EBLOCKED && e != API_EKEY &&
            transfer->failcount <= FILE_SYNC_MAX_RETRIES)
        // Infinite retries for storage overquota errors
        || e == API_EOVERQUOTA || e == API_EGOINGOVERQUOTA;
}

void File::displayname(string* dname)
{
    if (name.size())
    {
        *dname = name;
    }
    else
    {
        shared_ptr<Node> n;

        if ((n = transfer->client->nodeByHandle(h)))
        {
            *dname = n->displayname();
        }
        else
        {
            *dname = "DELETED/UNAVAILABLE";
        }
    }
}

string File::displayname()
{
    string result;

    displayname(&result);

    return result;
}

#ifdef ENABLE_SYNC

void SyncTransfer_inClient::terminated(error e)
{
    mError = e;

    File::terminated(e);

    if (e == API_EOVERQUOTA)
    {
        syncThreadSafeState->client()->syncs.disableSyncByBackupId(syncThreadSafeState->backupId(), FOREIGN_TARGET_OVERSTORAGE, false, true, nullptr);
    }

    // We shouldn't call terminated twice but, if we do, all the calls must be done before notifying
    // the apps
    assert(!wasTerminated || !terminatedReasonAlreadyKnown);
    wasTerminated = true;
    selfKeepAlive.reset();  // deletes this object! (if abandoned by sync)
}

void SyncTransfer_inClient::completed(Transfer*, [[maybe_unused]] putsource_t source)
{
    assert(source == PUTNODES_SYNC);

    // do not allow the base class to submit putnodes immediately
    //File::completed(t, source);

    assert(!wasFileTransferCompleted);
    wasFileTransferCompleted = true;
    selfKeepAlive.reset();  // deletes this object! (if abandoned by sync)
}

void SyncUpload_inClient::completed(Transfer* t, putsource_t source)
{
    // Keep the info required for putnodes and wait for
    // the sync thread to validate and activate the putnodes

    uploadHandle = t->uploadhandle;
    uploadToken = *t->ultoken;
    fileNodeKey = t->filekey;
    if (auto c = t->client)
        c->pendingattrstring(uploadHandle, &fileAttr);

    SyncTransfer_inClient::completed(t, source);
}

void SyncUpload_inClient::fullUpload(MegaClient& client,
                                     TransferDbCommitter& committer,
                                     const VersioningOption vo,
                                     const bool queueFirst)
{
    // Reset flags that signal transfer stage status
    wasFileTransferCompleted = false;
    upsyncStarted = false;
    tag = client.nextreqtag();
    selfKeepAlive = shared_from_this();
    client.startxfer(PUT, this, committer, false, queueFirst, false, vo, nullptr, tag);
}

void SyncUpload_inClient::cloneNode(MegaClient& client,
                                    std::shared_ptr<Node> cloneNodeCandidate,
                                    const NodeHandle ovHandleIfShortcut)
{
    // We have found a candidate node to clone with a valid key, call putNodesToCloneNode.
    const auto displayPath = cloneNodeCandidate->displaypath();
    LOG_debug << "Cloning node rather than sync uploading: " << displayPath << " for "
              << sourceLocalname;

    // completion function is supplied to putNodes command
    sendPutnodesToCloneNode(&client,
                            ovHandleIfShortcut,
                            cloneNodeCandidate.get(),
                            buildSyncClonePitag());
    // Set `true` even though no actual data transfer occurred, we're sending putnodes to clone
    // node instead
    wasFileTransferCompleted = true;
    upsyncStarted = true;
}

bool SyncUpload_inClient::updateNodeMtime(MegaClient* client,
                                          std::shared_ptr<Node> node,
                                          const m_time_t newMtime,
                                          std::function<void(NodeHandle, Error)>&& completion)
{
    return client->updateNodeMtime(node, newMtime, std::move(completion));
}

Pitag SyncUpload_inClient::buildSyncUploadPitag() const
{
    Pitag pitag{syncThreadSafeState->mCanChangeVault ? PitagPurpose::Backup : PitagPurpose::Sync,
                PitagTrigger::SyncAlgorithm,
                PitagNodeType::File,
                PitagTarget::NotApplicable,
                PitagImportSource::NotApplicable};
    return pitag;
}

Pitag SyncUpload_inClient::buildSyncClonePitag() const
{
    Pitag pitag{PitagPurpose::CopyInternal,
                PitagTrigger::SyncAlgorithm,
                PitagNodeType::File,
                PitagTarget::NotApplicable,
                PitagImportSource::NotApplicable};
    return pitag;
}

void SyncUpload_inClient::sendPutnodesOfUpload(MegaClient* client, NodeHandle ovHandle)
{
    // Always called from the client thread
    weak_ptr<SyncThreadsafeState> stts = syncThreadSafeState;

    // So we know whether it's safe to update putnodesCompleted.
    weak_ptr<SyncUpload_inClient> self = shared_from_this();

    // since we are now sending putnodes, no need to remember puts to inform the client on abandonment
    syncThreadSafeState->client()->transferBackstop.forget(tag);

    if (sourceLocalname.toPath(false) == ".gitignore")
    {
        ovHandle.isUndef() ? client->sendevent(99493, "New .gitignore file synced up") :
                             client->sendevent(99494, "Existing .gitignore file modified");
    }

    File::sendPutnodesOfUpload(
        client,
        uploadHandle,
        std::move(fileAttr),
        uploadToken,
        fileNodeKey,
        PUTNODES_SYNC,
        ovHandle,
        [self, stts, client](const Error& e,
                             targettype_t t,
                             vector<NewNode>& nn,
                             bool targetOverride,
                             int ownTag,
                             const map<string, string>& fileHandles)
        {
            // Is the originating transfer still alive?
            if (auto s = self.lock())
            {
                // Then track the result of its putnodes request.
                s->upsyncFailed = e != API_OK;

                // Capture the handle if the putnodes was successful.
                if (!s->upsyncFailed)
                {
                    assert(!nn.empty());
                    s->upsyncResultHandle.set6byte(nn.front().mAddedHandle);
                }

                // Let the engine know the putnodes has completed.
                s->wasUpsyncCompleted.store(true);
            }

            if (auto s = stts.lock())
            {
                if (e == API_EACCESS)
                {
                    client->sendevent(99402, "API_EACCESS putting node in sync transfer", 0);
                }
                else if (e == API_EOVERQUOTA)
                {
                    client->syncs.disableSyncByBackupId(s->backupId(),  FOREIGN_TARGET_OVERSTORAGE, false, true, nullptr);
                }
            }

            // since we used a completion function, putnodes_result is not called.
            // but the intermediate layer still needs that in order to call the client app back:
            client->app->putnodes_result(e, t, nn, targetOverride, ownTag, fileHandles);
        },
        nullptr,
        syncThreadSafeState->mCanChangeVault,
        buildSyncUploadPitag());
}

void SyncUpload_inClient::sendPutnodesToCloneNode(MegaClient* client,
                                                  NodeHandle ovHandle,
                                                  Node* nodeToClone,
                                                  Pitag pitag)
{
    // Always called from the client thread
    weak_ptr<SyncThreadsafeState> stts = syncThreadSafeState;

    if (sourceLocalname.toPath(false) == ".gitignore")
    {
        ovHandle.isUndef() ? client->sendevent(99493, "New .gitignore file synced up") :
                             client->sendevent(99494, "Existing .gitignore file modified");
    }

    // So we know whether it's safe to update putnodesCompleted.
    weak_ptr<SyncUpload_inClient> self = shared_from_this();

    File::sendPutnodesToCloneNode(
        client,
        nodeToClone,
        PUTNODES_SYNC,
        ovHandle,
        [self, stts, client](const Error& e,
                             targettype_t /*t*/,
                             vector<NewNode>& nn,
                             bool /*targetOverride*/,
                             int /*tag*/,
                             const map<string, string>& /*fileHandles*/)
        {
            // Is the originating transfer still alive?
            if (auto s = self.lock())
            {
                // Then track the result of its putnodes request.
                s->upsyncFailed = e != API_OK;

                // Capture the handle if the putnodes was successful.
                if (!s->upsyncFailed)
                {
                    assert(!nn.empty());
                    s->upsyncResultHandle.set6byte(nn.front().mAddedHandle);
                }

                // Let the engine know the putnodes has completed.
                s->wasUpsyncCompleted.store(true);
            }

            if (auto s = stts.lock())
            {
                if (e == API_EACCESS)
                {
                    client->sendevent(99402, "API_EACCESS putting node in sync transfer", 0);
                }
                else if (e == API_EOVERQUOTA)
                {
                    client->syncs.disableSyncByBackupId(s->backupId(),
                                                        FOREIGN_TARGET_OVERSTORAGE,
                                                        false,
                                                        true,
                                                        nullptr);
                }
            }
        },
        syncThreadSafeState->mCanChangeVault,
        pitag);
}

SyncUpload_inClient::SyncUpload_inClient(NodeHandle targetFolder,
                                         const LocalPath& fullPath,
                                         const string& nodeName,
                                         const FileFingerprint& ff,
                                         shared_ptr<SyncThreadsafeState> stss,
                                         handle fsid,
                                         const LocalPath& localname,
                                         bool fromInshare,
                                         const int64_t metamac,
                                         const AttributeOnlyUpdate attributeOnlyUpdate)
{
    *static_cast<FileFingerprint*>(this) = ff;

    // normalized name (UTF-8 with unescaped special chars)
    // todo: we did unescape them though?
    name = nodeName;

    // setting the full path means it works like a normal non-sync transfer
    setLocalname(fullPath);

    h = targetFolder;

    hprivate = false;
    hforeign = false;
    syncxfer = true;
    fromInsycShare = fromInshare;
    temporaryfile = false;
    chatauth = nullptr;
    transfer = nullptr;
    tag = 0;

    syncThreadSafeState = std::move(stss);
    syncThreadSafeState->transferBegin(PUT, size);

    sourceFsid = fsid;
    sourceLocalname = localname;
    this->attributeOnlyUpdate = attributeOnlyUpdate;
    if (metamac != INVALID_META_MAC)
    {
        mMetaMac.emplace(metamac);
    }

    LOG_debug << "[SyncUpload_inClient()] Name: '" << getLocalname() << "'. Source local name: '"
              << sourceLocalname.toPath(false) << "'. Source fsid: " << fsid
              << ". Fingerprint: " << fingerprintDebugString();
}

SyncUpload_inClient::~SyncUpload_inClient()
{
    if (!wasTerminated && !wasFileTransferCompleted)
    {
        assert(wasRequesterAbandoned);
        transfer = nullptr;  // don't try to remove File from Transfer from the wrong thread
    }

    if (wasFileTransferCompleted && wasUpsyncCompleted)
    {
        syncThreadSafeState->transferComplete(PUT, size);
    }
    else
    {
        syncThreadSafeState->transferFailed(PUT, size);
    }

    if (upsyncStarted)
    {
        syncThreadSafeState->removeExpectedUpload(h, name);
    }

    LOG_debug << "[~SyncUpload_inClient()] Name: '" << getLocalname() << "'. Source local name: '"
              << sourceLocalname.toPath(false) << "'. Source fsid: " << sourceFsid
              << ". Fingerprint: " << fingerprintDebugString();
}

void SyncUpload_inClient::prepare(FileSystemAccess&)
{
    transfer->localfilename = getLocalname();

    // is this transfer in progress? update file's filename.
    if (transfer->slot && transfer->slot->fa && !transfer->slot->fa->nonblocking_localname.empty())
    {
        transfer->slot->fa->updatelocalname(transfer->localfilename, false);
    }

    //todo: localNode.treestate(TREESTATE_SYNCING);
}

void SyncUpload_inClient::updateFingerprintMtime(const m_time_t newMtime)
{
    if (wasStarted)
    {
        assert(false && "Trying to update fingerprint with the upload alredy started");
        return;
    }

    LOG_debug << "[SyncUpload_inClient::updateFingerprintMtime] Name: '" << getLocalname()
              << "'. Source fsid: " << sourceFsid << ". Prev mTime: " << mtime
              << ". New mTime: " << newMtime;

    mtime = newMtime;
}

void SyncUpload_inClient::updateFingerprint(const FileFingerprint& newFingerprint)
{
    if (wasStarted)
    {
        assert(false && "Trying to update fingerprint with the upload alredy started");
        return;
    }

    if (size != newFingerprint.size)
    {
        // Reset transfer tracking values
        syncThreadSafeState->transferFailed(PUT, size);
        syncThreadSafeState->transferBegin(PUT, newFingerprint.size);
    }

    LOG_debug << "[SyncUpload_inClient::updateFingerprint] Name: '" << getLocalname()
              << "'. Source fsid: " << sourceFsid
              << ". Prev Fingerprint: " << fingerprintDebugString()
              << ". New Fingerprint: " << newFingerprint.fingerprintDebugString();

    FileFingerprint::operator=(newFingerprint);
}

SyncDownload_inClient::SyncDownload_inClient(CloudNode& n,
                                             const LocalPath& clocalname,
                                             bool fromInshare,
                                             shared_ptr<SyncThreadsafeState> stss,
                                             const FileFingerprint& overwriteFF,
                                             const int64_t metamac,
                                             const AttributeOnlyUpdate attributeOnlyUpdate)
{
    h = n.handle;
    *(FileFingerprint*)this = n.fingerprint;
    okToOverwriteFF = overwriteFF;

    syncxfer = true;
    fromInsycShare = fromInshare;

    setLocalname(clocalname);

    syncThreadSafeState = std::move(stss);
    syncThreadSafeState->transferBegin(GET, size);
    this->attributeOnlyUpdate = attributeOnlyUpdate;
    mMetaMac = metamac;
    LOG_debug << "[SyncDownload_inClient()] Name: '" << getLocalname() << "'. Handle: " << h
              << ". Cloud Fingerprint: " << fingerprintDebugString()
              << ". Local Fingerprint (overwrite): " << overwriteFF.fingerprintDebugString();
}

SyncDownload_inClient::~SyncDownload_inClient()
{
    if (!wasTerminated && !wasFileTransferCompleted)
    {
        assert(wasRequesterAbandoned);
        transfer = nullptr;  // don't try to remove File from Transfer from the wrong thread
    }

    if (!wasDistributed && downloadDistributor)
        downloadDistributor->removeTarget();

    if (wasFileTransferCompleted)
    {
        syncThreadSafeState->transferComplete(GET, size);
    }
    else
    {
        syncThreadSafeState->transferFailed(GET, size);
    }

    LOG_debug << "[~SyncDownload_inClient()] Name: '" << getLocalname() << "'. Handle: " << h
              << ". Cloud Fingerprint: " << fingerprintDebugString()
              << ". Local Fingerprint (overwrite): " << okToOverwriteFF.fingerprintDebugString();
}

void SyncDownload_inClient::prepare(FileSystemAccess&)
{
    if (transfer->localfilename.empty())
    {
        // set unique filename in sync-specific temp download directory
        transfer->localfilename = syncThreadSafeState->syncTmpFolder();
        transfer->localfilename.appendWithSeparator(LocalPath::tmpNameLocal(), true);

    }
}

bool SyncDownload_inClient::failed(error e, MegaClient* mc)
{
    // Squirrel away the error for later use.
    mError = e;

    // Should we retry the download?
    if (File::failed(e, mc))
        return true;

    // MAC validation error?
    if (e == API_EKEY)
        mc->sendevent(99433, "Undecryptable file", 0);

    return false;
}


#endif
} // namespace
