/**
 * @file megaapi_impl.cpp
 * @brief Private implementation of the intermediate layer for the MEGA C++ SDK.
 *
 * (c) 2013-2020 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.
 */

#define _LARGE_FILES

#define _GNU_SOURCE 1
#define _FILE_OFFSET_BITS 64

#define USE_VARARGS
#define PREFER_STDARG
#include "megaapi_impl.h"
#include "megaapi.h"
#include "mega/mediafileattribute.h"

#include <iomanip>
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
#include <thread>

#ifndef _WIN32
#ifndef _LARGEFILE64_SOURCE
    #define _LARGEFILE64_SOURCE
#endif
#include <signal.h>
#endif


#ifdef __APPLE__
    #include <xlocale.h>
    #include <strings.h>
#endif

#ifdef _WIN32
#include <shlwapi.h>
#endif

#ifdef USE_OPENSSL
#include <openssl/rand.h>
#endif

#include "mega/mega_zxcvbn.h"

namespace mega {

MegaNodePrivate::MegaNodePrivate(const char *name, int type, int64_t size, int64_t ctime, int64_t mtime, uint64_t nodehandle,
                                 string *nodekey, string *fileattrstring, const char *fingerprint, const char *originalFingerprint, MegaHandle owner, MegaHandle parentHandle,
                                 const char *privateauth, const char *publicauth, bool ispublic, bool isForeign, const char *chatauth, bool isNodeKeyDecrypted)
: MegaNode()
{
    this->name = MegaApi::strdup(name);
    this->fingerprint = MegaApi::strdup(fingerprint);
    this->originalfingerprint = MegaApi::strdup(originalFingerprint);
    this->customAttrs = NULL;
    this->duration = -1;
    this->width = -1;
    this->height = -1;
    this->shortformat = -1;
    this->videocodecid = -1;
    this->latitude = INVALID_COORDINATE;
    this->longitude = INVALID_COORDINATE;
    this->type = type;
    this->size = size;
    this->ctime = ctime;
    this->mtime = mtime;
    this->nodehandle = nodehandle;
    this->parenthandle = parentHandle;
    mIsNodeKeyDecrypted = isNodeKeyDecrypted;
    this->fileattrstring.assign(fileattrstring->data(), fileattrstring->size());
    this->nodekey.assign(nodekey->data(), nodekey->size());
    this->changed = 0;
    this->thumbnailAvailable = (Node::hasfileattribute(fileattrstring, GfxProc::THUMBNAIL) != 0);
    this->previewAvailable = (Node::hasfileattribute(fileattrstring, GfxProc::PREVIEW) != 0);
    this->isPublicNode = ispublic;
    this->outShares = false;
    this->inShare = false;
    this->plink = NULL;
    this->mNewLinkFormat = false;
    this->sharekey = NULL;
    this->foreign = isForeign;
    this->children = NULL;
    this->owner = owner;
    this->mFavourite = false;
    this->mLabel = LBL_UNKNOWN;

    if (privateauth)
    {
        this->privateAuth = privateauth;
    }

    if (publicauth)
    {
        this->publicAuth = publicauth;
    }

    this->chatAuth = chatauth ? MegaApi::strdup(chatauth) : NULL;
}

MegaNodePrivate::MegaNodePrivate(MegaNode *node)
: MegaNode()
{
    this->name = MegaApi::strdup(node->getName());
    this->fingerprint = MegaApi::strdup(node->getFingerprint());
    this->originalfingerprint = MegaApi::strdup(node->getOriginalFingerprint());
    this->customAttrs = NULL;

    MegaNodePrivate *np = dynamic_cast<MegaNodePrivate *>(node);
    if (np)
    {
        this->duration = np->duration;
        this->width = np->width;
        this->height = np->height;
        this->shortformat = np->shortformat;
        this->videocodecid = np->videocodecid;
        this->mFavourite = np->mFavourite;
        this->mLabel = np->mLabel;
        this->mDeviceId = np->mDeviceId;
        this->mS4 = np->mS4;
    }
    else
    {
        this->duration = node->getDuration();
        this->width = node->getWidth();
        this->height = node->getHeight();
        this->shortformat = node->getShortformat();
        this->videocodecid = node->getVideocodecid();
        this->mFavourite = node->isFavourite();
        this->mLabel = static_cast<nodelabel_t>(node->getLabel());
        mMarkedSensitive = node->isMarkedSensitive();
    }

    this->latitude = node->getLatitude();
    this->longitude = node->getLongitude();
    this->restorehandle = node->getRestoreHandle();
    this->type = node->getType();
    this->size = node->getSize();
    this->ctime = node->getCreationTime();
    this->mtime = node->getModificationTime();
    this->nodehandle = node->getHandle();
    this->parenthandle = node->getParentHandle();
    mIsNodeKeyDecrypted = node->isNodeKeyDecrypted();
    char* fileAttributeString = node->getFileAttrString();
    if (fileAttributeString)
    {
        this->fileattrstring = std::string(fileAttributeString);
        delete [] fileAttributeString;
    }
    string *nodekey = node->getNodeKey();
    this->nodekey.assign(nodekey->data(),nodekey->size());
    this->changed = node->getChanges();
    this->thumbnailAvailable = node->hasThumbnail();
    this->previewAvailable = node->hasPreview();
    this->isPublicNode = node->isPublic();
    this->privateAuth = *node->getPrivateAuth();
    this->publicAuth = *node->getPublicAuth();
    this->chatAuth = node->getChatAuth() ? MegaApi::strdup(node->getChatAuth()) : NULL;
    this->outShares = node->isOutShare();
    this->inShare = node->isInShare();
    this->foreign = node->isForeign();
    this->sharekey = NULL;
    this->children = NULL;
    this->owner = node->getOwner();

    if (node->isExported())
    {
        this->plink = new PublicLink(node->getPublicHandle(), node->getPublicLinkCreationTime(),
                                     node->getExpirationTime(), node->isTakenDown(), node->getWritableLinkAuthKey());

        if (type == FOLDERNODE)
        {
            MegaNodePrivate *n = dynamic_cast<MegaNodePrivate *>(node);
            if (n)
            {
                string *sk = n->getSharekey();
                if (sk)
                {
                    this->sharekey = new string(*sk);
                }
            }
        }
    }
    else
    {
        this->plink = NULL;
    }
    this->mNewLinkFormat = np->isNewLinkFormat();

    if (node->hasCustomAttrs())
    {
        this->customAttrs = new attr_map();
        MegaStringList *names = node->getCustomAttrNames();
        for (int i = 0; i < names->size(); i++)
        {
            (*customAttrs)[AttrMap::string2nameid(names->get(i))] = node->getCustomAttr(names->get(i));
        }
        delete names;
    }
}

MegaNodePrivate::MegaNodePrivate(Node *node)
: MegaNode()
{
    this->name = MegaApi::strdup(node->displayname());
    this->fingerprint = NULL;
    this->originalfingerprint = NULL;
    this->children = NULL;
    this->chatAuth = NULL;

    if (node->isvalid)
    {
        string fingerprint;
        node->serializefingerprint(&fingerprint);
        m_off_t size = node->size;
        char bsize[sizeof(size)+1];
        int l = Serialize64::serialize((byte *)bsize, size);
        char *buf = new char[l * 4 / 3 + 4];
        char ssize = static_cast<char>('A' + Base64::btoa((const byte *)bsize, l, buf));
        string result(1, ssize);
        result.append(buf);
        result.append(fingerprint);
        delete [] buf;

        this->fingerprint = MegaApi::strdup(result.c_str());
    }

    this->duration = -1;
    this->width = -1;
    this->height = -1;
    this->shortformat = -1;
    this->videocodecid = -1;
    this->latitude = INVALID_COORDINATE;
    this->longitude = INVALID_COORDINATE;
    this->customAttrs = NULL;
    this->restorehandle = UNDEF;
    this->mFavourite = false;
    this->mLabel = LBL_UNKNOWN;

    char buf[10];
    for (attr_map::iterator it = node->attrs.map.begin(); it != node->attrs.map.end(); it++)
    {
        int attrlen = node->attrs.nameid2string(it->first, buf);
        buf[attrlen] = '\0';
        if (buf[0] == '_')
        {
           if (!customAttrs)
           {
               customAttrs = new attr_map();
           }

           nameid id = AttrMap::string2nameid(&buf[1]);
           (*customAttrs)[id] = it->second;
        }
        else
        {
            if (it->first == AttrMap::string2nameid("d"))
            {
               if (node->type == FILENODE)
               {
                   duration = int(Base64::atoi(&it->second));
               }
            }
            else if (it->first == AttrMap::string2nameid("l") || it->first == AttrMap::string2nameid("gp"))
            {
                if (node->type == FILENODE)
                {
                    string coords = it->second;
                    if ((it->first == AttrMap::string2nameid("l") && coords.size() != 8) ||
                        (it->first == AttrMap::string2nameid("gp") && coords.size() != Base64Str<16>::STRLEN))
                    {
                       LOG_warn << "Malformed GPS coordinates attribute";
                    }
                    else
                    {
                        bool ok = true;
                        if (it->first == AttrMap::string2nameid("gp"))
                        {
                            if (node->client && node->client->unshareablekey.size() == Base64Str<SymmCipher::KEYLENGTH>::STRLEN && coords.size() == Base64Str<16>::STRLEN)
                            {
                                SymmCipher c;
                                byte data[SymmCipher::BLOCKSIZE] = { 0 };
                                Base64::atob(coords.data(), data, Base64Str<SymmCipher::BLOCKSIZE>::STRLEN);

                                node->client->setkey(&c, node->client->unshareablekey.data());
                                c.ctr_crypt(data, SymmCipher::BLOCKSIZE, 0, 0, NULL, false);
                                ok = !memcmp(data, "unshare/", 8);
                                if (ok)
                                {
                                    coords = string((char*)data + 8, 8);
                                }
                            }
                            else
                            {
                                ok = false;
                            }
                        }

                        if (ok)
                        {
                            byte buf[3];
                            int number = 0;
                            if (Base64::atob((const char *)coords.substr(0, 4).data(), buf, sizeof(buf)) == sizeof(buf))
                            {
                                number = (buf[2] << 16) | (buf[1] << 8) | (buf[0]);
                                latitude = -90 + 180 * (double)number / 0xFFFFFF;
                            }

                            if (Base64::atob((const char *)coords.substr(4, 4).data(), buf, sizeof(buf)) == sizeof(buf))
                            {
                                number = (buf[2] << 16) | (buf[1] << 8) | (buf[0]);
                                longitude = -180 + 360 * (double)number / 0x01000000;
                            }
                        }
                    }

                    if (longitude < -180 || longitude > 180)
                    {
                        longitude = INVALID_COORDINATE;
                    }
                    if (latitude < -90 || latitude > 90)
                    {
                        latitude = INVALID_COORDINATE;
                    }
                    if (longitude == INVALID_COORDINATE || latitude == INVALID_COORDINATE)
                    {
                        longitude = INVALID_COORDINATE;
                        latitude = INVALID_COORDINATE;
                    }
               }
            }
            else if (it->first == AttrMap::string2nameid("rr"))
            {
                handle rr = 0;
                if (Base64::atob(it->second.c_str(), (byte *)&rr, sizeof(rr)) == MegaClient::NODEHANDLE)
                {
                    restorehandle = rr;
                }
            }
            else if (it->first == AttrMap::string2nameid("c") && !fingerprint)
            {
                fingerprint = MegaApi::strdup(it->second.c_str());
            }
            else if (it->first == AttrMap::string2nameid("c0"))
            {
                originalfingerprint = MegaApi::strdup(it->second.c_str());
            }
            else if (it->first == AttrMap::string2nameid("fav"))
            {
                try
                {
                    int fav = it->second.empty() ? 0 : std::stoi(it->second);
                    if (fav != 1 && it->second != "0")
                    {
                        LOG_err << "Invalid value for node attr fav: " << fav;
                    }
                    else
                    {
                        mFavourite = fav;
                    }
                }
                catch (std::exception& ex)
                {
                    LOG_err << "Conversion failure for node attr fav: " << ex.what();
                }
            }
            else if (it->first == AttrMap::string2nameid("sen"))
            {
                try
                {
                    int sen = it->second.empty() ? 0 : std::stoi(it->second);
                    if (sen != 1 && it->second != "0")
                    {
                        LOG_err << "Invalid value for node attr sen: " << sen;
                    }
                    else
                    {
                        mMarkedSensitive = sen;
                    }
                }
                catch (std::exception& ex)
                {
                    LOG_err << "Conversion failure for node attr sen: " << ex.what();
                }
            }
            else if (it->first == AttrMap::string2nameid("lbl"))
            {
                try
                {
                    int lbl = it->second.empty() ? LBL_UNKNOWN : std::stoi(it->second);
                    if ((lbl < LBL_RED || lbl > LBL_GREY)  && it->second != "0")
                    {
                        LOG_err << "Invalid value for node attr lbl: " << lbl;
                    }
                    else
                    {
                        mLabel = static_cast<nodelabel_t>(lbl);
                    }
                }
                catch (std::exception& ex)
                {
                    LOG_err << "Conversion failure for node attr lbl: " << ex.what();
                }
            }
            else if (it->first == AttrMap::string2nameid("dev-id") ||
                     it->first == AttrMap::string2nameid("drv-id"))
            {
                mDeviceId = it->second;
            }
            else if (it->first == AttrMap::string2nameid("s4"))
            {
                mS4 = it->second;
            }
        }
    }

    this->type = node->type;
    this->size = node->size;
    this->ctime = node->ctime;
    this->mtime = node->mtime;
    this->nodehandle = node->nodehandle;
    this->parenthandle = node->parent ? node->parent->nodehandle : INVALID_HANDLE;
    this->owner = node->owner;

    mIsNodeKeyDecrypted = node->attrstring == nullptr;  // it's reset after node's key decryption successfull
    this->fileattrstring = node->fileattrstring;
    this->nodekey = node->nodekeyUnchecked();

    this->changed = 0;
    if(node->changed.attrs)
    {
        this->changed |= MegaNode::CHANGE_TYPE_ATTRIBUTES;
    }
    if(node->changed.ctime)
    {
        this->changed |= MegaNode::CHANGE_TYPE_TIMESTAMP;
    }
    if(node->changed.fileattrstring)
    {
        this->changed |= MegaNode::CHANGE_TYPE_FILE_ATTRIBUTES;
    }
    if(node->changed.inshare)
    {
        this->changed |= MegaNode::CHANGE_TYPE_INSHARE;
    }
    if(node->changed.outshares)
    {
        this->changed |= MegaNode::CHANGE_TYPE_OUTSHARE;
    }
    if(node->changed.pendingshares)
    {
        this->changed |= MegaNode::CHANGE_TYPE_PENDINGSHARE;
    }
    if(node->changed.owner)
    {
        this->changed |= MegaNode::CHANGE_TYPE_OWNER;
    }
    if(node->changed.parent)
    {
        this->changed |= MegaNode::CHANGE_TYPE_PARENT;
    }
    if(node->changed.removed)
    {
        this->changed |= MegaNode::CHANGE_TYPE_REMOVED;
    }
    if(node->changed.publiclink)
    {
        this->changed |= MegaNode::CHANGE_TYPE_PUBLIC_LINK;
    }
    if(node->changed.newnode)
    {
        this->changed |= MegaNode::CHANGE_TYPE_NEW;
    }
    if (node->changed.name)
    {
        this->changed |= MegaNode::CHANGE_TYPE_NAME;
    }
    if (node->changed.favourite)
    {
        this->changed |= MegaNode::CHANGE_TYPE_FAVOURITE;
    }
    if (node->changed.counter)
    {
        this->changed |= MegaNode::CHANGE_TYPE_COUNTER;
    }
    if (node->changed.sensitive)
    {
        this->changed |= MegaNode::CHANGE_TYPE_SENSITIVE;
    }

    this->thumbnailAvailable = (node->hasfileattribute(0) != 0);
    this->previewAvailable = (node->hasfileattribute(1) != 0);
    this->isPublicNode = false;
    this->foreign = false;

    // if there's only one share and it has no user --> public link
    this->outShares = (node->outshares) ? (node->outshares->size() > 1 || node->outshares->begin()->second->user) : false;
    this->inShare = node->inshare != nullptr;
    this->plink = node->plink ? new PublicLink(node->plink) : NULL;
    this->mNewLinkFormat = node->client->mNewLinkFormat;
    if (plink && type == FOLDERNODE && node->sharekey)
    {
        char key[FOLDERNODEKEYLENGTH*4/3+3];
        Base64::btoa(node->sharekey->key, FOLDERNODEKEYLENGTH, key);
        this->sharekey = new string(key);
    }
    else
    {
        this->sharekey = NULL;
    }
}

string* MegaNodePrivate::getSharekey()
{
    return sharekey;
}

MegaNode *MegaNodePrivate::copy()
{
    return new MegaNodePrivate(this);
}

char *MegaNodePrivate::serialize()
{
    string d;
    if (!serialize(&d))
    {
        return NULL;
    }

    char *ret = new char[d.size()*4/3+3];
    Base64::btoa((byte*) d.data(), int(d.size()), ret);

    return ret;
}

bool MegaNodePrivate::serialize(string *d) const
{
    CacheableWriter w(*d);
    w.serializecstr(name, true);
    w.serializecstr(fingerprint, true);
    w.serializei64(size);
    w.serializei64(ctime);
    w.serializei64(mtime);
    w.serializehandle(nodehandle);
    w.serializehandle(parenthandle);
    w.serializestring("");  // used to be attrstring
    w.serializestring(nodekey);
    w.serializestring(privateAuth);
    w.serializestring(publicAuth);
    w.serializebool(isPublicNode);
    w.serializebool(foreign);

    bool hasChatAuth = chatAuth && chatAuth[0];
    bool hasOwner = true;

    bool hasOriginalFingerprint = originalfingerprint && originalfingerprint[0];

    w.serializeexpansionflags(hasChatAuth, hasOwner, hasOriginalFingerprint, mIsNodeKeyDecrypted);

    if (hasChatAuth)
    {
        w.serializecstr(chatAuth, false);
    }
    if (hasOwner)
    {
        w.serializehandle(owner);
    }
    if (hasOriginalFingerprint)
    {
        w.serializecstr(originalfingerprint, false);
    }
    // 4th boolean in expansion flags will be set to the boolean value directly

    return true;
}

MegaNodePrivate *MegaNodePrivate::unserialize(string *d)
{
    CacheableReader r(*d);
    string name, fingerprint, originalfingerprint, attrstring, nodekey, privauth, pubauth, chatauth;
    int64_t size, ctime, mtime;
    MegaHandle nodehandle, parenthandle, owner = INVALID_HANDLE;
    bool isPublicNode, foreign, isNodeKeyDecrypted;
    unsigned char expansions[8];
    string fileattrstring; // fileattrstring is not serialized
    if (!r.unserializecstr(name, true) ||
        !r.unserializecstr(fingerprint, true) ||
        !r.unserializei64(size) ||
        !r.unserializei64(ctime) ||
        !r.unserializei64(mtime) ||
        !r.unserializehandle(nodehandle) ||
        !r.unserializehandle(parenthandle) ||
        !r.unserializestring(attrstring) ||
        !r.unserializestring(nodekey) ||
        !r.unserializestring(privauth) ||
        !r.unserializestring(pubauth) ||
        !r.unserializebool(isPublicNode) ||
        !r.unserializebool(foreign) ||
        !r.unserializeexpansionflags(expansions, 4) ||
        (expansions[0] && !r.unserializecstr(chatauth, false)) ||
        (expansions[1] && !r.unserializehandle(owner)) ||
        (expansions[2] && !r.unserializecstr(originalfingerprint, false)))
    {
        LOG_err << "MegaNode unserialization failed at field " << r.fieldnum;
        return NULL;
    }
    isNodeKeyDecrypted = expansions[3]; // the expansion flag is used to represent its value

    r.eraseused(*d);

    return new MegaNodePrivate(name.c_str(), FILENODE, size, ctime,
                               mtime, nodehandle, &nodekey, &fileattrstring,
                               fingerprint.empty() ? NULL : fingerprint.c_str(), originalfingerprint.empty() ? NULL : originalfingerprint.c_str(),
                               owner, parenthandle, privauth.c_str(), pubauth.c_str(),
                               isPublicNode, foreign, chatauth.empty() ? NULL : chatauth.c_str(), isNodeKeyDecrypted);
}

char *MegaNodePrivate::getBase64Handle()
{
    char *base64Handle = new char[12];
    Base64::btoa((byte*)&(nodehandle),MegaClient::NODEHANDLE,base64Handle);
    return base64Handle;
}

int MegaNodePrivate::getType()
{
    return type;
}

const char* MegaNodePrivate::getName()
{
    if(type <= FOLDERNODE)
    {
        return name;
    }

    switch(type)
    {
        case ROOTNODE:
            return "Cloud Drive";
        case VAULTNODE:
            return "Vault";
        case RUBBISHNODE:
            return "Rubbish Bin";
        default:
            return name;
    }
}

const char *MegaNodePrivate::getFingerprint()
{
    return fingerprint;
}

const char *MegaNodePrivate::getOriginalFingerprint()
{
    return originalfingerprint;
}

bool MegaNodePrivate::hasCustomAttrs()
{
    return customAttrs != NULL;
}

MegaStringList *MegaNodePrivate::getCustomAttrNames()
{
    if (!customAttrs)
    {
        return new MegaStringList();
    }

    string_vector names;
    for (attr_map::iterator it = customAttrs->begin(); it != customAttrs->end(); it++)
    {
        names.push_back(AttrMap::nameid2string(it->first));
    }
    return new MegaStringListPrivate(move(names));
}

const char *MegaNodePrivate::getCustomAttr(const char *attrName)
{
    if (!customAttrs)
    {
        return NULL;
    }

    nameid n = AttrMap::string2nameid(attrName);
    if (!n)
    {
        return NULL;
    }

    attr_map::iterator it = customAttrs->find(n);
    if (it == customAttrs->end())
    {
        return NULL;
    }

    return it->second.c_str();
}

int MegaNodePrivate::getDuration()
{
    if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size())
    {
        uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2);
        MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey);
        if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file
                && mediaProperties.shortformat != 254 // 254 = No information available
                && mediaProperties.playtime > 0)
        {
            return mediaProperties.playtime;
        }
    }

    return duration;
}

bool MegaNodePrivate::isFavourite()
{
    return mFavourite;
}

bool MegaNodePrivate::isMarkedSensitive()
{
    return mMarkedSensitive;
}

int MegaNodePrivate::getLabel()
{
    return mLabel;
}

int MegaNodePrivate::getWidth()
{
    if (width == -1)    // not initialized yet, or not available
    {
        if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size())
        {
            uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2);
            MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey);
            if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file
                    && mediaProperties.shortformat != 254 // 254 = No information available
                    && mediaProperties.width > 0)
            {
                width = mediaProperties.width;
            }
        }
    }

    return width;
}

int MegaNodePrivate::getHeight()
{
    if (height == -1)    // not initialized yet, or not available
    {
        if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size())
        {
            uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2);
            MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey);
            if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file
                    && mediaProperties.shortformat != 254 // 254 = No information available
                    && mediaProperties.height > 0)
            {
                height = mediaProperties.height;
            }
        }
    }

    return height;
}

int MegaNodePrivate::getShortformat()
{
    if (shortformat == -1)    // not initialized yet, or not available
    {
        if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size())
        {
            uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2);
            MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey);
            if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file
                && mediaProperties.shortformat != 254 // 254 = No information available
                && mediaProperties.shortformat > 0)
            {
                shortformat = mediaProperties.shortformat;
            }
        }
    }

    return shortformat;
}

int MegaNodePrivate::getVideocodecid()
{
    if (videocodecid == -1)    // not initialized yet, or not available
    {
        if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size())
        {
            uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2);
            MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey);
            if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file
                && mediaProperties.shortformat != 254 // 254 = No information available
                && mediaProperties.videocodecid > 0)
            {
                videocodecid = mediaProperties.videocodecid;
            }
        }
    }

    return videocodecid;
}

double MegaNodePrivate::getLatitude()
{
    return latitude;
}

double MegaNodePrivate::getLongitude()
{
    return longitude;
}

int64_t MegaNodePrivate::getSize()
{
    return size;
}

int64_t MegaNodePrivate::getCreationTime()
{
    return ctime;
}

int64_t MegaNodePrivate::getModificationTime()
{
    return mtime;
}

MegaHandle MegaNodePrivate::getRestoreHandle()
{
    return restorehandle;
}

MegaHandle MegaNodePrivate::getParentHandle()
{
    return parenthandle;
}

uint64_t MegaNodePrivate::getHandle()
{
    return nodehandle;
}

string *MegaNodePrivate::getNodeKey()
{
    return &nodekey;
}

bool MegaNodePrivate::isNodeKeyDecrypted()
{
    return mIsNodeKeyDecrypted;
}

char *MegaNodePrivate::getBase64Key()
{
    char *key = NULL;

    // the key
    if (type == FILENODE && nodekey.size() >= FILENODEKEYLENGTH)
    {
        key = new char[FILENODEKEYLENGTH * 4 / 3 + 3];
        Base64::btoa((const byte*)nodekey.data(), FILENODEKEYLENGTH, key);
    }
    else if (type == FOLDERNODE && sharekey)
    {
        key = MegaApi::strdup(sharekey->c_str());
    }
    else
    {
        key = new char[1];
        key[0] = 0;
    }

    return key;
}

char *MegaNodePrivate::getFileAttrString()
{
    char* fileAttributes = NULL;

    if (fileattrstring.size() > 0)
    {
        fileAttributes = MegaApi::strdup(fileattrstring.c_str());
    }

    return fileAttributes;
}

int64_t MegaNodePrivate::getExpirationTime()
{
    return plink ? plink->ets : -1;
}

MegaHandle MegaNodePrivate::getPublicHandle()
{
    return plink ? (MegaHandle) plink->ph : INVALID_HANDLE;
}

MegaNode* MegaNodePrivate::getPublicNode()
{
    if (!plink || plink->isExpired())
    {
        return NULL;
    }

    char *skey = getBase64Key();
    string key(skey);

    MegaNode *node = new MegaNodePrivate(
                name, type, size, ctime, mtime,
                plink->ph, &key, &fileattrstring, fingerprint, originalfingerprint,
                INVALID_HANDLE);

    delete [] skey;

    return node;
}

char *MegaNodePrivate::getPublicLink(bool includeKey)
{
    if (!plink)
    {
        return NULL;
    }

    char *base64k = getBase64Key();
    TypeOfLink lType = MegaClient::validTypeForPublicURL(static_cast<nodetype_t>(type));
    string strlink = MegaClient::publicLinkURL(mNewLinkFormat, lType, plink->ph, (includeKey ? base64k : nullptr));
    delete [] base64k;

    return MegaApi::strdup(strlink.c_str());
}

int64_t MegaNodePrivate::getPublicLinkCreationTime()
{
    return plink ? plink->cts : -1;
}

const char *MegaNodePrivate::getWritableLinkAuthKey()
{
    return (plink && !plink->mAuthKey.empty()) ? plink->mAuthKey.c_str() : nullptr;
}

bool MegaNodePrivate::isNewLinkFormat()
{
    return mNewLinkFormat;
}

bool MegaNodePrivate::isFile()
{
    return type == TYPE_FILE;
}

bool MegaNodePrivate::isFolder()
{
    return (type != TYPE_FILE) && (type != TYPE_UNKNOWN);
}

bool MegaNodePrivate::isRemoved()
{
    return hasChanged(MegaNode::CHANGE_TYPE_REMOVED);
}

bool MegaNodePrivate::hasChanged(int changeType)
{
    return (changed & changeType);
}

int MegaNodePrivate::getChanges()
{
    return changed;
}

MegaHandle MegaNodePrivate::getOwner() const
{
    return owner;
}

const char* MegaNodePrivate::getDeviceId() const
{
    return mDeviceId.c_str();
}

const char* MegaNodePrivate::getS4() const
{
    return mS4.c_str();
}

MegaBackgroundMediaUploadPrivate::MegaBackgroundMediaUploadPrivate(MegaApi* capi)
    : api(MegaApiImpl::ImplOf(capi))
{
    // generate fresh random encryption key/CTR IV for this file
    api->client->rng.genblock(filekey, sizeof filekey);
}

MegaBackgroundMediaUploadPrivate::MegaBackgroundMediaUploadPrivate(const string& serialised, MegaApi* capi)
    : api(MegaApiImpl::ImplOf(capi))
{
    CacheableReader r(serialised);
    string mediapropertiesstr;
    unsigned char expansions[8];
    string fileattrstring; // fileattrstring is not serialized
    if (!r.unserializebinary(filekey, sizeof(filekey)) ||
        !r.unserializechunkmacs(chunkmacs) ||
        !r.unserializestring(mediapropertiesstr) ||
        !r.unserializestring(url) ||
        !r.unserializedouble(latitude) ||
        !r.unserializedouble(longitude) ||
        !r.unserializebool(unshareableGPS) ||
        !r.unserializehandle(thumbnailFA) ||
        !r.unserializehandle(previewFA) ||
        !r.unserializeexpansionflags(expansions, 0))
    {
        LOG_err << "MegaBackgroundMediaUploadPrivate unserialization failed at field " << r.fieldnum;
    }
    else
    {
        mediaproperties = MediaProperties(mediapropertiesstr);
    }
}

bool MegaBackgroundMediaUploadPrivate::serialize(string* s)
{
    CacheableWriter w(*s);
    w.serializebinary(filekey, sizeof(filekey));
    w.serializechunkmacs(chunkmacs);
    w.serializestring(mediaproperties.serialize());
    w.serializestring(url);
    w.serializedouble(latitude);
    w.serializedouble(longitude);
    w.serializebool(unshareableGPS);
    w.serializehandle(thumbnailFA);
    w.serializehandle(previewFA);
    w.serializeexpansionflags();  // if/when we add more in future, set the first one true to signal the new set are present.
    return s;
}

char *MegaBackgroundMediaUploadPrivate::serialize()
{
    string d;
    return serialize(&d) ? MegaApi::binaryToBase64(d.data(), d.size()) : NULL;
}

void MegaBackgroundMediaUploadPrivate::setThumbnail(MegaHandle h)
{
    thumbnailFA = h;
}

void MegaBackgroundMediaUploadPrivate::setPreview(MegaHandle h)
{
    previewFA = h;
}

void MegaBackgroundMediaUploadPrivate::setCoordinates(double lat, double lon, bool unsh)
{
    latitude = lat;
    longitude = lon;
    unshareableGPS = unsh;
}

SymmCipher* MegaBackgroundMediaUploadPrivate::nodecipher(MegaClient* client)
{
    return client->getRecycledTemporaryNodeCipher(filekey);
}

MegaBackgroundMediaUploadPrivate::~MegaBackgroundMediaUploadPrivate()
{
}

bool MegaBackgroundMediaUploadPrivate::analyseMediaInfo(const char* inputFilepath)
{
#ifdef USE_MEDIAINFO
    if (!api->client->mediaFileInfo.mediaCodecsReceived)
    {
        // the client app should already have requested these but just in case:
        api->client->mediaFileInfo.requestCodecMappingsOneTime(api->client, LocalPath());
        return false;
    }

    auto localfilename = LocalPath::fromAbsolutePath(inputFilepath);

    string ext;
    if (api->fsAccess->getextension(localfilename, ext) && MediaProperties::isMediaFilenameExt(ext))
    {
        mediaproperties.extractMediaPropertyFileAttributes(localfilename, api->fsAccess.get());

        // cause the codec IDs to be looked up before serialization. Codec names are not serialized, just the codec IDs
        uint32_t dummykey[4];
        mediaproperties.convertMediaPropertyFileAttributes(dummykey, api->client->mediaFileInfo);
    }
#endif
    return true;
}

char *MegaBackgroundMediaUploadPrivate::encryptFile(const char* inputFilepath, int64_t startPos, int64_t* length, const char* outputFilepath, bool adjustsizeonly)
{
    if (startPos != ChunkedHash::chunkfloor(startPos))
    {
        LOG_err << "non-chunk start postion supplied";
        return nullptr;
    }

    std::unique_ptr<FileAccess> fain(api->fsAccess->newfileaccess());
    auto localfilename = LocalPath::fromAbsolutePath(inputFilepath);

    if (fain->fopen(localfilename, true, false, FSLogging::logOnError) || fain->type != FILENODE)
    {
        if (*length == -1)
        {
            *length = fain->size - startPos;
        }
        if (startPos < 0 || startPos > fain->size)
        {
            LOG_err << "invalid startPos supplied";
            return nullptr;
        }
        else if (*length < 0 || startPos + *length > fain->size)
        {
            LOG_err << "invalid enryption length supplied";
            return nullptr;
        }
        else
        {
            // make sure we load to a chunk boundary
            m_off_t endPos = ChunkedHash::chunkceil(startPos + *length, fain->size);
            *length = endPos - startPos;
            if (adjustsizeonly)
            {
                // return non-null to indicate success.  As it's a string return in the standard case, caller must deallocate as usual.
                return MegaApi::strdup("1");
            }
            else
            {
                auto localencryptedfilename = LocalPath::fromAbsolutePath(outputFilepath);

                std::unique_ptr<FileAccess> faout(api->fsAccess->newfileaccess());
                if (faout->fopen(localencryptedfilename, false, true, FSLogging::logOnError))
                {
                    SymmCipher cipher;
                    cipher.setkey(filekey);
                    uint64_t ctriv = MemAccess::get<uint64_t>((const char*)filekey + SymmCipher::KEYLENGTH);

                    EncryptFilePieceByChunks ef(fain.get(), startPos, faout.get(), 0, &cipher, &chunkmacs, ctriv);
                    string urlSuffix;
                    if (ef.encrypt(startPos, endPos, urlSuffix))
                    {
                        ((int64_t*)filekey)[3] = chunkmacs.macsmac(&cipher);
                        return MegaApi::strdup(urlSuffix.c_str());
                    }
                }
            }
        }
    }
    return nullptr;
}

char *MegaBackgroundMediaUploadPrivate::getUploadURL()
{
    return url.empty() ? nullptr : MegaApi::strdup(url.c_str());
}

EncryptFilePieceByChunks::EncryptFilePieceByChunks(FileAccess *cFain, m_off_t cInPos, FileAccess *cFaout, m_off_t cOutPos,
                                                   SymmCipher *cipher, chunkmac_map *chunkmacs, uint64_t ctriv)
    : EncryptByChunks(cipher, chunkmacs, ctriv)
    , fain(cFain), faout(cFaout)
    , inpos(cInPos), outpos(cOutPos)
    , lastsize(0)
{
}

byte *EncryptFilePieceByChunks::nextbuffer(unsigned bufsize)
{
    if (lastsize)
    {
        // write the last encrypted chunk
        if (!faout->fwrite((byte*)buffer.data(), lastsize, outpos))
        {
            return NULL;
        }
        outpos += lastsize;
    }

    buffer.resize(bufsize + SymmCipher::BLOCKSIZE);
    memset((void*)(buffer.data() + bufsize), 0, SymmCipher::BLOCKSIZE);
    if (!fain->frawread((byte*)buffer.data(), bufsize, inpos, false, FSLogging::logOnError))
    {
        return NULL;
    }
    lastsize = bufsize;
    inpos += bufsize;
    return (byte*)buffer.data();
}

#ifdef ENABLE_SYNC

bool MegaApiImpl::is_syncable(Sync *sync, const char *, const LocalPath& localpath)
{
    // Is this path excluded by any path filters?
    if (!excludedPaths.empty())
    {
        // Translate current path into something we can match.
        auto temp = localpath.toPath(true);

        // Is this path excluded by any path filters?
        for (const auto& xpath : excludedPaths)
        {
            auto xp = LocalPath::fromAbsolutePath(xpath);

            if (xp.isContainingPathOf(localpath))
            {
                return false;
            }

            if (wildcardMatch(temp.c_str(), xpath.c_str()))
            {
                return false;
            }
        }
    }

    // Check whether any path components are excluded.
    auto path = localpath;

    // Convenience.
    auto root = sync->localroot->getLocalname();

    while (root.isContainingPathOf(path) && path != root)
    {
        // Where does this component's name start?
        auto nameIndex = path.getLeafnameByteIndex();

        // Extract component's name.
        auto name = path.subpathFrom(nameIndex).toPath(false);

        // Skip these system files on OS X.
        if (name == "Icon\x0d")
        {
            return false;
        }

        // Is this component's name excluded by any filename filters?
        for (const auto& xname : excludedNames)
        {
            if (wildcardMatch(name.c_str(), xname.c_str()))
            {
                return false;
            }
        }

        // Climb to the next component.
        path.truncate(nameIndex - 1);
    }

    return true;
}

bool MegaApiImpl::is_syncable(long long size)
{
    if (!syncLowerSizeLimit)
    {
        // No lower limit. Check upper limit only
        if (syncUpperSizeLimit && size > syncUpperSizeLimit)
        {
            return false;
        }
    }
    else if (!syncUpperSizeLimit)
    {
        // No upper limit. Check lower limit only
        if (syncLowerSizeLimit && size < syncLowerSizeLimit)
        {
            return false;
        }
    }
    else
    {
        //Upper and lower limit
        if(syncLowerSizeLimit < syncUpperSizeLimit)
        {
            // Normal use case:
            // Exclude files with a size lower than the lower limit
            // or greater than the upper limit
            if(size < syncLowerSizeLimit || size > syncUpperSizeLimit)
            {
                return false;
            }
        }
        else
        {
            // Special use case:
            // Exclude files with a size lower than the lower limit
            // AND greater than the upper limit
            if(size < syncLowerSizeLimit && size > syncUpperSizeLimit)
            {
                return false;
            }
        }
    }

    return true;
}

int MegaApiImpl::isNodeSyncable(MegaNode *megaNode)
{
    MegaError *merror = isNodeSyncableWithError(megaNode);
    int r = merror->getErrorCode();
    delete merror;
    return r;
}

MegaError* MegaApiImpl::isNodeSyncableWithError(MegaNode* megaNode) {
    if (!megaNode)
    {
        return new MegaErrorPrivate(MegaError::API_EARGS);
    }

    SdkMutexGuard g(sdkMutex);
    Node* node = client->nodebyhandle(megaNode->getHandle());
    if (!node)
    {
        return new MegaErrorPrivate(MegaError::API_ENOENT);
    }

    SyncError se = SyncError::NO_SYNC_ERROR;
    error e = client->isnodesyncable(node, nullptr, &se);
    return new MegaErrorPrivate(e, se);
}

bool MegaApiImpl::isIndexing()
{
    if(client->syncscanstate)
    {
        return true;
    }

    SdkMutexGuard g(sdkMutex);
    bool indexing = false;
    client->syncs.forEachRunningSync([&](Sync* sync) {

        if(sync->state() == SYNC_INITIALSCAN)
        {
            indexing = true;
        }
    });
    return indexing;
}

bool MegaApiImpl::isSyncing()
{
    SdkMutexGuard g(sdkMutex);
    bool syncing = false;
    client->syncs.forEachRunningSync([&](Sync* sync) {

        if (sync->localroot->ts == TREESTATE_SYNCING || sync->localroot->ts == TREESTATE_PENDING)
        {
            syncing = true;
        }
    });
    return syncing;
}

MegaSync *MegaApiImpl::getSyncByBackupId(mega::MegaHandle backupId)
{
    SdkMutexGuard g(sdkMutex);

    // syncs has its own thread safety
    SyncConfig config;
    if (client->syncs.syncConfigByBackupId(backupId, config))
    {
        return new MegaSyncPrivate(config, client);
    }
    return nullptr;
}

MegaSync *MegaApiImpl::getSyncByNode(MegaNode *node)
{
    if (!node)
    {
        return nullptr;
    }

    SdkMutexGuard g(sdkMutex);

    NodeHandle nodeHandle = NodeHandle().set6byte(node->getHandle());

    // syncs has its own thread safety
    for (auto& config : client->syncs.getConfigs(false))
    {
        if (config.mRemoteNode == nodeHandle)
        {
            return new MegaSyncPrivate(config, client);
        }
    }
    return nullptr;
}

MegaSync *MegaApiImpl::getSyncByPath(const char *localPath)
{
    if (!localPath)
    {
        return nullptr;
    }

    SdkMutexGuard g(sdkMutex);

    // syncs has its own thread safety
    for (auto& config : client->syncs.getConfigs(false))
    {
        if (config.getLocalPath().toPath(false) == localPath)
        {
            return new MegaSyncPrivate(config, client);
        }
    }
    return nullptr;
}

char *MegaApiImpl::getBlockedPath()
{
    char *path = NULL;
    SdkMutexGuard g(sdkMutex);
    if (!client->blockedfile.empty())
    {
        path = MegaApi::strdup(client->blockedfile.toPath(false).c_str());
    }
    return path;
}

#endif // ENABLE_SYNC

MegaScheduledCopy *MegaApiImpl::getScheduledCopyByTag(int tag)
{
    SdkMutexGuard g(sdkMutex);
    if (backupsMap.find(tag) == backupsMap.end())
    {
        return NULL;
    }
    return backupsMap.at(tag)->copy();
}

MegaScheduledCopy *MegaApiImpl::getScheduledCopyByNode(MegaNode *node)
{
    if (!node)
    {
        return NULL;
    }

    MegaScheduledCopy *result = NULL;
    MegaHandle nodeHandle = node->getHandle();
    SdkMutexGuard g(sdkMutex);
    std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin();
    while(it != backupsMap.end())
    {
        MegaScheduledCopyController* backup = it->second;
        if (backup->getMegaHandle() == nodeHandle)
        {
            result = backup->copy();
            break;
        }
        it++;
    }
    return result;
}

MegaScheduledCopy *MegaApiImpl::getScheduledCopyByPath(const char *localPath)
{
    if (!localPath)
    {
        return NULL;
    }

    MegaScheduledCopy *result = NULL;
    SdkMutexGuard g(sdkMutex);
    std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin();
    while(it != backupsMap.end())
    {
        MegaScheduledCopyController* backup = it->second;
        if (!strcmp(localPath, backup->getLocalFolder()))
        {
            result = backup->copy();
            break;
        }
        it++;
    }
    return result;
}
bool MegaNodePrivate::hasThumbnail()
{
    return thumbnailAvailable;
}

bool MegaNodePrivate::hasPreview()
{
    return previewAvailable;
}

bool MegaNodePrivate::isPublic()
{
    return isPublicNode;
}

bool MegaNodePrivate::isShared()
{
    return outShares || inShare;
}

bool MegaNodePrivate::isOutShare()
{
    return outShares;
}

bool MegaNodePrivate::isInShare()
{
    return inShare;
}

bool MegaNodePrivate::isExported()
{
    return plink;
}

bool MegaNodePrivate::isExpired()
{
    return plink ? (plink->isExpired()) : false;
}

bool MegaNodePrivate::isTakenDown()
{
    return plink ? plink->takendown : false;
}

bool MegaNodePrivate::isForeign()
{
    return foreign;
}

string *MegaNodePrivate::getPrivateAuth()
{
    return &privateAuth;
}

MegaNodeList *MegaNodePrivate::getChildren()
{
    return children;
}

void MegaNodePrivate::setPrivateAuth(const char *privateAuth)
{
    if (!privateAuth || !privateAuth[0])
    {
        this->privateAuth.clear();
    }
    else
    {
        this->privateAuth = privateAuth;
    }
}

void MegaNodePrivate::setPublicAuth(const char *publicAuth)
{
    if (!publicAuth || !publicAuth[0])
    {
        this->publicAuth.clear();
    }
    else
    {
        this->publicAuth = publicAuth;
    }
}

void MegaNodePrivate::setChatAuth(const char *chatAuth)
{
    delete [] this->chatAuth;
    if (!chatAuth || !chatAuth[0])
    {
        this->chatAuth = NULL;
        this->foreign = false;
    }
    else
    {
        this->chatAuth = MegaApi::strdup(chatAuth);
        this->foreign = true;
    }
}

void MegaNodePrivate::setForeign(bool foreign)
{
    this->foreign = foreign;
}

void MegaNodePrivate::setChildren(MegaNodeList *children)
{
    this->children = children;
}

void MegaNodePrivate::setName(const char *newName)
{
    if (name)
        delete [] name;

    name = MegaApi::strdup(newName);
}

string *MegaNodePrivate::getPublicAuth()
{
    return &publicAuth;
}

const char *MegaNodePrivate::getChatAuth()
{
    return chatAuth;
}

MegaNodePrivate::~MegaNodePrivate()
{
    delete[] name;
    delete[] fingerprint;
    delete[] originalfingerprint;
    delete [] chatAuth;
    delete customAttrs;
    delete plink;
    delete sharekey;
    delete children;
}

MegaUserPrivate::MegaUserPrivate(User *user) : MegaUser()
{
    email = MegaApi::strdup(user->email.c_str());
    handle = user->userhandle;
    visibility = user->show;
    ctime = user->ctime;
    tag = user->getTag();
    changed = 0;
    if (user->changed.authring)
    {
        changed |= MegaUser::CHANGE_TYPE_AUTHRING;
    }
    if(user->changed.avatar)
    {
        changed |= MegaUser::CHANGE_TYPE_AVATAR;
    }
    if(user->changed.lstint)
    {
        changed |= MegaUser::CHANGE_TYPE_LSTINT;
    }
    if(user->changed.firstname)
    {
        changed |= MegaUser::CHANGE_TYPE_FIRSTNAME;
    }
    if(user->changed.lastname)
    {
        changed |= MegaUser::CHANGE_TYPE_LASTNAME;
    }
    if(user->changed.email)
    {
        changed |= MegaUser::CHANGE_TYPE_EMAIL;
    }
    if(user->changed.keyring)
    {
        changed |= MegaUser::CHANGE_TYPE_KEYRING;
    }
    if(user->changed.country)
    {
        changed |= MegaUser::CHANGE_TYPE_COUNTRY;
    }
    if(user->changed.birthday)
    {
        changed |= MegaUser::CHANGE_TYPE_BIRTHDAY;
    }
    if(user->changed.puCu255)
    {
        changed |= MegaUser::CHANGE_TYPE_PUBKEY_CU255;
    }
    if(user->changed.puEd255)
    {
        changed |= MegaUser::CHANGE_TYPE_PUBKEY_ED255;
    }
    if(user->changed.sigPubk)
    {
        changed |= MegaUser::CHANGE_TYPE_SIG_PUBKEY_RSA;
    }
    if(user->changed.sigCu255)
    {
        changed |= MegaUser::CHANGE_TYPE_SIG_PUBKEY_CU255;
    }
    if(user->changed.language)
    {
        changed |= MegaUser::CHANGE_TYPE_LANGUAGE;
    }
    if(user->changed.pwdReminder)
    {
        changed |= MegaUser::CHANGE_TYPE_PWD_REMINDER;
    }
    if(user->changed.disableVersions)
    {
        changed |= MegaUser::CHANGE_TYPE_DISABLE_VERSIONS;
    }
    if(user->changed.noCallKit)
    {
        changed |= MegaUser::CHANGE_TYPE_NO_CALLKIT;
    }
    if(user->changed.contactLinkVerification)
    {
        changed |= MegaUser::CHANGE_TYPE_CONTACT_LINK_VERIFICATION;
    }
    if(user->changed.richPreviews)
    {
        changed |= MegaUser::CHANGE_TYPE_RICH_PREVIEWS;
    }
    if(user->changed.rubbishTime)
    {
        changed |= MegaUser::CHANGE_TYPE_RUBBISH_TIME;
    }
    if(user->changed.storageState)
    {
        changed |= MegaUser::CHANGE_TYPE_STORAGE_STATE;
    }
    if(user->changed.geolocation)
    {
        changed |= MegaUser::CHANGE_TYPE_GEOLOCATION;
    }
    if(user->changed.cameraUploadsFolder)
    {
        changed |= MegaUser::CHANGE_TYPE_CAMERA_UPLOADS_FOLDER;
    }
    if(user->changed.myChatFilesFolder)
    {
        changed |= MegaUser::CHANGE_TYPE_MY_CHAT_FILES_FOLDER;
    }
    if (user->changed.pushSettings)
    {
        changed |= MegaUser::CHANGE_TYPE_PUSH_SETTINGS;
    }
    if (user->changed.alias)
    {
        changed |= MegaUser::CHANGE_TYPE_ALIAS;
    }
    if (user->changed.unshareablekey)
    {
        changed |= MegaUser::CHANGE_TYPE_UNSHAREABLE_KEY;
    }
    if (user->changed.devicenames)
    {
        changed |= MegaUser::CHANGE_TYPE_DEVICE_NAMES;
    }
    if (user->changed.myBackupsFolder)
    {
        changed |= MegaUser::CHANGE_TYPE_MY_BACKUPS_FOLDER;
    }
    if (user->changed.cookieSettings)
    {
        changed |= MegaUser::CHANGE_TYPE_COOKIE_SETTINGS;
    }
    if (user->changed.aPrefs)
    {
        changed |= MegaUser::CHANGE_APPS_PREFS;
    }
}

MegaUserPrivate::MegaUserPrivate(MegaUser *user) : MegaUser()
{
    email = MegaApi::strdup(user->getEmail());
    handle = user->getHandle();
    visibility = user->getVisibility();
    ctime = user->getTimestamp();
    changed = user->getChanges();
    tag = user->isOwnChange();
}

MegaUser *MegaUserPrivate::fromUser(User *user)
{
    if(!user)
    {
        return NULL;
    }
    return new MegaUserPrivate(user);
}

MegaUser *MegaUserPrivate::copy()
{
    return new MegaUserPrivate(this);
}

MegaUserPrivate::~MegaUserPrivate()
{
    delete[] email;
}

const char* MegaUserPrivate::getEmail()
{
    return email;
}

MegaHandle MegaUserPrivate::getHandle()
{
    return handle;
}

int MegaUserPrivate::getVisibility()
{
    return visibility;
}

int64_t MegaUserPrivate::getTimestamp()
{
    return ctime;
}

bool MegaUserPrivate::hasChanged(int changeType)
{
    return (changed & changeType);
}

int MegaUserPrivate::getChanges()
{
    return changed;
}

int MegaUserPrivate::isOwnChange()
{
    return tag;
}

bool MegaSetPrivate::hasChanged(int changeType) const
{
    assert((unsigned)changeType < (unsigned)CHANGE_TYPE_SIZE);
    return (unsigned)changeType < (unsigned)CHANGE_TYPE_SIZE ? mChanges[changeType] : false;
}

bool MegaSetElementPrivate::hasChanged(int changeType) const
{
    assert((unsigned)changeType < (unsigned)CHANGE_TYPE_ELEM_SIZE);
    return (unsigned)changeType < (unsigned)CHANGE_TYPE_ELEM_SIZE ? mChanges[changeType] : false;
}

MegaUserAlertPrivate::MegaUserAlertPrivate(UserAlert::Base *b, MegaClient* mc)
    : id(b->id)
    , seen(b->seen())
    , relevant(b->relevant())
    , type(-1)
    , tag(b->tag)
    , userHandle(UNDEF)
    , nodeHandle(UNDEF)
    , removed(b->removed())
{
    b->text(heading, title, mc);
    timestamps.push_back(b->ts());

    switch (b->type)
    {
    case UserAlert::type_ipc:
    {
        UserAlert::IncomingPendingContact* p = static_cast<UserAlert::IncomingPendingContact*>(b);
        if (p->requestWasDeleted)
        {
            type = TYPE_INCOMINGPENDINGCONTACT_CANCELLED;
        }
        else if (p->requestWasReminded)
        {
            type = TYPE_INCOMINGPENDINGCONTACT_REMINDER;
        }
        else
        {
            type = TYPE_INCOMINGPENDINGCONTACT_REQUEST;
        }
        userHandle = p->user();
        mPcrHandle = p->mPcrHandle;
        email = p->email();
    }
    break;
    case UserAlert::type_c:
    {
        UserAlert::ContactChange* p = static_cast<UserAlert::ContactChange*>(b);
        switch (p->action)
        {
        case 0: type = TYPE_CONTACTCHANGE_DELETEDYOU; break;
        case 1: type = TYPE_CONTACTCHANGE_CONTACTESTABLISHED; break;
        case 2: type = TYPE_CONTACTCHANGE_ACCOUNTDELETED; break;
        case 3: type = TYPE_CONTACTCHANGE_BLOCKEDYOU; break;
        }
        userHandle = p->user();
        email = p->email();
    }
    break;
    case UserAlert::type_upci:
    {
        UserAlert::UpdatedPendingContactIncoming* p = static_cast<UserAlert::UpdatedPendingContactIncoming*>(b);
        switch (p->action)
        {
        case 1: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED; break;
        case 2: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_ACCEPTED; break;
        case 3: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_DENIED; break;
        }
        userHandle = p->user();
        email = p->email();
    }
    break;
    case UserAlert::type_upco:
    {
        UserAlert::UpdatedPendingContactOutgoing* p = static_cast<UserAlert::UpdatedPendingContactOutgoing*>(b);
        switch (p->action)
        {
        case 1: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED; break;
        case 2: type = TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED; break;
        case 3: type = TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED; break;
        }
        userHandle = p->user();
        email = p->email();
    }
    break;
    case UserAlert::type_share:
    {
        UserAlert::NewShare* p = static_cast<UserAlert::NewShare*>(b);
        type = TYPE_NEWSHARE;
        userHandle = p->user();
        email = p->email();
        nodeHandle = p->folderhandle;
        if (Node* node = mc->nodebyhandle(p->folderhandle))
        {
            nodePath = node->displaypath();
            nodeName = node->displayname();
        }
    }
    break;
    case UserAlert::type_dshare:
    {
        UserAlert::DeletedShare* p = static_cast<UserAlert::DeletedShare*>(b);
        type = TYPE_DELETEDSHARE;
        userHandle = p->user();
        email = p->email();
        nodePath = p->folderPath;
        nodeName = p->folderName;
        nodeHandle = p->folderHandle;
        bool accessRevoked = p->user() == p->ownerHandle;
        numbers.push_back(accessRevoked ? 1 : 0);
    }
    break;
    case UserAlert::type_put:
    {
        UserAlert::NewSharedNodes* p = static_cast<UserAlert::NewSharedNodes*>(b);
        type = TYPE_NEWSHAREDNODES;
        userHandle = p->user();
        email = p->email();
        nodeHandle = p->parentHandle;
        numbers.push_back(p->folderNodeHandles.size());
        numbers.push_back(p->fileNodeHandles.size());
        handles.assign(begin(p->folderNodeHandles), end(p->folderNodeHandles));
        handles.insert(end(handles), begin(p->fileNodeHandles), end(p->fileNodeHandles));
    }
    break;
    case UserAlert::type_d:
    {
        UserAlert::RemovedSharedNode* p = static_cast<UserAlert::RemovedSharedNode*>(b);
        type = TYPE_REMOVEDSHAREDNODES;
        userHandle = p->user();
        email = p->email();
        numbers.push_back(p->nodeHandles.size());
    }
    break;
    case UserAlert::type_u:
    {
        UserAlert::UpdatedSharedNode* p = static_cast<UserAlert::UpdatedSharedNode*>(b);
        type = TYPE_UPDATEDSHAREDNODES;
        userHandle = p->user();
        email = p->email();
        numbers.push_back(p->nodeHandles.size());
    }
    break;
    case UserAlert::type_psts:
    {
        UserAlert::Payment* p = static_cast<UserAlert::Payment*>(b);
        type = p->success ? TYPE_PAYMENT_SUCCEEDED : TYPE_PAYMENT_FAILED;
        extraStrings.push_back(p->getProPlanName());
    }
    break;
    case UserAlert::type_pses:
    {
        UserAlert::PaymentReminder* p = static_cast<UserAlert::PaymentReminder*>(b);
        type = TYPE_PAYMENTREMINDER;
        timestamps.push_back(p->expiryTime);
    }
    break;
    case UserAlert::type_ph:
    {
        UserAlert::Takedown* p = static_cast<UserAlert::Takedown*>(b);
        if (p->isTakedown)
        {
            type = TYPE_TAKEDOWN;
        }
        else if (p->isReinstate)
        {
            type = TYPE_TAKEDOWN_REINSTATED;
        }
        nodeHandle = p->nodeHandle;
        Node* node = mc->nodebyhandle(nodeHandle);
        if (node)
        {
            nodePath = node->displaypath();
            nodeName = node->displayname();
        }
    }
    break;
#ifdef ENABLE_CHAT
    case UserAlert::type_nusm:
    {
         if (auto* p = dynamic_cast<UserAlert::NewScheduledMeeting*>(b))
         {
             type = TYPE_SCHEDULEDMEETING_NEW;
             userHandle = p->user();
             email = p->email();
             nodeHandle = p->mChatid;
             schedMeetingId = p->mSchedMeetingHandle;
             mPcrHandle = p->mParentSchedId;
             numbers.push_back(p->mStartDateTime);
         }
         else
         {
             if (auto* p = dynamic_cast<UserAlert::UpdatedScheduledMeeting*>(b))
             {
                 type = TYPE_SCHEDULEDMEETING_UPDATED;
                 userHandle = p->user();
                 email = p->email();
                 nodeHandle = p->mChatid;
                 schedMeetingId = p->mSchedMeetingHandle;
                 mPcrHandle = p->mParentSchedId;
                 numbers.push_back(p->mStartDateTime);
                 schedMeetingChangeset = p->mUpdatedChangeset;
             }
             else
             {
                 assert(false);
                 LOG_err << "Scheduled meeting user alert invalid sub-type (mangled): "
                         << typeid(*b).name()
                         << ", expected: NewSchedulingMeeting or UpdatedSchedulingMeeting";
             }
         }
    }
    break;
    case UserAlert::type_dsm:
    {
        if (auto* p = dynamic_cast<UserAlert::DeletedScheduledMeeting*>(b))
        {
            type = TYPE_SCHEDULEDMEETING_DELETED;
            userHandle = p->user();
            email = p->email();
            nodeHandle = p->mChatid;
            schedMeetingId = p->mSchedMeetingHandle;
        }
        else
        {
            LOG_err << "Scheduled meeting user alert invalid sub-type (mangled): "
                    << typeid(*b).name()
                    << ", expected: DeletedScheduledMeeting";
        }
    }
    break;
#endif
    } // end switch
}

MegaUserAlert *MegaUserAlertPrivate::copy() const
{
    return new MegaUserAlertPrivate(*this);
}

unsigned MegaUserAlertPrivate::getId() const
{
    return id;
}

bool MegaUserAlertPrivate::getSeen() const
{
    return seen;
}

bool MegaUserAlertPrivate::getRelevant() const
{
    return relevant;
}

int MegaUserAlertPrivate::getType() const
{
    return type;
}

const char *MegaUserAlertPrivate::getTypeString() const
{
    switch (type)
    {
    case TYPE_INCOMINGPENDINGCONTACT_REQUEST:           return "NEW_CONTACT_REQUEST";
    case TYPE_INCOMINGPENDINGCONTACT_CANCELLED:         return "CONTACT_REQUEST_CANCELLED";
    case TYPE_INCOMINGPENDINGCONTACT_REMINDER:          return "CONTACT_REQUEST_REMINDED";
    case TYPE_CONTACTCHANGE_DELETEDYOU:                 return "CONTACT_DISCONNECTED";
    case TYPE_CONTACTCHANGE_CONTACTESTABLISHED:         return "CONTACT_ESTABLISHED";
    case TYPE_CONTACTCHANGE_ACCOUNTDELETED:             return "CONTACT_ACCOUNTDELETED";
    case TYPE_CONTACTCHANGE_BLOCKEDYOU:                 return "CONTACT_BLOCKED";
    case TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED:    return "YOU_IGNORED_CONTACT";
    case TYPE_UPDATEDPENDINGCONTACTINCOMING_ACCEPTED:   return "YOU_ACCEPTED_CONTACT";
    case TYPE_UPDATEDPENDINGCONTACTINCOMING_DENIED:     return "YOU_DENIED_CONTACT";
    case TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED:   return "CONTACT_ACCEPTED_YOU";
    case TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED:     return "CONTACT_DENIED_YOU";
    case TYPE_NEWSHARE:                                 return "NEW_SHARE";
    case TYPE_DELETEDSHARE:                             return "SHARE_UNSHARED";
    case TYPE_NEWSHAREDNODES:                           return "NEW_NODES_IN_SHARE";
    case TYPE_REMOVEDSHAREDNODES:                       return "NODES_IN_SHARE_REMOVED";
    case TYPE_UPDATEDSHAREDNODES:                       return "NODES_IN_SHARE_UPDATED";
    case TYPE_PAYMENT_SUCCEEDED:                        return "PAYMENT_SUCCEEDED";
    case TYPE_PAYMENT_FAILED:                           return "PAYMENT_FAILED";
    case TYPE_PAYMENTREMINDER:                          return "PAYMENT_REMINDER";
    case TYPE_TAKEDOWN:                                 return "TAKEDOWN";
    case TYPE_TAKEDOWN_REINSTATED:                      return "TAKEDOWN_REINSTATED";
    case TYPE_SCHEDULEDMEETING_NEW:                     return "SCHEDULEDMEETING_NEW";
    case TYPE_SCHEDULEDMEETING_UPDATED:                 return "SCHEDULEDMEETING_UPDATED";
    case TYPE_SCHEDULEDMEETING_DELETED:                 return "SCHEDULEDMEETING_DELETED";
    }
    return "<new type>";
}

MegaHandle MegaUserAlertPrivate::getUserHandle() const
{
    return userHandle;
}

MegaHandle MegaUserAlertPrivate::getNodeHandle() const
{
    return nodeHandle;
}

MegaHandle mega::MegaUserAlertPrivate::getPcrHandle() const
{
    return mPcrHandle;
}

const char* MegaUserAlertPrivate::getEmail() const
{
    return email.empty() ? NULL : email.c_str();
}

const char*MegaUserAlertPrivate::getPath() const
{
    return  nodePath.empty() ? NULL : nodePath.c_str();
}

const char *MegaUserAlertPrivate::getName() const
{
    return  nodeName.empty() ? NULL : nodeName.c_str();
}

const char *MegaUserAlertPrivate::getHeading() const
{
    return heading.c_str();
}

const char *MegaUserAlertPrivate::getTitle() const
{
    return title.c_str();
}

int64_t MegaUserAlertPrivate::getNumber(unsigned index) const
{
    return index < numbers.size() ? numbers[index] : -1;
}

int64_t MegaUserAlertPrivate::getTimestamp(unsigned index) const
{
    return index < timestamps.size() ? timestamps[index] : -1;
}

const char* MegaUserAlertPrivate::getString(unsigned index) const
{
    return index < extraStrings.size() ? extraStrings[index].c_str() : NULL;
}

MegaHandle MegaUserAlertPrivate::getHandle(unsigned index) const
{
    return index < handles.size() ? handles[index] : INVALID_HANDLE;
}

#ifdef ENABLE_CHAT
MegaHandle MegaUserAlertPrivate::getSchedId() const
{
    return schedMeetingId;
}

bool MegaUserAlertPrivate::hasSchedMeetingChanged(int changeType) const
{
    return schedMeetingChangeset.hasChanged(changeType);
}

MegaStringList* MegaUserAlertPrivate::getUpdatedTitle() const
{
    if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_TITLE)
            || !schedMeetingChangeset.getUpdatedTitle())
    {
        return nullptr;
    }

    MegaStringList* updatedTitle = MegaStringList::createInstance();
    updatedTitle->add(Base64::atob(schedMeetingChangeset.getUpdatedTitle()->oldValue).c_str());
    updatedTitle->add(Base64::atob(schedMeetingChangeset.getUpdatedTitle()->newValue).c_str());
    return updatedTitle;
}

MegaStringList* MegaUserAlertPrivate::getUpdatedTimeZone() const
{
    if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_TIMEZONE)
            || !schedMeetingChangeset.getUpdatedTimeZone())
    {
        return nullptr;
    }

    MegaStringList* updatedTimezone = MegaStringList::createInstance();
    updatedTimezone->add(Base64::atob(schedMeetingChangeset.getUpdatedTimeZone()->oldValue).c_str());
    updatedTimezone->add(Base64::atob(schedMeetingChangeset.getUpdatedTimeZone()->newValue).c_str());
    return updatedTimezone;
}

MegaIntegerList* MegaUserAlertPrivate::getUpdatedStartDate() const
{
    if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_STARTDATE)
            || !schedMeetingChangeset.getUpdatedStartDateTime())
    {
        return nullptr;
    }

    MegaIntegerList* updatedStartDateTime = MegaIntegerList::createInstance();
    updatedStartDateTime->add(schedMeetingChangeset.getUpdatedStartDateTime()->oldValue);
    updatedStartDateTime->add(schedMeetingChangeset.getUpdatedStartDateTime()->newValue);
    return updatedStartDateTime;
}

MegaIntegerList* MegaUserAlertPrivate::getUpdatedEndDate() const
{
    if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_ENDDATE)
            || !schedMeetingChangeset.getUpdatedEndDateTime())
    {
        return nullptr;
    }

    MegaIntegerList* updatedEndDateTime = MegaIntegerList::createInstance();
    updatedEndDateTime->add(schedMeetingChangeset.getUpdatedEndDateTime()->oldValue);
    updatedEndDateTime->add(schedMeetingChangeset.getUpdatedEndDateTime()->newValue);
    return updatedEndDateTime;
}
#endif

bool MegaUserAlertPrivate::isOwnChange() const
{
    return tag != 0;
}

bool mega::MegaUserAlertPrivate::isRemoved() const
{
    return removed;
}

MegaNode *MegaNodePrivate::fromNode(Node *node)
{
    if(!node) return NULL;
    return new MegaNodePrivate(node);
}

MegaSharePrivate::MegaSharePrivate(MegaShare *share) : MegaShare()
{
    this->nodehandle = share->getNodeHandle();
    this->user = MegaApi::strdup(share->getUser());
    this->access = share->getAccess();
    this->ts = share->getTimestamp();
    this->pending = share->isPending();
    this->mVerified = share->isVerified();
}

MegaShare *MegaSharePrivate::copy()
{
    return new MegaSharePrivate(this);
}

MegaSharePrivate::MegaSharePrivate(uint64_t handle, Share *share, bool verified)
{
    this->nodehandle = handle;
    this->user = share->user ? MegaApi::strdup(share->user->email.c_str()) : NULL;
    if ((!user || !*user) && share->pcr)
    {
        delete [] user;
        user = MegaApi::strdup(share->pcr->isoutgoing ? share->pcr->targetemail.c_str() : share->pcr->originatoremail.c_str());
    }
    this->access = share->access;
    this->ts = share->ts;
    this->pending = share->pcr;
    this->mVerified = verified;
}

MegaShare *MegaSharePrivate::fromShare(uint64_t nodeuint64_t, Share *share, bool verified)
{
    return new MegaSharePrivate(nodeuint64_t, share, verified);
}

MegaSharePrivate::~MegaSharePrivate()
{
    delete[] user;
}

const char *MegaSharePrivate::getUser()
{
    return user;
}

uint64_t MegaSharePrivate::getNodeHandle()
{
    return nodehandle;
}

int MegaSharePrivate::getAccess()
{
    return access;
}

int64_t MegaSharePrivate::getTimestamp()
{
    return ts;
}

bool MegaSharePrivate::isPending()
{
    return pending;
}

bool MegaSharePrivate::isVerified()
{
    return mVerified;
}


MegaTransferPrivate::MegaTransferPrivate(int type, MegaTransferListener *listener)
{
    this->type = type;
    this->tag = -1;
    this->path = NULL;
    this->nodeHandle = UNDEF;
    this->parentHandle = UNDEF;
    this->startPos = -1;
    this->endPos = -1;
    this->parentPath = NULL;
    this->listener = listener;
    this->retry = 0;
    this->maxRetries = 7;
    this->time = -1;
    this->startTime = 0;
    this->transferredBytes = 0;
    this->totalBytes = 0;
    this->fileName = NULL;
    this->transfer = NULL;
    this->speed = 0;
    this->deltaSize = 0;
    this->updateTime = 0;
    this->publicNode = NULL;
    this->lastBytes = NULL;
    this->syncTransfer = false;
    this->streamingTransfer = false;
    this->temporarySourceFile = false;
    this->startFirst = false;
    this->backupTransfer = false;
    this->foreignOverquota = false;
    this->folderTransferTag = 0;
    this->appData = NULL;
    this->state = STATE_NONE;
    this->priority = 0;
    this->meanSpeed = 0;
    this->notificationNumber = 0;
    this->mStage = MegaTransfer::STAGE_NONE;
}

MegaTransferPrivate::MegaTransferPrivate(const MegaTransferPrivate *transfer)
{
    path = NULL;
    parentPath = NULL;
    fileName = NULL;
    publicNode = NULL;
    lastBytes = NULL;
    appData = NULL;

    this->listener = transfer->getListener();
    this->transfer = transfer->getTransfer();
    this->type = transfer->getType();
    this->setState(transfer->getState());
    this->setPriority(transfer->getPriority());
    this->setTag(transfer->getTag());
    this->setPath(transfer->getPath());
    this->setNodeHandle(transfer->getNodeHandle());
    this->setParentHandle(transfer->getParentHandle());
    this->setStartPos(transfer->getStartPos());
    this->setEndPos(transfer->getEndPos());
    this->setParentPath(transfer->getParentPath());
    this->setNumRetry(transfer->getNumRetry());
    this->setMaxRetries(transfer->getMaxRetries());
    this->setTime(transfer->getTime());
    this->startTime = transfer->getStartTime();
    this->setTransferredBytes(transfer->getTransferredBytes());
    this->setTotalBytes(transfer->getTotalBytes());
    this->setFileName(transfer->getFileName());
    this->setSpeed(transfer->getSpeed());
    this->setMeanSpeed(transfer->getMeanSpeed());
    this->setDeltaSize(transfer->getDeltaSize());
    this->setUpdateTime(transfer->getUpdateTime());
    this->setPublicNode(transfer->getPublicNode());
    this->setTransfer(transfer->getTransfer());
    this->setSyncTransfer(transfer->isSyncTransfer());
    this->setStreamingTransfer(transfer->isStreamingTransfer());
    this->setSourceFileTemporary(transfer->isSourceFileTemporary());
    this->setStartFirst(transfer->shouldStartFirst());
    this->setBackupTransfer(transfer->isBackupTransfer());
    this->setForeignOverquota(transfer->isForeignOverquota());
    this->setForceNewUpload(transfer->isForceNewUpload());
    this->setLastError(transfer->lastError.get());
    this->setFolderTransferTag(transfer->getFolderTransferTag());
    this->setAppData(transfer->getAppData());
    this->setNotificationNumber(transfer->getNotificationNumber());
    mCancelToken = transfer->mCancelToken;
    this->setStage(transfer->getStage());
}

MegaTransfer* MegaTransferPrivate::copy()
{
    return new MegaTransferPrivate(this);
}

void MegaTransferPrivate::setTransfer(Transfer *transfer)
{
    this->transfer = transfer;
}

Transfer* MegaTransferPrivate::getTransfer() const
{
    return transfer;
}

int MegaTransferPrivate::getTag() const
{
    return tag;
}

long long MegaTransferPrivate::getSpeed() const
{
    return speed;
}

long long MegaTransferPrivate::getMeanSpeed() const
{
    return meanSpeed;
}

long long MegaTransferPrivate::getDeltaSize() const
{
    return deltaSize;
}

int64_t MegaTransferPrivate::getUpdateTime() const
{
    return updateTime;
}

MegaNode *MegaTransferPrivate::getPublicNode() const
{
    return publicNode;
}

MegaNode *MegaTransferPrivate::getPublicMegaNode() const
{
    if(publicNode)
    {
        return publicNode->copy();
    }

    return NULL;
}

bool MegaTransferPrivate::isSyncTransfer() const
{
    return syncTransfer;
}

bool MegaTransferPrivate::isStreamingTransfer() const
{
    return streamingTransfer;
}

bool MegaTransferPrivate::isFinished() const
{
    return state == STATE_COMPLETED || state == STATE_CANCELLED || state == STATE_FAILED;
}

bool MegaTransferPrivate::isBackupTransfer() const
{
    return backupTransfer;
}

bool MegaTransferPrivate::isForeignOverquota() const
{
    return foreignOverquota;
}

bool MegaTransferPrivate::isForceNewUpload() const
{
    return forceNewUpload;
}

bool MegaTransferPrivate::isSourceFileTemporary() const
{
    return temporarySourceFile;
}

bool MegaTransferPrivate::shouldStartFirst() const
{
    return startFirst;
}

int MegaTransferPrivate::getType() const
{
    return type;
}

int64_t MegaTransferPrivate::getStartTime() const
{
    return startTime;
}

long long MegaTransferPrivate::getTransferredBytes() const
{
    return transferredBytes;
}

long long MegaTransferPrivate::getTotalBytes() const
{
    return totalBytes;
}

const char* MegaTransferPrivate::getPath() const
{
    return path;
}

const char* MegaTransferPrivate::getParentPath() const
{
    return parentPath;
}

uint64_t MegaTransferPrivate::getNodeHandle() const
{
    return nodeHandle;
}

uint64_t MegaTransferPrivate::getParentHandle() const
{
    return parentHandle;
}

long long MegaTransferPrivate::getStartPos() const
{
    return startPos;
}

long long MegaTransferPrivate::getEndPos() const
{
    return endPos;
}

int MegaTransferPrivate::getNumRetry() const
{
    return retry;
}

unsigned MegaTransferPrivate::getStage() const
{
    return mStage;
}

int MegaTransferPrivate::getMaxRetries() const
{
    return maxRetries;
}

int64_t MegaTransferPrivate::getTime() const
{
    return time;
}

const char* MegaTransferPrivate::getFileName() const
{
    return fileName;
}

char * MegaTransferPrivate::getLastBytes() const
{
    return lastBytes;
}

MegaError MegaTransferPrivate::getLastError() const
{
    return lastError ? *lastError.get() : MegaTransfer::getLastError();
}

const MegaError *MegaTransferPrivate::getLastErrorExtended() const
{
    return lastError.get();
}

bool MegaTransferPrivate::isFolderTransfer() const
{
    return folderTransferTag < 0;
}

int MegaTransferPrivate::getFolderTransferTag() const
{
    return this->folderTransferTag;
}

void MegaTransferPrivate::setAppData(const char *data)
{
    if (this->appData)
    {
        delete [] this->appData;
    }
    this->appData = MegaApi::strdup(data);
}

const char *MegaTransferPrivate::getAppData() const
{
    return this->appData;
}

void MegaTransferPrivate::setState(int state)
{
    this->state = state;
}

int MegaTransferPrivate::getState() const
{
    return state;
}

void MegaTransferPrivate::setPriority(unsigned long long p)
{
    this->priority = p;
}

unsigned long long MegaTransferPrivate::getPriority() const
{
    return priority;
}

long long MegaTransferPrivate::getNotificationNumber() const
{
    return notificationNumber;
}

bool MegaTransferPrivate::getTargetOverride() const
{
    return mTargetOverride;
}

bool MegaTransferPrivate::serialize(string *d) const
{
    d->append((const char*)&type, sizeof(type));
    d->append((const char*)&nodeHandle, sizeof(nodeHandle));
    d->append((const char*)&parentHandle, sizeof(parentHandle));

    unsigned short ll;
    ll = (unsigned short)(path ? strlen(path) + 1 : 0);
    d->append((char*)&ll, sizeof(ll));
    d->append(path, ll);

    ll = (unsigned short)(parentPath ? strlen(parentPath) + 1 : 0);
    d->append((char*)&ll, sizeof(ll));
    d->append(parentPath, ll);

    ll = (unsigned short)(fileName ? strlen(fileName) + 1 : 0);
    d->append((char*)&ll, sizeof(ll));
    d->append(fileName, ll);

    d->append((const char*)&folderTransferTag, sizeof(folderTransferTag));
    d->append("\0\0\0\0\0\0", 7);

    ll = (unsigned short)(appData ? strlen(appData) + 1 : 0);
    if (ll)
    {
        char hasAppData = 1;
        d->append(&hasAppData, 1);
        d->append((char*)&ll, sizeof(ll));
        d->append(appData, ll);
    }
    else
    {
        d->append("", 1);
    }

    MegaNodePrivate *node = dynamic_cast<MegaNodePrivate *>(publicNode);
    bool isPublic = (node != NULL);
    d->append((const char*)&isPublic, sizeof(bool));
    if (isPublic)
    {
        node->serialize(d);
    }
    return true;
}

MegaTransferPrivate *MegaTransferPrivate::unserialize(string *d)
{
    const char* ptr = d->data();
    const char* end = ptr + d->size();

    if (ptr + sizeof(int) + sizeof(MegaHandle)
            + sizeof(MegaHandle) + sizeof(unsigned short) > end)
    {
        LOG_err << "MegaTransfer unserialization failed - data too short";
        return NULL;
    }

    int type = MemAccess::get<int>(ptr);
    ptr += sizeof(int);

    MegaTransferPrivate *transfer = new MegaTransferPrivate(type);
    transfer->nodeHandle = MemAccess::get<MegaHandle>(ptr);
    ptr += sizeof(MegaHandle);

    transfer->parentHandle = MemAccess::get<MegaHandle>(ptr);
    ptr += sizeof(MegaHandle);

    unsigned short pathlen = MemAccess::get<unsigned short>(ptr);
    ptr += sizeof(unsigned short);

    if (ptr + pathlen + sizeof(unsigned short) > end)
    {
        LOG_err << "MegaTransfer unserialization failed - path too long";
        delete transfer;
        return NULL;
    }

    if (pathlen)
    {
        string path;
        path.assign(ptr, pathlen - 1);
        transfer->setPath(path.c_str());
    }
    ptr += pathlen;

    unsigned short parentPathLen = MemAccess::get<unsigned short>(ptr);
    ptr += sizeof(unsigned short);

    if (ptr + parentPathLen + sizeof(unsigned short) > end)
    {
        LOG_err << "MegaTransfer unserialization failed - parentpath too long";
        delete transfer;
        return NULL;
    }

    if (parentPathLen)
    {
        string path;
        path.assign(ptr, parentPathLen - 1);
        transfer->setParentPath(path.c_str());
    }
    ptr += parentPathLen;

    unsigned short fileNameLen = MemAccess::get<unsigned short>(ptr);
    ptr += sizeof(unsigned short);

    if (ptr + fileNameLen + sizeof(int) + 7 + sizeof(char) > end)
    {
        LOG_err << "MegaTransfer unserialization failed - filename too long";
        delete transfer;
        return NULL;
    }

    if (fileNameLen)
    {
        string path;
        path.assign(ptr, fileNameLen - 1);
        transfer->setFileName(path.c_str());
    }
    ptr += fileNameLen;

    transfer->folderTransferTag = MemAccess::get<int>(ptr);
    ptr += sizeof(int);

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

    char hasAppData = MemAccess::get<char>(ptr);
    ptr += sizeof(char);
    if (hasAppData > 1)
    {
        LOG_err << "MegaTransfer unserialization failed - invalid app data";
        delete transfer;
        return NULL;
    }

    if (hasAppData)
    {
        if (ptr + sizeof(unsigned short) > end)
        {
            LOG_err << "MegaTransfer unserialization failed - no app data header";
            delete transfer;
            return NULL;
        }

        unsigned short appDataLen = MemAccess::get<unsigned short>(ptr);
        ptr += sizeof(unsigned short);
        if (!appDataLen || (ptr + appDataLen > end))
        {
            LOG_err << "MegaTransfer unserialization failed - invalid appData";
            delete transfer;
            return NULL;
        }

        string data;
        data.assign(ptr, appDataLen - 1);
        transfer->setAppData(data.c_str());
        ptr += appDataLen;
    }

    if (ptr + sizeof(bool) > end)
    {
        LOG_err << "MegaTransfer unserialization failed - reading public node";
        delete transfer;
        return NULL;
    }

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

    d->erase(0, ptr - d->data());

    if (isPublic)
    {
        MegaNodePrivate *publicNode = MegaNodePrivate::unserialize(d);
        if (!publicNode)
        {
            LOG_err << "MegaTransfer unserialization failed - unable to unserialize MegaNode";
            delete transfer;
            return NULL;
        }

        transfer->setPublicNode(publicNode);
        delete publicNode;
    }

    return transfer;
}

void MegaTransferPrivate::setTag(int tag)
{
    this->tag = tag;
}

void MegaTransferPrivate::setSpeed(long long speed)
{
    this->speed = speed;
}

void MegaTransferPrivate::setMeanSpeed(long long meanSpeed)
{
    this->meanSpeed = meanSpeed;
}

void MegaTransferPrivate::setDeltaSize(long long deltaSize)
{
    this->deltaSize = deltaSize;
}

void MegaTransferPrivate::setUpdateTime(int64_t updateTime)
{
    this->updateTime = updateTime;
}
void MegaTransferPrivate::setPublicNode(MegaNode *publicNode, bool copyChildren)
{
    if (this->publicNode)
    {
        delete this->publicNode;
    }

    if (!publicNode)
    {
        this->publicNode = NULL;
    }
    else
    {
        MegaNodePrivate *nodePrivate = new MegaNodePrivate(publicNode);
        MegaNodeListPrivate *children = dynamic_cast<MegaNodeListPrivate *>(publicNode->getChildren());
        if (children && copyChildren)
        {
            nodePrivate->setChildren(new MegaNodeListPrivate(children, true));
        }
        this->publicNode = nodePrivate;
    }
}

void MegaTransferPrivate::setSyncTransfer(bool syncTransfer)
{
    this->syncTransfer = syncTransfer;
}

void MegaTransferPrivate::setSourceFileTemporary(bool temporary)
{
    this->temporarySourceFile = temporary;
}

void MegaTransferPrivate::setStartFirst(bool startFirst)
{
    this->startFirst = startFirst;
}

void MegaTransferPrivate::setBackupTransfer(bool backupTransfer)
{
    this->backupTransfer = backupTransfer;
}

void MegaTransferPrivate::setForeignOverquota(bool foreignOverquota)
{
    this->foreignOverquota = foreignOverquota;
}

void MegaTransferPrivate::setForceNewUpload(bool forceNewUpload)
{
    this->forceNewUpload = forceNewUpload;
}

void MegaTransferPrivate::setStreamingTransfer(bool streamingTransfer)
{
    this->streamingTransfer = streamingTransfer;
}

void MegaTransferPrivate::setStartTime(int64_t startTime)
{
    if (!this->startTime)
    {
        this->startTime = startTime;
    }
}

void MegaTransferPrivate::setTransferredBytes(long long transferredBytes)
{
    this->transferredBytes = transferredBytes;
}

void MegaTransferPrivate::setTotalBytes(long long totalBytes)
{
    this->totalBytes = totalBytes;
}

void MegaTransferPrivate::setLastBytes(char *lastBytes)
{
    this->lastBytes = lastBytes;
}

void MegaTransferPrivate::setLastError(const MegaError *e)
{
   lastError.reset(e ? e->copy() : nullptr);
}

void MegaTransferPrivate::setFolderTransferTag(int tag)
{
    this->folderTransferTag = tag;
}

void MegaTransferPrivate::setNotificationNumber(long long notificationNumber)
{
    this->notificationNumber = notificationNumber;
}

void MegaTransferPrivate::setListener(MegaTransferListener *listener)
{
    this->listener = listener;
}

void MegaTransferPrivate::setTargetOverride(bool targetOverride)
{
    mTargetOverride = targetOverride;
}

void MegaTransferPrivate::setCancelToken(CancelToken cancelToken)
{
    mCancelToken = MegaCancelTokenPrivate(cancelToken);
}

void MegaTransferPrivate::startRecursiveOperation(shared_ptr<MegaRecursiveOperation> op, MegaNode* node)
{
    assert(op && !recursiveOperation);
    recursiveOperation = move(op);

    // for folder transfers, we must have a CancelToken even if the user did not supply one
    // so that we can cancel the remainder of the batch if the user cancels the folder transfer
    if (!mCancelToken.cancelFlag.exists())
    {
        mCancelToken.cancelFlag = CancelToken(false);
    }

    recursiveOperation->start(node);
}

void MegaTransferPrivate::stopRecursiveOperationThread()
{
    if (recursiveOperation) recursiveOperation->ensureThreadStopped();
}

long long MegaTransferPrivate::getPlaceInQueue() const
{
    return placeInQueue;
}

void MegaTransferPrivate::setPlaceInQueue(long long value)
{
    placeInQueue = value;
}

MegaCancelToken* MegaTransferPrivate::getCancelToken()
{
    return mCancelToken.existencePtr();
}

void MegaTransferPrivate::setPath(const char* path)
{
    if(this->path) delete [] this->path;
    this->path = MegaApi::strdup(path);
    if(!this->path) return;

    for (int i = int(strlen(path) - 1); i >= 0; i--)
    {
        if (path[i] == LocalPath::localPathSeparator_utf8)
        {
            setFileName(&(path[i+1]));
            char *parentPath = MegaApi::strdup(path);
            parentPath[i+1] = '\0';
            setParentPath(parentPath);
            delete [] parentPath;
            return;
        }
    }
    setFileName(path);
}

void MegaTransferPrivate::setParentPath(const char* path)
{
    if(this->parentPath) delete [] this->parentPath;
    this->parentPath =  MegaApi::strdup(path);
}

void MegaTransferPrivate::setFileName(const char* fileName)
{
    if(this->fileName) delete [] this->fileName;
    this->fileName =  MegaApi::strdup(fileName);
}

void MegaTransferPrivate::setNodeHandle(uint64_t nodeHandle)
{
    this->nodeHandle = nodeHandle;
}

void MegaTransferPrivate::setParentHandle(uint64_t parentHandle)
{
    this->parentHandle = parentHandle;
}

void MegaTransferPrivate::setStartPos(long long startPos)
{
    this->startPos = startPos;
}

void MegaTransferPrivate::setEndPos(long long endPos)
{
    this->endPos = endPos;
}

void MegaTransferPrivate::setNumRetry(int retry)
{
    this->retry = retry;
}

void MegaTransferPrivate::setStage(unsigned stage)
{
    this->mStage = static_cast<uint8_t>(stage);
}

void MegaTransferPrivate::setMaxRetries(int maxRetries)
{
    this->maxRetries = maxRetries;
}

void MegaTransferPrivate::setTime(int64_t time)
{
    this->time = time;
}

const char * MegaTransferPrivate::getTransferString() const
{
    switch(type)
    {
    case TYPE_UPLOAD:
        return "UPLOAD";
    case TYPE_DOWNLOAD:
        return "DOWNLOAD";
    case TYPE_LOCAL_TCP_DOWNLOAD:
        return "LOCAL_HTTP_DOWNLOAD";
    }

    return "UNKNOWN";
}

MegaTransferListener* MegaTransferPrivate::getListener() const
{
    return listener;
}

MegaTransferPrivate::~MegaTransferPrivate()
{
    if (recursiveOperation && !recursiveOperation->allSubtransfersResolved())
    {
        // Folder transfers can only be resolved after all their sub-transfers
        // are resolved.  Eg, cancellation is via setting the cancelToken for all subTransfers
        // Therefore, logically we should never be deleting this object- assert to catch any errors
        assert(false);

        // from review: also log just in case
        LOG_warn << "~MegaTransferPrivate called before all sub-transfers were resolved";
    }
    delete[] path;
    delete[] parentPath;
    delete [] fileName;
    delete [] appData;
    delete publicNode;
}

const char * MegaTransferPrivate::toString() const
{
    return getTransferString();
}

const char * MegaTransferPrivate::__str__() const
{
    return getTransferString();
}

const char *MegaTransferPrivate::__toString() const
{
    return getTransferString();
}

MegaContactRequestPrivate::MegaContactRequestPrivate(PendingContactRequest *request)
{
    handle = request->id;
    sourceEmail = request->originatoremail.size() ? MegaApi::strdup(request->originatoremail.c_str()) : NULL;
    sourceMessage = request->msg.size() ? MegaApi::strdup(request->msg.c_str()) : NULL;
    targetEmail = request->targetemail.size() ? MegaApi::strdup(request->targetemail.c_str()) : NULL;
    creationTime = request->ts;
    modificationTime = request->uts;
    autoaccepted = request->autoaccepted;

    if(request->changed.accepted)
    {
        status = MegaContactRequest::STATUS_ACCEPTED;
    }
    else if(request->changed.deleted)
    {
        status = MegaContactRequest::STATUS_DELETED;
    }
    else if(request->changed.denied)
    {
        status = MegaContactRequest::STATUS_DENIED;
    }
    else if(request->changed.ignored)
    {
        status = MegaContactRequest::STATUS_IGNORED;
    }
    else if(request->changed.reminded)
    {
        status = MegaContactRequest::STATUS_REMINDED;
    }
    else
    {
        status = MegaContactRequest::STATUS_UNRESOLVED;
    }

    outgoing = request->isoutgoing;
}

MegaContactRequestPrivate::MegaContactRequestPrivate(const MegaContactRequest *request)
{
    handle = request->getHandle();
    sourceEmail = MegaApi::strdup(request->getSourceEmail());
    sourceMessage = MegaApi::strdup(request->getSourceMessage());
    targetEmail = MegaApi::strdup(request->getTargetEmail());
    creationTime = request->getCreationTime();
    modificationTime = request->getModificationTime();
    status = request->getStatus();
    outgoing = request->isOutgoing();
    autoaccepted = request->isAutoAccepted();
}

MegaContactRequestPrivate::~MegaContactRequestPrivate()
{
    delete [] sourceEmail;
    delete [] sourceMessage;
    delete [] targetEmail;
}

MegaContactRequest *MegaContactRequestPrivate::fromContactRequest(PendingContactRequest *request)
{
    return new MegaContactRequestPrivate(request);
}

MegaContactRequest *MegaContactRequestPrivate::copy() const
{
    return new MegaContactRequestPrivate(this);
}

MegaHandle MegaContactRequestPrivate::getHandle() const
{
    return handle;
}

char *MegaContactRequestPrivate::getSourceEmail() const
{
    return sourceEmail;
}

char *MegaContactRequestPrivate::getSourceMessage() const
{
    return sourceMessage;
}

char *MegaContactRequestPrivate::getTargetEmail() const
{
    return targetEmail;
}

int64_t MegaContactRequestPrivate::getCreationTime() const
{
    return creationTime;
}

int64_t MegaContactRequestPrivate::getModificationTime() const
{
    return modificationTime;
}

int MegaContactRequestPrivate::getStatus() const
{
    return status;
}

bool MegaContactRequestPrivate::isOutgoing() const
{
    return outgoing;
}

bool MegaContactRequestPrivate::isAutoAccepted() const
{
    return autoaccepted;
}

MegaAccountDetails *MegaAccountDetailsPrivate::fromAccountDetails(AccountDetails *details)
{
    return new MegaAccountDetailsPrivate(details);
}

MegaAccountDetailsPrivate::MegaAccountDetailsPrivate(AccountDetails *details)
{
    this->details = (*details);
}

MegaAccountDetailsPrivate::~MegaAccountDetailsPrivate()
{ }

MegaRequest *MegaRequestPrivate::copy()
{
    return new MegaRequestPrivate(this);
}

MegaRequestPrivate::MegaRequestPrivate(int type, MegaRequestListener *listener)
{
    this->type = type;
    this->tag = 0;
    this->transfer = 0;
    this->listener = listener;
    this->backupListener = NULL;
    this->nodeHandle = UNDEF;
    this->link = NULL;
    this->parentHandle = UNDEF;
    this->sessionKey = NULL;
    this->name = NULL;
    this->email = NULL;
    this->text = NULL;
    this->password = NULL;
    this->newPassword = NULL;
    this->privateKey = NULL;
    this->access = MegaShare::ACCESS_UNKNOWN;
    this->numRetry = 0;
    this->publicNode = NULL;
    this->numDetails = 0;
    this->file = NULL;
    this->attrType = 0;
    this->flag = false;
    this->totalBytes = -1;
    this->transferredBytes = 0;
    this->number = 0;
    this->timeZoneDetails = NULL;

    if (type == MegaRequest::TYPE_ACCOUNT_DETAILS || type == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)
    {
        this->accountDetails = std::make_shared<AccountDetails>();
    }
    else
    {
        this->accountDetails.reset();
    }

    if (type == MegaRequest::TYPE_GET_ACHIEVEMENTS)
    {
        this->achievementsDetails = new AchievementsDetails();
    }
    else
    {
        this->achievementsDetails = NULL;
    }

    if ((type == MegaRequest::TYPE_GET_PRICING) || (type == MegaRequest::TYPE_GET_PAYMENT_ID) || type == MegaRequest::TYPE_UPGRADE_ACCOUNT || type == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)
    {
        megaPricing = new MegaPricingPrivate();
        megaCurrency = new MegaCurrencyPrivate();
    }
    else
    {
        megaPricing = NULL;
        megaCurrency = nullptr;
    }

#ifdef ENABLE_CHAT
    if (type == MegaRequest::TYPE_CHAT_CREATE)
    {
        this->chatPeerList = new MegaTextChatPeerListPrivate();
    }
    else
    {
        this->chatPeerList = NULL;
    }

    if (type == MegaRequest::TYPE_CHAT_FETCH)
    {
        this->chatList = new MegaTextChatListPrivate();
    }
    else
    {
        this->chatList = NULL;
    }
#endif

    stringMap = NULL;
    mStringListMap = NULL;
    mStringTable = NULL;
    folderInfo = NULL;
    settings = NULL;
    backgroundMediaUpload = NULL;
}

MegaRequestPrivate::MegaRequestPrivate(MegaRequestPrivate *request)
{
    this->link = NULL;
    this->sessionKey = NULL;
    this->name = NULL;
    this->email = NULL;
    this->text = NULL;
    this->password = NULL;
    this->newPassword = NULL;
    this->privateKey = NULL;
    this->access = MegaShare::ACCESS_UNKNOWN;
    this->file = NULL;
    this->publicNode = NULL;
    this->type = request->getType();
    this->setTag(request->getTag());
    this->setNodeHandle(request->getNodeHandle());
    this->setLink(request->getLink());
    this->setParentHandle(request->getParentHandle());
    this->setSessionKey(request->getSessionKey());
    this->setName(request->getName());
    this->setEmail(request->getEmail());
    this->setPassword(request->getPassword());
    this->setNewPassword(request->getNewPassword());
    this->setPrivateKey(request->getPrivateKey());
    this->setAccess(request->getAccess());
    this->setNumRetry(request->getNumRetry());
    this->setNumDetails(request->getNumDetails());
    this->setFile(request->getFile());
    this->setParamType(request->getParamType());
    this->setText(request->getText());
    this->setNumber(request->getNumber());
    this->setPublicNode(request->getPublicNode());
    this->setFlag(request->getFlag());
    this->setTransferTag(request->getTransferTag());
    this->setTotalBytes(request->getTotalBytes());
    this->setTransferredBytes(request->getTransferredBytes());
    this->listener = request->getListener();

    this->backupListener = request->getBackupListener();
    this->megaPricing = (MegaPricingPrivate *)request->getPricing();
    this->megaCurrency = (MegaCurrencyPrivate *)request->getCurrency();

    this->accountDetails.reset();
    if(request->getAccountDetails())
    {
        this->accountDetails = std::make_shared<AccountDetails>();
        *(this->accountDetails) = *(request->getAccountDetails());
    }

    this->achievementsDetails = NULL;
    if(request->getAchievementsDetails())
    {
        this->achievementsDetails = new AchievementsDetails();
        *(this->achievementsDetails) = *(request->getAchievementsDetails());
    }

    this->timeZoneDetails = request->getMegaTimeZoneDetails() ? request->timeZoneDetails->copy() : NULL;

#ifdef ENABLE_CHAT
    this->chatPeerList = request->getMegaTextChatPeerList() ? request->chatPeerList->copy() : NULL;
    this->chatList = request->getMegaTextChatList() ? request->chatList->copy() : NULL;
    this->mScheduledMeetingList.reset(request->mScheduledMeetingList ? request->mScheduledMeetingList->copy() : nullptr);
#endif

    this->stringMap = request->getMegaStringMap() ? request->stringMap->copy() : NULL;
    this->mStringListMap = request->getMegaStringListMap() ? request->mStringListMap->copy() : NULL;
    this->mStringTable = request->getMegaStringTable() ? request->mStringTable->copy() : NULL;
    this->folderInfo = request->getMegaFolderInfo() ? request->folderInfo->copy() : NULL;
    this->settings = request->getMegaPushNotificationSettings() ? request->settings->copy() : NULL;
    this->backgroundMediaUpload = NULL;
    this->mBannerList.reset(request->mBannerList ? request->mBannerList->copy() : nullptr);
    this->mHandleList.reset(request->mHandleList ? request->mHandleList->copy() : nullptr);
    this->mRecentActions.reset(request->mRecentActions ? request->mRecentActions->copy() : nullptr);
    this->mMegaSet.reset(request->mMegaSet ? request->mMegaSet->copy() : nullptr);
    this->mMegaSetElementList.reset(request->mMegaSetElementList ? request->mMegaSetElementList->copy() : nullptr);
    this->mMegaIntegerList.reset(request->mMegaIntegerList ? request->mMegaIntegerList->copy() : nullptr);
}

std::shared_ptr<AccountDetails> MegaRequestPrivate::getAccountDetails() const
{
    return accountDetails;
}

MegaAchievementsDetails *MegaRequestPrivate::getMegaAchievementsDetails() const
{
    if (achievementsDetails)
    {
        return MegaAchievementsDetailsPrivate::fromAchievementsDetails(achievementsDetails);
    }
    return NULL;
}

AchievementsDetails *MegaRequestPrivate::getAchievementsDetails() const
{
    return achievementsDetails;
}

MegaTimeZoneDetails *MegaRequestPrivate::getMegaTimeZoneDetails() const
{
    return timeZoneDetails;
}

MegaStringList *MegaRequestPrivate::getMegaStringList() const
{
    return mStringList.get();
}

MegaHandleList* MegaRequestPrivate::getMegaHandleList() const
{
    return mHandleList.get();
}

#ifdef ENABLE_CHAT
MegaTextChatPeerList *MegaRequestPrivate::getMegaTextChatPeerList() const
{
    return chatPeerList;
}

void MegaRequestPrivate::setMegaTextChatPeerList(MegaTextChatPeerList *chatPeers)
{
    if (this->chatPeerList)
        delete this->chatPeerList;

    this->chatPeerList = chatPeers->copy();
}

MegaTextChatList *MegaRequestPrivate::getMegaTextChatList() const
{
    return chatList;
}

void MegaRequestPrivate::setMegaTextChatList(MegaTextChatList *chatList)
{
    if (this->chatList)
        delete this->chatList;

    this->chatList = chatList->copy();
}

MegaScheduledMeetingList* MegaRequestPrivate::getMegaScheduledMeetingList() const
{
    return mScheduledMeetingList.get();
}

void MegaRequestPrivate::setMegaScheduledMeetingList(const MegaScheduledMeetingList* schedMeetingList)
{
    mScheduledMeetingList.reset();

    if (schedMeetingList)
    {
       mScheduledMeetingList = unique_ptr<MegaScheduledMeetingList>(schedMeetingList->copy());
    }
}
#endif

MegaStringMap *MegaRequestPrivate::getMegaStringMap() const
{
    return stringMap;
}

void MegaRequestPrivate::setMegaStringMap(const MegaStringMap *stringMap)
{
    if (this->stringMap)
    {
        delete this->stringMap;
    }

    this->stringMap = stringMap ? stringMap->copy() : NULL;
}

MegaStringListMap *MegaRequestPrivate::getMegaStringListMap() const
{
    return mStringListMap;
}

void MegaRequestPrivate::setMegaStringListMap(const MegaStringListMap* stringListMap)
{
    if (mStringListMap)
    {
        delete mStringListMap;
    }
    mStringListMap = stringListMap ? stringListMap->copy() : nullptr;
}

MegaStringTable *MegaRequestPrivate::getMegaStringTable() const
{
    return mStringTable;
}

void MegaRequestPrivate::setMegaStringTable(const MegaStringTable* stringTable)
{
    if (mStringTable)
    {
        delete mStringTable;
    }
    mStringTable = stringTable ? stringTable->copy() : nullptr;
}

MegaFolderInfo *MegaRequestPrivate::getMegaFolderInfo() const
{
    return folderInfo;
}

void MegaRequestPrivate::setMegaFolderInfo(const MegaFolderInfo *folderInfo)
{
    if (this->folderInfo)
    {
        delete this->folderInfo;
    }

    this->folderInfo = folderInfo ? folderInfo->copy() : NULL;
}

const MegaPushNotificationSettings *MegaRequestPrivate::getMegaPushNotificationSettings() const
{
    return settings;
}

void MegaRequestPrivate::setMegaPushNotificationSettings(const MegaPushNotificationSettings *settings)
{
    if (this->settings)
    {
        delete this->settings;
    }

    this->settings = settings ? settings->copy() : NULL;
}

MegaBackgroundMediaUpload *MegaRequestPrivate::getMegaBackgroundMediaUploadPtr() const
{
    // non-owned pointer
    return backgroundMediaUpload;
}

void MegaRequestPrivate::setMegaBackgroundMediaUploadPtr(MegaBackgroundMediaUpload *p)
{
    // non-owned pointer
    backgroundMediaUpload = p;
}

void MegaRequestPrivate::setMegaStringList(const MegaStringList* stringList)
{
    mStringList.reset();

    if (stringList)
    {
       mStringList = unique_ptr<MegaStringList>(stringList->copy());
    }
}

void MegaRequestPrivate::setMegaHandleList(const vector<handle> &handles)
{
    mHandleList.reset(new MegaHandleListPrivate(handles));
}

void MegaRequestPrivate::setMegaHandleList(const MegaHandleList* handles)
{
    mHandleList.reset(handles ? handles->copy() : nullptr);
}

MegaScheduledCopyListener *MegaRequestPrivate::getBackupListener() const
{
    return backupListener;
}

void MegaRequestPrivate::setBackupListener(MegaScheduledCopyListener *value)
{
    backupListener = value;
}

MegaAccountDetails *MegaRequestPrivate::getMegaAccountDetails() const
{
    if(accountDetails)
    {
        return MegaAccountDetailsPrivate::fromAccountDetails(accountDetails.get());
    }
    return NULL;
}

MegaRequestPrivate::~MegaRequestPrivate()
{
    delete [] link;
    delete [] name;
    delete [] email;
    delete [] password;
    delete [] newPassword;
    delete [] privateKey;
    delete [] sessionKey;
    delete publicNode;
    delete [] file;
    delete megaPricing;
    delete megaCurrency;
    delete achievementsDetails;
    delete [] text;
    delete stringMap;
    delete mStringListMap;
    delete mStringTable;
    delete folderInfo;
    delete timeZoneDetails;
    delete settings;

#ifdef ENABLE_CHAT
    delete chatPeerList;
    delete chatList;
#endif
}

int MegaRequestPrivate::getType() const
{
    return type;
}

uint64_t MegaRequestPrivate::getNodeHandle() const
{
    return nodeHandle;
}

const char* MegaRequestPrivate::getLink() const
{
    return link;
}

uint64_t MegaRequestPrivate::getParentHandle() const
{
    return parentHandle;
}

const char* MegaRequestPrivate::getSessionKey() const
{
    return sessionKey;
}

const char* MegaRequestPrivate::getName() const
{
    return name;
}

const char* MegaRequestPrivate::getEmail() const
{
    return email;
}

const char* MegaRequestPrivate::getPassword() const
{
    return password;
}

const char* MegaRequestPrivate::getNewPassword() const
{
    return newPassword;
}

const char* MegaRequestPrivate::getPrivateKey() const
{
    return privateKey;
}

int MegaRequestPrivate::getAccess() const
{
    return access;
}

const char* MegaRequestPrivate::getFile() const
{
    return file;
}

int MegaRequestPrivate::getParamType() const
{
    return attrType;
}

const char *MegaRequestPrivate::getText() const
{
    return text;
}

long long MegaRequestPrivate::getNumber() const
{
    return number;
}

bool MegaRequestPrivate::getFlag() const
{
    return flag;
}

long long MegaRequestPrivate::getTransferredBytes() const
{
    return transferredBytes;
}

long long MegaRequestPrivate::getTotalBytes() const
{
    return totalBytes;
}

int MegaRequestPrivate::getNumRetry() const
{
    return numRetry;
}

int MegaRequestPrivate::getNumDetails() const
{
    return numDetails;
}

int MegaRequestPrivate::getTag() const
{
    return tag;
}

MegaPricing *MegaRequestPrivate::getPricing() const
{
    return megaPricing ? megaPricing->copy() : NULL;
}

MegaCurrency *MegaRequestPrivate::getCurrency() const
{
    return megaCurrency ? megaCurrency->copy() : nullptr;
}

void MegaRequestPrivate::setNumDetails(int numDetails)
{
    this->numDetails = numDetails;
}

MegaNode *MegaRequestPrivate::getPublicNode() const
{
    return publicNode;
}

MegaNode *MegaRequestPrivate::getPublicMegaNode() const
{
    if(publicNode)
    {
        return publicNode->copy();
    }

    return NULL;
}

void MegaRequestPrivate::setNodeHandle(uint64_t nodeHandle)
{
    this->nodeHandle = nodeHandle;
}

void MegaRequestPrivate::setParentHandle(uint64_t parentHandle)
{
    this->parentHandle = parentHandle;
}

void MegaRequestPrivate::setSessionKey(const char* sessionKey)
{
    if(this->sessionKey) delete [] this->sessionKey;
    this->sessionKey = MegaApi::strdup(sessionKey);
}

void MegaRequestPrivate::setNumRetry(int numRetry)
{
    this->numRetry = numRetry;
}

void MegaRequestPrivate::setLink(const char* link)
{
    if(this->link)
        delete [] this->link;

    this->link = MegaApi::strdup(link);
}
void MegaRequestPrivate::setName(const char* name)
{
    if(this->name)
        delete [] this->name;

    this->name = MegaApi::strdup(name);
}
void MegaRequestPrivate::setEmail(const char* email)
{
    if(this->email)
        delete [] this->email;

    this->email = MegaApi::strdup(email);
}
void MegaRequestPrivate::setPassword(const char* password)
{
    if(this->password)
        delete [] this->password;

    this->password = MegaApi::strdup(password);
}
void MegaRequestPrivate::setNewPassword(const char* newPassword)
{
    if(this->newPassword)
        delete [] this->newPassword;

    this->newPassword = MegaApi::strdup(newPassword);
}
void MegaRequestPrivate::setPrivateKey(const char* privateKey)
{
    if(this->privateKey)
        delete [] this->privateKey;

    this->privateKey = MegaApi::strdup(privateKey);
}
void MegaRequestPrivate::setAccess(int access)
{
    this->access = access;
}

void MegaRequestPrivate::setFile(const char* file)
{
    if(this->file)
        delete [] this->file;

    this->file = MegaApi::strdup(file);
}

void MegaRequestPrivate::setParamType(int type)
{
    this->attrType = type;
}

void MegaRequestPrivate::setText(const char *text)
{
    if(this->text) delete [] this->text;
    this->text = MegaApi::strdup(text);
}

void MegaRequestPrivate::setNumber(long long number)
{
    this->number = number;
}

void MegaRequestPrivate::setFlag(bool flag)
{
    this->flag = flag;
}

void MegaRequestPrivate::setTransferTag(int transfer)
{
    this->transfer = transfer;
}

void MegaRequestPrivate::setListener(MegaRequestListener *listener)
{
    this->listener = listener;
}

void MegaRequestPrivate::setTotalBytes(long long totalBytes)
{
    this->totalBytes = totalBytes;
}

void MegaRequestPrivate::setTransferredBytes(long long transferredBytes)
{
    this->transferredBytes = transferredBytes;
}

void MegaRequestPrivate::setTag(int tag)
{
    this->tag = tag;
}

void MegaRequestPrivate::addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer,
                                    int months, int amount, int amountMonth, int localPrice,
                                    const char* description, const char* iosid, const char* androidid,
                                    std::unique_ptr<BusinessPlan> bizPlan)
{
    if (megaPricing)
    {
        megaPricing->addProduct(type, product, proLevel, gbStorage, gbTransfer,
                                months, amount, amountMonth, localPrice,
                                description, iosid, androidid, move(bizPlan));
    }
}

void MegaRequestPrivate::setCurrency(std::unique_ptr<CurrencyData> currencyData)
{
    if (megaCurrency)
    {
        megaCurrency->setCurrency(move(currencyData));
    }
}

void MegaRequestPrivate::setProxy(Proxy *proxy)
{
    this->proxy = proxy;
}

Proxy *MegaRequestPrivate::getProxy()
{
    return proxy;
}

void MegaRequestPrivate::setTimeZoneDetails(MegaTimeZoneDetails *timeZoneDetails)
{
    if (this->timeZoneDetails)
    {
        delete this->timeZoneDetails;
    }
    this->timeZoneDetails = timeZoneDetails ? timeZoneDetails->copy() : NULL;
}

void MegaRequestPrivate::setPublicNode(MegaNode *publicNode, bool copyChildren)
{
    if (this->publicNode)
    {
        delete this->publicNode;
    }

    if (!publicNode)
    {
        this->publicNode = NULL;
    }
    else
    {
        MegaNodePrivate *nodePrivate = new MegaNodePrivate(publicNode);
        MegaNodeListPrivate *children = dynamic_cast<MegaNodeListPrivate *>(publicNode->getChildren());
        if (children && copyChildren)
        {
            nodePrivate->setChildren(new MegaNodeListPrivate(children, true));
        }
        this->publicNode = nodePrivate;
    }
}

MegaBannerList* MegaRequestPrivate::getMegaBannerList() const
{
    return mBannerList.get();
}

void MegaRequestPrivate::setBanners(vector< tuple<int, string, string, string, string, string, string> >&& banners)
{
    mBannerList = make_unique<MegaBannerListPrivate>();

    for (auto&& b : banners)
    {
        mBannerList->add(MegaBannerPrivate(move(b)));
    }
}

MegaRecentActionBucketList* MegaRequestPrivate::getRecentActions() const
{
    return mRecentActions.get();
}

void MegaRequestPrivate::setRecentActions(std::unique_ptr<MegaRecentActionBucketList> recentActionBucketList)
{
    mRecentActions.reset(recentActionBucketList.release());
}

MegaSet* MegaRequestPrivate::getMegaSet() const
{
    return mMegaSet.get();
}

void MegaRequestPrivate::setMegaSet(std::unique_ptr<MegaSet> s)
{
    mMegaSet.swap(s);
}

MegaSetElementList* MegaRequestPrivate::getMegaSetElementList() const
{
    return mMegaSetElementList.get();
}

void MegaRequestPrivate::setMegaSetElementList(std::unique_ptr<MegaSetElementList> els)
{
    mMegaSetElementList.swap(els);
}

const MegaIntegerList* MegaRequestPrivate::getMegaIntegerList() const
{
    return mMegaIntegerList.get();
}

void MegaRequestPrivate::setMegaIntegerList(std::unique_ptr<MegaIntegerList> ints)
{
    mMegaIntegerList.swap(ints);
}

const char *MegaRequestPrivate::getRequestString() const
{
    switch(type)
    {
        case TYPE_LOGIN: return "LOGIN";
        case TYPE_CREATE_FOLDER: return "CREATE_FOLDER";
        case TYPE_MOVE: return "MOVE";
        case TYPE_COPY: return "COPY";
        case TYPE_RENAME: return "RENAME";
        case TYPE_REMOVE: return "REMOVE";
        case TYPE_SHARE: return "SHARE";
        case TYPE_IMPORT_LINK: return "IMPORT_LINK";
        case TYPE_EXPORT: return "EXPORT";
        case TYPE_FETCH_NODES: return "FETCH_NODES";
        case TYPE_ACCOUNT_DETAILS: return "ACCOUNT_DETAILS";
        case TYPE_CHANGE_PW: return "CHANGE_PW";
        case TYPE_UPLOAD: return "UPLOAD";
        case TYPE_LOGOUT: return "LOGOUT";
        case TYPE_GET_PUBLIC_NODE: return "GET_PUBLIC_NODE";
        case TYPE_GET_ATTR_FILE: return "GET_ATTR_FILE";
        case TYPE_SET_ATTR_FILE: return "SET_ATTR_FILE";
        case TYPE_GET_ATTR_USER: return "GET_ATTR_USER";
        case TYPE_SET_ATTR_USER: return "SET_ATTR_USER";
        case TYPE_RETRY_PENDING_CONNECTIONS: return "RETRY_PENDING_CONNECTIONS";
        case TYPE_REMOVE_CONTACT: return "REMOVE_CONTACT";
        case TYPE_CREATE_ACCOUNT: return "CREATE_ACCOUNT";
        case TYPE_CONFIRM_ACCOUNT: return "CONFIRM_ACCOUNT";
        case TYPE_QUERY_SIGNUP_LINK: return "QUERY_SIGNUP_LINK";
        case TYPE_ADD_SYNC: return "ADD_SYNC";
        //case TYPE_ENABLE_SYNC: return "ENABLE_SYNC";
        //case TYPE_DISABLE_SYNC: return "DISABLE_SYNC";
        case TYPE_COPY_SYNC_CONFIG: return "TYPE_COPY_SYNC_CONFIG";
        case TYPE_COPY_CACHED_STATUS: return "TYPE_COPY_CACHED_STATUS";
        case TYPE_IMPORT_SYNC_CONFIGS: return "TYPE_IMPORT_SYNC_CONFIGS";
        case TYPE_REMOVE_SYNC: return "REMOVE_SYNC";
        case TYPE_REMOVE_SYNCS: return "REMOVE_SYNCS";
        case TYPE_PAUSE_TRANSFERS: return "PAUSE_TRANSFERS";
        case TYPE_CANCEL_TRANSFER: return "CANCEL_TRANSFER";
        case TYPE_CANCEL_TRANSFERS: return "CANCEL_TRANSFERS";
        case TYPE_DELETE: return "DELETE";
        case TYPE_REPORT_EVENT: return "REPORT_EVENT";
        case TYPE_CANCEL_ATTR_FILE: return "CANCEL_ATTR_FILE";
        case TYPE_GET_PRICING: return "GET_PRICING";
        case TYPE_GET_PAYMENT_ID: return "GET_PAYMENT_ID";
        case TYPE_UPGRADE_ACCOUNT: return "UPGRADE_ACCOUNT";
        case TYPE_GET_USER_DATA: return "GET_USER_DATA";
        case TYPE_LOAD_BALANCING: return "LOAD_BALANCING";
        case TYPE_KILL_SESSION: return "KILL_SESSION";
        case TYPE_SUBMIT_PURCHASE_RECEIPT: return "SUBMIT_PURCHASE_RECEIPT";
        case TYPE_CREDIT_CARD_STORE: return "CREDIT_CARD_STORE";
        case TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS: return "CREDIT_CARD_QUERY_SUBSCRIPTIONS";
        case TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS: return "CREDIT_CARD_CANCEL_SUBSCRIPTIONS";
        case TYPE_GET_SESSION_TRANSFER_URL: return "GET_SESSION_TRANSFER_URL";
        case TYPE_GET_PAYMENT_METHODS: return "GET_PAYMENT_METHODS";
        case TYPE_INVITE_CONTACT: return "INVITE_CONTACT";
        case TYPE_REPLY_CONTACT_REQUEST: return "REPLY_CONTACT_REQUEST";
        case TYPE_SUBMIT_FEEDBACK: return "SUBMIT_FEEDBACK";
        case TYPE_SEND_EVENT: return "SEND_EVENT";
        case TYPE_CLEAN_RUBBISH_BIN: return "CLEAN_RUBBISH_BIN";
        case TYPE_SET_ATTR_NODE: return "SET_ATTR_NODE";
        case TYPE_CHAT_CREATE: return "CHAT_CREATE";
        case TYPE_CHAT_FETCH: return "CHAT_FETCH";
        case TYPE_CHAT_INVITE: return "CHAT_INVITE";
        case TYPE_CHAT_REMOVE: return "CHAT_REMOVE";
        case TYPE_CHAT_URL: return "CHAT_URL";
        case TYPE_CHAT_GRANT_ACCESS: return "CHAT_GRANT_ACCESS";
        case TYPE_CHAT_REMOVE_ACCESS: return "CHAT_REMOVE_ACCESS";
        case TYPE_USE_HTTPS_ONLY: return "USE_HTTPS_ONLY";
        case TYPE_SET_PROXY: return "SET_PROXY";
        case TYPE_GET_RECOVERY_LINK: return "GET_RECOVERY_LINK";
        case TYPE_QUERY_RECOVERY_LINK: return "QUERY_RECOVERY_LINK";
        case TYPE_CONFIRM_RECOVERY_LINK: return "CONFIRM_RECOVERY_LINK";
        case TYPE_GET_CANCEL_LINK: return "GET_CANCEL_LINK";
        case TYPE_CONFIRM_CANCEL_LINK: return "CONFIRM_CANCEL_LINK";
        case TYPE_GET_CHANGE_EMAIL_LINK: return "GET_CHANGE_EMAIL_LINK";
        case TYPE_CONFIRM_CHANGE_EMAIL_LINK: return "CONFIRM_CHANGE_EMAIL_LINK";
        case TYPE_PAUSE_TRANSFER: return "PAUSE_TRANSFER";
        case TYPE_MOVE_TRANSFER: return "MOVE_TRANSFER";
        case TYPE_CHAT_SET_TITLE: return "CHAT_SET_TITLE";
        case TYPE_CHAT_UPDATE_PERMISSIONS: return "CHAT_UPDATE_PERMISSIONS";
        case TYPE_CHAT_TRUNCATE: return "CHAT_TRUNCATE";
        case TYPE_SET_MAX_CONNECTIONS: return "SET_MAX_CONNECTIONS";
        case TYPE_CHAT_PRESENCE_URL: return "CHAT_PRESENCE_URL";
        case TYPE_REGISTER_PUSH_NOTIFICATION: return "REGISTER_PUSH_NOTIFICATION";
        case TYPE_GET_USER_EMAIL: return "GET_USER_EMAIL";
        case TYPE_APP_VERSION: return "APP_VERSION";
        case TYPE_GET_LOCAL_SSL_CERT: return "GET_LOCAL_SSL_CERT";
        case TYPE_SEND_SIGNUP_LINK: return "SEND_SIGNUP_LINK";
        case TYPE_QUERY_DNS: return "QUERY_DNS";
        case TYPE_CHAT_STATS: return "CHAT_STATS";
        case TYPE_DOWNLOAD_FILE: return "DOWNLOAD_FILE";
        case TYPE_QUERY_TRANSFER_QUOTA: return "QUERY_TRANSFER_QUOTA";
        case TYPE_PASSWORD_LINK: return "PASSWORD_LINK";
        case TYPE_RESTORE: return "RESTORE";
        case TYPE_GET_ACHIEVEMENTS: return "GET_ACHIEVEMENTS";
        case TYPE_REMOVE_VERSIONS: return "REMOVE_VERSIONS";
        case TYPE_CHAT_ARCHIVE: return "CHAT_ARCHIVE";
        case TYPE_WHY_AM_I_BLOCKED: return "WHY_AM_I_BLOCKED";
        case TYPE_CONTACT_LINK_CREATE: return "CONTACT_LINK_CREATE";
        case TYPE_CONTACT_LINK_QUERY: return "CONTACT_LINK_QUERY";
        case TYPE_CONTACT_LINK_DELETE: return "CONTACT_LINK_DELETE";
        case TYPE_FOLDER_INFO: return "FOLDER_INFO";
        case TYPE_RICH_LINK: return "RICH_LINK";
        case TYPE_CHAT_LINK_HANDLE: return "CHAT_LINK_HANDLE";
        case TYPE_CHAT_LINK_URL: return "CHAT_LINK_URL";
        case TYPE_SET_PRIVATE_MODE: return "SET_PRIVATE_MODE";
        case TYPE_AUTOJOIN_PUBLIC_CHAT: return "AUTOJOIN_PUBLIC_CHAT";
        case TYPE_KEEP_ME_ALIVE: return "KEEP_ME_ALIVE";
        case TYPE_MULTI_FACTOR_AUTH_CHECK: return "MULTI_FACTOR_AUTH_CHECK";
        case TYPE_MULTI_FACTOR_AUTH_GET: return "MULTI_FACTOR_AUTH_GET";
        case TYPE_MULTI_FACTOR_AUTH_SET: return "MULTI_FACTOR_AUTH_SET";
        case TYPE_ADD_SCHEDULED_COPY: return "ADD_BACKUP"; // TODO: consider renaming this in the future.
        case TYPE_REMOVE_SCHEDULED_COPY: return "REMOVE_BACKUP"; // TODO: consider renaming this in the future..
        case TYPE_ABORT_CURRENT_SCHEDULED_COPY: return "ABORT_BACKUP"; // TODO: consider renaming this in the future.
        case TYPE_TIMER: return "SET_TIMER";
        case TYPE_GET_PSA: return "GET_PSA";
        case TYPE_FETCH_TIMEZONE: return "FETCH_TIMEZONE";
        case TYPE_USERALERT_ACKNOWLEDGE: return "USERALERT_ACKNOWLEDGE";
        case TYPE_CATCHUP: return "CATCHUP";
        case TYPE_PUBLIC_LINK_INFORMATION: return "PUBLIC_LINK_INFORMATION";
        case TYPE_GET_BACKGROUND_UPLOAD_URL: return "GET_BACKGROUND_UPLOAD_URL";
        case TYPE_COMPLETE_BACKGROUND_UPLOAD: return "COMPLETE_BACKGROUND_UPLOAD";
        case TYPE_GET_CLOUD_STORAGE_USED: return "GET_CLOUD_STORAGE_USED";
        case TYPE_SEND_SMS_VERIFICATIONCODE: return "SEND_SMS_VERIFICATIONCODE";
        case TYPE_CHECK_SMS_VERIFICATIONCODE: return "CHECK_SMS_VERIFICATIONCODE";
        case TYPE_GET_REGISTERED_CONTACTS: return "GET_REGISTERED_CONTACTS";
        case TYPE_GET_COUNTRY_CALLING_CODES: return "GET_COUNTRY_CALLING_CODES";
        case TYPE_VERIFY_CREDENTIALS: return "VERIFY_CREDENTIALS";
        case TYPE_GET_MISC_FLAGS: return "GET_MISC_FLAGS";
        case TYPE_RESEND_VERIFICATION_EMAIL: return "RESEND_VERIFICATION_EMAIL";
        case TYPE_SUPPORT_TICKET: return "SUPPORT_TICKET";
        case TYPE_SET_RETENTION_TIME: return "SET_RETENTION_TIME";
        case TYPE_RESET_SMS_VERIFIED_NUMBER: return "RESET_SMS_VERIFIED_NUMBER";
        case TYPE_SEND_DEV_COMMAND: return "SEND_DEV_COMMAND";
        case TYPE_GET_BANNERS: return "GET_BANNERS";
        case TYPE_DISMISS_BANNER: return "DISMISS_BANNER";
        case TYPE_BACKUP_PUT: return "BACKUP_PUT";
        case TYPE_BACKUP_REMOVE: return "BACKUP_REMOVE";
        case TYPE_BACKUP_PUT_HEART_BEAT: return "BACKUP_PUT_HEART_BEAT";
        case TYPE_FETCH_GOOGLE_ADS: return "FETCH_GOOGLE_ADS";
        case TYPE_QUERY_GOOGLE_ADS: return "QUERY_GOOGLE_ADS";
        case TYPE_GET_ATTR_NODE: return "GET_ATTR_NODE";
        case TYPE_START_CHAT_CALL: return "START_CHAT_CALL";
        case TYPE_JOIN_CHAT_CALL: return "JOIN_CHAT_CALL";
        case TYPE_END_CHAT_CALL: return "END_CHAT_CALL";
        case TYPE_LOAD_EXTERNAL_DRIVE_BACKUPS: return "LOAD_EXTERNAL_DRIVE_BACKUPS";
        case TYPE_CLOSE_EXTERNAL_DRIVE_BACKUPS: return "CLOSE_EXTERNAL_DRIVE_BACKUPS";
        case TYPE_GET_DOWNLOAD_URLS: return "GET_DOWNLOAD_URLS";
        case TYPE_GET_FA_UPLOAD_URL: return "GET_FA_UPLOAD_URL";
        case TYPE_EXECUTE_ON_THREAD: return "EXECUTE_ON_THREAD";
        case TYPE_SET_CHAT_OPTIONS: return "SET_CHAT_OPTIONS";
        case TYPE_GET_RECENT_ACTIONS: return "GET_RECENT_ACTIONS";
        case TYPE_CHECK_RECOVERY_KEY: return "CHECK_RECOVERY_KEY";
        case TYPE_SET_MY_BACKUPS: return "SET_MY_BACKUPS";
        case TYPE_EXPORT_SET: return "EXPORT_SET";
        case TYPE_PUT_SET: return "PUT_SET";
        case TYPE_REMOVE_SET: return "REMOVE_SET";
        case TYPE_FETCH_SET: return "FETCH_SET";
        case TYPE_PUT_SET_ELEMENTS: return "PUT_SET_ELEMENTS";
        case TYPE_PUT_SET_ELEMENT: return "PUT_SET_ELEMENT";
        case TYPE_REMOVE_SET_ELEMENT: return "REMOVE_SET_ELEMENT";
        case TYPE_REMOVE_SET_ELEMENTS: return "REMOVE_SET_ELEMENTS";
        case TYPE_REMOVE_OLD_BACKUP_NODES: return "REMOVE_OLD_BACKUP_NODES";
        case TYPE_ADD_UPDATE_SCHEDULED_MEETING: return "ADD_SCHEDULED_MEETING";
        case TYPE_DEL_SCHEDULED_MEETING: return "DEL_SCHEDULED_MEETING";
        case TYPE_FETCH_SCHEDULED_MEETING: return "FETCH_SCHEDULED_MEETING";
        case TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES: return "FETCH_SCHEDULED_MEETING_EVENTS";
        case TYPE_GET_EXPORTED_SET_ELEMENT: return "GET_EXPORTED_SET_ELEMENT";
        case TYPE_OPEN_SHARE_DIALOG: return "OPEN_SHARE_DIALOG";
        case TYPE_UPGRADE_SECURITY: return "UPGRADE_SECURITY";
        case TYPE_GET_RECOMMENDED_PRO_PLAN: return "GET_RECOMMENDED_PRO_PLAN";
    }
    return "UNKNOWN";
}

MegaRequestListener *MegaRequestPrivate::getListener() const
{
    return listener;
}

int MegaRequestPrivate::getTransferTag() const
{
    return transfer;
}

const char *MegaRequestPrivate::toString() const
{
    return getRequestString();
}

const char *MegaRequestPrivate::__str__() const
{
    return getRequestString();
}

const char *MegaRequestPrivate::__toString() const
{
    return getRequestString();
}

MegaBannerPrivate::MegaBannerPrivate(std::tuple<int, std::string, std::string, std::string, std::string, std::string, std::string>&& details)
                  :mDetails(move(details))
{
}

MegaBanner* MegaBannerPrivate::copy() const
{
    return new MegaBannerPrivate(*this);
}

int MegaBannerPrivate::getId() const
{
    return std::get<0>(mDetails);
}

const char* MegaBannerPrivate::getTitle() const
{
    return std::get<1>(mDetails).c_str();
}

const char* MegaBannerPrivate::getDescription() const
{
    return std::get<2>(mDetails).c_str();
}

const char* MegaBannerPrivate::getImage() const
{
    return std::get<3>(mDetails).c_str();
}

const char* MegaBannerPrivate::getUrl() const
{
    return std::get<4>(mDetails).c_str();
}

const char* MegaBannerPrivate::getBackgroundImage() const
{
    return std::get<5>(mDetails).c_str();
}

const char* MegaBannerPrivate::getImageLocation() const
{
    return std::get<6>(mDetails).c_str();
}

MegaBannerListPrivate* MegaBannerListPrivate::copy() const
{
    return new MegaBannerListPrivate(*this);
}

const MegaBanner* MegaBannerListPrivate::get(int i) const
{
    return (i >= 0 && static_cast<size_t>(i) < mVector.size()) ? &(mVector[i]) : nullptr;
}

int MegaBannerListPrivate::size() const
{
    return int(mVector.size());
}

void MegaBannerListPrivate::add(MegaBannerPrivate&& banner)
{
    mVector.emplace_back(std::move(banner));
}

MegaStringMapPrivate::MegaStringMapPrivate()
{

}

MegaStringMapPrivate::MegaStringMapPrivate(const string_map *map, bool toBase64)
{
    strMap.insert(map->begin(),map->end());

    if (toBase64)
    {
        char* buf;
        string_map::iterator it;
        for (it = strMap.begin(); it != strMap.end(); it++)
        {
            buf = new char[it->second.length() * 4 / 3 + 4];
            Base64::btoa((const byte *) it->second.data(), int(it->second.length()), buf);

            it->second.assign(buf);

            delete [] buf;
        }
    }
}

MegaStringMapPrivate::~MegaStringMapPrivate()
{

}

MegaStringMap *MegaStringMapPrivate::copy() const
{
    return new MegaStringMapPrivate(this);
}

const char *MegaStringMapPrivate::get(const char *key) const
{
    string_map::const_iterator it = strMap.find(key);

    if (it == strMap.end())
    {
        return NULL;
    }

    return it->second.data();
}

MegaStringList *MegaStringMapPrivate::getKeys() const
{
    string_vector keys;
    for (auto& it : strMap)
    {
        keys.push_back(it.first);
    }

    return new MegaStringListPrivate(move(keys));
}

void MegaStringMapPrivate::set(const char *key, const char *value)
{
    strMap[key] = value;
}

int MegaStringMapPrivate::size() const
{
    return int(strMap.size());
}

const string_map *MegaStringMapPrivate::getMap() const
{
    return &strMap;
}

MegaStringMapPrivate::MegaStringMapPrivate(const MegaStringMapPrivate *megaStringMap)
{
    MegaStringList *keys = megaStringMap->getKeys();
    const char *key = NULL;
    const char *value = NULL;
    for (int i=0; i < keys->size(); i++)
    {
        key = keys->get(i);
        value = megaStringMap->get(key);

        strMap[key] = value;
    }

    delete keys;
}

MegaIntegerMapPrivate::MegaIntegerMapPrivate()
{
}

MegaIntegerMapPrivate::MegaIntegerMapPrivate(const MegaIntegerMapPrivate& megaIntegerMap)
    :mIntegerMap(megaIntegerMap.getMap() ? *megaIntegerMap.getMap() : integer_map())
{
}

MegaIntegerMapPrivate::MegaIntegerMapPrivate(const std::multimap<int8_t, int8_t>& bytesMap)
{
    for (const auto& element: bytesMap)
    {
        mIntegerMap.emplace(static_cast<int64_t>(element.first), static_cast<int64_t>(element.second));
    }
}

MegaIntegerMapPrivate::MegaIntegerMapPrivate(const std::multimap<int64_t, int64_t>& integerMap)
    :mIntegerMap(integerMap)
{
}

MegaIntegerMapPrivate::~MegaIntegerMapPrivate()
{
}


MegaSmallIntMap* MegaIntegerMapPrivate::toByteMap() const
{
    MegaSmallIntMap* byteMap = new MegaSmallIntMap();
    for (const auto& pair: mIntegerMap)
    {
        byteMap->emplace(static_cast<int8_t>(pair.first), static_cast<int8_t>(pair.second));
    }
    return byteMap;
}

MegaIntegerMap* MegaIntegerMapPrivate::copy() const
{
    return new MegaIntegerMapPrivate(*this);
}

MegaIntegerList* MegaIntegerMapPrivate::getKeys() const
{
    vector<int64_t> keys;
    for (auto& it : mIntegerMap)
    {
        keys.push_back(it.first);
    }

    return new MegaIntegerListPrivate(keys);
}

int64_t MegaIntegerMapPrivate::size() const
{
    return static_cast<int64_t>(mIntegerMap.size());
}

MegaIntegerList* MegaIntegerMapPrivate::get(int64_t key) const
{
    vector<int64_t> values;
    auto range = mIntegerMap.equal_range(key);
    for (auto i = range.first; i != range.second; ++i)
    {
        values.emplace_back(i->second);
    }
    return new MegaIntegerListPrivate(values);
}

void MegaIntegerMapPrivate::set(int64_t key, int64_t value)
{
    mIntegerMap.emplace(key, value);
}

const integer_map* MegaIntegerMapPrivate::getMap() const
{
    return &mIntegerMap;
}

MegaStringListPrivate::MegaStringListPrivate(string_vector&& v)
    : mList(move(v))
{
}

MegaStringList *MegaStringListPrivate::copy() const
{
    return new MegaStringListPrivate(*this);
}

const char *MegaStringListPrivate::get(int i) const
{
    if((i < 0) || (static_cast<size_t>(i) >= mList.size()))
        return nullptr;

    return mList[i].c_str();
}

int MegaStringListPrivate::size() const
{
    return int(mList.size());
}

void MegaStringListPrivate::add(const char *value)
{
    if (value)
    {
        mList.push_back(value);
    }
}

const string_vector& MegaStringListPrivate::getVector()
{
    return mList;
}

bool operator==(const MegaStringList& lhs, const MegaStringList& rhs)
{
    if (lhs.size() != rhs.size())
    {
        return false;
    }
    for (int i = 0; i < lhs.size(); ++i)
    {
        if (strcmp(lhs.get(i), rhs.get(i)) != 0)
        {
            return false;
        }
    }
    return true;
}


MegaStringListMap* MegaStringListMapPrivate::copy() const
{
    auto map = new MegaStringListMapPrivate;
    for (const auto& pair : mMap)
    {
        map->set(pair.first.get(), pair.second->copy());
    }
    return map;
}

const MegaStringList* MegaStringListMapPrivate::get(const char* key) const
{
    auto key_ptr = std::unique_ptr<const char[]>{key};
    auto iter = mMap.find(key_ptr);
    key_ptr.release();
    if (iter != mMap.end())
    {
        return iter->second.get();
    }
    return nullptr;
}

MegaStringList *MegaStringListMapPrivate::getKeys() const
{
    string_vector list;
    for (const auto& pair : mMap)
    {
        list.push_back(pair.first.get());
    }
    return new MegaStringListPrivate(move(list));
}

void MegaStringListMapPrivate::set(const char* key, const MegaStringList* value)
{
    std::unique_ptr<const char[]> key_ptr{MegaApi::strdup(key)};
    mMap[std::move(key_ptr)] = std::unique_ptr<const MegaStringList>{value};
}

int MegaStringListMapPrivate::size() const
{
    return static_cast<int>(mMap.size());
}

bool MegaStringListMapPrivate::Compare::operator()(const std::unique_ptr<const char[]>& rhs,
                                                   const std::unique_ptr<const char[]>& lhs) const
{
    return strcmp(rhs.get(), lhs.get()) < 0;
}


MegaStringTable* MegaStringTablePrivate::copy() const
{
    auto table = new MegaStringTablePrivate;
    for (const auto& value : mTable)
    {
        table->append(value->copy());
    }
    return table;
}

void MegaStringTablePrivate::append(const MegaStringList* value)
{
    mTable.emplace_back(value);
}

const MegaStringList* MegaStringTablePrivate::get(int i) const
{
    if (i >= 0 && i < size())
    {
        return mTable[i].get();
    }
    return nullptr;
}

int MegaStringTablePrivate::size() const
{
    return static_cast<int>(mTable.size());
}


MegaNodeListPrivate::MegaNodeListPrivate()
{
    list = NULL;
    s = 0;
}

MegaNodeListPrivate::MegaNodeListPrivate(node_vector& v)
{
    list = NULL; s = static_cast<int>(v.size());
    if (!s) return;

    list = new MegaNode*[s];
    for (int i = 0; i < s; i++)
        list[i] = MegaNodePrivate::fromNode(v[i]);
}

MegaNodeListPrivate::MegaNodeListPrivate(Node** newlist, int size)
{
    list = NULL; s = size;
    if(!size) return;

    list = new MegaNode*[size];
    for(int i=0; i<size; i++)
        list[i] = MegaNodePrivate::fromNode(newlist[i]);
}

MegaNodeListPrivate::MegaNodeListPrivate(const MegaNodeListPrivate *nodeList, bool copyChildren)
{
    s = nodeList->size();
    if (!s)
    {
        list = NULL;
        return;
    }

    list = new MegaNode*[s];
    for (int i = 0; i<s; i++)
    {
        MegaNode *node = nodeList->get(i);
        MegaNodePrivate *nodePrivate = new MegaNodePrivate(node);
        MegaNodeListPrivate *children = dynamic_cast<MegaNodeListPrivate *>(node->getChildren());
        if (children && copyChildren)
        {
            nodePrivate->setChildren(new MegaNodeListPrivate(children, true));
        }
        list[i] = nodePrivate;
    }
}

MegaNodeListPrivate::~MegaNodeListPrivate()
{
    if(!list)
        return;

    for(int i=0; i<s; i++)
        delete list[i];
    delete [] list;
}

MegaNodeList *MegaNodeListPrivate::copy() const
{
    return new MegaNodeListPrivate(this);
}

MegaNode *MegaNodeListPrivate::get(int i) const
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaNodeListPrivate::size() const
{
    return s;
}


void MegaNodeListPrivate::addNode(std::unique_ptr<MegaNode> node)
{
    MegaNode** copyList = list;
    s = s + 1;
    list = new MegaNode*[s];
    for (int i = 0; i < s - 1; ++i)
    {
        list[i] = copyList[i];
    }

    list[s - 1] = node.release();

    if (copyList != NULL)
    {
        delete [] copyList;
    }
}

void MegaNodeListPrivate::addNode(MegaNode *node)
{
    MegaNode** copyList = list;
    s = s + 1;
    list = new MegaNode*[s];
    for (int i = 0; i < s - 1; ++i)
    {
        list[i] = copyList[i];
    }

    list[s - 1] = node->copy();

    if (copyList != NULL)
    {
        delete [] copyList;
    }
}

MegaUserListPrivate::MegaUserListPrivate()
{
    list = NULL;
    s = 0;
}

MegaUserListPrivate::MegaUserListPrivate(User** newlist, int size)
{
    list = NULL;
    s = size;

    if(!size)
        return;

    list = new MegaUser*[size];
    for(int i=0; i<size; i++)
        list[i] = MegaUserPrivate::fromUser(newlist[i]);
}

MegaUserListPrivate::MegaUserListPrivate(MegaUserListPrivate *userList)
{
    s = userList->size();
    if (!s)
    {
        list = NULL;
        return;
    }
    list = new MegaUser*[s];
    for (int i = 0; i<s; i++)
        list[i] = new MegaUserPrivate(userList->get(i));
}

MegaUserListPrivate::~MegaUserListPrivate()
{
    if(!list)
        return;

    for(int i=0; i<s; i++)
        delete list[i];

    delete [] list;
}

MegaUserList *MegaUserListPrivate::copy()
{
    return new MegaUserListPrivate(this);
}

MegaUser *MegaUserListPrivate::get(int i)
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaUserListPrivate::size()
{
    return s;
}

MegaUserAlertListPrivate::MegaUserAlertListPrivate()
{
    list = NULL;
    s = 0;
}

MegaUserAlertListPrivate::MegaUserAlertListPrivate(UserAlert::Base** newlist, int size, MegaClient* mc)
{
    list = NULL;
    s = size;

    if (!size)
        return;

    list = new MegaUserAlert*[size];
    for (int i = 0; i < size; i++)
    {
        list[i] = new MegaUserAlertPrivate(newlist[i], mc);
    }
}

MegaUserAlertListPrivate::MegaUserAlertListPrivate(const MegaUserAlertListPrivate &userList)
{
    s = userList.size();
    list = s ? new MegaUserAlert*[s] : NULL;
    for (int i = 0; i < s; ++i)
    {
        list[i] = userList.get(i)->copy();
    }
}

MegaUserAlertListPrivate::~MegaUserAlertListPrivate()
{
    for (int i = 0; i < s; i++)
    {
        delete list[i];
    }
    delete[] list;
}

MegaUserAlertList *MegaUserAlertListPrivate::copy() const
{
    return new MegaUserAlertListPrivate(*this);
}

MegaUserAlert *MegaUserAlertListPrivate::get(int i) const
{
    if (!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaUserAlertListPrivate::size() const
{
    return s;
}

void MegaUserAlertListPrivate::clear()
{
    delete[] list;
    s = 0;
    list = nullptr;
}

MegaRecentActionBucketPrivate::MegaRecentActionBucketPrivate(recentaction& ra, MegaClient* mc)
{
    User* u = mc->finduser(ra.user);

    timestamp = ra.time;
    user = u ? u->email : "";
    parent = ra.parent;
    update = ra.updated;
    media = ra.media;
    nodes = new MegaNodeListPrivate(ra.nodes);
}

MegaRecentActionBucketPrivate::MegaRecentActionBucketPrivate(int64_t ts, const string& u, handle p, bool up, bool m, MegaNodeList* l)
{
    timestamp = ts;
    user = u;
    parent = p;
    update = up;
    media = m;
    nodes = l;
}

MegaRecentActionBucketPrivate::~MegaRecentActionBucketPrivate()
{
    delete nodes;
}

MegaRecentActionBucket *MegaRecentActionBucketPrivate::copy() const
{
    return new MegaRecentActionBucketPrivate(timestamp, user, parent, update, media, nodes->copy());
}

int64_t MegaRecentActionBucketPrivate::getTimestamp() const
{
    return timestamp;
}

const char* MegaRecentActionBucketPrivate::getUserEmail() const
{
    return user.c_str();
}

MegaHandle MegaRecentActionBucketPrivate::getParentHandle() const
{
    return parent;
}

bool MegaRecentActionBucketPrivate::isUpdate() const
{
    return update;
}

bool MegaRecentActionBucketPrivate::isMedia() const
{
    return media;
}

const MegaNodeList* MegaRecentActionBucketPrivate::getNodes() const
{
    return nodes;
}

MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate()
{
    list = NULL;
    s = 0;
}

MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate(recentactions_vector& v, MegaClient* mc)
{
    list = NULL;
    s = static_cast<int>(v.size());

    if (!s)
        return;

    list = new MegaRecentActionBucketPrivate*[s];
    for (int i = 0; i < s; i++)
    {
        list[i] = new MegaRecentActionBucketPrivate(v[i], mc);
    }
}

MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate(const MegaRecentActionBucketListPrivate &o)
{
    s = o.size();
    list = s ? new MegaRecentActionBucketPrivate*[s] : NULL;
    for (int i = 0; i < s; ++i)
    {
        list[i] = (MegaRecentActionBucketPrivate*)o.get(i)->copy();
    }
}

MegaRecentActionBucketListPrivate::~MegaRecentActionBucketListPrivate()
{
    for (int i = 0; i < s; i++)
    {
        delete list[i];
    }
    delete[] list;
}

MegaRecentActionBucketList *MegaRecentActionBucketListPrivate::copy() const
{
    return new MegaRecentActionBucketListPrivate(*this);
}

MegaRecentActionBucket *MegaRecentActionBucketListPrivate::get(int i) const
{
    if (!list || (i < 0) || (i >= s))
    {
        return NULL;
    }
    return list[i];
}

int MegaRecentActionBucketListPrivate::size() const
{
    return s;
}

MegaShareListPrivate::MegaShareListPrivate()
{
    list = NULL;
    s = 0;
}

MegaShareListPrivate::MegaShareListPrivate(Share** newlist, uint64_t *uint64_tlist, byte *verified, int size)
{
    list = NULL; s = size;
    if(!size) return;

    list = new MegaShare*[size];
    for(int i=0; i<size; i++)
    {
        list[i] = MegaSharePrivate::fromShare(uint64_tlist[i], newlist[i], verified[i]);
    }
}

MegaShareListPrivate::~MegaShareListPrivate()
{
    if(!list)
        return;

    for(int i=0; i<s; i++)
        delete list[i];

    delete [] list;
}

MegaShare *MegaShareListPrivate::get(int i)
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaShareListPrivate::size()
{
    return s;
}

MegaTransferListPrivate::MegaTransferListPrivate()
{
    list = NULL;
    s = 0;
}

MegaTransferListPrivate::MegaTransferListPrivate(MegaTransfer** newlist, int size)
{
    list = NULL;
    s = size;

    if(!size)
        return;

    list = new MegaTransfer*[size];
    for(int i=0; i<size; i++)
        list[i] = newlist[i]->copy();
}

MegaTransferListPrivate::~MegaTransferListPrivate()
{
    if(!list)
        return;

    for(int i=0; i < s; i++)
        delete list[i];

    delete [] list;
}

MegaTransfer *MegaTransferListPrivate::get(int i)
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaTransferListPrivate::size()
{
    return s;
}

MegaContactRequestListPrivate::MegaContactRequestListPrivate()
{
    list = NULL;
    s = 0;
}

MegaContactRequestListPrivate::MegaContactRequestListPrivate(PendingContactRequest **newlist, int size)
{
    list = NULL;
    s = size;

    if(!size)
        return;

    list = new MegaContactRequest*[size];
    for(int i=0; i<size; i++)
        list[i] = new MegaContactRequestPrivate(newlist[i]);
}

MegaContactRequestListPrivate::~MegaContactRequestListPrivate()
{
    if(!list)
        return;

    for(int i=0; i < s; i++)
        delete list[i];

    delete [] list;
}

MegaContactRequestList *MegaContactRequestListPrivate::copy()
{
    return new MegaContactRequestListPrivate(this);
}

MegaContactRequest *MegaContactRequestListPrivate::get(int i)
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaContactRequestListPrivate::size()
{
    return s;
}

MegaContactRequestListPrivate::MegaContactRequestListPrivate(MegaContactRequestListPrivate *requestList)
{
    s = requestList->size();
    if (!s)
    {
        list = NULL;
        return;
    }
    list = new MegaContactRequest*[s];
    for (int i = 0; i < s; i++)
        list[i] = new MegaContactRequestPrivate(requestList->get(i));
}

MegaFile::MegaFile() : File()
{
    megaTransfer = NULL;
}

void MegaFile::setTransfer(MegaTransferPrivate *transfer)
{
    this->megaTransfer = transfer;
}

MegaTransferPrivate *MegaFile::getTransfer()
{
    return megaTransfer;
}

bool MegaFile::serialize(string *d) const
{
    if (!megaTransfer)
    {
        return false;
    }

    if (!File::serialize(d))
    {
        return false;
    }

    if (!megaTransfer->serialize(d))
    {
        return false;
    }

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

    return true;
}

MegaFile *MegaFile::unserialize(string *d)
{
    File *file = File::unserialize(d);
    if (!file)
    {
        LOG_err << "Error unserializing MegaFile: Unable to unserialize File";
        return NULL;
    }

    MegaFile *megaFile = new MegaFile();
    *(File *)megaFile = *(File *)file;
    file->chatauth = NULL;
    delete file;

    MegaTransferPrivate *transfer = MegaTransferPrivate::unserialize(d);
    if (!transfer)
    {
        delete megaFile;
        return NULL;
    }

    const char* ptr = d->data();
    const char* end = ptr + d->size();
    if (ptr + 8 > end)
    {
        LOG_err << "MegaFile unserialization failed - data too short";
        delete megaFile;
        delete transfer;
        return NULL;
    }

    if (memcmp(ptr, "\0\0\0\0\0\0\0", 8))
    {
        LOG_err << "MegaFile unserialization failed - invalid version";
        delete megaFile;
        delete transfer;
        return NULL;
    }
    ptr += 8;

    d->erase(0, ptr - d->data());

    transfer->setSourceFileTemporary(megaFile->temporaryfile);

    megaFile->setTransfer(transfer);
    return megaFile;
}

MegaFileGet::MegaFileGet(MegaClient *client, Node *n, const LocalPath& dstPath, FileSystemType fsType) : MegaFile()
{
    h = n->nodeHandle();
    *(FileFingerprint*)this = *n;

    name = n->displayname();
    auto lpName = LocalPath::fromRelativeName(name, *client->fsaccess, fsType);

    LocalPath finalPath;
    if(!dstPath.empty())
    {
        if (dstPath.endsInSeparator())
        {
            finalPath = dstPath;
            finalPath.appendWithSeparator(lpName, true);
        }
        else finalPath = dstPath;
    }
    else
        finalPath = lpName;

    size = n->size;
    mtime = n->mtime;

    if(n->nodekey().size()>=sizeof(filekey))
        memcpy(filekey,n->nodekey().data(),sizeof filekey);

    setLocalname(finalPath);
    hprivate = true;
    hforeign = false;
}

MegaFileGet::MegaFileGet(MegaClient *client, MegaNode *n, const LocalPath& dstPath) : MegaFile()
{
    h.set6byte(n->getHandle());

    FileSystemType fsType = client->fsaccess->getlocalfstype(dstPath);

    name = n->getName();
    auto lpName = LocalPath::fromRelativeName(name, *client->fsaccess, fsType);

    LocalPath finalPath;
    if(!dstPath.empty())
    {
        if (dstPath.endsInSeparator())
        {
            finalPath = dstPath;
            finalPath.appendWithSeparator(lpName, true);
        }
        else finalPath = dstPath;
    }
    else finalPath = lpName;

    const char *fingerprint = n->getFingerprint();
    if (fingerprint)
    {
        unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
        if (fp)
        {
            *(FileFingerprint *)this = *(FileFingerprint *)fp.get();
        }
    }

    size = n->getSize();
    mtime = n->getModificationTime();

    if(n->getNodeKey()->size()>=sizeof(filekey))
        memcpy(filekey,n->getNodeKey()->data(),sizeof filekey);

    setLocalname(finalPath);
    hprivate = !n->isPublic();
    hforeign = n->isForeign();

    if(n->getPrivateAuth()->size())
    {
        privauth = *n->getPrivateAuth();
    }

    if(n->getPublicAuth()->size())
    {
        pubauth = *n->getPublicAuth();
    }

    chatauth = n->getChatAuth() ? MegaApi::strdup(n->getChatAuth()) : NULL;
}

bool MegaFileGet::serialize(string *d) const
{
    if (!MegaFile::serialize(d))
    {
        return false;
    }

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

    return true;
}

MegaFileGet *MegaFileGet::unserialize(string *d)
{
    MegaFile *file = MegaFile::unserialize(d);
    if (!file)
    {
        LOG_err << "Error unserializing MegaFileGet: Unable to unserialize MegaFile";
        return NULL;
    }

    const char* ptr = d->data();
    const char* end = ptr + d->size();
    if (ptr + 8 > end)
    {
        LOG_err << "MegaFileGet unserialization failed - data too short";
        delete file;
        return NULL;
    }

    if (memcmp(ptr, "\0\0\0\0\0\0\0", 8))
    {
        LOG_err << "MegaFileGet unserialization failed - invalid version";
        delete file;
        return NULL;
    }

    ptr += 8;
    if (ptr != end)
    {
        LOG_err << "MegaFileGet unserialization failed - wrong size";
        delete file;
        return NULL;
    }

    MegaFileGet *megaFile = new MegaFileGet();
    *(MegaFile *)megaFile = *(MegaFile *)file;
    file->chatauth = NULL;
    delete file;

    return megaFile;
}

void MegaFileGet::prepare(FileSystemAccess&)
{
    if (transfer->localfilename.empty())
    {
        transfer->localfilename = getLocalname();
        assert(transfer->localfilename.isAbsolute());

        size_t leafIndex = transfer->localfilename.getLeafnameByteIndex();
        transfer->localfilename.truncate(leafIndex);
        transfer->localfilename.appendWithSeparator(LocalPath::tmpNameLocal(), false);
    }
}

void MegaFileGet::updatelocalname()
{
#ifdef _WIN32
    RemoveHiddenFileAttribute(transfer->localfilename);
#endif
}

void MegaFileGet::progress()
{
#ifdef _WIN32
    if(transfer->slot && !transfer->slot->progressreported)
    {
        AddHiddenFileAttribute(transfer->localfilename);
    }
#endif
}

void MegaFileGet::completed(Transfer*, putsource_t source)
{
    delete this;
}

void MegaFileGet::terminated(error e)
{
    delete this;
}

MegaFilePut::MegaFilePut(MegaClient *, LocalPath clocalname, string *filename, NodeHandle ch, const char* ctargetuser, int64_t mtime, bool isSourceTemporary, Node *pvNode)

    : MegaFile()
{
    // full local path
    setLocalname(clocalname);

    // target parent node
    h = ch;

    // target user
    targetuser = ctargetuser;

    // new node name
    name = *filename;

    // If the file's time is to be used, this is MegaApi::INVALID_CUSTOM_MOD_TIME
    customMtime = mtime;

    temporaryfile = isSourceTemporary;

    previousNode = pvNode;
}

bool MegaFilePut::serialize(string *d) const
{
    if (!MegaFile::serialize(d))
    {
        return false;
    }

    d->append((char*)&customMtime, sizeof(customMtime));
    d->append("\0\0\0\0\0\0\0", 8);

    return true;
}

MegaFilePut *MegaFilePut::unserialize(string *d)
{
    MegaFile *file = MegaFile::unserialize(d);
    if (!file)
    {
        LOG_err << "Error unserializing MegaFilePut: Unable to unserialize MegaFile";
        return NULL;
    }

    const char* ptr = d->data();
    const char* end = ptr + d->size();
    if (ptr + sizeof(int64_t) + 8 > end)
    {
        LOG_err << "MegaFilePut unserialization failed - data too short";
        delete file;
        return NULL;
    }

    int64_t customMtime = MemAccess::get<int64_t>(ptr);
    ptr += sizeof(customMtime);

    if (memcmp(ptr, "\0\0\0\0\0\0\0", 8))
    {
        LOG_err << "MegaFilePut unserialization failed - invalid version";
        delete file;
        return NULL;
    }

    ptr += 8;
    if (ptr != end)
    {
        LOG_err << "MegaFilePut unserialization failed - wrong size";
        delete file;
        return NULL;
    }

    MegaFilePut *megaFile = new MegaFilePut();
    *(MegaFile *)megaFile = *(MegaFile *)file;
    file->chatauth = NULL;
    delete file;

    megaFile->customMtime = customMtime;
    return megaFile;
}

void MegaFilePut::completed(Transfer* t, putsource_t source)
{
    assert(!transfer || t == transfer);
    assert(source == PUTNODES_APP);
    assert(t->type == PUT);

    // allow for putnodes with a different mtime to the actual file
    sendPutnodesOfUpload(t->client, t->uploadhandle, *t->ultoken, t->filekey, source, NodeHandle(),
        nullptr, nullptr, customMtime == MegaApi::INVALID_CUSTOM_MOD_TIME ? nullptr : &customMtime, false);

    delete this;
}

void MegaFilePut::terminated(error e)
{
    delete this;
}

//Entry point for the blocking thread
void *MegaApiImpl::threadEntryPoint(void *param)
{
#ifndef _WIN32
    struct sigaction noaction;
    memset(&noaction, 0, sizeof(noaction));
    noaction.sa_handler = SIG_IGN;
    ::sigaction(SIGPIPE, &noaction, 0);
#endif

    MegaApiImpl *megaApiImpl = (MegaApiImpl *)param;
    megaApiImpl->loop();
    return 0;
}

MegaTransferPrivate *MegaApiImpl::getMegaTransferPrivate(int tag)
{
    map<int, MegaTransferPrivate *>::iterator it = transferMap.find(tag);
    if (it == transferMap.end())
    {
        return NULL;
    }
    return it->second;
}

MegaApiImpl::MegaApiImpl(MegaApi *api, const char *appKey, MegaGfxProcessor* processor, const char *basePath, const char *userAgent, unsigned workerThreadCount)
{
    init(api, appKey, processor, basePath, userAgent, workerThreadCount);
}

void MegaApiImpl::init(MegaApi *api, const char *appKey, MegaGfxProcessor* processor, const char *basePath, const char *userAgent, unsigned clientWorkerThreadCount)
{
    this->api = api;

    maxRetries = 7;
    currentTransfer = NULL;
    pendingUploads = 0;
    pendingDownloads = 0;
    totalUploads = 0;
    totalDownloads = 0;
    client = NULL;
    waitingRequest = RETRY_NONE;
    totalDownloadedBytes = 0;
    totalUploadedBytes = 0;
    totalDownloadBytes = 0;
    totalUploadBytes = 0;
    notificationNumber = 0;
    syncLowerSizeLimit = 0;
    syncUpperSizeLimit = 0;

#ifdef HAVE_LIBUV
    httpServer = NULL;
    httpServerMaxBufferSize = 0;
    httpServerMaxOutputSize = 0;
    httpServerEnableFiles = true;
    httpServerEnableFolders = false;
    httpServerOfflineAttributeEnabled = false;
    httpServerRestrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS;
    httpServerSubtitlesSupportEnabled = false;

    ftpServer = NULL;
    ftpServerMaxBufferSize = 0;
    ftpServerMaxOutputSize = 0;
    ftpServerRestrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS;
    const char *uvversion = uv_version_string();
    if (uvversion)
    {
        LOG_debug << "libuv version: " <<uvversion;
    }
#endif

    mPushSettings = NULL;
    mTimezones = NULL;

    httpio = new MegaHttpIO();
    waiter.reset(new MegaWaiter());

    fsAccess.reset(new MegaFileSystemAccess());

    dbAccess = nullptr;
    if (basePath)
    {
        dbAccess = new MegaDbAccess(LocalPath::fromAbsolutePath(basePath));
        this->basePath = basePath;
    }

    gfxAccess = NULL;
    if(processor)
    {
        auto externalGfx = ::mega::make_unique<GfxProviderExternal>();
        externalGfx->setProcessor(processor);
        gfxAccess = new GfxProc(std::move(externalGfx));
        gfxAccess->startProcessingThread();
    }
    else
    {
        gfxAccess = new GfxProc(::mega::make_unique<MegaGfxProvider>());
        gfxAccess->startProcessingThread();
    }

    if(!userAgent)
    {
        userAgent = "";
    }

    nocache = false;
    if (appKey)
    {
        this->appKey = appKey;
    }
    client = new MegaClient(this, waiter, httpio, dbAccess, gfxAccess, appKey, userAgent, clientWorkerThreadCount);

#if defined(_WIN32)
    httpio->unlock();
#endif

    //Start blocking thread
    threadExit = 0;
    thread = std::thread([this](){ threadEntryPoint(this); } );
    threadId = thread.get_id();
}

MegaApiImpl::~MegaApiImpl()
{
    // the fireOnFinish won't be called for this one, so delete it ourselves
    auto shutdownRequest = ::mega::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_DELETE);

    requestQueue.push(shutdownRequest.get());
    waiter->notify();
    thread.join();
    assert(client == nullptr);

    delete mPushSettings;
    delete mTimezones;

    assert(requestMap.empty());
    assert(backupsMap.empty());
    assert(transferMap.empty());

    delete gfxAccess;

#ifndef DONT_RELEASE_HTTPIO
    delete httpio;
#endif
}

MegaApiImpl* MegaApiImpl::ImplOf(MegaApi* api)
{
    // Sometimes we need to be able to reference the MegaApiImpl from objects other than MegaApi (without giving clients access to the pImpl pointer)
    return api->pImpl;
}

void MegaApiImpl::loggedInStateChanged(sessiontype_t s, handle me)
{
    std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex);
    mLastReceivedLoggedInState = s;
    mLastReceivedLoggedInMeHandle = me;
}

int MegaApiImpl::isLoggedIn()
{
    std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex);
    return mLastReceivedLoggedInState;
}

bool MegaApiImpl::isEphemeralPlusPlus()
{
    return isLoggedIn() == EPHEMERALACCOUNTPLUSPLUS;
}

char* MegaApiImpl::getMyEmail()
{
    User* u;
    SdkMutexGuard g(sdkMutex);
    if (!client->loggedin() || !(u = client->finduser(client->me)))
    {
        return NULL;
    }

    return MegaApi::strdup(u->email.c_str());
}

int64_t MegaApiImpl::getAccountCreationTs()
{
    return client->accountsince;
}

char *MegaApiImpl::getMyUserHandle()
{
    std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex);

    if (mLastReceivedLoggedInState == NOTLOGGEDIN ||
        ISUNDEF(mLastReceivedLoggedInMeHandle))
    {
        return NULL;
    }

    char buf[12];
    Base64::btoa((const byte*)&mLastReceivedLoggedInMeHandle, MegaClient::USERHANDLE, buf);
    char *result = MegaApi::strdup(buf);
    return result;
}

MegaHandle MegaApiImpl::getMyUserHandleBinary()
{
    SdkMutexGuard g(sdkMutex);
    return client->me;
}

MegaUser *MegaApiImpl::getMyUser()
{
    SdkMutexGuard g(sdkMutex);
    return MegaUserPrivate::fromUser(client->finduser(client->me));
}

bool MegaApiImpl::isAchievementsEnabled()
{
    assert(!isBusinessAccount() || !client->achievements_enabled);
    return client->achievements_enabled;
}

bool MegaApiImpl::isBusinessAccount()
{
    return client->mBizStatus != BIZ_STATUS_INACTIVE
             && client->mBizStatus != BIZ_STATUS_UNKNOWN;
}

bool MegaApiImpl::isMasterBusinessAccount()
{
    return client->mBizMode == BIZ_MODE_MASTER;
}

bool MegaApiImpl::isBusinessAccountActive()
{
    return getBusinessStatus() >= BIZ_STATUS_ACTIVE;
}

int MegaApiImpl::getBusinessStatus()
{
    // Prevent return apps unknown status
    return (client->mBizStatus == BIZ_STATUS_UNKNOWN)
            ? BIZ_STATUS_INACTIVE
            : client->mBizStatus;
}

int64_t MegaApiImpl::getOverquotaDeadlineTs()
{
    return client->mOverquotaDeadlineTs;
}

MegaIntegerList *MegaApiImpl::getOverquotaWarningsTs()
{
    return new MegaIntegerListPrivate(client->mOverquotaWarningTs);
}

bool MegaApiImpl::checkPassword(const char *password)
{
    SdkMutexGuard g(sdkMutex);
    return client->validatepwdlocally(password);
}

char *MegaApiImpl::getMyCredentials()
{
    SdkMutexGuard g(sdkMutex);
    if (ISUNDEF(client->me))
    {
        return NULL;
    }

    string result;
    if (client->signkey)
    {
        result = AuthRing::fingerprint(string((const char*)client->signkey->pubKey, EdDSA::PUBLIC_KEY_LENGTH), true);
    }

    return result.size() ? MegaApi::strdup(result.c_str()) : nullptr;
}

void MegaApiImpl::getUserCredentials(MegaUser *user, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);

    request->setParamType(ATTR_ED25519_PUBK);
    request->setFlag(true);
    if(user)
    {
        request->setEmail(user->getEmail());
    }

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

bool MegaApiImpl::areCredentialsVerified(MegaUser *user)
{
    SdkMutexGuard g(sdkMutex);
    return user ? client->areCredentialsVerified(user->getHandle()) : false;
}

void MegaApiImpl::verifyCredentials(MegaUser *user, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_VERIFY_CREDENTIALS, listener);

    if(user)
    {
        request->setNodeHandle(user->getHandle());
    }

    request->performRequest = [this, request]()
    {
        return performRequest_verifyCredentials(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resetCredentials(MegaUser *user, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_VERIFY_CREDENTIALS, listener);

    if(user)
    {
        request->setNodeHandle(user->getHandle());
    }
    request->setFlag(true);

    request->performRequest = [this, request]()
    {
        return performRequest_verifyCredentials(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

char *MegaApiImpl::getMyRSAPrivateKey()
{
    SdkMutexGuard g(sdkMutex);
    if (ISUNDEF(client->me) || client->mPrivKey.empty())
    {
        return nullptr;
    }

    return MegaApi::strdup(client->mPrivKey.c_str());
}

void MegaApiImpl::setLogExtraForModules(bool networking, bool syncs)
{
    g_netLoggingOn = networking;
#ifdef ENABLE_SYNC
    client->syncs.mDetailedSyncLogging = syncs;
#endif
}

void MegaApiImpl::setLogLevel(int logLevel)
{
    SimpleLogger::setLogLevel(LogLevel(logLevel));
}

void MegaApiImpl::setMaxPayloadLogSize(long long maxSize)
{
    SimpleLogger::setMaxPayloadLogSize(maxSize);
}

void MegaApiImpl::addLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger)
{

    if (singleExclusiveLogger)
    {
        assert(!g_exclusiveLogger.exclusiveCallback);
        g_exclusiveLogger.exclusiveCallback = [megaLogger](const char *time, int loglevel, const char *source, const char *message
#ifdef ENABLE_LOG_PERFORMANCE
            , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages
#endif
            ){
                megaLogger->log(time, loglevel, source, message
#ifdef ENABLE_LOG_PERFORMANCE
                    , directMessages, directMessagesSizes, numberMessages
#endif
                );
        };

        SimpleLogger::setOutputClass(&g_exclusiveLogger);
    }
    else
    {

    g_externalLogger.addMegaLogger(megaLogger,
        [megaLogger](const char *time, int loglevel, const char *source, const char *message
#ifdef ENABLE_LOG_PERFORMANCE
            , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages
#endif
        ){
            megaLogger->log(time, loglevel, source, message
#ifdef ENABLE_LOG_PERFORMANCE
                , directMessages, directMessagesSizes, numberMessages
#endif
            );
        });
    }
}

void MegaApiImpl::removeLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger)
{
    if (singleExclusiveLogger)
    {
        SimpleLogger::setOutputClass(&g_externalLogger);
        g_exclusiveLogger.exclusiveCallback = nullptr;
    }
    else
    {
        g_externalLogger.removeMegaLogger(megaLogger);
    }
}

void MegaApiImpl::setLogToConsole(bool enable)
{
    // only supported for external (not exclusive) loggers
    g_externalLogger.setLogToConsole(enable);
}

void MegaApiImpl::log(int logLevel, const char *message, const char *filename, int line)
{
    SimpleLogger::postLog(LogLevel(logLevel), message, filename, line);
}

void MegaApiImpl::setLoggingName(const char* loggingName)
{
    SdkMutexGuard g(sdkMutex);
    if (loggingName)
    {
        client->clientname = string(loggingName) + " ";
    }
    else
    {
        client->clientname.clear();
    }
}

void MegaApiImpl::setFilenameAnomalyReporter(MegaFilenameAnomalyReporter* reporter)
{
    unique_ptr<FilenameAnomalyReporter> proxy;

    if (reporter)
    {
        proxy.reset(new MegaFilenameAnomalyReporterProxy(*reporter));
    }

    SdkMutexGuard guard(sdkMutex);
    client->mFilenameAnomalyReporter = std::move(proxy);
}

long long MegaApiImpl::getSDKtime()
{
    return Waiter::ds;
}

char* MegaApiImpl::getStringHash(const char* base64pwkey, const char* inBuf)
{
    if (!base64pwkey || !inBuf)
    {
        return NULL;
    }

    char pwkey[2 * SymmCipher::KEYLENGTH];
    if (Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey) != SymmCipher::KEYLENGTH)
    {
        return MegaApi::strdup("");
    }

    SymmCipher key;
    key.setkey((byte*)pwkey);

    uint64_t strhash;
    string neBuf = inBuf;

    strhash = client->stringhash64(&neBuf, &key);

    char* buf = new char[8*4/3+4];
    Base64::btoa((byte*)&strhash, 8, buf);
    return buf;
}

MegaHandle MegaApiImpl::base32ToHandle(const char *base32Handle)
{
    if(!base32Handle) return INVALID_HANDLE;

    handle h = 0;
    Base32::atob(base32Handle,(byte*)&h, MegaClient::USERHANDLE);
    return h;
}

const char* MegaApiImpl::ebcEncryptKey(const char* encryptionKey, const char* plainKey)
{
    if(!encryptionKey || !plainKey) return NULL;

    char pwkey[SymmCipher::KEYLENGTH];
    Base64::atob(encryptionKey, (byte *)pwkey, sizeof pwkey);

    SymmCipher key;
    key.setkey((byte*)pwkey);

    char plkey[SymmCipher::KEYLENGTH];
    Base64::atob(plainKey, (byte*)plkey, sizeof plkey);
    key.ecb_encrypt((byte*)plkey);

    char* buf = new char[SymmCipher::KEYLENGTH*4/3+4];
    Base64::btoa((byte*)plkey, SymmCipher::KEYLENGTH, buf);
    return buf;
}

handle MegaApiImpl::base64ToHandle(const char* base64Handle)
{
    if(!base64Handle) return UNDEF;

    handle h = 0;
    Base64::atob(base64Handle,(byte*)&h,MegaClient::NODEHANDLE);
    return h;
}

handle MegaApiImpl::base64ToUserHandle(const char *base64Handle)
{
    if(!base64Handle) return UNDEF;

    handle h = 0;
    Base64::atob(base64Handle,(byte*)&h,MegaClient::USERHANDLE);
    return h;
}

handle MegaApiImpl::base64ToBackupId(const char* backupId)
{
    if (!backupId || !*backupId) return UNDEF;

    handle result = 0x0;

    Base64::atob(backupId,
                 reinterpret_cast<byte*>(&result),
                 MegaClient::BACKUPHANDLE);

    return result;
}

char *MegaApiImpl::handleToBase64(MegaHandle handle)
{
    char *base64Handle = new char[12];
    Base64::btoa((byte*)&(handle),MegaClient::NODEHANDLE,base64Handle);
    return base64Handle;
}

char *MegaApiImpl::userHandleToBase64(MegaHandle handle)
{
    char *base64Handle = new char[14];
    Base64::btoa((byte*)&(handle),MegaClient::USERHANDLE,base64Handle);
    return base64Handle;
}

const char* MegaApiImpl::backupIdToBase64(MegaHandle backupId)
{
    unique_ptr<char[]> result(new char[14]);

    Base64::btoa(reinterpret_cast<byte*>(&backupId),
                 MegaClient::BACKUPHANDLE,
                 result.get());

    return result.release();
}

char *MegaApiImpl::binaryToBase64(const char *binaryData, size_t length)
{
    char *ret = new char[length * 4 / 3 + 3];
    Base64::btoa((byte*)binaryData, int(length), ret);
    return ret;
}

void MegaApiImpl::base64ToBinary(const char *base64string, unsigned char **binary, size_t* binarysize)
{
    string data;
    data.resize(strlen(base64string) * 3 / 4 + 3);
    data.resize(Base64::atob(base64string, (byte*)data.data(), int(data.size())));
    *binarysize = data.size();
    *binary = new unsigned char[*binarysize];
    memcpy(*binary, data.data(), *binarysize);
}

void MegaApiImpl::retryPendingConnections(bool disconnect, bool includexfers, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS, listener);
    request->setFlag(disconnect);
    request->setNumber(includexfers);

    request->performRequest = [this, request]()
    {
        return performRequest_retryPendingConnections(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setDnsServers(const char *dnsServers, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS, listener);
    request->setFlag(true);
    request->setNumber(true);
    request->setText(dnsServers);

    request->performRequest = [this, request]()
    {
        return performRequest_retryPendingConnections(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::addEntropy(char *data, unsigned int size)
{
    if(client && client->rng.CanIncorporateEntropy())
    {
        client->rng.IncorporateEntropy((const byte*)data, size);
    }

#ifdef USE_OPENSSL
    RAND_seed(data, size);
#endif
}

string MegaApiImpl::userAttributeToString(int type)
{
    return User::attr2string((::mega::attr_t) type);
}

string MegaApiImpl::userAttributeToLongName(int type)
{
    return User::attr2longname((::mega::attr_t) type);
}

int MegaApiImpl::userAttributeFromString(const char *name)
{
    if (!name)
    {
        return MegaApi::USER_ATTR_UNKNOWN;
    }
    return User::string2attr(name);
}

char MegaApiImpl::userAttributeToScope(int type)
{
    char scope;

    switch(type)
    {
        case MegaApi::USER_ATTR_AVATAR:
        case MegaApi::USER_ATTR_ED25519_PUBLIC_KEY:
        case MegaApi::USER_ATTR_CU25519_PUBLIC_KEY:
        case MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY:
        case MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY:
            scope = '+';
            break;

        case MegaApi::USER_ATTR_FIRSTNAME:
        case MegaApi::USER_ATTR_LASTNAME:
            scope = '0';
            break;

        case MegaApi::USER_ATTR_AUTHRING:
        case MegaApi::USER_ATTR_LAST_INTERACTION:
        case MegaApi::USER_ATTR_KEYRING:
        case MegaApi::USER_ATTR_RICH_PREVIEWS:
        case MegaApi::USER_ATTR_GEOLOCATION:
        case MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER:
        case MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER:
        case MegaApi::USER_ATTR_ALIAS:
        case MegaApi::USER_ATTR_DEVICE_NAMES:
        case MegaApi::USER_ATTR_APPS_PREFS:
            scope = '*';
            break;

        case MegaApi::USER_ATTR_LANGUAGE:
        case MegaApi::USER_ATTR_PWD_REMINDER:
        case MegaApi::USER_ATTR_DISABLE_VERSIONS:
        case MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION:
        case MegaApi::USER_ATTR_LAST_PSA:
        case MegaApi::USER_ATTR_RUBBISH_TIME:
        case MegaApi::USER_ATTR_STORAGE_STATE:
        case MegaApi::USER_ATTR_PUSH_SETTINGS:
        case MegaApi::USER_ATTR_COOKIE_SETTINGS:
        case MegaApi::USER_ATTR_MY_BACKUPS_FOLDER:
            scope = '^';
            break;

        default:
            LOG_err << "Getting invalid scope";
            scope = 0;
            break;
    }

    return scope;
}

bool MegaApiImpl::serverSideRubbishBinAutopurgeEnabled()
{
    return client->ssrs_enabled;
}

bool MegaApiImpl::appleVoipPushEnabled()
{
    return client->aplvp_enabled;
}

bool MegaApiImpl::newLinkFormatEnabled()
{
    return client->mNewLinkFormat;
}

int MegaApiImpl::smsAllowedState()
{
    return (client->mSmsVerificationState != SMS_STATE_UNKNOWN) ? client->mSmsVerificationState : 0;
}

char* MegaApiImpl::smsVerifiedPhoneNumber()
{
    SdkMutexGuard g(sdkMutex);
    return client->mSmsVerifiedPhone.empty() ? NULL : MegaApi::strdup(client->mSmsVerifiedPhone.c_str());
}

bool MegaApiImpl::multiFactorAuthAvailable()
{
    return client->gmfa_enabled;
}

void MegaApiImpl::multiFactorAuthEnable(const char *pin, MegaRequestListener *listener)
{
    return multiFactorAuthEnableOrDisable(pin, true, listener);
}

void MegaApiImpl::multiFactorAuthDisable(const char *pin, MegaRequestListener *listener)
{
    return multiFactorAuthEnableOrDisable(pin, false, listener);
}

void MegaApiImpl::multiFactorAuthLogin(const char *email, const char *password, const char *pin, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener);
    request->setEmail(email);
    request->setPassword(password);
    request->setText(pin);

    request->performRequest = [this, request]()
    {
        return performRequest_login(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::multiFactorAuthChangePassword(const char *oldPassword, const char *newPassword, const char *pin, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_PW, listener);
    request->setPassword(oldPassword);
    request->setNewPassword(newPassword);
    request->setText(pin);

    request->performRequest = [this, request]()
    {
        return performRequest_changePw(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::multiFactorAuthChangeEmail(const char *email, const char *pin, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK, listener);
    request->setEmail(email);
    request->setText(pin);

    request->performRequest = [this, request]()
    {
        return performRequest_getChangeEmailLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::multiFactorAuthCancelAccount(const char *pin, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CANCEL_LINK, listener);
    request->setText(pin);

    request->performRequest = [this, request]()
    {
        return performRequest_getCancelLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fastLogin(const char* email, const char *stringHash, const char *base64pwkey, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener);
    request->setEmail(email);
    request->setPassword(stringHash);
    request->setPrivateKey(base64pwkey);

    request->performRequest = [this, request]()
    {
        return performRequest_login(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fastLogin(const char *session, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener);
    request->setSessionKey(session);

    request->performRequest = [this, request]()
    {
        return performRequest_login(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUserData(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener);

    request->performRequest = [this, request]()
    {
        return performRequest_getUserData(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUserData(MegaUser *user, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener);
    request->setFlag(true);
    if(user)
    {
        request->setEmail(user->getEmail());
    }

    request->performRequest = [this, request]()
    {
        return performRequest_getUserData(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUserData(const char *user, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener);
    request->setFlag(true);
    request->setEmail(user);

    request->performRequest = [this, request]()
    {
        return performRequest_getUserData(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::login(const char *login, const char *password, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener);
    request->setEmail(login);
    request->setPassword(password);

    request->performRequest = [this, request]()
    {
        return performRequest_login(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

char *MegaApiImpl::dumpSession()
{
    SdkMutexGuard g(sdkMutex);
    string session;
    if (client->dumpsession(session))
    {
        return MegaApi::strdup(Base64::btoa(session).c_str());
    }
    return nullptr;
}

char *MegaApiImpl::getSequenceNumber()
{
    SdkMutexGuard g(sdkMutex);
    return MegaApi::strdup(client->scsn.text());
}

char *MegaApiImpl::getAccountAuth()
{
    SdkMutexGuard g(sdkMutex);
    if (client->loggedin())
    {
        return MegaApi::strdup(Base64::btoa(client->sid).c_str());
    }
    return nullptr;
}

void MegaApiImpl::setAccountAuth(const char *auth)
{
    SdkMutexGuard g(sdkMutex);

    client->setFolderLinkAccountAuth(auth);
}

void MegaApiImpl::createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener);
    request->setEmail(email);
    request->setPassword(password);
    request->setName(firstname);
    request->setText(lastname);
    request->setNodeHandle(lastPublicHandle);
    request->setAccess(lastPublicHandleType);
    request->setTransferredBytes(lastAccessTimestamp);
    request->setParamType(MegaApi::CREATE_ACCOUNT);

    request->performRequest = [this, request]()
    {
        return performRequest_createAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::createEphemeralAccountPlusPlus(const char *firstname, const char *lastname, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener);
    request->setName(firstname);
    request->setText(lastname);
    request->setParamType(MegaApi::CREATE_EPLUSPLUS_ACCOUNT);

    request->performRequest = [this, request]()
    {
        return performRequest_createAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resumeCreateAccount(const char *sid, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener);
    request->setSessionKey(sid);
    request->setParamType(MegaApi::RESUME_ACCOUNT);

    request->performRequest = [this, request]()
    {
        return performRequest_createAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resumeCreateAccountEphemeralPlusPlus(const char *sid, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener);
    request->setSessionKey(sid);
    request->setParamType(MegaApi::RESUME_EPLUSPLUS_ACCOUNT);

    request->performRequest = [this, request]()
    {
        return performRequest_createAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::cancelCreateAccount(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener);
    request->setParamType(MegaApi::CANCEL_ACCOUNT);

    request->performRequest = [this, request]()
    {
        return performRequest_createAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::sendSignupLink(const char *email, const char *name, const char *password, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SIGNUP_LINK, listener);
    request->setEmail(email);
    request->setPassword(password);
    request->setName(name);

    request->performRequest = [this, request]()
    {
        return performRequest_sendSignupLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resendSignupLink(const char *email, const char *name, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SIGNUP_LINK, listener);
    request->setEmail(email);
    request->setName(name);

    request->performRequest = [this, request]()
    {
        return performRequest_sendSignupLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fastSendSignupLink(const char *email, const char *base64pwkey, const char *name, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SIGNUP_LINK, listener);
    request->setEmail(email);
    request->setPrivateKey(base64pwkey);
    request->setName(name);

    request->performRequest = [this, request]()
    {
        return performRequest_sendSignupLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::confirmAccount(const char* link, const char *password, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_ACCOUNT, listener);
    request->setLink(link);
    request->setPassword(password);

    request->performRequest = [this, request]()
    {
        return performRequest_confirmAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fastConfirmAccount(const char* link, const char *base64pwkey, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_ACCOUNT, listener);
    request->setLink(link);
    request->setPrivateKey(base64pwkey);

    request->performRequest = [this, request]()
    {
        return performRequest_confirmAccount(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::cancelAccount(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CANCEL_LINK, listener);

    request->performRequest = [this, request]()
    {
        return performRequest_getCancelLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::changeEmail(const char *email, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK, listener);
    request->setEmail(email);

    request->performRequest = [this, request]()
    {
        return performRequest_getChangeEmailLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaProxy *MegaApiImpl::getAutoProxySettings()
{
    MegaProxy *proxySettings = new MegaProxy;
    unique_ptr<Proxy> localProxySettings;

    {
        SdkMutexGuard g(sdkMutex);
        localProxySettings.reset(httpio->getautoproxy());
    }

    proxySettings->setProxyType(localProxySettings->getProxyType());
    if(localProxySettings->getProxyType() == Proxy::CUSTOM)
    {
        string localProxyURL = localProxySettings->getProxyURL();
        string proxyURL;
        LocalPath::local2path(&localProxyURL, &proxyURL, true);
        LOG_debug << "Autodetected proxy: " << proxyURL;
        proxySettings->setProxyURL(proxyURL.c_str());
    }

    return proxySettings;
}

void MegaApiImpl::loop()
{
#ifdef _WIN32
    httpio->lock();
#endif

    while(true)
    {
        int r;
        {
            SdkMutexGuard g(sdkMutex);
            r = client->preparewait();
        }

        if (!r)
        {
            r = client->dowait();

            {
                SdkMutexGuard g(sdkMutex);
                r |= client->checkevents();
            }
        }

        if (r & Waiter::NEEDEXEC)
        {
            WAIT_CLASS::bumpds();
            updateBackups();
            if (sendPendingTransfers(nullptr))
            {
                yield();
            }
            sendPendingRequests();
            sendPendingScRequest();
            if (threadExit)
            {
                break;
            }

            {
                SdkMutexGuard g(sdkMutex);
                client->exec();
            }
        }
    }

    SdkMutexGuard g(sdkMutex);
    delete client;
    client = nullptr;
}

bool MegaApiImpl::createLocalFolder(const char *path)
{
    if (!path)
    {
        return false;
    }

    string sPath(path);

    auto localpath = LocalPath::fromAbsolutePath(sPath);

    SdkMutexGuard g(sdkMutex);
    return client->fsaccess->mkdirlocal(localpath, false, true);
}

Error MegaApiImpl::createLocalFolder_unlocked(LocalPath & localPath,  FileSystemAccess& fsaccess)
{
    auto da = fsaccess.newfileaccess();
    if (!da->fopen(localPath, true, false, FSLogging::logOnError))
    {
        if (!fsaccess.mkdirlocal(localPath, false, false))
        {
            LOG_err << "Unable to create folder: " << localPath;
            return API_EWRITE;
        }
    }
    else if (da->type == FILENODE)
    {
        LOG_err << "Local file detected where there should be a folder: " << localPath;
        return API_EARGS;
    }
    else
    {
        LOG_debug << "Already existing folder detected: " << localPath;
        return API_EEXIST;
    }
    return API_OK;
}

void MegaApiImpl::moveNode(MegaNode *node, MegaNode *newParent, MegaRequestListener *listener)
{
    moveNode(node, newParent, nullptr, listener);
}

void MegaApiImpl::copyNode(MegaNode *node, MegaNode* target, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener);
    if (node)
    {
        request->setPublicNode(node, true);
        request->setNodeHandle(node->getHandle());
    }
    if(target) request->setParentHandle(target->getHandle());

    request->performRequest = [this, request]()
    {
        return performRequest_copy(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::copyNode(MegaNode *node, MegaNode *target, const char *newName, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener);
    if (node)
    {
        request->setPublicNode(node, true);
        request->setNodeHandle(node->getHandle());
    }
    if(target) request->setParentHandle(target->getHandle());
    request->setName(newName);

    request->performRequest = [this, request]()
    {
        return performRequest_copy(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::sendFileToUser(MegaNode *node, MegaUser *user, MegaRequestListener *listener)
{
    return sendFileToUser(node, user ? user->getEmail() : NULL, listener);
}

void MegaApiImpl::sendFileToUser(MegaNode *node, const char* email, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener);
    if (node)
    {
        request->setPublicNode(node, true);
        request->setNodeHandle(node->getHandle());
    }
    request->setEmail(email);

    request->performRequest = [this, request]()
    {
        return performRequest_copy(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::upgradeSecurity(MegaRequestListener* listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_UPGRADE_SECURITY, listener);
    request->performRequest = [this, request]()
    {
        client->upgradeSecurity([this, request](Error e) {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        });

        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setSecureFlag(bool enable)
{
    SdkMutexGuard m(sdkMutex);
    client->mKeyManager.setSecureFlag(enable);
}

void MegaApiImpl::setManualVerificationFlag(bool enable)
{
    SdkMutexGuard m(sdkMutex);
    client->mKeyManager.setManualVerificationFlag(enable);
}

void MegaApiImpl::openShareDialog(MegaNode* node, MegaRequestListener* listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_OPEN_SHARE_DIALOG, listener);
    if (node)
    {
        request->setNodeHandle(node->getHandle());
    }

    request->performRequest = [this, request]()
    {
        Node *node = client->nodebyhandle(request->getNodeHandle());

        client->openShareDialog(node, [this, request](Error e) {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        });

        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaShareList *MegaApiImpl::getUnverifiedInShares(int order)
{
    SdkMutexGuard lock(sdkMutex);

    node_vector nodes = client->getUnverifiedInShares();

    sortByComparatorFunction(nodes, order, *client);

    vector<Share*> shares;
    handle_vector handles;
    vector<byte> verified;

    for (Node *node : nodes)
    {
        shares.push_back(node->inshare);
        handles.push_back(node->nodehandle);
        verified.push_back(false);
    }

    MegaShareList *shareList = new MegaShareListPrivate(shares.data(), handles.data(), verified.data(), int(shares.size()));
    return shareList;
}

MegaShareList *MegaApiImpl::getUnverifiedOutShares(int order)
{
    SdkMutexGuard guard(sdkMutex);

    node_vector outshares = client->mNodeManager.getNodesWithOutShares();

    // Avoid duplicate nodes present in both outshares and pending shares
    node_vector pendingShares = client->mNodeManager.getNodesWithPendingOutShares();
    for (Node* pendingShare : pendingShares)
    {
        bool found = false;
        for (Node* node : outshares)
        {
            if (node->nodeHandle() == pendingShare->nodeHandle())
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            outshares.push_back(pendingShare);
        }
    }

    std::map<NodeHandle, std::set<Share *>> nodeSharesMap;
    for (const Node* n : outshares)
    {
        if (n->outshares)
        {
            for (auto &share : *n->outshares)
            {
                if (share.second->user && client->mKeyManager.isUnverifiedOutShare(n->nodehandle, toHandle(share.second->user->userhandle)))  // public links have no user
                {
                    nodeSharesMap[n->nodeHandle()].insert(share.second);
                }
            }
        }

        if (n->pendingshares)
        {
            for (auto &share : *n->pendingshares)
            {
                assert(share.second->pcr);
                if (share.second->pcr &&
                        client->mKeyManager.isUnverifiedOutShare(n->nodehandle, share.second->pcr->targetemail))
                {
                    nodeSharesMap[n->nodeHandle()].insert(share.second);
                }
            }
        }
    }

    MegaApiImpl::sortByComparatorFunction(outshares, order, *client);
    vector<handle> handles;
    vector<Share *> shares;
    vector<byte> verified;
    for (const Node *n: outshares)
    {
        for (const auto it : nodeSharesMap[n->nodeHandle()])
        {
            handles.push_back(n->nodehandle);
            shares.push_back(it);
            verified.push_back(false);
        }
    }

    return new MegaShareListPrivate(shares.data(), handles.data(), verified.data(), int(shares.size()));
}

void MegaApiImpl::share(MegaNode* node, MegaUser *user, int access, MegaRequestListener *listener)
{
    return share(node, user ? user->getEmail() : NULL, access, listener);
}

void MegaApiImpl::loginToFolder(const char* megaFolderLink, const char* authKey, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener);
    request->setLink(megaFolderLink);
    request->setPassword(authKey);
    request->setEmail("FOLDER");

    request->performRequest = [this, request]()
    {
        return performRequest_login(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::importFileLink(const char* megaFileLink, MegaNode *parent, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_IMPORT_LINK, listener);
    if(parent) request->setParentHandle(parent->getHandle());
    request->setLink(megaFileLink);

    request->performRequest = [this, request]()
    {
        return performRequest_importLink_getPublicNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::decryptPasswordProtectedLink(const char *link, const char *password, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_PASSWORD_LINK, listener);
    request->setLink(link);
    request->setPassword(password);

    request->performRequest = [this, request]()
    {
        return performRequest_passwordLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::encryptLinkWithPassword(const char *link, const char *password, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_PASSWORD_LINK, listener);
    request->setLink(link);
    request->setPassword(password);
    request->setFlag(true); // encrypt

    request->performRequest = [this, request]()
    {
        return performRequest_passwordLink(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPublicNode(const char* megaFileLink, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PUBLIC_NODE, listener);
    request->setLink(megaFileLink);

    request->performRequest = [this, request]()
    {
        return performRequest_importLink_getPublicNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

const char *MegaApiImpl::buildPublicLink(const char *publicHandle, const char *key, bool isFolder)
{
    handle ph = MegaApi::base64ToHandle(publicHandle);
    string link = client->publicLinkURL(client->mNewLinkFormat, isFolder ? TypeOfLink::FOLDER : TypeOfLink::FILE, ph, key);
    return MegaApi::strdup(link.c_str());
}

void MegaApiImpl::getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener)
{
    getNodeAttribute(node, GfxProc::THUMBNAIL, dstFilePath, listener);
}

void MegaApiImpl::cancelGetThumbnail(MegaNode* node, MegaRequestListener *listener)
{
    cancelGetNodeAttribute(node, GfxProc::THUMBNAIL, listener);
}

void MegaApiImpl::setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener)
{
    setNodeAttribute(node, GfxProc::THUMBNAIL, srcFilePath, INVALID_HANDLE, listener);
}

void MegaApiImpl::putThumbnail(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener)
{
    putNodeAttribute(bu, GfxProc::THUMBNAIL, srcFilePath, listener);
}

void MegaApiImpl::setThumbnailByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener)
{
    setNodeAttribute(node, GfxProc::THUMBNAIL, nullptr, attributehandle, listener);
}

void MegaApiImpl::getPreview(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener)
{
    getNodeAttribute(node, GfxProc::PREVIEW, dstFilePath, listener);
}

void MegaApiImpl::cancelGetPreview(MegaNode* node, MegaRequestListener *listener)
{
    cancelGetNodeAttribute(node, GfxProc::PREVIEW, listener);
}

void MegaApiImpl::setPreview(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener)
{
    setNodeAttribute(node, GfxProc::PREVIEW, srcFilePath, INVALID_HANDLE, listener);
}

void MegaApiImpl::putPreview(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener)
{
    putNodeAttribute(bu, GfxProc::PREVIEW, srcFilePath, listener);
}

void MegaApiImpl::setPreviewByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener)
{
    setNodeAttribute(node, GfxProc::PREVIEW, nullptr, attributehandle, listener);
}

void MegaApiImpl::getUserAvatar(MegaUser* user, const char *dstFilePath, MegaRequestListener *listener)
{
    const char *email = NULL;
    if (user)
    {
        email = user->getEmail();
    }
    getUserAttr(email, MegaApi::USER_ATTR_AVATAR, dstFilePath, 0, listener);
}

void MegaApiImpl::getUserAvatar(const char* email_or_handle, const char *dstFilePath, MegaRequestListener *listener)
{
    getUserAttr(email_or_handle, MegaApi::USER_ATTR_AVATAR, dstFilePath, 0, listener);
}

char *MegaApiImpl::getUserAvatarColor(MegaUser *user)
{
    return user ? MegaApiImpl::getAvatarColor((handle) user->getHandle()) : NULL;
}

char *MegaApiImpl::getUserAvatarColor(const char *userhandle)
{
    return userhandle ? MegaApiImpl::getAvatarColor(MegaApiImpl::base64ToUserHandle(userhandle)) : NULL;
}

char *MegaApiImpl::getUserAvatarSecondaryColor(MegaUser *user)
{
    return user ? MegaApiImpl::getAvatarSecondaryColor((handle) user->getHandle()) : NULL;
}

char *MegaApiImpl::getUserAvatarSecondaryColor(const char *userhandle)
{
    return userhandle ? MegaApiImpl::getAvatarSecondaryColor(MegaApiImpl::base64ToUserHandle(userhandle)) : NULL;
}

void MegaApiImpl::setAvatar(const char *dstFilePath, MegaRequestListener *listener)
{
    setUserAttr(MegaApi::USER_ATTR_AVATAR, dstFilePath, listener);
}

char* MegaApiImpl::getPrivateKey(int type)
{
    SdkMutexGuard g(sdkMutex);

    if (type < MegaApi::PRIVATE_KEY_ED25519 || type > MegaApi::PRIVATE_KEY_CU25519)
    {
        return nullptr;
    }

    User *u = client->ownuser();
    if (!u)
    {
        LOG_warn << "User is not defined yet";
        assert(false);
        return nullptr;
    }

    string privateKey;
    if (client->mKeyManager.generation())   // account has ^!keys already available
    {
        switch (type)
        {
        case MegaApi::PRIVATE_KEY_ED25519:
            privateKey = client->mKeyManager.privEd25519();
            break;
        case MegaApi::PRIVATE_KEY_CU25519:
            privateKey = client->mKeyManager.privCu25519();
            break;
        default:
            assert(false);
            return nullptr;
        }
    }
    else
    {
        const string *av = (u->isattrvalid(ATTR_KEYRING)) ? u->getattr(ATTR_KEYRING) : NULL;
        if (av)
        {
            unique_ptr<TLVstore> tlvRecords(TLVstore::containerToTLVrecords(av, &client->key));
            if (tlvRecords &&  (type == MegaApi::PRIVATE_KEY_ED25519 || type == MegaApi::PRIVATE_KEY_CU25519))
            {
                tlvRecords->get(type == MegaApi::PRIVATE_KEY_ED25519 ? EdDSA::TLV_KEY : ECDH::TLV_KEY, privateKey);
            }
            else
            {
                LOG_warn << "Failed to decrypt keyring while initialization or invalid key type";
                return nullptr;
            }
        }
        else
        {
            return nullptr;
        }
    }


    std::string privateKeyBase64 = Base64::btoa(privateKey);

    return MegaApi::strdup(privateKeyBase64.c_str());
}

void MegaApiImpl::getUserAttribute(MegaUser* user, int type, MegaRequestListener *listener)
{
    const char *email = NULL;
    if (user)
    {
        email = user->getEmail();
    }
    getUserAttr(email, type ? type : -1, NULL, 0, listener);
}

bool MegaApiImpl::testAllocation(unsigned allocCount, size_t allocSize)
{
    bool success = true;
    std::vector<char*> v;
    try
    {
        for (unsigned i = allocCount; i--; )
        {
            v.push_back(new char[allocSize]);
        }
    }
    catch (std::bad_alloc&)
    {
        LOG_warn << "MegaApi::testAllocation detected low memory: " << allocCount << " " << allocSize;
        success = false;
    }
    for (auto it : v)
    {
        delete[] it;
    }
    return success;
}

void MegaApiImpl::getUserAttribute(const char* email_or_handle, int type, MegaRequestListener *listener)
{
    getUserAttr(email_or_handle, type ? type : -1, NULL, 0, listener);
}

void MegaApiImpl::getChatUserAttribute(const char *email_or_handle, int type, const char *ph, MegaRequestListener *listener)
{
    getChatUserAttr(email_or_handle, type ? type : -1, NULL, ph, 0, listener);
}

void MegaApiImpl::setUserAttribute(int type, const char *value, MegaRequestListener *listener)
{
    setUserAttr(type ? type : -1, value, listener);
}

void MegaApiImpl::setUserAttribute(int type, const MegaStringMap *value, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);

    request->setMegaStringMap(value);
    request->setParamType(type);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getRubbishBinAutopurgePeriod(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_RUBBISH_TIME);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setRubbishBinAutopurgePeriod(int days, MegaRequestListener *listener)
{
    ostringstream oss;
    oss << days;
    string value = oss.str();
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    request->setText(value.data());
    request->setParamType(MegaApi::USER_ATTR_RUBBISH_TIME);
    request->setNumber(days);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

const char* MegaApiImpl::getDeviceId() const
{
    return MegaApi::strdup(client->getDeviceidHash().c_str());
}

void MegaApiImpl::getDeviceName(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setDeviceName(const char *deviceName, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    MegaStringMapPrivate stringMap;
    string buf = deviceName ? deviceName : "";
    stringMap.set(client->getDeviceidHash().c_str(), Base64::btoa(buf).c_str());
    request->setMegaStringMap(&stringMap);
    request->setName(deviceName);
    request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getDriveName(const char *pathToDrive, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES);
    request->setFlag(true); // external drive
    request->setFile(pathToDrive);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setDriveName(const char *pathToDrive, const char *driveName, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    request->setFile(pathToDrive);
    request->setName(driveName);
    request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES);
    request->setFlag(true); // external drive

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setCustomNodeAttribute(MegaNode *node, const char *attrName, const char *value, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setName(attrName);
    request->setText(value);
    request->setFlag(false);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setNodeS4(MegaNode *node, const char *value, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setParamType(MegaApi::NODE_ATTR_S4);
    request->setText(value);
    request->setFlag(true);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}


void MegaApiImpl::setNodeDuration(MegaNode *node, int secs, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setParamType(MegaApi::NODE_ATTR_DURATION);
    request->setNumber(secs);
    request->setFlag(true);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setNodeLabel(MegaNode *node, int label, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setParamType(MegaApi::NODE_ATTR_LABEL);
    request->setNumDetails(label);
    request->setFlag(true);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setNodeFavourite(MegaNode *node, bool fav, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setParamType(MegaApi::NODE_ATTR_FAV);
    request->setNumDetails(fav);
    request->setFlag(true);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setNodeSensitive(MegaNode* node, bool sensitive, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    if (node) request->setNodeHandle(node->getHandle());
    request->setParamType(MegaApi::NODE_ATTR_SEN);
    request->setNumDetails(sensitive);
    request->setFlag(true);     // is official attribute or not

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

bool MegaApiImpl::isSensitiveInherited(MegaNode* mnode)
{
    // there is no MegaNode::getParentNode() so the traversal must be here in MegaApi

    SdkMutexGuard g(sdkMutex);

    Node *node = client->nodeByHandle(NodeHandle().set6byte(mnode->getHandle()));
    if (node == nullptr)
        return false;
    return node->isSensitiveInherited();
}

static void encodeCoordinates(double latitude, double longitude, int& lat, int& lon)
{
    lat = int(latitude);
    if (latitude != MegaNode::INVALID_COORDINATE)
    {
        lat = int(((latitude + 90) / 180) * 0xFFFFFF);
    }

    lon = int(longitude);
    if (longitude != MegaNode::INVALID_COORDINATE)
    {
        lon = int((longitude == 180) ? 0 : ((longitude + 180) / 360) * 0x01000000);
    }
}

void MegaApiImpl::setNodeCoordinates(MegaNode *node, bool unshareable, double latitude, double longitude, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);

    if(node)
    {
        request->setNodeHandle(node->getHandle());
    }

    int lat, lon;
    encodeCoordinates(latitude, longitude, lat, lon);

    request->setParamType(MegaApi::NODE_ATTR_COORDINATES);
    request->setTransferTag(lat);
    request->setNumDetails(lon);
    request->setAccess(unshareable);
    request->setFlag(true);     // official attribute (otherwise it would go in the custom section)

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrNode(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::exportNode(MegaNode *node, int64_t expireTime, bool writable, bool megaHosted, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setNumber(expireTime);
    request->setAccess(1);
    request->setTransferTag(megaHosted ? 1 : 0);
    request->setFlag(writable);

    request->performRequest = [this, request]()
    {
        return performRequest_export(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::disableExport(MegaNode *node, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT, listener);
    if(node) request->setNodeHandle(node->getHandle());
    request->setAccess(0);

    request->performRequest = [this, request]()
    {
        return performRequest_export(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPricing(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PRICING, listener);

    request->performRequest = [this, request]()
    {
        return performRequest_enumeratequotaitems(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getRecommendedProLevel(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN, listener);
    
    request->performRequest = [this, request]() {
        if (client->loggedin() != FULLACCOUNT)
        {
            return API_EACCESS;
        }

        client->getaccountdetails(request->getAccountDetails(), true /*storage*/, false, false, false, false, false, -1);
        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPaymentId(handle productHandle, handle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PAYMENT_ID, listener);
    request->setNodeHandle(productHandle);
    request->setParentHandle(lastPublicHandle);
    request->setParamType(lastPublicHandleType);
    request->setTransferredBytes(lastAccessTimestamp);

    request->performRequest = [this, request]()
    {
        return performRequest_enumeratequotaitems(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::upgradeAccount(MegaHandle productHandle, int paymentMethod, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_UPGRADE_ACCOUNT, listener);
    request->setNodeHandle(productHandle);
    request->setNumber(paymentMethod);

    request->performRequest = [this, request]()
    {
        return performRequest_enumeratequotaitems(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

char *MegaApiImpl::exportMasterKey()
{
    SdkMutexGuard g(sdkMutex);
    char* buf = NULL;

    if(client->loggedin())
    {
        buf = new char[SymmCipher::KEYLENGTH * 4 / 3 + 4];
        Base64::btoa(client->key.key, SymmCipher::KEYLENGTH, buf);
    }

    return buf;
}

void MegaApiImpl::updatePwdReminderData(bool lastSuccess, bool lastSkipped, bool mkExported, bool dontShowAgain, bool lastLogin, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_PWD_REMINDER);
    int numDetails = 0;
    if (lastSuccess) numDetails |= 0x01;
    if (lastSkipped) numDetails |= 0x02;
    if (mkExported) numDetails |= 0x04;
    if (dontShowAgain) numDetails |= 0x08;
    if (lastLogin) numDetails |= 0x10;
    request->setNumDetails(numDetails);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::changePassword(const char *oldPassword, const char *newPassword, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_PW, listener);
    request->setPassword(oldPassword);
    request->setNewPassword(newPassword);

    request->performRequest = [this, request]()
    {
        return performRequest_changePw(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::logout(bool keepSyncConfigsFile, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT, listener);
    request->setFlag(true);
    request->setTransferTag(keepSyncConfigsFile);

    request->performRequest = [this, request]()
    {
        return performRequest_logout(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::localLogout(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT, listener);
    request->setFlag(false);

    request->performRequest = [this, request]()
    {
        return performRequest_logout(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::invalidateCache()
{
    SdkMutexGuard g(sdkMutex);
    nocache = true;
}

int MegaApiImpl::getPasswordStrength(const char *password)
{
    if (!password || strlen(password) < 8)
    {
        return MegaApi::PASSWORD_STRENGTH_VERYWEAK;
    }

    double entrophy = ZxcvbnMatch(password, NULL, NULL);
    if (entrophy > 75)
    {
        return MegaApi::PASSWORD_STRENGTH_STRONG;
    }
    if (entrophy > 50)
    {
        return MegaApi::PASSWORD_STRENGTH_GOOD;
    }
    if (entrophy > 40)
    {
        return MegaApi::PASSWORD_STRENGTH_MEDIUM;
    }
    if (entrophy > 15)
    {
        return MegaApi::PASSWORD_STRENGTH_WEAK;
    }
    return MegaApi::PASSWORD_STRENGTH_VERYWEAK;
}

bool MegaApiImpl::usingHttpsOnly()
{
    return client->usehttps;
}

void MegaApiImpl::setNodeAttribute(MegaNode *node, int type, const char *srcFilePath, MegaHandle attributehandle, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_FILE, listener);
    if (srcFilePath) request->setFile(srcFilePath);
    request->setNumber(srcFilePath ? INVALID_HANDLE : attributehandle);
    request->setParamType(type);
    request->setNodeHandle(node ? node->getHandle() : INVALID_HANDLE);
    request->setMegaBackgroundMediaUploadPtr(nullptr);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrFile(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::putNodeAttribute(MegaBackgroundMediaUpload* bu, int type, const char *srcFilePath, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_FILE, listener);
    request->setFile(srcFilePath);
    request->setParamType(type);
    request->setMegaBackgroundMediaUploadPtr(bu);
    request->setNumber(INVALID_HANDLE);
    request->setParentHandle(INVALID_HANDLE);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrFile(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUserAttr(const char *email_or_handle, int type, const char *dstFilePath, int number, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);

    if (type == MegaApi::USER_ATTR_AVATAR && dstFilePath)
    {
        string path(dstFilePath);

        int c = path[path.size()-1];
        if((c=='/') || (c == '\\'))
        {
            path.append(email_or_handle);
            path.push_back(static_cast<char>('0' + type));
            path.append(".jpg");
        }

        request->setFile(path.c_str());
    }

    request->setParamType(type);
    request->setNumber(number);
    if(email_or_handle)
    {
        request->setEmail(email_or_handle);
    }

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getChatUserAttr(const char *email_or_handle, int type, const char *dstFilePath, const char *ph, int number, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);

    if (type == MegaApi::USER_ATTR_AVATAR && dstFilePath)
    {
        string path(dstFilePath);

        int c = path[path.size()-1];
        if((c=='/') || (c == '\\'))
        {
            path.append(email_or_handle);
            path.push_back(static_cast<char>('0' + type));
            path.append(".jpg");
        }

        request->setFile(path.c_str());
    }

    request->setSessionKey(ph);
    request->setParamType(type);
    request->setNumber(number);
    if(email_or_handle)
    {
        request->setEmail(email_or_handle);
    }

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setUserAttr(int type, const char *value, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    if(type == MegaApi::USER_ATTR_AVATAR)
    {
        request->setFile(value);
    }
    else
    {
        request->setText(value);
    }

    request->setParamType(type);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

char *MegaApiImpl::getAvatarColor(handle userhandle)
{
    string colors[] = {
        "#55D2F0", // Blue
        "#BC2086", // Eggplant
        "#FFD200", // Gold
        "#5FDB00", // Green
        "#00BDB2", // Jade
        "#FFA700", // Orange
        "#E4269B", // Purple
        "#FF626C", // Red
        "#FF8989", // Salmon
        "#9AEAFF", // Sky
        "#00D5E2", // Teal
        "#FFEB00"  // Yellow
    };

    auto index = userhandle % (handle)(sizeof(colors)/sizeof(colors[0]));

    return MegaApi::strdup(colors[index].c_str());
}

char *MegaApiImpl::getAvatarSecondaryColor(handle userhandle)
{
    string colors[] = {
        "#2BA6DE", // Blue
        "#880E4F", // Eggplant
        "#FFA500", // Gold
        "#31B500", // Green
        "#00897B", // Jade
        "#FF6F00", // Orange
        "#C51162", // Purple
        "#FF333A", // Red
        "#FF5252", // Salmon
        "#61D2FF", // Sky
        "#00ACC1", // Teal
        "#FFD300"  // Yellow
    };

    auto index = userhandle % (handle)(sizeof(colors)/sizeof(colors[0]));

    return MegaApi::strdup(colors[index].c_str());
}

bool MegaApiImpl::isGlobalNotifiable()
{
    return !mPushSettings || (!mPushSettings->isGlobalDndEnabled() && isScheduleNotifiable());
}

bool MegaApiImpl::isScheduleNotifiable()
{
    if (!mTimezones)
    {
        LOG_warn << "Timezones are not available yet";
        return true;
    }

    if (!mPushSettings || !mPushSettings->isGlobalScheduleEnabled())
    {
        return true;
    }

    // find the configured timezone for notification's schedule and get the corresponding offset based on UTC
    int offsetTz = 0;
    bool tzFound = false;
    for (int i = 0; i < mTimezones->getNumTimeZones(); i++)
    {
        if (strcmp(mPushSettings->getGlobalScheduleTimezone(), mTimezones->getTimeZone(i)) == 0)
        {
            offsetTz = mTimezones->getTimeOffset(i);
            tzFound = true;
            break;
        }
    }

    if (!tzFound)
    {
        LOG_err << "Timezone not found: " << mPushSettings->getGlobalScheduleTimezone();
        assert(false);
        return true;    // better to generate the notification, in this case
    }

    // calculate the timestamp for time 00:00:00 of the current day in the configured timezone
    m_time_t now = m_time(NULL) + offsetTz;
    struct tm tmp;
    m_gmtime(now, &tmp);
    tmp.tm_hour = tmp.tm_min = tmp.tm_sec = 0;  // set the time to 00:00:00
    m_time_t dayStart = m_mktime_UTC(&tmp);

    // calculate the timestamps for the scheduled period
    int offsetStart = mPushSettings->getGlobalScheduleStart() * 60; // convert minutes into seconds
    int offsetEnd = mPushSettings->getGlobalScheduleEnd() * 60;
    m_time_t scheduleStart = dayStart + offsetStart;
    m_time_t scheduleEnd = dayStart + offsetEnd;

    if (offsetStart <= offsetEnd)
    {
        return now >= scheduleStart && now <= scheduleEnd;
    }
    else    // the scheduled period covers 2 days
    {
        assert(now >= dayStart && now <= dayStart + 24 * 60 * 60);
        return now <= scheduleEnd || now >= scheduleStart;
    }
}

// clears backups/requests/transfers notifying failure with EACCESS (and resets total up/down bytes)
void MegaApiImpl::abortPendingActions(error preverror)
{
    if (!preverror)
    {
        preverror = API_EACCESS;
    }

    // -- Backups --
    for (auto it : backupsMap)
    {
        delete it.second;
    }
    backupsMap.clear();

    // -- CS Requests in progress --
    deque<MegaRequestPrivate*> requests;
    for (auto requestPair : requestMap)
    {
        if (requestPair.second)
        {
            requests.push_back(requestPair.second);
        }
    }
    for (auto request : requests)
    {
        if (request->getType() == MegaRequest::TYPE_DELETE)
        {
            continue; // this request is deleted in MegaApiImpl dtor, its finish is the Impl destructor exiting.
        }
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(preverror));
    }
    requestMap.clear();

    // -- Transfers in progress --
    {
        // -- Transfers in the queue --
        // clear queued transfers, not yet started (and not added to cache)
        while (MegaTransferPrivate *transfer = transferQueue.pop())
        {
            fireOnTransferStart(transfer);
            transfer->setState(MegaTransfer::STATE_FAILED);
            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(preverror));
        }

        // clear existing transfers
        while (!transferMap.empty())
        {
            MegaTransferPrivate* transfer = transferMap.begin()->second;
            if (transfer->isRecursive())
            {
                // just remove it from the map.  When its last dependent transfer is deleted
                // then it will have its fireOnTransferFinish called also.
                transferMap.erase(transferMap.begin());
            }
            else
            {
                transfer->setState(MegaTransfer::STATE_FAILED);
                fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(preverror));
            }
        }
        assert(transferMap.empty());
        transferMap.clear();
    }

    resetTotalDownloads();
    resetTotalUploads();
}

bool MegaApiImpl::hasToForceUpload(const Node &node, const MegaTransferPrivate &transfer) const
{
    bool hasPreview = (Node::hasfileattribute(&node.fileattrstring, GfxProc::PREVIEW) != 0);
    bool hasThumbnail = (Node::hasfileattribute(&node.fileattrstring, GfxProc::THUMBNAIL) != 0);
    string name = node.displayname();
    LocalPath lp = LocalPath::fromRelativePath(name);
    bool isMedia = gfxAccess->isgfx(lp) || gfxAccess->isvideo(lp);
    bool canForceUpload = transfer.isForceNewUpload();
    bool isPdf = name.find(".pdf") != string::npos;

    return canForceUpload && (isMedia || isPdf) && !(hasPreview && hasThumbnail);
}

void MegaApiImpl::moveTransferUp(int transferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(true);
    request->setNumber(MegaTransfer::MOVE_TYPE_UP);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_moveTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::moveTransferDown(int transferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(true);
    request->setNumber(MegaTransfer::MOVE_TYPE_DOWN);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_moveTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::moveTransferToFirst(int transferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(true);
    request->setNumber(MegaTransfer::MOVE_TYPE_TOP);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_moveTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::moveTransferToLast(int transferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(true);
    request->setNumber(MegaTransfer::MOVE_TYPE_BOTTOM);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_moveTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::moveTransferBefore(int transferTag, int prevTransferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(false);
    request->setNumber(prevTransferTag);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_moveTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

bool MegaApiImpl::areTransfersPaused(int direction)
{
    if(direction != MegaTransfer::TYPE_DOWNLOAD && direction != MegaTransfer::TYPE_UPLOAD)
    {
        return false;
    }

    bool result;
    SdkMutexGuard g(sdkMutex);
    if(direction == MegaTransfer::TYPE_DOWNLOAD)
    {
        result = client->xferpaused[GET];
    }
    else
    {
        result = client->xferpaused[PUT];
    }
    return result;
}

//-1 -> AUTO, 0 -> NONE, >0 -> b/s
void MegaApiImpl::setUploadLimit(int bpslimit)
{
    client->putmbpscap = bpslimit;
}

void MegaApiImpl::setDownloadMethod(int method)
{
    switch(method)
    {
        case MegaApi::TRANSFER_METHOD_NORMAL:
            client->usealtdownport = false;
            client->autodownport = false;
            break;
        case MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT:
            client->usealtdownport = true;
            client->autodownport = false;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO:
            client->autodownport = true;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO_NORMAL:
            client->usealtdownport = false;
            client->autodownport = true;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE:
            client->usealtdownport = true;
            client->autodownport = true;
            break;
        default:
            break;
    }
}

void MegaApiImpl::setUploadMethod(int method)
{
    switch(method)
    {
        case MegaApi::TRANSFER_METHOD_NORMAL:
            client->usealtupport = false;
            client->autoupport = false;
            break;
        case MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT:
            client->usealtupport = true;
            client->autoupport = false;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO:
            client->autoupport = true;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO_NORMAL:
            client->usealtupport = false;
            client->autoupport = true;
            break;
        case MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE:
            client->usealtupport = true;
            client->autoupport = true;
            break;
        default:
            break;
    }
}

bool MegaApiImpl::setMaxDownloadSpeed(m_off_t bpslimit)
{
    SdkMutexGuard g(sdkMutex);
    return client->setmaxdownloadspeed(bpslimit);
}

bool MegaApiImpl::setMaxUploadSpeed(m_off_t bpslimit)
{
    SdkMutexGuard g(sdkMutex);
    return client->setmaxuploadspeed(bpslimit);
}

int MegaApiImpl::getMaxDownloadSpeed()
{
    return int(client->getmaxdownloadspeed());
}

int MegaApiImpl::getMaxUploadSpeed()
{
    return int(client->getmaxuploadspeed());
}

int MegaApiImpl::getCurrentDownloadSpeed()
{
    return int(httpio->downloadSpeed);
}

int MegaApiImpl::getCurrentUploadSpeed()
{
    return int(httpio->uploadSpeed);
}

int MegaApiImpl::getCurrentSpeed(int type)
{
    switch (type)
    {
    case MegaTransfer::TYPE_DOWNLOAD:
        return int(httpio->downloadSpeed);
    case MegaTransfer::TYPE_UPLOAD:
        return int(httpio->uploadSpeed);
    default:
        return 0;
    }
}

int MegaApiImpl::getDownloadMethod()
{
    if (client->autodownport)
    {
        if(client->usealtdownport)
        {
            return MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE;
        }
        else
        {
            return MegaApi::TRANSFER_METHOD_AUTO_NORMAL;
        }
    }

    if (client->usealtdownport)
    {
        return MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT;
    }

    return MegaApi::TRANSFER_METHOD_NORMAL;
}

int MegaApiImpl::getUploadMethod()
{
    if (client->autoupport)
    {
        if(client->usealtupport)
        {
            return MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE;
        }
        else
        {
            return MegaApi::TRANSFER_METHOD_AUTO_NORMAL;
        }
    }

    if (client->usealtupport)
    {
        return MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT;
    }

    return MegaApi::TRANSFER_METHOD_NORMAL;
}

MegaTransferData *MegaApiImpl::getTransferData(MegaTransferListener *listener)
{
    SdkMutexGuard g(sdkMutex);
    MegaTransferData *data = new MegaTransferDataPrivate(&client->transferlist, notificationNumber);
    if (listener)
    {
        transferListeners.insert(listener);
    }
    return data;
}

MegaTransfer *MegaApiImpl::getFirstTransfer(int type)
{
    if (type != MegaTransfer::TYPE_DOWNLOAD && type != MegaTransfer::TYPE_UPLOAD)
    {
        return NULL;
    }

    MegaTransfer* transfer = NULL;
    SdkMutexGuard g(sdkMutex);
    auto it = client->transferlist.begin((direction_t)type);
    if (it != client->transferlist.end((direction_t)type))
    {
         Transfer *t = (*it);
         if (t->files.size())
         {
             MegaTransferPrivate *megaTransfer = getMegaTransferPrivate(t->files.front()->tag);
             if (megaTransfer)
             {
                 transfer = megaTransfer->copy();
             }
         }
    }
    return transfer;
}

void MegaApiImpl::notifyTransfer(int transferTag, MegaTransferListener *listener)
{
    SdkMutexGuard g(sdkMutex);
    MegaTransferPrivate *t = getMegaTransferPrivate(transferTag);
    if (!t)
    {
        return;
    }

    fireOnTransferUpdate(t);
    if (listener)
    {
        listener->onTransferUpdate(api, t);
    }
}

MegaTransferList *MegaApiImpl::getTransfers()
{
    SdkMutexGuard g(sdkMutex);
    vector<MegaTransfer *> transfers;
    for (int d = GET; d == GET || d == PUT; d += PUT - GET)
    {
        auto end = client->transferlist.end((direction_t)d);
        for (auto it = client->transferlist.begin((direction_t)d); it != end; it++)
        {
            Transfer *t = (*it);
            for (file_list::iterator it2 = t->files.begin(); it2 != t->files.end(); it2++)
            {
                MegaTransferPrivate* transfer = getMegaTransferPrivate((*it2)->tag);
                if (transfer)
                {
                    transfers.push_back(transfer);
                }
            }
        }
    }
    return new MegaTransferListPrivate(transfers.data(), int(transfers.size()));
}

MegaTransferList *MegaApiImpl::getStreamingTransfers()
{
    SdkMutexGuard g(sdkMutex);

    vector<MegaTransfer *> transfers;
    for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++)
    {
        MegaTransferPrivate *transfer = it->second;
        if (transfer->isStreamingTransfer())
        {
            transfers.push_back(transfer);
        }
    }
    return new MegaTransferListPrivate(transfers.data(), int(transfers.size()));
}

MegaTransfer *MegaApiImpl::getTransferByTag(int transferTag)
{
    SdkMutexGuard g(sdkMutex);
    MegaTransfer* value = getMegaTransferPrivate(transferTag);
    if (value)
    {
        value = value->copy();
    }
    return value;
}

MegaTransferList *MegaApiImpl::getTransfers(int type)
{
    if(type != MegaTransfer::TYPE_DOWNLOAD && type != MegaTransfer::TYPE_UPLOAD)
    {
        return new MegaTransferListPrivate();
    }

    SdkMutexGuard g(sdkMutex);
    vector<MegaTransfer *> transfers;
    auto end = client->transferlist.end((direction_t)type);
    for (auto it = client->transferlist.begin((direction_t)type); it != end; it++)
    {
        Transfer *t = (*it);
        for (file_list::iterator it2 = t->files.begin(); it2 != t->files.end(); it2++)
        {
            MegaTransferPrivate* transfer = getMegaTransferPrivate((*it2)->tag);
            if (transfer)
            {
                transfers.push_back(transfer);
            }
        }
    }
    return new MegaTransferListPrivate(transfers.data(), int(transfers.size()));
}

MegaTransferList *MegaApiImpl::getChildTransfers(int transferTag)
{
    SdkMutexGuard g(sdkMutex);

    MegaTransfer *transfer = getMegaTransferPrivate(transferTag);
    if (!transfer)
    {
        return new MegaTransferListPrivate();
    }

    if (!transfer->isFolderTransfer())
    {
        return new MegaTransferListPrivate();
    }

    vector<MegaTransfer *> transfers;
    for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++)
    {
        MegaTransferPrivate *t = it->second;
        if (t->getFolderTransferTag() == transferTag)
        {
            transfers.push_back(transfer);
        }
    }

    return new MegaTransferListPrivate(transfers.data(), int(transfers.size()));
}

MegaTransferList *MegaApiImpl::getTansfersByFolderTag(int folderTransferTag)
{
    SdkMutexGuard g(sdkMutex);
    vector<MegaTransfer *> transfers;
    for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++)
    {
        MegaTransferPrivate *t = it->second;
        if (t->getFolderTransferTag() == folderTransferTag)
        {
            transfers.push_back(t);
        }
    }

    return new MegaTransferListPrivate(transfers.data(), int(transfers.size()));
}

MegaStringList *MegaApiImpl::getBackupFolders(int backuptag)
{
    map<int64_t, string> backupTimesPaths;
    {
        SdkMutexGuard g(sdkMutex);

        map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(backuptag) ;
        if (itr == backupsMap.end())
        {
            LOG_err << "Failed to find backup with tag " << backuptag;
            return NULL;
        }

        MegaScheduledCopyController *mbc = itr->second;

        MegaNode * parentNode = getNodeByHandle(mbc->getMegaHandle());
        if (parentNode)
        {
            MegaNodeList* children = getChildren(parentNode, MegaApi::ORDER_NONE);
            if (children)
            {
                for (int i = 0; i < children->size(); i++)
                {
                    MegaNode *childNode = children->get(i);
                    string childname = childNode->getName();
                    if (mbc->isBackup(childname, mbc->getBackupName()) )
                    {
                        int64_t timeofbackup = mbc->getTimeOfBackup(childname);
                        if (timeofbackup)
                        {
                            backupTimesPaths[timeofbackup]=getNodePath(childNode);
                        }
                        else
                        {
                            LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded.";
                        }
                    }
                }

                delete children;
            }
            delete parentNode;
        }
    }

    string_vector listofpaths;

    for(map<int64_t, string>::iterator itr = backupTimesPaths.begin(); itr != backupTimesPaths.end(); itr++)
    {
        listofpaths.push_back(itr->second);
    }
    return new MegaStringListPrivate(move(listofpaths));
}

void MegaApiImpl::abortCurrentScheduledCopy(int tag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_ABORT_CURRENT_SCHEDULED_COPY, listener);
    request->setNumber(tag);

    request->performRequest = [this, request]()
    {
        return processAbortBackupRequest(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaTransferPrivate* MegaApiImpl::createUploadTransfer(bool startFirst, const char *localPath, MegaNode *parent, const char *fileName, const char *targetUser, int64_t mtime, int folderTransferTag, bool isBackup, const char *appData,
    bool isSourceFileTemporary, bool forceNewUpload, FileSystemType fsType, CancelToken cancelToken, MegaTransferListener *listener, const FileFingerprint* preFingerprintedFile)
{
    if (fsType == FS_UNKNOWN && localPath)
    {
        fsType = fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(localPath));
    }

    MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_UPLOAD, listener);
    if(localPath)
    {
        transfer->setPath(localPath);
    }

    if (parent)
    {
        transfer->setParentHandle(parent->getHandle());
    }

    if (targetUser)
    {
        transfer->setParentPath(targetUser);
    }

    transfer->setMaxRetries(maxRetries);
    transfer->setAppData(appData);
    transfer->setSourceFileTemporary(isSourceFileTemporary);
    transfer->setStartFirst(startFirst);
    transfer->setCancelToken(cancelToken);
    transfer->setBackupTransfer(isBackup);

    if (fileName || transfer->getFileName())
    {
        string auxName = fileName ? fileName : transfer->getFileName();

        client->fsaccess->unescapefsincompatible(&auxName);
        transfer->setFileName(auxName.c_str());
    }

    transfer->setTime(mtime);

    if (preFingerprintedFile)
    {
        transfer->fingerprint_error = preFingerprintedFile->isvalid ? API_OK : API_EREAD;
        transfer->fingerprint_filetype = FILENODE;
        transfer->fingerprint_onDisk = *preFingerprintedFile;
    }
    else // if no fingerprint provided, calculate it, to avoid extra workload (and reduce mutex locking time) to SDK thread at sendPendingTransfers
    {
        lock_guard<mutex> g(fingerprintingFsAccessMutex);
        auto fa = fingerprintingFsAccess.newfileaccess();

        if (!localPath ||
            !fa->fopen(LocalPath::fromAbsolutePath(localPath), true, false, FSLogging::logOnError))
        {
            transfer->fingerprint_error = API_EREAD;
        }
        else
        {
            transfer->fingerprint_filetype = fa->type; // => [FILENODE | FOLDERNODE]

            bool uploadToInbox = ISUNDEF(transfer->getParentHandle()) && transfer->getParentPath() && (strchr(transfer->getParentPath(), '@') || (strlen(transfer->getParentPath()) == 11));

            if (fa->type == FOLDERNODE && uploadToInbox)
            {
                //Folder upload is not possible when sending to Inbox:
                //API won't return handle for folder creation, and even if that was the case
                //doing a put nodes with t = userhandle & the corresponding handle as parent p, API returns EACCESS
                transfer->fingerprint_error = API_EREAD;
            }
            else
            {
                transfer->fingerprint_error = API_OK;
            }

            if (fa->type == FILENODE) // just file nodes have a valid fingerprint
            {
                transfer->fingerprint_onDisk.genfingerprint(fa.get());
            }
        }
    }

    if(folderTransferTag)
    {
        transfer->setFolderTransferTag(folderTransferTag);
    }

    transfer->setForceNewUpload(forceNewUpload);
    return transfer;
}

void MegaApiImpl::startUpload(bool startFirst, const char* localPath, MegaNode* parent, const char* fileName, const char* targetUser, int64_t mtime, int folderTransferTag, bool isBackup, const char* appData, bool isSourceFileTemporary, bool forceNewUpload, FileSystemType fsType, CancelToken cancelToken, MegaTransferListener* listener)
{
    MegaTransferPrivate* transfer = createUploadTransfer(startFirst, localPath, parent, fileName, targetUser, mtime, folderTransferTag, isBackup, appData, isSourceFileTemporary, forceNewUpload, fsType, cancelToken, listener);
    transferQueue.push(transfer);
    waiter->notify();
}

void MegaApiImpl::startUploadForSupport(const char* localPath, bool isSourceFileTemporary, FileSystemType fsType, MegaTransferListener* listener)
{
    MegaTransferPrivate* transfer = createUploadTransfer(true, localPath, nullptr, nullptr, MegaClient::SUPPORT_USER_HANDLE.c_str(), MegaApi::INVALID_CUSTOM_MOD_TIME, 0, false, nullptr, isSourceFileTemporary, false, fsType, CancelToken(), listener);
    transferQueue.push(transfer);
    waiter->notify();
}

void MegaApiImpl::startDownload (bool startFirst, MegaNode *node, const char* localPath, const char *customName, int folderTransferTag, const char *appData, CancelToken cancelToken, MegaTransferListener *listener)
{
    FileSystemType fsType = fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(localPath));
    MegaTransferPrivate *transfer = createDownloadTransfer(startFirst, node, localPath, customName, folderTransferTag, appData, cancelToken, listener, fsType);
    transferQueue.push(transfer);
    waiter->notify();
}

MegaTransferPrivate* MegaApiImpl::createDownloadTransfer(bool startFirst, MegaNode *node, const char* localPath, const char *customName, int folderTransferTag, const char *appData, CancelToken cancelToken, MegaTransferListener *listener, FileSystemType fsType)
{
    MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_DOWNLOAD, listener);

    if(localPath)
    {
        int c = localPath[strlen(localPath)-1];
        if (c == LocalPath::localPathSeparator_utf8)
        {
            transfer->setParentPath(localPath);
        }
        else
        {
            transfer->setPath(localPath);
        }
    }

    if (node)
    {
        transfer->setNodeHandle(node->getHandle());
        if (node->isPublic() || node->isForeign())
        {
            transfer->setPublicNode(node, true);
        }
    }

    transfer->setMaxRetries(maxRetries);
    transfer->setAppData(appData);
    transfer->setStartFirst(startFirst);
    transfer->setCancelToken(cancelToken);

    if (customName)
    {
       // set custom file/folder name if exists and escape incompatible characters for destination FS
       std::string auxName = customName;
       client->fsaccess->escapefsincompatible(&auxName, fsType);
       transfer->setFileName(auxName.c_str());
    }

    if (folderTransferTag)
    {
        transfer->setFolderTransferTag(folderTransferTag);
    }

    return transfer;
}

void MegaApiImpl::cancelTransfer(MegaTransfer *t, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFER, listener);
    if(t)
    {
        request->setTransferTag(t->getTag());
    }

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_cancelTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::cancelTransferByTag(int transferTag, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFER, listener);
    request->setTransferTag(transferTag);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
    {
        return performTransferRequest_cancelTransfer(request, committer);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::startStreaming(MegaNode* node, m_off_t startPos, m_off_t size, MegaTransferListener *listener)
{
    MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_DOWNLOAD, listener);

    if (node)
    {
        transfer->setNodeHandle(node->getHandle());
        if (node->isPublic() || node->isForeign())
        {
            transfer->setPublicNode(node, true);
        }
    }

    transfer->setStreamingTransfer(true);
    transfer->setStartPos(startPos);
    transfer->setEndPos(startPos + size - 1);
    transfer->setMaxRetries(maxRetries);
    transferQueue.push(transfer);
    waiter->notify();
}

void MegaApiImpl::setStreamingMinimumRate(int bytesPerSecond)
{
    SdkMutexGuard g(sdkMutex);
    client->minstreamingrate = bytesPerSecond;
}

void MegaApiImpl::retryTransfer(MegaTransfer *transfer, MegaTransferListener *listener)
{
    MegaTransferPrivate *t = dynamic_cast<MegaTransferPrivate*>(transfer);
    if (!t || (t->getType() != MegaTransfer::TYPE_DOWNLOAD && t->getType() != MegaTransfer::TYPE_UPLOAD))
    {
        return;
    }

    int type = t->getType();
    if (type == MegaTransfer::TYPE_DOWNLOAD)
    {
        MegaNode *node = t->getPublicMegaNode();
        if (!node)
        {
            node = getNodeByHandle(t->getNodeHandle());
        }
        this->startDownload(true, node, t->getPath(), NULL, 0, t->getAppData(), CancelToken(), listener);
        delete node;
    }
    else
    {
        MegaNode *parent = getNodeByHandle(t->getParentHandle());
        this->startUpload (true, t->getPath(), parent, t->getFileName(), nullptr,
                    t->getTime(), 0, t->isBackupTransfer(), t->getAppData(), t->isSourceFileTemporary(),
                    t->isForceNewUpload(), client->fsaccess->getlocalfstype(LocalPath::fromAbsolutePath(t->getPath())),
                    t->accessCancelToken(), listener);

        delete parent;
    }
}

#ifdef ENABLE_SYNC

//Move local files inside synced folders to the "Rubbish" folder.
bool MegaApiImpl::moveToLocalDebris(const char *path)
{
    if (!path)
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);

    string utf8path = path;

    auto localpath = LocalPath::fromAbsolutePath(utf8path);

    Sync *sync = NULL;
    client->syncs.forEachRunningSync([&](Sync* s) {

        if (s->localroot->getLocalname().isContainingPathOf(localpath))
        {
            sync = s;
        }
    });

    if(!sync)
    {
        return false;
    }

    bool result = sync->movetolocaldebris(localpath);

    return result;
}

int MegaApiImpl::syncPathState(string* pathParam)
{
    LocalPath localpath = LocalPath::fromPlatformEncodedAbsolute(*pathParam);

    // Avoid blocking on the mutex for a long time, as we may be blocking windows explorer (or another platform's equivalent) from opening or displaying a window, unrelated to sync folders
    // We try to lock the SDK mutex.  If we can't get it in 10ms then we return a simple default, and subsequent requests try to lock the mutex but don't wait.
    SdkMutexGuard g(sdkMutex, std::defer_lock);
    if ((!syncPathStateLockTimeout && !g.try_lock_for(std::chrono::milliseconds(10))) ||
        (syncPathStateLockTimeout && !g.try_lock()))
    {
        syncPathStateLockTimeout = true;
        return MegaApi::STATE_IGNORED;
    }

    // once we do manage to lock, return to normal operation.
    syncPathStateLockTimeout = false;

    int state = MegaApi::STATE_NONE;
    if (client->syncs.mSyncVecIsEmpty)
    {
        return state;
    }

    client->syncs.forEachRunningSync_shortcircuit([&](Sync* sync) {

        if (!sync->localroot->getLocalname().isContainingPathOf(localpath))
        {
            return true;
        }

        if (sync->localdebris.isContainingPathOf(localpath))
        {
            state = MegaApi::STATE_IGNORED;
            return false;
        }

        if (localpath == sync->localroot->getLocalname())
        {
            state = sync->localroot->ts;
            return false;
        }
        else
        {
            LocalNode* l = sync->localnodebypath(NULL, localpath);
            if (l)
            {
                state = l->ts;
            }
            else
            {
                string name = localpath.leafName().toName(*fsAccess);
                if (is_syncable(sync, name.c_str(), localpath))
                {
                    auto fa = fsAccess->newfileaccess();
                    if (fa->fopen(localpath, false, false, FSLogging::logOnError) && (fa->type == FOLDERNODE || is_syncable(fa->size)))
                    {
                        state = MegaApi::STATE_PENDING;
                    }
                    else
                    {
                        state = MegaApi::STATE_IGNORED;
                    }
                }
                else
                {
                    state = MegaApi::STATE_IGNORED;
                }
            }
            return false;
        }
    });
    return state;
}


MegaNode *MegaApiImpl::getSyncedNode(const LocalPath& path)
{
    SdkMutexGuard g(sdkMutex);

    MegaNode *node = NULL;
    client->syncs.forEachRunningSync([&](Sync* sync) {

        if (!node)
        {
            if (path == sync->localroot->getLocalname())
            {
                node = MegaNodePrivate::fromNode(sync->localroot->node);
            }
            else
            {
                LocalNode * localNode = sync->localnodebypath(NULL, path);
                if(localNode) node = MegaNodePrivate::fromNode(localNode->node);
            }
        }
    });
    return node;
}

const char* MegaApiImpl::exportSyncConfigs()
{
    string configs;

    {
        SdkMutexGuard guard(sdkMutex);
        configs = client->syncs.exportSyncConfigs();
    }

    return MegaApi::strdup(configs.c_str());
}

void MegaApiImpl::setSyncRunState(MegaHandle backupId, MegaSync::SyncRunningState targetState, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_SYNC_RUNSTATE, listener);
    request->setParentHandle(backupId);

    request->performRequest = [this, request, targetState](){

        auto backupId = request->getParentHandle();

        switch (targetState)
        {
            case MegaSync::RUNSTATE_RUNNING:
            case MegaSync::RUNSTATE_PAUSED:
            {
                client->syncs.enableSyncByBackupId(backupId, targetState == MegaSync::RUNSTATE_PAUSED, false, true, true, [this, request](error err, SyncError serr, handle)
                    {
                        request->setNumDetails(serr);
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(err, serr), true);
                    }, false, "");

                return API_OK;
            }
            case MegaSync::RUNSTATE_SUSPENDED:
            case MegaSync::RUNSTATE_DISABLED:
            {
                // until SRW, only disabled is supported
                client->syncs.disableSyncByBackupId(
                    backupId,
                    false,
                    NO_SYNC_ERROR,
                    false,
                    [this, request](){
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK), true);
                    });

                return API_OK;
            }
            case MegaSync::RUNSTATE_PENDING:
            case MegaSync::RUNSTATE_LOADING:
            default:
                assert(false);
                return API_EARGS;
        }
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaSyncList *MegaApiImpl::getSyncs()
{
    SdkMutexGuard g(sdkMutex);
    vector<MegaSyncPrivate*> vMegaSyncs;

    // syncs has its own thread safety
    for (auto& config : client->syncs.getConfigs(false))
    {
        vMegaSyncs.push_back(new MegaSyncPrivate(config, client));
    }

    MegaSyncList *syncList = new MegaSyncListPrivate(vMegaSyncs.data(), int(vMegaSyncs.size()));

    for (auto p : vMegaSyncs) delete p;

    return syncList;
}

bool MegaApiImpl::isSynced(MegaNode *n)
{
    if(!n) return false;
    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(n->getHandle());
    if(!node)
    {
        return false;
    }

    return node->localnode!=NULL;
}

void MegaApiImpl::setExcludedNames(vector<string> *excludedNames)
{
    SdkMutexGuard g(sdkMutex);
    if (!excludedNames)
    {
        this->excludedNames.clear();
        return;
    }

    this->excludedNames.clear();
    for (unsigned int i = 0; i < excludedNames->size(); i++)
    {
        string name = excludedNames->at(i);
        LocalPath::utf8_normalize(&name);
        if (name.size())
        {
            this->excludedNames.push_back(name);
            LOG_debug << "Excluded name: " << name;
        }
        else
        {
            LOG_warn << "Invalid excluded name: " << excludedNames->at(i);
        }
    }
}

void MegaApiImpl::setExcludedPaths(vector<string> *excludedPaths)
{
    SdkMutexGuard g(sdkMutex);
    if (!excludedPaths)
    {
        this->excludedPaths.clear();
        return;
    }

    this->excludedPaths.clear();
    for (unsigned int i = 0; i < excludedPaths->size(); i++)
    {
        string path = excludedPaths->at(i);
        LocalPath::utf8_normalize(&path);
        if (path.size())
        {
            this->excludedPaths.push_back(path);
            LOG_debug << "Excluded path: " << path;
        }
        else
        {
            LOG_warn << "Invalid excluded path: " << excludedPaths->at(i);
        }
    }
}

void MegaApiImpl::setExclusionLowerSizeLimit(long long limit)
{
    syncLowerSizeLimit = limit;
}

void MegaApiImpl::setExclusionUpperSizeLimit(long long limit)
{
    syncUpperSizeLimit = limit;
}

string MegaApiImpl::getLocalPath(MegaNode *n)
{
    if(!n) return string();
    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(n->getHandle());
    if(!node || !node->localnode)
    {
        return string();
    }

    string result = node->localnode->getLocalPath().platformEncoded();
    result.append("", 1);

    return result;
}

long long MegaApiImpl::getNumLocalNodes()
{
    return client->totalLocalNodes;
}

bool MegaApiImpl::isSyncable(const char *path, long long size)
{
    if (!path)
    {
        return false;
    }

    string utf8path = path;

    LocalNode *parent = NULL;
    auto localpath = LocalPath::fromAbsolutePath(utf8path);

    bool result = false;
    SdkMutexGuard g(sdkMutex);
    if (size >= 0)
    {
        if (!is_syncable(size))
        {
            return false;
        }
    }

    client->syncs.forEachRunningSync([&](Sync* sync) {

        if (sync->localnodebypath(NULL, localpath, &parent) || parent)
        {
            if (!sync->localdebris.isContainingPathOf(localpath))
            {
                auto temp = localpath.leafName();
                auto name = temp.toName(*fsAccess);
                result = is_syncable(sync, name.c_str(), localpath);
            }
        }
    });
    return result;
}

bool MegaApiImpl::isInsideSync(MegaNode *node)
{
    if (!node)
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    Node *n = client->nodebyhandle(node->getHandle());
    while (n)
    {
        if (n->localnode)
        {
            return true;
        }
        n = n->parent;
    }
    return false;
}

#endif

void MegaApiImpl::moveOrRemoveDeconfiguredBackupNodes(MegaHandle deconfiguredBackupRoot, MegaHandle backupDestination, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_OLD_BACKUP_NODES, listener);
    request->setNodeHandle(backupDestination);

    request->performRequest = [deconfiguredBackupRoot, backupDestination, this, request]() {

        Node* n1 = client->nodebyhandle(deconfiguredBackupRoot);
        Node* n2 = client->nodebyhandle(backupDestination);

        if (!n1)
        {
            LOG_debug << "Backup root node not found";
            return API_ENOENT;
        }
        LOG_debug << "About to move/remove backup nodes from " << n1->displaypath();

        if (!n1->parent ||   // device
            !n1->parent->parent ||  // my backups node
            !n1->parent->parent->parent ||  // Vault root
            n1->parent->parent->parent->nodehandle != client->mNodeManager.getRootNodeVault().as8byte())
        {
            LOG_debug << "Node not in the right place to be a backup root";
            return API_EARGS;
        }

        if (n2 &&
            n2->firstancestor()->nodeHandle() != client->mNodeManager.getRootNodeFiles().as8byte() &&
            n2->firstancestor()->nodeHandle() != client->mNodeManager.getRootNodeRubbish().as8byte())
        {
            LOG_debug << "Destination node not in the main files root, or in rubbish: " << n2->displaypath();
            return API_EARGS;
        }

        NodeHandle root = NodeHandle().set6byte(deconfiguredBackupRoot);
        NodeHandle destination = NodeHandle().set6byte(backupDestination);

        client->unlinkOrMoveBackupNodes(root, destination, [request, this](Error e) {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            });
        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

int MegaApiImpl::getNumPendingUploads()
{
    return pendingUploads;
}

int MegaApiImpl::getNumPendingDownloads()
{
    return pendingDownloads;
}

int MegaApiImpl::getTotalUploads()
{
    return totalUploads;
}

int MegaApiImpl::getTotalDownloads()
{
    return totalDownloads;
}

void MegaApiImpl::resetTotalDownloads()
{
    totalDownloads = 0;
    totalDownloadBytes = 0;
    totalDownloadedBytes = 0;
}

void MegaApiImpl::resetTotalUploads()
{
    totalUploads = 0;
    totalUploadBytes = 0;
    totalUploadedBytes = 0;
}

MegaNode *MegaApiImpl::getRootNode()
{
    // return without locking the main mutex if possible.
    // (always lock for folder links, since node attributes can change)
    // Only compare fixed-location 8-byte values
    std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex);
    if (client->mNodeManager.getRootNodeFiles().isUndef()) return nullptr;
    if (!mLastKnownRootNode ||
            client->loggedIntoFolder() ||
            mLastKnownRootNode->getHandle() != client->mNodeManager.getRootNodeFiles().as8byte())
    {
        // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownRootNode with the mutexes locking in the other order
        g.unlock();
        MegaNode* newPtr = nullptr;
        {
            SdkMutexGuard lock(sdkMutex);
            newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeFiles()));
        }
        g.lock();
        mLastKnownRootNode.reset(newPtr);
    }

    return mLastKnownRootNode ? mLastKnownRootNode->copy() : nullptr;
}

MegaNode* MegaApiImpl::getVaultNode()
{
    // return without locking the main mutex if possible.
    // Only compare fixed-location 8-byte values
    std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex);
    if (client->mNodeManager.getRootNodeVault().isUndef()) return nullptr;
    if (!mLastKnownVaultNode ||
        mLastKnownVaultNode->getHandle() != client->mNodeManager.getRootNodeVault().as8byte())
    {
        // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownVaultNode with the mutexes locking in the other order
        g.unlock();
        MegaNode* newPtr = nullptr;
        {
            SdkMutexGuard lock(sdkMutex);
            newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeVault()));
        }
        g.lock();
        mLastKnownVaultNode.reset(newPtr);
    }

    return mLastKnownVaultNode ? mLastKnownVaultNode->copy() : nullptr;
}

MegaNode* MegaApiImpl::getRubbishNode()
{
    // return without locking the main mutex if possible.
    // Only compare fixed-location 8-byte values
    std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex);
    if (client->mNodeManager.getRootNodeRubbish().isUndef()) return nullptr;
    if (!mLastKnownRubbishNode ||
        mLastKnownRubbishNode->getHandle() != client->mNodeManager.getRootNodeRubbish().as8byte())
    {
        // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownRubbishNode with the mutexes locking in the other order
        g.unlock();
        MegaNode* newPtr = nullptr;
        {
            SdkMutexGuard lock(sdkMutex);
            newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeRubbish()));
        }
        g.lock();
        mLastKnownRubbishNode.reset(newPtr);
    }

    return mLastKnownRubbishNode ? mLastKnownRubbishNode->copy() : nullptr;
}

MegaNode *MegaApiImpl::getRootNode(MegaNode *node)
{
    MegaNode *rootnode = NULL;

    SdkMutexGuard lock(sdkMutex);

    Node *n;
    if (node && (n = client->nodebyhandle(node->getHandle())))
    {
        while (n->parent)
        {
            n = n->parent;
        }

        rootnode = MegaNodePrivate::fromNode(n);
    }
    return rootnode;
}

bool MegaApiImpl::isInRootnode(MegaNode *node, int index)
{
    bool ret = false;

    SdkMutexGuard lock(sdkMutex);

    if (MegaNode *rootnode = getRootNode(node))
    {
        ret = (index == 0 && rootnode->getHandle() == client->mNodeManager.getRootNodeFiles().as8byte()) ||
              (index == 1 && rootnode->getHandle() == client->mNodeManager.getRootNodeVault().as8byte()) ||
              (index == 2 && rootnode->getHandle() == client->mNodeManager.getRootNodeRubbish().as8byte());

        delete rootnode;
    }

    return ret;
}

void MegaApiImpl::setDefaultFilePermissions(int permissions)
{
    SdkMutexGuard lock(sdkMutex);
    fsAccess->setdefaultfilepermissions(permissions);
    client->fsaccess->setdefaultfilepermissions(permissions);
}

int MegaApiImpl::getDefaultFilePermissions()
{
    return fsAccess->getdefaultfilepermissions();
}

void MegaApiImpl::setDefaultFolderPermissions(int permissions)
{
    SdkMutexGuard lock(sdkMutex);
    fsAccess->setdefaultfolderpermissions(permissions);
    client->fsaccess->setdefaultfolderpermissions(permissions);
}

int MegaApiImpl::getDefaultFolderPermissions()
{
    return fsAccess->getdefaultfolderpermissions();
}

long long MegaApiImpl::getBandwidthOverquotaDelay()
{
    long long result = client->overquotauntil;
    return result > Waiter::ds ? (result - Waiter::ds) / 10 : 0;
}

bool MegaApiImpl::userComparatorDefaultASC (User *i, User *j)
{
    if(strcasecmp(i->email.c_str(), j->email.c_str())<=0) return 1;
    return 0;
}

m_off_t MegaApiImpl::sizeDifference(Node *i, Node *j)
{
    m_off_t iSize = 0;
    if (i->type == FILENODE)
    {
        iSize = i->size;
    }
    else
    {
        iSize = i->getCounter().storage + i->getCounter().versionStorage;
    }

    m_off_t jSize = 0;
    if (i->type == FILENODE)
    {
        jSize = j->size;
    }
    else
    {
        jSize = j->getCounter().storage + j->getCounter().versionStorage;
    }

    return iSize - jSize;
}

char *MegaApiImpl::escapeFsIncompatible(const char *filename, const char *dstPath)
{
    if(!filename)
    {
        return NULL;
    }
    string name = filename;
    client->fsaccess->escapefsincompatible(&name,
        dstPath ? client->fsaccess->getlocalfstype(LocalPath::fromAbsolutePath(dstPath))
                : FS_UNKNOWN);
    return MegaApi::strdup(name.c_str());
}

char *MegaApiImpl::unescapeFsIncompatible(const char *name, const char *path)
{
    if(!name)
    {
        return NULL;
    }
    string filename = name;
    client->fsaccess->unescapefsincompatible(&filename);
    return MegaApi::strdup(filename.c_str());
}

bool MegaApiImpl::createThumbnail(const char *imagePath, const char *dstPath)
{
    if (!gfxAccess || !imagePath || !dstPath)
    {
        return false;
    }

    LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath);
    LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath);

    SdkMutexGuard g(sdkMutex);
    return gfxAccess->savefa(localImagePath, GfxProc::dimensions[GfxProc::THUMBNAIL][0],
            GfxProc::dimensions[GfxProc::THUMBNAIL][1], localDstPath);
}

bool MegaApiImpl::createPreview(const char *imagePath, const char *dstPath)
{
    if (!gfxAccess || !imagePath || !dstPath)
    {
        return false;
    }

    LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath);
    LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath);

    SdkMutexGuard g(sdkMutex);
    return gfxAccess->savefa(localImagePath, GfxProc::dimensions[GfxProc::PREVIEW][0],
            GfxProc::dimensions[GfxProc::PREVIEW][1], localDstPath);
}

bool MegaApiImpl::createAvatar(const char *imagePath, const char *dstPath)
{
    if (!gfxAccess || !imagePath || !dstPath)
    {
        return false;
    }

    LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath);
    LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath);

    SdkMutexGuard g(sdkMutex);
    return gfxAccess->savefa(localImagePath, GfxProc::dimensionsavatar[GfxProc::AVATAR250X250][0],
            GfxProc::dimensionsavatar[GfxProc::AVATAR250X250][1], localDstPath);
}

void MegaApiImpl::getUploadURL(int64_t fullFileSize, bool forceSSL, MegaRequestListener *listener)
{
    MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL, listener);
    req->setNumber(fullFileSize);
    req->setFlag(forceSSL);

    req->performRequest = [this, req]()
    {
        return performRequest_getBackgroundUploadURL(req);
    };

    requestQueue.push(req);
    waiter->notify();
}

void MegaApiImpl::completeUpload(const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal,
                                  const char *string64UploadToken, const char *string64FileKey,  MegaRequestListener *listener)
{
    MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD, listener);
    req->setPassword(fingerprintoriginal);
    req->setNewPassword(fingerprint);
    req->setName(utf8Name);
    req->setPrivateKey(string64FileKey);
    if (parent)
    {
        req->setParentHandle(parent->getHandle());
    }
    if (string64UploadToken)
    {
        req->setSessionKey(string64UploadToken);
    }

    req->performRequest = [this, req]()
    {
        return performRequest_completeBackgroundUpload(req);
    };

    requestQueue.push(req);
    waiter->notify();
}


void MegaApiImpl::backgroundMediaUploadRequestUploadURL(int64_t fullFileSize, MegaBackgroundMediaUpload* state, MegaRequestListener *listener)
{
    MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL, listener);
    req->setNumber(fullFileSize);
    req->setFlag(true); // always SSL for background uploads
    req->setMegaBackgroundMediaUploadPtr(state);

    req->performRequest = [this, req]()
    {
        return performRequest_getBackgroundUploadURL(req);
    };

    requestQueue.push(req);
    waiter->notify();
}

void MegaApiImpl::backgroundMediaUploadComplete(MegaBackgroundMediaUpload* state, const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal,
    const char *string64UploadToken, MegaRequestListener *listener)
{
    MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD, listener);
    req->setMegaBackgroundMediaUploadPtr(static_cast<MegaBackgroundMediaUploadPrivate*>(state));
    req->setPassword(fingerprintoriginal);
    req->setNewPassword(fingerprint);
    req->setName(utf8Name);
    if (parent)
    {
        req->setParentHandle(parent->getHandle());
    }
    if (string64UploadToken)
    {
        req->setSessionKey(string64UploadToken);
    }

    req->performRequest = [this, req]()
    {
        return performRequest_completeBackgroundUpload(req);
    };

    requestQueue.push(req);
    waiter->notify();
}

bool MegaApiImpl::ensureMediaInfo()
{
#ifdef USE_MEDIAINFO
    if (client->mediaFileInfo.mediaCodecsReceived)
    {
        return true;
    }
    else
    {
        SdkMutexGuard g(sdkMutex);
        client->mediaFileInfo.requestCodecMappingsOneTime(client, LocalPath());
        return false;
    }
#else
    return false;
#endif
}

void MegaApiImpl::setOriginalFingerprint(MegaNode* node, const char* originalFingerprint, MegaRequestListener *listener)
{
    MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener);
    req->setParamType(MegaApi::NODE_ATTR_ORIGINALFINGERPRINT);
    req->setText(originalFingerprint);
    req->setNodeHandle(node->getHandle());
    req->setFlag(true);

    req->performRequest = [this, req]()
    {
        return performRequest_setAttrNode(req);
    };

    requestQueue.push(req);
    waiter->notify();
}

bool MegaApiImpl::isOnline()
{
    return !client->httpio->noinetds;
}

#ifdef HAVE_LIBUV
bool MegaApiImpl::httpServerStart(bool localOnly, int port, bool useTLS, const char *certificatepath, const char *keypath, bool useIPv6)
{
    #ifndef ENABLE_EVT_TLS
    if (useTLS)
    {
        LOG_err << "Could not start HTTP server: TLS is not supported in current compilation";
        return false;
    }
    #endif

    if (useTLS && (!certificatepath || !keypath || !strlen(certificatepath) || !strlen(keypath)))
    {
        LOG_err << "Could not start HTTP server: No certificate/key provided";
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    if (httpServer && httpServer->getPort() == port && httpServer->isLocalOnly() == localOnly)
    {
        httpServer->clearAllowedHandles();
        return true;
    }

    httpServerStop();
    httpServer = new MegaHTTPServer(this, basePath, useTLS, certificatepath ? certificatepath : string(), keypath ? keypath : string(), useIPv6);
    httpServer->setMaxBufferSize(httpServerMaxBufferSize);
    httpServer->setMaxOutputSize(httpServerMaxOutputSize);
    httpServer->enableFileServer(httpServerEnableFiles);
    httpServer->enableOfflineAttribute(httpServerOfflineAttributeEnabled);
    httpServer->enableFolderServer(httpServerEnableFolders);
    httpServer->setRestrictedMode(httpServerRestrictedMode);
    httpServer->enableSubtitlesSupport(httpServerRestrictedMode);

    bool result = httpServer->start(port, localOnly);
    if (!result)
    {
        MegaHTTPServer *server = httpServer;
        httpServer = NULL;
        g.unlock();
        delete server;
    }
    return result;
}

void MegaApiImpl::httpServerStop()
{
    SdkMutexGuard g(sdkMutex);
    if (httpServer)
    {
        MegaHTTPServer *server = httpServer;
        httpServer = NULL;
        g.unlock();
        server->stop();
        delete server;
    }
}

int MegaApiImpl::httpServerIsRunning()
{
    bool result = false;
    SdkMutexGuard g(sdkMutex);
    if (httpServer)
    {
        result = httpServer->getPort();
    }
    return result;
}

char *MegaApiImpl::httpServerGetLocalLink(MegaNode *node)
{
    if (!node)
    {
        return NULL;
    }

    SdkMutexGuard g(sdkMutex);
    if (!httpServer)
    {
        return NULL;
    }

    return httpServer->getLink(node, "http");
}

char *MegaApiImpl::httpServerGetLocalWebDavLink(MegaNode *node)
{
    if (!node)
    {
        return NULL;
    }

    SdkMutexGuard g(sdkMutex);
    if (!httpServer)
    {
        return NULL;
    }

    return httpServer->getWebDavLink(node);
}

MegaStringList *MegaApiImpl::httpServerGetWebDavLinks()
{
    SdkMutexGuard g(sdkMutex);
    if (!httpServer)
    {
        return NULL;
    }

    set<handle> handles = httpServer->getAllowedWebDavHandles();

    string_vector listoflinks;

    for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it)
    {
        handle h = *it;
        MegaNode *n = getNodeByHandle(h);
        if (n)
        {
            unique_ptr<char[]> link(httpServer->getWebDavLink(n));
            listoflinks.push_back(link.get());
        }
    }

    return new MegaStringListPrivate(move(listoflinks));
}

MegaNodeList *MegaApiImpl::httpServerGetWebDavAllowedNodes()
{
    SdkMutexGuard g(sdkMutex);
    if (!httpServer)
    {
        return NULL;
    }

    set<handle> handles = httpServer->getAllowedWebDavHandles();

    vector<Node *> listofnodes;

    for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it)
    {
        handle h = *it;
        Node *n = client->nodebyhandle(h);
        if (n)
        {
            listofnodes.push_back(n);
        }
    }

    return new MegaNodeListPrivate(listofnodes.data(), int(listofnodes.size()));
}

void MegaApiImpl::httpServerRemoveWebDavAllowedNode(MegaHandle handle)
{
    SdkMutexGuard g(sdkMutex);
    if (httpServer)
    {
        httpServer->removeAllowedWebDavHandle(handle);
    }
}

void MegaApiImpl::httpServerRemoveWebDavAllowedNodes()
{
    SdkMutexGuard g(sdkMutex);
    if (httpServer)
    {
        httpServer->clearAllowedHandles();
    }
}

void MegaApiImpl::httpServerSetMaxBufferSize(int bufferSize)
{
    SdkMutexGuard g(sdkMutex);
    httpServerMaxBufferSize = bufferSize <= 0 ? 0 : bufferSize;
    httpServerMaxOutputSize = httpServerMaxBufferSize / 10;
    if (httpServer)
    {
        httpServer->setMaxBufferSize(httpServerMaxBufferSize);
        httpServer->setMaxOutputSize(httpServerMaxOutputSize);
    }
}

int MegaApiImpl::httpServerGetMaxBufferSize()
{
    SdkMutexGuard g(sdkMutex);
    if (httpServerMaxBufferSize)
    {
        return httpServerMaxBufferSize;
    }
    else
    {
        return StreamingBuffer::MAX_BUFFER_SIZE;
    }
}

void MegaApiImpl::httpServerSetMaxOutputSize(int outputSize)
{
    SdkMutexGuard g(sdkMutex);
    httpServerMaxOutputSize = outputSize <= 0 ? 0 : outputSize;
    if (httpServer)
    {
        httpServer->setMaxOutputSize(httpServerMaxOutputSize);
    }
}

int MegaApiImpl::httpServerGetMaxOutputSize()
{
    SdkMutexGuard g(sdkMutex);
    if (httpServerMaxOutputSize)
    {
        return httpServerMaxOutputSize;
    }
    else
    {
        return StreamingBuffer::MAX_OUTPUT_SIZE;
    }
}

void MegaApiImpl::httpServerEnableFileServer(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    this->httpServerEnableFiles = enable;
    if (httpServer)
    {
        httpServer->enableFileServer(enable);
    }
}

bool MegaApiImpl::httpServerIsFileServerEnabled()
{
    return httpServerEnableFiles;
}

void MegaApiImpl::httpServerEnableFolderServer(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    this->httpServerEnableFolders = enable;
    if (httpServer)
    {
        httpServer->enableFolderServer(enable);
    }
}

void MegaApiImpl::httpServerEnableOfflineAttribute(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    this->httpServerOfflineAttributeEnabled = enable;
    if (httpServer)
    {
        httpServer->enableOfflineAttribute(enable);
    }
}

bool MegaApiImpl::httpServerIsFolderServerEnabled()
{
    return httpServerEnableFolders;
}

bool MegaApiImpl::httpServerIsOfflineAttributeEnabled()
{
    return httpServerOfflineAttributeEnabled;
}

void MegaApiImpl::httpServerSetRestrictedMode(int mode)
{
    if (mode != MegaApi::TCP_SERVER_DENY_ALL
            && mode != MegaApi::TCP_SERVER_ALLOW_ALL
            && mode != MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS
            && mode != MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    httpServerRestrictedMode = mode;
    if (httpServer)
    {
        httpServer->setRestrictedMode(httpServerRestrictedMode);
    }
}

int MegaApiImpl::httpServerGetRestrictedMode()
{
    return httpServerRestrictedMode;
}

void MegaApiImpl::httpServerEnableSubtitlesSupport(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    httpServerSubtitlesSupportEnabled = enable;
    if (httpServer)
    {
        httpServer->enableSubtitlesSupport(httpServerSubtitlesSupportEnabled);
    }
}

bool MegaApiImpl::httpServerIsSubtitlesSupportEnabled()
{
    return httpServerSubtitlesSupportEnabled;
}

bool MegaApiImpl::httpServerIsLocalOnly()
{
    bool localOnly = true;
    SdkMutexGuard g(sdkMutex);
    if (httpServer)
    {
        localOnly = httpServer->isLocalOnly();
    }
    return localOnly;
}

void MegaApiImpl::httpServerAddListener(MegaTransferListener *listener)
{
    if (!listener)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    httpServerListeners.insert(listener);
}

void MegaApiImpl::httpServerRemoveListener(MegaTransferListener *listener)
{
    if (!listener)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    httpServerListeners.erase(listener);
}

void MegaApiImpl::fireOnStreamingStart(MegaTransferPrivate *transfer)
{
    for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++)
        (*it)->onTransferStart(api, transfer);
}

void MegaApiImpl::fireOnStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++)
        (*it)->onTransferTemporaryError(api, transfer, e.get());
}

void MegaApiImpl::fireOnStreamingFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    if(e->getErrorCode())
    {
        LOG_warn << "Streaming request finished with error: " << e->getErrorString();
    }
    else
    {
        LOG_info << "Streaming request finished";
    }

    for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++)
        (*it)->onTransferFinish(api, transfer, e.get());

    delete transfer;
}

bool MegaApiImpl::ftpServerStart(bool localOnly, int port, int dataportBegin, int dataPortEnd, bool useTLS, const char *certificatepath, const char *keypath)
{
    #ifndef ENABLE_EVT_TLS
    if (useTLS)
    {
        LOG_err << "Could not start FTP server: TLS is not supported in current compilation";
        return false;
    }
    #endif

    SdkMutexGuard g(sdkMutex);
    if (ftpServer && ftpServer->getPort() == port && ftpServer->isLocalOnly() == localOnly)
    {
        ftpServer->clearAllowedHandles();
        return true;
    }

    ftpServerStop();
    ftpServer = new MegaFTPServer(this, basePath, dataportBegin, dataPortEnd, useTLS, certificatepath ? certificatepath : string(), keypath ? keypath : string());
    ftpServer->setRestrictedMode(MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS);
    ftpServer->setRestrictedMode(ftpServerRestrictedMode);
    ftpServer->setMaxBufferSize(ftpServerMaxBufferSize);
    ftpServer->setMaxOutputSize(ftpServerMaxOutputSize);

    bool result = ftpServer->start(port, localOnly);
    if (!result)
    {
        MegaFTPServer *server = ftpServer;
        ftpServer = NULL;
        g.unlock();
        delete server;
    }
    return result;
}

void MegaApiImpl::ftpServerStop()
{
    SdkMutexGuard g(sdkMutex);
    if (ftpServer)
    {
        MegaFTPServer *server = ftpServer;
        ftpServer = NULL;
        g.unlock();
        server->stop();
        delete server;
    }
}

int MegaApiImpl::ftpServerIsRunning()
{
    bool result = false;
    SdkMutexGuard g(sdkMutex);
    if (ftpServer)
    {
        result = ftpServer->getPort();
    }
    return result;
}

char *MegaApiImpl::ftpServerGetLocalLink(MegaNode *node)
{
    if (!node)
    {
        return NULL;
    }

    SdkMutexGuard g(sdkMutex);
    if (!ftpServer)
    {
        return NULL;
    }

    return ftpServer->getLink(node, "ftp");
}

MegaStringList *MegaApiImpl::ftpServerGetLinks()
{
    SdkMutexGuard g(sdkMutex);
    if (!ftpServer)
    {
        return NULL;
    }

    set<handle> handles = ftpServer->getAllowedHandles();

    string_vector listoflinks;

    for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it)
    {
        handle h = *it;
        MegaNode *n = getNodeByHandle(h);
        if (n)
        {
            unique_ptr<char[]> link(ftpServer->getLink(n));
            listoflinks.push_back(link.get());
        }
    }

    return new MegaStringListPrivate(move(listoflinks));
}

MegaNodeList *MegaApiImpl::ftpServerGetAllowedNodes()
{
    SdkMutexGuard g(sdkMutex);
    if (!ftpServer)
    {
        return NULL;
    }

    set<handle> handles = ftpServer->getAllowedHandles();

    vector<Node *> listofnodes;

    for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it)
    {
        handle h = *it;
        Node *n = client->nodebyhandle(h);
        if (n)
        {
            listofnodes.push_back(n);
        }
    }

    return new MegaNodeListPrivate(listofnodes.data(), int(listofnodes.size()));
}

void MegaApiImpl::ftpServerRemoveAllowedNode(MegaHandle handle)
{
    SdkMutexGuard g(sdkMutex);
    if (ftpServer)
    {
        ftpServer->removeAllowedHandle(handle);
    }
}

void MegaApiImpl::ftpServerRemoveAllowedNodes()
{
    SdkMutexGuard g(sdkMutex);
    if (ftpServer)
    {
        ftpServer->clearAllowedHandles();
    }
}

void MegaApiImpl::ftpServerSetMaxBufferSize(int bufferSize)
{
    SdkMutexGuard g(sdkMutex);
    ftpServerMaxBufferSize = bufferSize <= 0 ? 0 : bufferSize;
    if (ftpServer)
    {
        ftpServer->setMaxBufferSize(ftpServerMaxBufferSize);
    }
}

int MegaApiImpl::ftpServerGetMaxBufferSize()
{
    SdkMutexGuard g(sdkMutex);
    if (ftpServerMaxBufferSize)
    {
        return ftpServerMaxBufferSize;
    }
    else
    {
        return StreamingBuffer::MAX_BUFFER_SIZE;
    }
}

void MegaApiImpl::ftpServerSetMaxOutputSize(int outputSize)
{
    SdkMutexGuard g(sdkMutex);
    ftpServerMaxOutputSize = outputSize <= 0 ? 0 : outputSize;
    if (ftpServer)
    {
        ftpServer->setMaxOutputSize(ftpServerMaxOutputSize);
    }
}

int MegaApiImpl::ftpServerGetMaxOutputSize()
{
    SdkMutexGuard g(sdkMutex);
    if (ftpServerMaxOutputSize)
    {
        return ftpServerMaxOutputSize;
    }
    else
    {
        return StreamingBuffer::MAX_OUTPUT_SIZE;
    }
}

void MegaApiImpl::ftpServerSetRestrictedMode(int mode)
{
    if (mode != MegaApi::TCP_SERVER_DENY_ALL
            && mode != MegaApi::TCP_SERVER_ALLOW_ALL
            && mode != MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS
            && mode != MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    ftpServerRestrictedMode = mode;
    if (ftpServer)
    {
        ftpServer->setRestrictedMode(ftpServerRestrictedMode);
    }
}

int MegaApiImpl::ftpServerGetRestrictedMode()
{
    return ftpServerRestrictedMode;
}

bool MegaApiImpl::ftpServerIsLocalOnly()
{
    bool localOnly = true;
    SdkMutexGuard g(sdkMutex);
    if (ftpServer)
    {
        localOnly = ftpServer->isLocalOnly();
    }
    return localOnly;
}

void MegaApiImpl::ftpServerAddListener(MegaTransferListener *listener)
{
    if (!listener)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    ftpServerListeners.insert(listener);
}

void MegaApiImpl::ftpServerRemoveListener(MegaTransferListener *listener)
{
    if (!listener)
    {
        return;
    }

    SdkMutexGuard g(sdkMutex);
    ftpServerListeners.erase(listener);
}

void MegaApiImpl::fireOnFtpStreamingStart(MegaTransferPrivate *transfer)
{
    assert(threadId == std::this_thread::get_id());
    for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++)
        (*it)->onTransferStart(api, transfer);
}

void MegaApiImpl::fireOnFtpStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    assert(threadId == std::this_thread::get_id());
    for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++)
        (*it)->onTransferTemporaryError(api, transfer, e.get());
}

void MegaApiImpl::fireOnFtpStreamingFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    assert(threadId == std::this_thread::get_id());
    if(e->getErrorCode())
    {
        LOG_warn << "Streaming request finished with error: " << e->getErrorString();
    }
    else
    {
        LOG_info << "Streaming request finished";
    }

    for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++)
        (*it)->onTransferFinish(api, transfer, e.get());

    delete transfer;
}

#endif

#ifdef ENABLE_CHAT
void MegaApiImpl::sendChatStats(const char *data, int port, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_STATS, listener);
    request->setName(data);
    request->setNumber(port);
    request->setParamType(1);

    request->performRequest = [this, request]()
    {
        return performRequest_chatStats(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::sendChatLogs(const char *data, MegaHandle userid, MegaHandle callid, int port, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_STATS, listener);
    request->setName(data);
    request->setNodeHandle(userid);
    request->setParentHandle(callid);
    request->setParamType(2);
    request->setNumber(port);

    request->performRequest = [this, request]()
    {
        return performRequest_chatStats(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaTextChatList *MegaApiImpl::getChatList()
{
    SdkMutexGuard g(sdkMutex);
    return new MegaTextChatListPrivate(&client->chats);
}

MegaHandleList *MegaApiImpl::getAttachmentAccess(MegaHandle chatid, MegaHandle h)
{
    MegaHandleList *uhList = new MegaHandleListPrivate();

    if (chatid == INVALID_HANDLE || h == INVALID_HANDLE)
    {
        return uhList;
    }

    SdkMutexGuard g(sdkMutex);

    textchat_map::iterator itc = client->chats.find(chatid);
    if (itc != client->chats.end())
    {
        attachments_map::iterator ita = itc->second->attachedNodes.find(h);
        if (ita != itc->second->attachedNodes.end())
        {
            set<handle> userList = ita->second;
            set<handle>::iterator ituh;
            for (ituh = userList.begin(); ituh != userList.end(); ituh++)
            {
                uhList->addMegaHandle(*ituh);
            }
        }
    }
    return uhList;
}

bool MegaApiImpl::hasAccessToAttachment(MegaHandle chatid, MegaHandle h, MegaHandle uh)
{
    bool ret = false;

    if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || uh == INVALID_HANDLE)
    {
        return ret;
    }

    SdkMutexGuard g(sdkMutex);

    textchat_map::iterator itc = client->chats.find(chatid);
    if (itc != client->chats.end())
    {
        attachments_map::iterator ita = itc->second->attachedNodes.find(h);
        if (ita != itc->second->attachedNodes.end())
        {
            set<handle> userList = ita->second;
            ret = (userList.find(uh) != userList.end());
        }
    }
    return ret;
}

const char* MegaApiImpl::getFileAttribute(MegaHandle h)
{
    char* fileAttributes = NULL;

    SdkMutexGuard g(sdkMutex);
    if (Node *node = client->nodebyhandle(h))
    {
        fileAttributes = MegaApi::strdup(node->fileattrstring.c_str());
    }
    return fileAttributes;
}

void MegaApiImpl::enableRichPreviews(bool enable, MegaRequestListener *listener)
{
    MegaStringMap *stringMap = new MegaStringMapPrivate();
    string rawvalue = enable ? "1" : "0";
    string base64value;
    Base64::btoa(rawvalue, base64value);
    stringMap->set("num", base64value.c_str());
    setUserAttribute(MegaApi::USER_ATTR_RICH_PREVIEWS, stringMap, listener);
    delete stringMap;
}

void MegaApiImpl::isRichPreviewsEnabled(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_RICH_PREVIEWS);
    request->setNumDetails(0);  // 0 --> flag should indicate whether rich-links are enabled or not

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::shouldShowRichLinkWarning(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_RICH_PREVIEWS);
    request->setNumDetails(1);  // 1 --> flag should indicate whether to show the warning or not

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setRichLinkWarningCounterValue(int value, MegaRequestListener *listener)
{
    MegaStringMap *stringMap = new MegaStringMapPrivate();
    std::ostringstream oss;
    oss << value;
    string base64value;
    Base64::btoa(oss.str(), base64value);
    stringMap->set("c", base64value.c_str());
    setUserAttribute(MegaApi::USER_ATTR_RICH_PREVIEWS, stringMap, listener);
    delete stringMap;
}

void MegaApiImpl::enableGeolocation(MegaRequestListener *listener)
{
    MegaStringMap *stringMap = new MegaStringMapPrivate();
    string base64value;
    Base64::btoa("1", base64value);
    stringMap->set("v", base64value.c_str());
    setUserAttribute(MegaApi::USER_ATTR_GEOLOCATION, stringMap, listener);
    delete stringMap;
}

void MegaApiImpl::isGeolocationEnabled(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_GEOLOCATION);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

bool MegaApiImpl::isChatNotifiable(MegaHandle chatid)
{
    if (mPushSettings)
    {
        if (mPushSettings->isChatAlwaysNotifyEnabled(chatid))
        {
            return true;
        }

        return (!mPushSettings->isChatDndEnabled(chatid) && isGlobalNotifiable() && !mPushSettings->isGlobalChatsDndEnabled());
    }

    return true;
}

void MegaApiImpl::setSFUid(int sfuid)
{
    SdkMutexGuard g(sdkMutex);
    client->mSfuid = sfuid;
}
#endif

void MegaApiImpl::getCameraUploadsFolder(bool secondary, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER);
    request->setFlag(secondary);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setCameraUploadsFolder(MegaHandle nodehandle, bool secondary, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);

    MegaStringMapPrivate stringMap;
    const char *key = secondary ? "sh" : "h";
    stringMap.set(key, Base64Str<MegaClient::NODEHANDLE>(nodehandle));
    request->setMegaStringMap(&stringMap);
    request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER);
    request->setFlag(secondary);
    request->setNodeHandle(nodehandle);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setCameraUploadsFolders(MegaHandle primaryFolder, MegaHandle secondaryFolder, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);

    MegaStringMapPrivate stringMap;
    if (!ISUNDEF(primaryFolder))
    {
        stringMap.set("h", Base64Str<MegaClient::NODEHANDLE>(primaryFolder));
    }
    if (!ISUNDEF(secondaryFolder))
    {
        stringMap.set("sh", Base64Str<MegaClient::NODEHANDLE>(secondaryFolder));
    }
    request->setMegaStringMap(&stringMap);
    request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER);
    request->setNodeHandle(primaryFolder);
    request->setParentHandle(secondaryFolder);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getMyChatFilesFolder(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setMyChatFilesFolder(MegaHandle nodehandle, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);

    MegaStringMapPrivate stringMap;
    stringMap.set("h", Base64Str<MegaClient::NODEHANDLE>(nodehandle));
    request->setMegaStringMap(&stringMap);
    request->setParamType(MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER);
    request->setNodeHandle(nodehandle);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUserAlias(MegaHandle uh, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_ALIAS);
    request->setNodeHandle(uh);
    request->setText(Base64Str<MegaClient::USERHANDLE>(uh));

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    MegaStringMapPrivate stringMap;
    string buf = alias ? alias : "";    // alias is null to remove it
    stringMap.set(Base64Str<MegaClient::USERHANDLE>(uh), Base64::btoa(buf).c_str());
    request->setMegaStringMap(&stringMap);
    request->setParamType(MegaApi::USER_ATTR_ALIAS);
    request->setNodeHandle(uh);
    request->setText(alias);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPushNotificationSettings(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_PUSH_SETTINGS);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setPushNotificationSettings(MegaPushNotificationSettings *settings, MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_PUSH_SETTINGS);
    request->setMegaPushNotificationSettings(settings);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

bool MegaApiImpl::isSharesNotifiable()
{
    return !mPushSettings || (mPushSettings->isSharesEnabled() && isScheduleNotifiable());
}

bool MegaApiImpl::isContactsNotifiable()
{
    return !mPushSettings || (mPushSettings->isContactsEnabled() && isScheduleNotifiable());
}

void MegaApiImpl::getAccountAchievements(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ACHIEVEMENTS, listener);

    request->performRequest = [this, request]()
    {
        return performRequest_getAchievements(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getMegaAchievements(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ACHIEVEMENTS, listener);
    request->setFlag(true);

    request->performRequest = [this, request]()
    {
        return performRequest_getAchievements(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::catchup(MegaRequestListener *listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CATCHUP, listener);
    scRequestQueue.push(request);
    waiter->notify();
}

MegaUserList* MegaApiImpl::getContacts()
{
    SdkMutexGuard g(sdkMutex);

    vector<User*> vUsers;
    for (user_map::iterator it = client->users.begin() ; it != client->users.end() ; it++ )
    {
        User *u = &(it->second);
        if (u->userhandle == client->me)
        {
            continue;
        }
        vector<User *>::iterator i = std::lower_bound(vUsers.begin(), vUsers.end(), u, MegaApiImpl::userComparatorDefaultASC);
        vUsers.insert(i, u);
    }
    return new MegaUserListPrivate(vUsers.data(), int(vUsers.size()));
}


MegaUser* MegaApiImpl::getContact(const char *uid)
{
    SdkMutexGuard g(sdkMutex);
    MegaUser *user = MegaUserPrivate::fromUser(client->finduser(uid, 0));

    if (user && user->getHandle() == client->me)
    {
        delete user;
        user = NULL;    // it's not a contact
    }

    return user;
}

MegaUserAlertList* MegaApiImpl::getUserAlerts()
{
    SdkMutexGuard g(sdkMutex);

    vector<UserAlert::Base*> v;
    v.reserve(client->useralerts.alerts.size());
    for (UserAlerts::Alerts::iterator it = client->useralerts.alerts.begin(); it != client->useralerts.alerts.end(); ++it)
    {
        if (!(*it)->removed())
        {
            v.push_back(*it);
        }
    }
    return new MegaUserAlertListPrivate(v.data(), int(v.size()), client);
}

int MegaApiImpl::getNumUnreadUserAlerts()
{
    int result = 0;
    SdkMutexGuard g(sdkMutex);
    for (UserAlerts::Alerts::iterator it = client->useralerts.alerts.begin(); it != client->useralerts.alerts.end(); ++it)
    {
        if (!(*it)->removed() && !(*it)->seen())
        {
            result++;
        }
    }
    return result;
}

MegaNodeList* MegaApiImpl::getInShares(MegaUser *megaUser, int order)
{
    if (!megaUser)
    {
        return new MegaNodeListPrivate();
    }

    SdkMutexGuard g(sdkMutex);
    node_vector vNodes;
    User *user = client->finduser(megaUser->getEmail(), 0);
    if (!user)
    {
        return new MegaNodeListPrivate();
    }

    for (handle_set::iterator sit = user->sharing.begin(); sit != user->sharing.end(); sit++)
    {
        Node *n;
        if ((n = client->nodebyhandle(*sit)) && !n->parent)
        {
            vNodes.push_back(n);
        }
    }

    MegaNodeList *nodeList;
    if (vNodes.size())
    {
        sortByComparatorFunction(vNodes, order, *client);
        nodeList = new MegaNodeListPrivate(vNodes.data(), int(vNodes.size()));
    }
    else
    {
        nodeList = new MegaNodeListPrivate();
    }
    return nodeList;
}

MegaNodeList* MegaApiImpl::getInShares(int order)
{
    SdkMutexGuard lock(sdkMutex);

    node_vector nodes = client->getInShares();

    sortByComparatorFunction(nodes, order, *client);

    return new MegaNodeListPrivate(nodes.data(), int(nodes.size()));
}

MegaShareList* MegaApiImpl::getInSharesList(int order)
{
    SdkMutexGuard lock(sdkMutex);

    node_vector nodes = client->getVerifiedInShares();

    sortByComparatorFunction(nodes, order, *client);

    vector<Share*> shares;
    handle_vector handles;
    vector<byte> verified;

    for (Node *node : nodes)
    {
        shares.push_back(node->inshare);
        handles.push_back(node->nodehandle);
        verified.push_back(true);
    }

    MegaShareList *shareList = new MegaShareListPrivate(shares.data(), handles.data(), verified.data(), int(shares.size()));
    return shareList;
}

MegaUser *MegaApiImpl::getUserFromInShare(MegaNode *megaNode, bool recurse)
{
    if (!megaNode)
    {
        return NULL;
    }

    MegaUser *user = NULL;

    SdkMutexGuard g(sdkMutex);

    Node *node = client->nodebyhandle(megaNode->getHandle());
    if (recurse && node)
    {
        node = client->getrootnode(node);
    }

    if (node && node->inshare && node->inshare->user)
    {
        user = MegaUserPrivate::fromUser(node->inshare->user);
    }
    return user;
}

bool MegaApiImpl::isPendingShare(MegaNode *megaNode)
{
    if(!megaNode) return false;

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    if(!node)
    {
        return false;
    }

    return node->pendingshares != NULL;
}

MegaShareList *MegaApiImpl::getOutShares(int order)
{
    SdkMutexGuard guard(sdkMutex);

    node_vector outshares = client->mNodeManager.getNodesWithOutShares();

    // Avoid duplicate nodes present in both outshares and pending shares
    node_vector pendingShares = client->mNodeManager.getNodesWithPendingOutShares();
    for (Node* pendingShare : pendingShares)
    {
        bool found = false;
        for (Node* node : outshares)
        {
            if (node->nodeHandle() == pendingShare->nodeHandle())
            {
                found = true;
                break;
            }
        }

        if (!found)
        {
            outshares.push_back(pendingShare);
        }
    }

    std::map<NodeHandle, std::set<Share *>> nodeSharesMap;
    for (const Node* n : outshares)
    {
        if (n->outshares)
        {
            for (auto &share : *n->outshares)
            {
                assert(!share.second->pcr);
                if (share.second->user) // public links have no user
                {
                    nodeSharesMap[n->nodeHandle()].insert(share.second);
                }
            }
        }

        if (n->pendingshares)
        {
            for (auto &share : *n->pendingshares)
            {
                if (share.second->pcr)
                {
                    nodeSharesMap[n->nodeHandle()].insert(share.second);
                }
            }
        }
    }

    MegaApiImpl::sortByComparatorFunction(outshares, order, *client);
    vector<handle> handles;
    vector<Share *> shares;
    vector<byte> verified;
    for (const Node *n: outshares)
    {
        for (const auto it : nodeSharesMap[n->nodeHandle()])
        {
            handles.push_back(n->nodehandle);
            shares.push_back(it);

            bool isUnverified;
            if (it->pcr)
            {
                isUnverified = client->mKeyManager.isUnverifiedOutShare(n->nodehandle, it->pcr->targetemail);
            }
            else    // here we have always a it->user, since folder links are already filtered out
            {
                isUnverified = client->mKeyManager.isUnverifiedOutShare(n->nodehandle, toHandle(it->user->userhandle));
            }
            verified.push_back(!isUnverified);
        }
    }

    return new MegaShareListPrivate(shares.data(), handles.data(), verified.data(), int(shares.size()));
}

MegaShareList* MegaApiImpl::getOutShares(MegaNode *megaNode)
{
    if(!megaNode) return new MegaShareListPrivate();

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    if(!node)
    {
        return new MegaShareListPrivate();
    }

    if(!node->outshares && !node->pendingshares)
    {
        return new MegaShareListPrivate();
    }

    vector<Share*> vShares;
    vector<handle> vHandles;
    vector<byte> vVerified;

    if (node->outshares)
    {
        for (share_map::iterator it = node->outshares->begin(); it != node->outshares->end(); it++)
        {
            Share *share = it->second;
            assert(!share->pcr);
            if (share->user)    // public links have no user
            {
                vShares.push_back(share);
                vHandles.push_back(node->nodehandle);
                vVerified.push_back(!client->mKeyManager.isUnverifiedOutShare(node->nodehandle, toHandle(share->user->userhandle)));
            }
        }
    }

    if (node->pendingshares)
    {
        for (share_map::iterator it = node->pendingshares->begin(); it != node->pendingshares->end(); it++)
        {
            Share *share = it->second;
            assert(share->pcr);
            assert(!share->user);
            if (share->pcr)
            {
                vShares.push_back(share);
                vHandles.push_back(node->nodehandle);
                vVerified.push_back(!client->mKeyManager.isUnverifiedOutShare(node->nodehandle, share->pcr->targetemail));
            }
        }
    }

    return new MegaShareListPrivate(vShares.data(), vHandles.data(), vVerified.data(), int(vShares.size()));
}

MegaShareList *MegaApiImpl::getPendingOutShares()
{
    SdkMutexGuard guard(sdkMutex);

    node_vector nodes = client->mNodeManager.getNodesWithPendingOutShares();
    vector<handle> handles;
    vector<Share *> shares;
    vector<byte> verified;

    for (const Node* n : nodes)
    {
        assert(n->pendingshares);
        for (auto &share : *n->pendingshares)
        {
            if (share.second->pcr)
            {
                handles.push_back(n->nodehandle);
                shares.push_back(share.second);
                verified.push_back(!client->mKeyManager.isUnverifiedOutShare(n->nodehandle, share.second->pcr->targetemail));
            }
        }
    }
    return new MegaShareListPrivate(shares.data(), handles.data(), verified.data(), int(shares.size()));
}

MegaShareList *MegaApiImpl::getPendingOutShares(MegaNode *megaNode)
{
    if(!megaNode)
    {
        return new MegaShareListPrivate();
    }

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    if(!node || !node->pendingshares)
    {
        return new MegaShareListPrivate();
    }

    vector<Share*> vShares;
    vector<handle> vHandles;
    vector<byte> vVerified;
    for (share_map::iterator it = node->pendingshares->begin(); it != node->pendingshares->end(); it++)
    {
        vShares.push_back(it->second);
        vHandles.push_back(node->nodehandle);
        vVerified.push_back(!client->mKeyManager.isUnverifiedOutShare(node->nodehandle, it->second->pcr->targetemail));
    }

    return new MegaShareListPrivate(vShares.data(), vHandles.data(), vVerified.data(), int(vShares.size()));
}

bool MegaApiImpl::isPrivateNode(MegaHandle h)
{
    SdkMutexGuard lock(sdkMutex);

    return client->isPrivateNode(NodeHandle().set6byte(h));
}

bool MegaApiImpl::isForeignNode(MegaHandle h)
{
    SdkMutexGuard lock(sdkMutex);

    return client->isForeignNode(NodeHandle().set6byte(h));
}

MegaNodeList *MegaApiImpl::getPublicLinks(int order)
{
    SdkMutexGuard g(sdkMutex);

    node_vector nodes = client->mNodeManager.getNodesWithLinks();
    sortByComparatorFunction(nodes, order, *client);
    return new MegaNodeListPrivate(nodes.data(), int(nodes.size()));
}

MegaContactRequestList *MegaApiImpl::getIncomingContactRequests()
{
    SdkMutexGuard g(sdkMutex);
    vector<PendingContactRequest*> vContactRequests;
    for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++)
    {
        if(!it->second->isoutgoing && !it->second->removed())
        {
            vContactRequests.push_back(it->second.get());
        }
    }

    return new MegaContactRequestListPrivate(vContactRequests.data(), int(vContactRequests.size()));
}

MegaContactRequestList *MegaApiImpl::getOutgoingContactRequests()
{
    SdkMutexGuard g(sdkMutex);
    vector<PendingContactRequest*> vContactRequests;
    for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++)
    {
        if(it->second->isoutgoing && !it->second->removed())
        {
            vContactRequests.push_back(it->second.get());
        }
    }

    return new MegaContactRequestListPrivate(vContactRequests.data(), int(vContactRequests.size()));
}

int MegaApiImpl::getAccess(MegaNode* megaNode)
{
    if(!megaNode) return MegaShare::ACCESS_UNKNOWN;

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    if(!node)
    {
        return MegaShare::ACCESS_UNKNOWN;
    }

    if (!client->loggedin())
    {
        return MegaShare::ACCESS_READ;
    }

    if(node->type > FOLDERNODE)
    {
        return MegaShare::ACCESS_OWNER;
    }

    Node *n = node;
    accesslevel_t a = OWNER;
    while (n)
    {
        if (n->inshare) { a = n->inshare->access; break; }
        n = n->parent;
    }

    switch(a)
    {
        case RDONLY: return MegaShare::ACCESS_READ;
        case RDWR: return MegaShare::ACCESS_READWRITE;
        case FULL: return MegaShare::ACCESS_FULL;
        default: return MegaShare::ACCESS_OWNER;
    }
}

MegaRecentActionBucketList* MegaApiImpl::getRecentActions(unsigned days, unsigned maxnodes)
{
    SdkMutexGuard g(sdkMutex);
    m_time_t since = m_time() - days * 86400;
    recentactions_vector v = client->getRecentActions(maxnodes, since);
    return new MegaRecentActionBucketListPrivate(v, client);
}

bool MegaApiImpl::processMegaTree(MegaNode* n, MegaTreeProcessor* processor, bool recursive)
{
    if (!n)
    {
        return true;
    }

    if (!processor)
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    Node *node = NULL;

    if (!n->isForeign() && !n->isPublic())
    {
        node = client->nodebyhandle(n->getHandle());
    }

    if (!node)
    {
        if (n->getType() != FILENODE)
        {
            MegaNodeList *nList = n->getChildren();
            if (nList)
            {
                for (int i = 0; i < nList->size(); i++)
                {
                    MegaNode *child = nList->get(i);
                    if (recursive)
                    {
                        if (!processMegaTree(child, processor))
                        {
                            return 0;
                        }
                    }
                    else
                    {
                        if (!processor->processMegaNode(child))
                        {
                            return 0;
                        }
                    }
                }
            }
        }
        return processor->processMegaNode(n);
    }

    if (node->type != FILENODE)
    {
        node_list nodeList = client->getChildren(node);
        for (node_list::iterator it = nodeList.begin(); it != nodeList.end(); )
        {
            unique_ptr<MegaNode> megaNode(MegaNodePrivate::fromNode(*it++));
            if (recursive)
            {
                if (!processMegaTree(megaNode.get(), processor))
                {
                    return 0;
                }
            }
            else
            {
                if (!processor->processMegaNode(megaNode.get()))
                {
                    return 0;
                }
            }
        }
    }
    return processor->processMegaNode(n);
}


MegaNode *MegaApiImpl::createForeignFileNode(MegaHandle handle, const char *key, const char *name, m_off_t size, m_off_t mtime,
                                            MegaHandle parentHandle, const char* privateauth, const char *publicauth, const char *chatauth)
{
    string nodekey;
    string fileattrsting;
    nodekey.resize(strlen(key) * 3 / 4 + 3);
    nodekey.resize(Base64::atob(key, (byte *)nodekey.data(), int(nodekey.size())));
    return new MegaNodePrivate(name, FILENODE, size, mtime, mtime, handle, &nodekey, &fileattrsting, NULL, NULL, INVALID_HANDLE,
                               parentHandle, privateauth, publicauth, false, true, chatauth, true);
}

MegaNode *MegaApiImpl::createForeignFolderNode(MegaHandle handle, const char *name, MegaHandle parentHandle, const char *privateauth, const char *publicauth)
{
    string nodekey;
    string fileattrsting;
    return new MegaNodePrivate(name, FOLDERNODE, 0, 0, 0, handle, &nodekey, &fileattrsting, NULL, NULL, INVALID_HANDLE, parentHandle,
                               privateauth, publicauth, false, true, nullptr, true);
}

MegaNode *MegaApiImpl::authorizeNode(MegaNode *node)
{
    if (!node)
    {
        return NULL;
    }

    if (node->isPublic() || node->isForeign())
    {
        return node->copy();
    }

    MegaNodePrivate *result = NULL;
    SdkMutexGuard g(sdkMutex);
    Node *n = client->nodebyhandle(node->getHandle());
    if (n)
    {
        result = new MegaNodePrivate(node);
        authorizeMegaNodePrivate(result);
    }
    return result;
}

void MegaApiImpl::authorizeMegaNodePrivate(MegaNodePrivate *node)
{
    // Versions are not added to authorized MegaNodes.
    // If it's decided to do so, it's needed to modify the processing
    // of MegaNode copies accordingly

    node->setForeign(true);
    if (node->getType() == MegaNode::TYPE_FILE)
    {
        char *h = NULL;
        if (client->sid.size())
        {
            h = getAccountAuth();
            node->setPrivateAuth(h);
        }
        else
        {
            h = MegaApiImpl::handleToBase64(client->mNodeManager.getRootNodeFiles().as8byte());
            node->setPublicAuth(h);
        }
        delete [] h;
    }
    else
    {
        MegaNodeList *children = getChildren(node, MegaApi::ORDER_NONE);
        node->setChildren(children);
        for (int i = 0; i < children->size(); i++)
        {
            MegaNodePrivate *privNode = (MegaNodePrivate *)children->get(i);
            authorizeMegaNodePrivate(privNode);
        }
    }
}

MegaNode *MegaApiImpl::authorizeChatNode(MegaNode *node, const char *cauth)
{
    if (!node)
    {
        return NULL;
    }

    MegaNodePrivate *result = new MegaNodePrivate(node);
    result->setChatAuth(cauth);

    return result;
}

const char *MegaApiImpl::getVersion()
{
    return client->version();
}

char *MegaApiImpl::getOperatingSystemVersion()
{
    string version;
    fsAccess->osversion(&version, false);
    return MegaApi::strdup(version.c_str());
}

void MegaApiImpl::setPSA(int id, MegaRequestListener *listener)
{
    std::ostringstream oss;
    oss << id;
    string value = oss.str();
    setUserAttr(MegaApi::USER_ATTR_LAST_PSA, value.c_str(), listener);
}

void MegaApiImpl::disableGfxFeatures(bool disable)
{
    client->gfxdisabled = disable;
}

bool MegaApiImpl::areGfxFeaturesDisabled()
{
    return !client->gfx || client->gfxdisabled;
}

const char *MegaApiImpl::getUserAgent()
{
    return client->useragent.c_str();
}

const char *MegaApiImpl::getBasePath()
{
    return basePath.c_str();
}

void MegaApiImpl::changeApiUrl(const char *apiURL, bool disablepkp)
{
    {
        // change defaults for future MegaApi construction
        lock_guard<mutex> g(g_APIURL_default_mutex);
        g_APIURL_default = apiURL;
        g_disablepkp_default = disablepkp;
    }

    // change this MegaApi too
    SdkMutexGuard g(sdkMutex);
    client->httpio->APIURL = apiURL;
    client->httpio->disablepkp = disablepkp;

    client->abortbackoff();
    client->disconnect();
}

bool MegaApiImpl::setLanguage(const char *languageCode)
{
    string code;
    if (!getLanguageCode(languageCode, &code))
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    return client->setlang(&code);
}

string MegaApiImpl::generateViewId()
{
    return MegaClient::generateViewId(client->rng);
}

void MegaApiImpl::setLanguagePreference(const char *languageCode, MegaRequestListener *listener)
{
    setUserAttr(MegaApi::USER_ATTR_LANGUAGE, languageCode, listener);
}

void MegaApiImpl::getLanguagePreference(MegaRequestListener *listener)
{
    getUserAttr(NULL, MegaApi::USER_ATTR_LANGUAGE, NULL, 0, listener);
}

bool MegaApiImpl::getLanguageCode(const char *languageCode, string *code)
{
    if (!languageCode || !code)
    {
        return false;
    }

    size_t len = strlen(languageCode);
    if (len < 2 || len > 7)
    {
        return false;
    }

    code->clear();
    string s = languageCode;
    tolower_string(s);

    while (s.size() >= 2)
    {
        JSON json;
        nameid id = json.getnameid(s.c_str());
        switch (id)
        {
            // Regular language codes
            case MAKENAMEID2('a', 'r'):
            case MAKENAMEID2('b', 'g'):
            case MAKENAMEID2('d', 'e'):
            case MAKENAMEID2('e', 'n'):
            case MAKENAMEID2('e', 's'):
            case MAKENAMEID2('f', 'a'):
            case MAKENAMEID2('f', 'i'):
            case MAKENAMEID2('f', 'r'):
            case MAKENAMEID2('h', 'e'):
            case MAKENAMEID2('h', 'u'):
            case MAKENAMEID2('i', 'd'):
            case MAKENAMEID2('i', 't'):
            case MAKENAMEID2('n', 'l'):
            case MAKENAMEID2('p', 'l'):
            case MAKENAMEID2('r', 'o'):
            case MAKENAMEID2('r', 'u'):
            case MAKENAMEID2('s', 'k'):
            case MAKENAMEID2('s', 'l'):
            case MAKENAMEID2('s', 'r'):
            case MAKENAMEID2('t', 'h'):
            case MAKENAMEID2('t', 'l'):
            case MAKENAMEID2('t', 'r'):
            case MAKENAMEID2('u', 'k'):
            case MAKENAMEID2('v', 'i'):

            // Not used on apps
            case MAKENAMEID2('c', 'z'):
            case MAKENAMEID2('j', 'p'):
            case MAKENAMEID2('k', 'r'):
            case MAKENAMEID2('b', 'r'):
            case MAKENAMEID2('s', 'e'):
            case MAKENAMEID2('c', 'n'):
            case MAKENAMEID2('c', 't'):
                *code = s;
                break;

            // Conversions
            case MAKENAMEID2('c', 's'):
                *code = "cz";
                break;

            case MAKENAMEID2('j', 'a'):
                *code = "jp";
                break;

            case MAKENAMEID2('k', 'o'):
                *code = "kr";
                break;

            case MAKENAMEID2('p', 't'):
            case MAKENAMEID5('p', 't', '_', 'b', 'r'):
            case MAKENAMEID5('p', 't', '-', 'b', 'r'):
            case MAKENAMEID5('p', 't', '_', 'p', 't'):
            case MAKENAMEID5('p', 't', '-', 'p', 't'):
                *code = "br";
                break;

            case MAKENAMEID2('s', 'v'):
                *code = "se";
                break;

            case MAKENAMEID2('z', 'h'):
            case MAKENAMEID5('z', 'h', '_', 'c', 'n'):
            case MAKENAMEID5('z', 'h', '-', 'c', 'n'):
            case MAKENAMEID7('z', 'h', '_', 'h', 'a', 'n', 's'):
            case MAKENAMEID7('z', 'h', '-', 'h', 'a', 'n', 's'):
                *code = "cn";
                break;

            case MAKENAMEID5('z', 'h', '_', 't', 'w'):
            case MAKENAMEID5('z', 'h', '-', 't', 'w'):
            case MAKENAMEID7('z', 'h', '_', 'h', 'a', 'n', 't'):
            case MAKENAMEID7('z', 'h', '-', 'h', 'a', 'n', 't'):
                *code = "ct";
                break;

            case MAKENAMEID2('i', 'n'):
                *code = "id";
                break;

            case MAKENAMEID2('i', 'w'):
                *code = "he";
                break;

            // Not supported in the web
            case MAKENAMEID2('e', 'e'):
            case MAKENAMEID2('h', 'r'):
            case MAKENAMEID2('k', 'a'):
                break;

            default:
                LOG_debug << "Unknown language code: " << s.c_str();
                break;
        }

        if (code->size())
        {
            return true;
        }

        s.resize(s.size() - 1);
    }

    LOG_debug << "Unsupported language code: " << languageCode;
    return false;
}

void MegaApiImpl::setFileVersionsOption(bool disable, MegaRequestListener *listener)
{
    string av = disable ? "1" : "0";
    setUserAttr(MegaApi::USER_ATTR_DISABLE_VERSIONS, av.data(), listener);
}

void MegaApiImpl::getFileVersionsOption(MegaRequestListener *listener)
{
    getUserAttr(NULL, MegaApi::USER_ATTR_DISABLE_VERSIONS, NULL, 0, listener);
}

void MegaApiImpl::setContactLinksOption(bool disable, MegaRequestListener *listener)
{
    string av = disable ? "0" : "1";
    setUserAttr(MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION, av.data(), listener);
}

void MegaApiImpl::getContactLinksOption(MegaRequestListener *listener)
{
    getUserAttr(NULL, MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION, NULL, 0, listener);
}

void MegaApiImpl::retrySSLerrors(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    client->retryessl = enable;
}

void MegaApiImpl::setPublicKeyPinning(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    client->httpio->disablepkp = !enable;
}

void MegaApiImpl::pauseActionPackets()
{
    SdkMutexGuard g(sdkMutex);
    LOG_debug << "Pausing action packets";
    client->scpaused = true;
}

void MegaApiImpl::resumeActionPackets()
{
    SdkMutexGuard g(sdkMutex);
    LOG_debug << "Resuming action packets";
    client->scpaused = false;
}

node_vector MegaApiImpl::searchInNodeManager(MegaHandle ancestorHandle, const char *searchString, int mimeType, bool recursive, Node::Flags requiredFlags, Node::Flags excludeFlags, Node::Flags excludeRecursiveFlags, CancelToken cancelToken)
{
    node_vector nodeVector;

    if (!searchString || searchString[0] == '\0')
    {
        // always recursive
        assert(mimeType != MegaApi::FILE_TYPE_DEFAULT);
        nodeVector = client->mNodeManager.getNodesByMimeType(static_cast<MimeType_t>(mimeType), NodeHandle().set6byte(ancestorHandle), requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
    }
    else
    {
        nodeVector = client->mNodeManager.search(NodeHandle().set6byte(ancestorHandle), searchString, recursive, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);

        auto it = nodeVector.begin();
        while (it != nodeVector.end() && !cancelToken.isCancelled())
        {
            if (!isValidTypeNode(*it, mimeType))
            {
                it = nodeVector.erase(it);
            }
            else
            {
                ++it;
            }
        }
    }

    return nodeVector;
}

bool MegaApiImpl::isValidTypeNode(Node *node, int type)
{
    assert(node);
    if (!client)
    {
        return true;
    }

    switch (type)
    {
        case MegaApi::FILE_TYPE_PHOTO:
            return client->nodeIsPhoto(node, false);
        case MegaApi::FILE_TYPE_AUDIO:
            return client->nodeIsAudio(node);
        case MegaApi::FILE_TYPE_VIDEO:
            return client->nodeIsVideo(node);
        case MegaApi::FILE_TYPE_DOCUMENT:
            return client->nodeIsDocument(node);
        case MegaApi::FILE_TYPE_DEFAULT:
        default:
            return true;
    }
}

// map to an ACCOUNT_* value to an int that can be compared:
// free -> lite -> proi -> proii -> proiii
// the ACCOUNT_* values are out of order
// int proLevel is a MegaAccountDetails::ACCOUNT_*
static inline int orderProLevel(int proLevel)
{
    switch (proLevel)
    {
    case MegaAccountDetails::ACCOUNT_TYPE_FREE: // 0
        return -1;
    case MegaAccountDetails::ACCOUNT_TYPE_LITE: // 4
        return 0;
    default:
        return proLevel; // 1..3
    }
}

int MegaApiImpl::calcRecommendedProLevel(MegaPricing& pricing, MegaAccountDetails& details)
{
    // if this algorithm changes also have the webclient implementation updated
    int currProLevel = details.getProLevel();
    if (currProLevel == MegaAccountDetails::ACCOUNT_TYPE_BUSINESS || currProLevel == MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI)
        return currProLevel; 
        // business can not upgrade, flexi can only change to free so we do not recommend that
    int orderedCurrProLevel = orderProLevel(currProLevel); 
    uint64_t usedStorageBytes = details.getStorageUsed();
    int bestProLevel = -1;
    uint64_t bestStorageBytes = UINT64_MAX;
    for (int i = 0; i <= pricing.getNumProducts(); ++i)
    {
        // only upgrade to lite, pro1, pro2 and pro3
        int planProLevel = pricing.getProLevel(i);
        if (planProLevel < MegaAccountDetails::ACCOUNT_TYPE_PROI || planProLevel > MegaAccountDetails::ACCOUNT_TYPE_LITE)
            continue;
        // only monthly plans
        int planMonths = pricing.getMonths(i);
        if (planMonths != 1)
            continue;
        // must have enough space for user's data
        int planStorageGb = pricing.getGBStorage(i);
        if (planStorageGb < 0)
        {
            assert(!"business plan, should never happen");
            continue;
        }
        uint64_t planStorageBytes = (uint64_t)planStorageGb * (uint64_t)(1024 * 1024 * 1024);
        if (usedStorageBytes > planStorageBytes)
            continue;
        // must be an upgrade free->lite->proi->proii->proiii
        int orderedPlanProLevel = orderProLevel(planProLevel);
        if (orderedCurrProLevel >= orderedPlanProLevel)
            continue;
        // get smallest storage
        if (planStorageBytes >= bestStorageBytes)
            continue;
        bestProLevel = planProLevel;
        bestStorageBytes = planStorageBytes;
    }
    if (bestStorageBytes != UINT64_MAX)
    {
        assert(bestProLevel != -1);
        return bestProLevel;
    }
    // too much storage required
    return MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI;
}

#if defined(_WIN32) || defined(__APPLE__)

char* strcasestr(const char* string, const char* substring)
{
    int i, j;
    for (i = 0; string[i]; i++)
    {
        for (j = 0; substring[j]; j++)
        {
            unsigned char c1 = string[i + j];
            if (!c1)
                return NULL;

            unsigned char c2 = substring[j];
            if (toupper(c1) != toupper(c2))
                break;
        }

        if (!substring[j])
            return (char*)string + i;
    }
    return NULL;
}

#endif

MegaNodeList* MegaApiImpl::search(MegaNode* n, const char* searchString, CancelToken cancelToken, bool recursive, int order, int mimeType, int target, bool includeSensitive)
{
    Node::Flags requiredFlags;
    Node::Flags excludeFlags;
    Node::Flags excludeRecursiveFlags = Node::Flags().set(Node::FLAGS_IS_MARKED_SENSTIVE, !includeSensitive);
    return searchWithFlags(n, searchString, cancelToken, recursive, order, mimeType, target, requiredFlags, excludeFlags, excludeRecursiveFlags);
}

MegaNodeList* MegaApiImpl::searchWithFlags(MegaNode* n, const char* searchString, CancelToken cancelToken, bool recursive, int order, int mimeType, int target, Node::Flags requiredFlags, Node::Flags excludeFlags, Node::Flags excludeRecursiveFlags)
{
    if (!n && !searchString && (mimeType < MegaApi::FILE_TYPE_PHOTO || mimeType > MegaApi::FILE_TYPE_DOCUMENT))
    {
        // If node is not valid, and no search string, and mimeType is not valid
        return new MegaNodeListPrivate();
    }

    if (order < MegaApi::ORDER_NONE || order > MegaApi::ORDER_FAV_DESC)
    {
        return new MegaNodeListPrivate();
    }

    if (mimeType != MegaApi::FILE_TYPE_DEFAULT
            && (order >= MegaApi::ORDER_PHOTO_ASC && order <= MegaApi::ORDER_VIDEO_DESC))
    {
        return new MegaNodeListPrivate();
    }

    if (cancelToken.isCancelled())
    {
        return new MegaNodeListPrivate();
    }

    SdkMutexGuard g(sdkMutex);

    if (cancelToken.isCancelled())
    {
        return new MegaNodeListPrivate();
    }

    MegaNodeList *nodeList = nullptr;
    if (n)
    {
        assert(target == MegaApi::SEARCH_TARGET_ALL); // target ignored if node specified

        // if node is provided, it will be the parent node of the tree to explore
        Node *node = client->nodebyhandle(n->getHandle());
        if (!node)
        {
            return new MegaNodeListPrivate();
        }

        // searchString and nodeType (if provided), are considered in search
        node_vector nodeVector = searchInNodeManager(n->getHandle(), searchString, mimeType, recursive, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);

        sortByComparatorFunction(nodeVector, order, *client);
        nodeList = new MegaNodeListPrivate(nodeVector.data(), int(nodeVector.size()));
    }
    else
    {
        node_vector result;

        // Target parameter is only considered if node is not provided
        if (target < MegaApi::SEARCH_TARGET_INSHARE || target > MegaApi::SEARCH_TARGET_ALL)
        {
            return new MegaNodeListPrivate();
        }

        if (target == MegaApi::SEARCH_TARGET_ALL)
        {
            result = searchInNodeManager(UNDEF, searchString, mimeType, recursive, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
        }
        else if (target == MegaApi::SEARCH_TARGET_ROOTNODE)
        {
            // Search on rootnode (Cloud and Vault, excludes Rubbish)
            if (recursive)
            {
                Node* node = client->nodeByHandle(client->mNodeManager.getRootNodeFiles());
                if (!node)
                {
                    return new MegaNodeListPrivate();
                }
                node_vector nodeVector = searchInNodeManager(node->nodehandle, searchString, mimeType, true, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
                result.insert(result.end(), nodeVector.begin(), nodeVector.end());

                node = client->nodeByHandle(client->mNodeManager.getRootNodeVault());
                if (node) {
                    nodeVector = searchInNodeManager(node->nodehandle, searchString, mimeType, true, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
                    result.insert(result.end(), nodeVector.begin(), nodeVector.end());
                }
            }
            else
            {
                // We kept this case for compatibility with no-NOD version but it doesn't make
                // sense to search under file root node and vault root node by name
                assert(false);
                Node* node = client->nodeByHandle(client->mNodeManager.getRootNodeFiles());
                if (!node)
                {
                    return new MegaNodeListPrivate();
                }
                if (node->getMimeType() == mimeType &&
                    strcasestr(node->displayname(), searchString) != NULL &&
                    node->areFlagsValid(requiredFlags, excludeFlags, excludeRecursiveFlags))
                    {
                        result.push_back(node);
                }

                node = client->nodeByHandle(client->mNodeManager.getRootNodeVault());
                if (node &&
                    node->getMimeType() == mimeType &&
                    strcasestr(node->displayname(), searchString) != NULL &&
                    node->areFlagsValid(requiredFlags, excludeFlags, excludeRecursiveFlags))
                    {
                        result.push_back(node);
                }
            }

        }
        else if (target == MegaApi::SEARCH_TARGET_INSHARE)
        {
            // always recursive
            assert(recursive);
            // ignores mimeType, requiredFlags, excludeFlags, excludeRecursiveFlags

            // find in-shares themselves
            node_vector nodeVector = client->mNodeManager.getInSharesWithName(searchString, cancelToken);
            result.swap(nodeVector);

            // Search in each inshare
            unique_ptr<MegaShareList> shares(getInSharesList(MegaApi::ORDER_NONE));
            for (int i = 0; i < shares->size() && !cancelToken.isCancelled(); i++)
            {
                Node* node = client->nodebyhandle(shares->get(i)->getNodeHandle());
                assert(node);
                if (node)
                {
                    nodeVector = searchInNodeManager(node->nodehandle, searchString, mimeType, true, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
                    result.insert(result.end(), nodeVector.begin(), nodeVector.end());
                }
            }
        }
        else if (target == MegaApi::SEARCH_TARGET_OUTSHARE)
        {
            // always recursive
            assert(recursive);
            // ignores mimeType, requiredFlags, excludeFlags, excludeRecursiveFlags

            // find out-shares themselves
            node_vector nodeVector = client->mNodeManager.getOutSharesWithName(searchString, cancelToken);
            result.swap(nodeVector);

            // Search in each outshare
            std::set<MegaHandle> outsharesHandles;
            unique_ptr<MegaShareList> shares(getOutShares(MegaApi::ORDER_NONE));
            for (int i = 0; i < shares->size() && !cancelToken.isCancelled(); i++)
            {
                handle h = shares->get(i)->getNodeHandle();
                if (outsharesHandles.find(h) != outsharesHandles.end())
                {
                    // shares list includes an item per outshare AND per sharee/user
                    continue;   // avoid duplicates
                }
                outsharesHandles.insert(h);
                Node* node = client->nodebyhandle(shares->get(i)->getNodeHandle());
                assert(node);
                if (node)
                {
                    node_vector nodeVector = searchInNodeManager(node->nodehandle, searchString, mimeType, true, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
                    result.insert(result.end(), nodeVector.begin(), nodeVector.end());
                }
            }
        }
        else if (target == MegaApi::SEARCH_TARGET_PUBLICLINK)
        {
            // always recursive
            assert(recursive);
            // ignores mimeType, requiredFlags, excludeFlags, excludeRecursiveFlags

            // find public links themselves
            node_vector nodeVector = client->mNodeManager.getPublicLinksWithName(searchString, cancelToken);
            result.swap(nodeVector);

            // Search under each public link
            node_vector publicLinks = client->mNodeManager.getNodesWithLinks();
            for (const auto& p : publicLinks)
            {
                Node* node = client->nodebyhandle(p->nodehandle);
                assert(node);
                if (node)
                {
                    nodeVector = searchInNodeManager(node->nodehandle, searchString, mimeType, true, requiredFlags, excludeFlags, excludeRecursiveFlags, cancelToken);
                    result.insert(result.end(), nodeVector.begin(), nodeVector.end());
                }
            }
        }
        else
        {
            assert(!"no start node specified and unknown target");
        }

        sortByComparatorFunction(result, order, *client);
        nodeList = new MegaNodeListPrivate(result.data(), int(result.size()));
    }

    return nodeList;
}

long long MegaApiImpl::getSize(MegaNode *n)
{
    if(!n) return 0;

    if (n->getType() == MegaNode::TYPE_FILE)
    {
       return n->getSize();
    }

    if (n->isForeign())
    {
        MegaSizeProcessor megaSizeProcessor;
        processMegaTree(n, &megaSizeProcessor);
        return megaSizeProcessor.getTotalBytes();
    }

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(n->getHandle());
    if(!node)
    {
        return 0;
    }

    NodeCounter nodeCounter = node->getCounter();
    return nodeCounter.storage;
}

char *MegaApiImpl::getFingerprint(const char *filePath)
{
    if(!filePath) return NULL;

    auto localpath = LocalPath::fromAbsolutePath(filePath);

    auto fa = fsAccess->newfileaccess();
    if(!fa->fopen(localpath, true, false, FSLogging::logOnError))
        return NULL;

    FileFingerprint fp;
    fp.genfingerprint(fa.get());
    m_off_t size = fa->size;
    if(fp.size < 0)
        return NULL;

    string fingerprint;
    fp.serializefingerprint(&fingerprint);

    char bsize[sizeof(size)+1];
    int l = Serialize64::serialize((byte *)bsize, size);
    char *buf = new char[l * 4 / 3 + 4];
    char ssize = static_cast<char>('A' + Base64::btoa((const byte *)bsize, l, buf));

    string result(1, ssize);
    result.append(buf);
    result.append(fingerprint);
    delete [] buf;

    return MegaApi::strdup(result.c_str());
}

char *MegaApiImpl::getFingerprint(MegaNode *n)
{
    if(!n) return NULL;

    return MegaApi::strdup(n->getFingerprint());
}

void MegaApiImpl::transfer_failed(Transfer* t, const Error& e, dstime timeleft)
{
    for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++)
    {
        MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag);
        if (!transfer)
        {
            continue;
        }
        processTransferFailed(t, transfer, e, timeleft);
    }
}

char *MegaApiImpl::getFingerprint(MegaInputStream *inputStream, int64_t mtime)
{
    if(!inputStream) return NULL;

    ExternalInputStream is(inputStream);
    m_off_t size = is.size();
    if(size < 0)
        return NULL;

    FileFingerprint fp;
    fp.genfingerprint(&is, mtime);

    if(fp.size < 0)
        return NULL;

    string fingerprint;
    fp.serializefingerprint(&fingerprint);

    char bsize[sizeof(size)+1];
    int l = Serialize64::serialize((byte *)bsize, size);
    char *buf = new char[l * 4 / 3 + 4];
    char ssize = static_cast<char>('A' + Base64::btoa((const byte *)bsize, l, buf));

    string result(1, ssize);
    result.append(buf);
    result.append(fingerprint);
    delete [] buf;

    return MegaApi::strdup(result.c_str());
}

MegaNode *MegaApiImpl::getNodeByFingerprint(const char *fingerprint)
{
    if(!fingerprint) return NULL;

    SdkMutexGuard g(sdkMutex);
    return MegaNodePrivate::fromNode(getNodeByFingerprintInternal(fingerprint));
}

MegaNodeList *MegaApiImpl::getNodesByFingerprint(const char *fingerprint)
{
    unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
    if (!fp)
    {
        return new MegaNodeListPrivate();
    }

    SdkMutexGuard g(sdkMutex);
    node_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp);
    return new MegaNodeListPrivate(nodes.data(), int(nodes.size()));
}

MegaNodeList *MegaApiImpl::getNodesByOriginalFingerprint(const char *originalfingerprint, MegaNode* megaparent)
{
    SdkMutexGuard g(sdkMutex);
    Node* parent = megaparent ? client->nodebyhandle(megaparent->getHandle()) : NULL;

    if (!originalfingerprint || (megaparent && (!parent || parent->type == FILENODE)))
    {
        return new MegaNodeListPrivate();
    }

    node_vector nodes = client->mNodeManager.getNodesByOrigFingerprint(originalfingerprint, parent);
    MegaNodeList *result = new MegaNodeListPrivate(nodes.data(), int(nodes.size()));
    return result;
}

MegaNode *MegaApiImpl::getExportableNodeByFingerprint(const char *fingerprint, const char *name)
{
    MegaNode *result = NULL;

    unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
    if (!fp)
    {
        return NULL;
    }

    SdkMutexGuard g(sdkMutex);
    node_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp);
    for (auto &node : nodes)
    {
        if ((!name || !strcmp(name, node->displayname())) &&
                client->checkaccess(node, OWNER))
        {
            Node *n = node;
            while (n)
            {
                if (n->type == RUBBISHNODE)
                {
                    node = NULL;
                    break;
                }
                n = n->parent;
            }

            if (!node)
            {
                continue;
            }

            result = MegaNodePrivate::fromNode(node);
            break;
        }
    }
    return result;
}

MegaNode *MegaApiImpl::getNodeByFingerprint(const char *fingerprint, MegaNode* parent)
{
    if(!fingerprint) return NULL;

    SdkMutexGuard g(sdkMutex);
    Node *p = NULL;
    if(parent)
    {
        p = client->nodebyhandle(parent->getHandle());
    }

    return MegaNodePrivate::fromNode(getNodeByFingerprintInternal(fingerprint, p));
}

bool MegaApiImpl::hasFingerprint(const char *fingerprint)
{
    return (getNodeByFingerprintInternal(fingerprint) != NULL);
}

char *MegaApiImpl::getCRC(const char *filePath)
{
    if(!filePath) return NULL;

    auto localpath = LocalPath::fromAbsolutePath(filePath);

    auto fa = fsAccess->newfileaccess();
    if(!fa->fopen(localpath, true, false, FSLogging::logOnError))
        return NULL;

    FileFingerprint fp;
    fp.genfingerprint(fa.get());
    if(fp.size < 0)
        return NULL;

    string result;
    result.resize((sizeof fp.crc) * 4 / 3 + 4);
    result.resize(Base64::btoa((const byte *)fp.crc.data(), sizeof fp.crc, (char*)result.c_str()));
    return MegaApi::strdup(result.c_str());
}

char *MegaApiImpl::getCRCFromFingerprint(const char *fingerprint)
{
    unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
    if (!fp)
    {
        return NULL;
    }

    string result;
    result.resize((sizeof fp->crc) * 4 / 3 + 4);
    result.resize(Base64::btoa((const byte *)fp->crc.data(), sizeof fp->crc,(char*)result.c_str()));
    return MegaApi::strdup(result.c_str());
}

char *MegaApiImpl::getCRC(MegaNode *n)
{
    if(!n) return NULL;

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(n->getHandle());
    if(!node || node->type != FILENODE || node->size < 0 || !node->isvalid)
    {
        return NULL;
    }

    string result;
    result.resize((sizeof node->crc) * 4 / 3 + 4);
    result.resize(Base64::btoa((const byte *)node->crc.data(), sizeof node->crc.data(), (char*)result.c_str()));
    return MegaApi::strdup(result.c_str());
}

MegaNode *MegaApiImpl::getNodeByCRC(const char *crc, MegaNode *parent)
{
    if(!parent) return NULL;

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(parent->getHandle());
    if(!node || node->type == FILENODE)
    {
        return NULL;
    }

    byte binarycrc[sizeof(node->crc)];
    Base64::atob(crc, binarycrc, sizeof(binarycrc));

    node_list nodeList = client->getChildren(node);
    for (node_list::iterator it = nodeList.begin(); it != nodeList.end(); it++)
    {
        Node *child = (*it);
        if(!memcmp(child->crc.data(), binarycrc, sizeof(node->crc)))
        {
            MegaNode *result = MegaNodePrivate::fromNode(child);
            return result;
        }
    }
    return NULL;
}

void MegaApiImpl::file_added(File *f)
{
    Transfer *t = f->transfer;
    MegaTransferPrivate *transfer = currentTransfer;
    if (!transfer)
    {
        transfer = new MegaTransferPrivate(t->type);
        transfer->setSyncTransfer(true);

        if (t->type == GET)
        {
            transfer->setNodeHandle(f->h.as8byte());
        }
        else
        {
#ifdef ENABLE_SYNC
            LocalNode *ll = dynamic_cast<LocalNode *>(f);
            if (ll && ll->parent && ll->parent->node)
            {
                transfer->setParentHandle(ll->parent->node->nodehandle);
            }
            else
#endif
            {
                transfer->setParentHandle(f->h.as8byte());
            }
        }


        string path;
#ifdef ENABLE_SYNC
        LocalNode *l = dynamic_cast<LocalNode *>(f);
        if (l)
        {
            path = l->getLocalPath().toPath(false);
        }
        else
#endif
        {
            path = f->getLocalname().toPath(false);
        }
        transfer->setPath(path.c_str());
    }

    currentTransfer = NULL;
    transfer->setTransfer(t);
    transfer->setState(t->state);
    transfer->setPriority(t->priority);
    transfer->setTotalBytes(t->size);
    transfer->setTransferredBytes(t->progresscompleted);
    transfer->setTag(f->tag);
    transferMap[f->tag] = transfer;

    if (t->type == GET)
    {
        totalDownloads++;
        pendingDownloads++;
        totalDownloadBytes += t->size;
        totalDownloadedBytes += t->progresscompleted;
    }
    else
    {
        totalUploads++;
        pendingUploads++;
        totalUploadBytes += t->size;
        totalUploadedBytes += t->progresscompleted;
    }

    fireOnTransferStart(transfer);
}

void MegaApiImpl::file_removed(File *f, const Error &e)
{
    MegaTransferPrivate* transfer = getMegaTransferPrivate(f->tag);
    if (transfer)
    {
        processTransferRemoved(f->transfer, transfer, e);
    }
}

void MegaApiImpl::file_complete(File *f)
{
    MegaTransferPrivate* transfer = getMegaTransferPrivate(f->tag);
    if (transfer)
    {
        if (f->transfer->type == GET)
        {
            // The final name can change when downloads are complete
            // if there is another file in the same path

            string path = f->getLocalname().toPath(false);
            transfer->setPath(path.c_str());
        }

        processTransferComplete(f->transfer, transfer);
    }
}

void MegaApiImpl::transfer_complete(Transfer *t)
{
    MegaTransferPrivate* transfer = getMegaTransferPrivate(t->tag);
    if (transfer)
    {
        transfer->setTransfer(nullptr);
    }
}

void MegaApiImpl::transfer_removed(Transfer *t)
{
    MegaTransferPrivate* transfer = getMegaTransferPrivate(t->tag);
    if (transfer)
    {
        transfer->setTransfer(nullptr);
    }
}
void MegaApiImpl::transfer_prepare(Transfer *t)
{
    for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++)
    {
        MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag);
        if (!transfer)
        {
            continue;
        }
        processTransferPrepare(t, transfer);
    }
}

void MegaApiImpl::transfer_update(Transfer *t)
{
    for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++)
    {
        MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag);
        if (!transfer)
        {
            continue;
        }

        if (it == t->files.begin()
                && transfer->getUpdateTime() == Waiter::ds
                && transfer->getState() == t->state
                && transfer->getPriority() == t->priority
                && (!t->slot
                    || (t->slot->progressreported
                        && t->slot->progressreported != t->size)))
        {
            // don't send more than one callback per decisecond
            // if the state doesn't change, the priority doesn't change
            // and there isn't anything new or it's not the first
            // nor the last callback
            return;
        }

        processTransferUpdate(t, transfer);
    }
}

File *MegaApiImpl::file_resume(string *d, direction_t *type)
{
    if (!d || d->size() < sizeof(char))
    {
        return NULL;
    }

    MegaFile *file = NULL;
    *type = (direction_t)MemAccess::get<char>(d->data());
    switch (*type)
    {
    case GET:
    {
        file = MegaFileGet::unserialize(d);
        break;
    }
    case PUT:
    {
        file = MegaFilePut::unserialize(d);
        if (!file)
        {
            break;
        }
        MegaTransferPrivate* transfer = file->getTransfer();
        Node *parent = client->nodebyhandle(transfer->getParentHandle());
        node_vector nodes = client->mNodeManager.getNodesByFingerprint(*file);
        const char *name = transfer->getFileName();
        if (parent && nodes.size() && name)
        {
            // Get previous node if any
            file->previousNode = client->childnodebyname(parent, name, true);
            for (auto &node : nodes)
            {
                if (node->parent == parent && !strcmp(node->displayname(), name))
                {
                    // don't resume the upload if the node already exist in the target folder
                    TransferDbCommitter committer(client->tctable);
                    delete file;
                    delete transfer;   // committer needed here
                    file = NULL;
                    break;
                }
            }
        }
        break;
    }
    default:
        break;
    }

    if (file)
    {
        currentTransfer = file->getTransfer();
        waiter->notify();
    }
    return file;
}

dstime MegaApiImpl::pread_failure(const Error &e, int retry, void* param, dstime timeLeft)
{
    MegaTransferPrivate *transfer = (MegaTransferPrivate *)param;

    if (!transfer)
    {
        LOG_warn << "pread_failure: transfer is invalid";
        return NEVER;
    }

    transfer->setUpdateTime(Waiter::ds);
    transfer->setDeltaSize(0);
    transfer->setSpeed(0);
    transfer->setMeanSpeed(0);
    transfer->setLastBytes(NULL);
    if (retry <= transfer->getMaxRetries() && e != API_EINCOMPLETE && !(e == API_ETOOMANY && e.hasExtraInfo()))
    {
        auto megaError = make_unique<MegaErrorPrivate>(e, timeLeft / 10);
        transfer->setLastError(megaError.get());
        transfer->setState(MegaTransfer::STATE_RETRYING);
        fireOnTransferTemporaryError(transfer, std::move(megaError));
        LOG_debug << "Streaming temporarily failed " << retry;
        if (retry <= 1)
        {
            return 0;
        }

        return (dstime)(1 << (retry - 1));
    }
    else
    {
        if (e && (e != API_EINCOMPLETE || (e == API_ETOOMANY && e.hasExtraInfo())))
        {
            transfer->setState(MegaTransfer::STATE_FAILED);
        }
        else
        {
            transfer->setState(MegaTransfer::STATE_COMPLETED);
        }
        fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(e));
        return NEVER;
    }
}

bool MegaApiImpl::pread_data(byte *buffer, m_off_t len, m_off_t, m_off_t speed, m_off_t meanSpeed, void* param)
{
    MegaTransferPrivate *transfer = (MegaTransferPrivate *)param;
    LOG_verbose << "Read new data received from transfer: len = " << len << ", speed = " << (speed/1024) << " KB/s, meanSpeed = " << (meanSpeed/1024) << " KB/s, total transferred bytes = " << transfer->getTransferredBytes() << "";
    dstime currentTime = Waiter::ds;
    transfer->setStartTime(currentTime);
    transfer->setState(MegaTransfer::STATE_ACTIVE);
    transfer->setUpdateTime(currentTime);
    transfer->setDeltaSize(len);
    transfer->setLastBytes((char *)buffer);
    transfer->setTransferredBytes(transfer->getTransferredBytes() + len);
    transfer->setSpeed(speed);
    transfer->setMeanSpeed(meanSpeed);

    bool end = (transfer->getTransferredBytes() == transfer->getTotalBytes());
    fireOnTransferUpdate(transfer);
    if (!fireOnTransferData(transfer) || end)
    {
        transfer->setState(end ? MegaTransfer::STATE_COMPLETED : MegaTransfer::STATE_CANCELLED);
        fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(end ? API_OK : API_EINCOMPLETE));
        return end;
    }
    return true;
}

void MegaApiImpl::reportevent_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_REPORT_EVENT)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::sessions_killed(handle, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_KILL_SESSION)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::cleanrubbishbin_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CLEAN_RUBBISH_BIN)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getrecoverylink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_GET_RECOVERY_LINK) &&
                    (request->getType() != MegaRequest::TYPE_GET_CANCEL_LINK))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::queryrecoverylink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_QUERY_RECOVERY_LINK) &&
                    (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) &&
                    (request->getType() != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::queryrecoverylink_result(int type, const char *email, const char *ip, time_t, handle uh, const vector<string> *)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    int reqType = request->getType();
    if(!request || ((reqType != MegaRequest::TYPE_QUERY_RECOVERY_LINK) &&
                    (reqType != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) &&
                    (reqType != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK))) return;

    request->setEmail(email);
    request->setFlag(type == RECOVER_WITH_MASTERKEY);
    request->setNumber(type);   // not specified in MegaApi documentation
    request->setText(ip);       // not specified in MegaApi documentation
    request->setNodeHandle(uh); // not specified in MegaApi documentation

    if (reqType == MegaRequest::TYPE_QUERY_RECOVERY_LINK)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>());
        return;
    }
    else if (reqType == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK)
    {
        int creqtag = client->reqtag;
        client->reqtag = client->restag;
        client->prelogin(email);
        client->reqtag = creqtag;
        return;
    }
    else if (reqType == MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK)
    {
        if (type != CHANGE_EMAIL)
        {
            LOG_debug << "Unknown type of change email link";

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
            return;
        }

        const char* code;
        if ((code = strstr(request->getLink(), MegaClient::verifyLinkPrefix())))
        {
            code += strlen(MegaClient::verifyLinkPrefix());

            if (!checkPassword(request->getPassword()))
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
                return;
            }

            int creqtag = client->reqtag;
            client->reqtag = client->restag;
            if (client->accountversion == 1)
            {
                byte pwkey[SymmCipher::KEYLENGTH];
                client->pw_key(request->getPassword(), pwkey);
                client->confirmemaillink(code, request->getEmail(), pwkey);
            }
            else if (client->accountversion == 2)
            {
                client->confirmemaillink(code, request->getEmail(), NULL);
            }
            else
            {
                LOG_warn << "Version of account not supported";
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EINTERNAL));
            }
            client->reqtag = creqtag;
        }
        else
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
        }
    }
}

void MegaApiImpl::getprivatekey_result(error e, const byte *privk, const size_t len_privk)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK && request->getType() != MegaRequest::TYPE_CHECK_RECOVERY_KEY)) return;

    if (e)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    const char *link = request->getLink();
    const char* code;
    if ((code = strstr(link, MegaClient::recoverLinkPrefix())))
    {
        code += strlen(MegaClient::recoverLinkPrefix());
    }
    else
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
        return;
    }

    byte mk[SymmCipher::KEYLENGTH];
    Base64::atob(request->getPrivateKey(), mk, sizeof mk);

    // check the private RSA is valid after decryption with master key
    SymmCipher key;
    key.setkey(mk);

    byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2];
    memcpy(privkbuf, privk, len_privk);
    key.ecb_decrypt(privkbuf, len_privk);

    AsymmCipher uk;
    if (!uk.setkey(AsymmCipher::PRIVKEY, privkbuf, int(len_privk)))
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EKEY));
        return;
    }

    if (request->getType() == MegaRequest::TYPE_CHECK_RECOVERY_KEY) {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
        return;
    }

    int creqtag = client->reqtag;
    client->reqtag = client->restag;
    client->confirmrecoverylink(code, request->getEmail(), request->getPassword(), mk, request->getParamType());
    client->reqtag = creqtag;
}

void MegaApiImpl::confirmrecoverylink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::confirmcancellink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_CANCEL_LINK)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getemaillink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::resendverificationemail_result(error e)
{
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end()) return;
    MegaRequestPrivate *request = it->second;
    if (!request || ((request->getType() != MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::resetSmsVerifiedPhoneNumber_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate *request = requestMap.at(client->restag);
    if (!request || (request->getType() != MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::confirmemaillink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getversion_result(int versionCode, const char *versionString, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_APP_VERSION)) return;

    if (!e)
    {
        request->setNumber(versionCode);
        request->setName(versionString);
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getlocalsslcertificate_result(m_time_t ts, string *certdata, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_LOCAL_SSL_CERT)) return;

    if (!e)
    {
        string result;
        const char *data = certdata->data();
        const char *enddata = certdata->data() + certdata->size();
        MegaStringMapPrivate *datamap = new MegaStringMapPrivate();
        for (int i = 0; data < enddata; i++)
        {
            result = i ? "-----BEGIN CERTIFICATE-----\n"
                       : "-----BEGIN RSA PRIVATE KEY-----\n";

            const char *end = strstr(data, ";");
            if (!end)
            {
                if (!i)
                {
                    delete datamap;
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EINTERNAL));
                    return;
                }
                end = enddata;
            }

            while (data < end)
            {
                int remaining = int(end - data);
                int dataSize = (remaining > 64) ? 64 : remaining;
                result.append(data, dataSize);
                result.append("\n");
                data += dataSize;
            }

            switch (i)
            {
                case 0:
                {
                    result.append("-----END RSA PRIVATE KEY-----\n");
                    datamap->set("key", result.c_str());
                    break;
                }
                case 1:
                {
                    result.append("-----END CERTIFICATE-----\n");
                    datamap->set("cert", result.c_str());
                    break;
                }
                default:
                {
                    result.append("-----END CERTIFICATE-----\n");
                    std::ostringstream oss;
                    oss << "intermediate_" << (i - 1);
                    datamap->set(oss.str().c_str(), result.c_str());
                    break;
                }
            }
            data++;
        }

        request->setNumber(ts);
        request->setMegaStringMap(datamap);
        delete datamap;
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getmegaachievements_result(AchievementsDetails *, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_ACHIEVEMENTS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::mediadetection_ready()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MEDIA_INFO_READY);
    fireOnEvent(event);
}

void MegaApiImpl::storagesum_changed(int64_t newsum)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_STORAGE_SUM_CHANGED);
    event->setNumber(newsum);
    fireOnEvent(event);
}

void MegaApiImpl::getmiscflags_result(error e)
{
    if (e == API_OK)
    {
        MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MISC_FLAGS_READY);
        fireOnEvent(event);
    }

    if (requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || (request->getType() != MegaRequest::TYPE_GET_MISC_FLAGS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

#ifdef ENABLE_CHAT

void MegaApiImpl::chatcreate_result(TextChat *chat, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_CREATE)) return;

    if (!e)
    {
        // encapsulate the chat in a list for the request
        textchat_map chatList;
        chatList[chat->id] = chat;

        auto megaChatList = mega::make_unique<MegaTextChatListPrivate>(&chatList);
        request->setMegaTextChatList(megaChatList.get());

        if (request->getMegaScheduledMeetingList() && request->getMegaScheduledMeetingList()->size())
        {
           const map<handle/*schedId*/, std::unique_ptr<ScheduledMeeting>>& schedmap = chat->getSchedMeetings();
           if (!schedmap.empty())
           {
               if (schedmap.size() > 1)
               {
                    LOG_warn << "Scheduled meeting list contains more than 1 element for a new chatroom";
               }
               std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
               for (auto const& sm: schedmap)
               {
                   l->insert(new MegaScheduledMeetingPrivate(sm.second.get()));
               }
               request->setMegaScheduledMeetingList(l.get());
           }
        }
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatinvite_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_INVITE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatremove_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_REMOVE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chaturl_result(string *url, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_URL)) return;

    if (!e)
    {
        request->setLink(url->c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatgrantaccess_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_GRANT_ACCESS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatremoveaccess_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_REMOVE_ACCESS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatupdatepermissions_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chattruncate_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_TRUNCATE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatsettitle_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_SET_TITLE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatpresenceurl_result(string *url, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_PRESENCE_URL)) return;

    if (!e)
    {
        request->setLink(url->c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::registerpushnotification_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::archivechat_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_ARCHIVE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::setchatretentiontime_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate *request = requestMap.at(client->restag);
    if (!request || (request->getType() != MegaRequest::TYPE_SET_RETENTION_TIME)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chats_updated(textchat_map *chats, int count)
{
    if (chats)
    {
        MegaTextChatList *chatList = new MegaTextChatListPrivate(chats);
        fireOnChatsUpdate(chatList);
        delete chatList;
    }
    else
    {
        fireOnChatsUpdate(NULL);
    }
}

void MegaApiImpl::richlinkrequest_result(string *richLink, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_RICH_LINK)) return;

    if (!e)
    {
        request->setText(richLink->c_str());
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatlink_result(handle h, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_LINK_HANDLE)) return;

    if (!e && !request->getFlag())
    {
        request->setParentHandle(h);
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatlinkurl_result(handle chatid, int shard, string *link, string *ct, int numPeers, m_time_t ts, bool meetingRoom, handle callid, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CHAT_LINK_URL)) return;

    if (!e)
    {
        request->setLink(link->c_str());
        request->setAccess(shard);
        request->setParentHandle(chatid);
        request->setText(ct->c_str());
        request->setNumDetails(numPeers);
        request->setNumber(ts);
        request->setFlag(meetingRoom);
        if (callid != INVALID_HANDLE)
        {
            std::vector<MegaHandle> handleList;
            handleList.push_back(callid);
            request->setMegaHandleList(handleList);
        }
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatlinkclose_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SET_PRIVATE_MODE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::chatlinkjoin_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_AUTOJOIN_PUBLIC_CHAT)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

#endif

void MegaApiImpl::folderlinkinfo_result(error e, handle owner, handle /*ph*/, string *attr, string* k, m_off_t currentSize, uint32_t numFiles, uint32_t numFolders, m_off_t versionsSize, uint32_t numVersions)
{
    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
            || request->getType() != MegaRequest::TYPE_PUBLIC_LINK_INFORMATION) return;

    if (e == API_OK)
    {
        // Decrypt nodekey with the key of the folder link
        SymmCipher cipher;
        byte folderkey[SymmCipher::KEYLENGTH];
        Base64::atob(request->getPrivateKey(), folderkey, sizeof(folderkey));
        cipher.setkey(folderkey);
        const char *nodekeystr = k->data() + 9;    // skip the userhandle(8) and the `:`
        byte nodekey[FOLDERNODEKEYLENGTH];
        if (client->decryptkey(nodekeystr, nodekey, sizeof(nodekey), &cipher, 0, UNDEF))
        {
            // Decrypt node attributes with the nodekey
            cipher.setkey(nodekey);
            byte* buf = Node::decryptattr(&cipher, attr->c_str(), attr->size());
            if (buf)
            {
                AttrMap attrs;
                string fileName;
                string fingerprint;
                FileFingerprint ffp;
                m_time_t mtime = 0;
                Node::parseattr(buf, attrs, currentSize, mtime, fileName, fingerprint, ffp);

                // Normalize node name to UTF-8 string
                attr_map::iterator it = attrs.map.find('n');
                if (it != attrs.map.end() && !it->second.empty())
                {
                    LocalPath::utf8_normalize(&(it->second));
                    fileName = it->second.c_str();
                }

                MegaFolderInfoPrivate *folderInfo = new MegaFolderInfoPrivate(numFiles, numFolders - 1, numVersions, currentSize, versionsSize);
                request->setMegaFolderInfo(folderInfo);
                request->setParentHandle(owner);
                request->setText(fileName.c_str());

                delete folderInfo;
                delete [] buf;
            }
            else
            {
                LOG_err << "Error decrypting node attributes with decrypted nodekey";
                e = API_EKEY;
            }
        }
        else
        {
            LOG_err << "Error decrypting nodekey with folder link key";
            e = API_EKEY;
        }
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

#ifdef ENABLE_SYNC

MegaSyncPrivate* MegaApiImpl::cachedMegaSyncPrivateByBackupId(const SyncConfig& config)
{
    if (mCachedMegaSyncPrivate && config.mBackupId == mCachedMegaSyncPrivate->getBackupId())
    {
        return mCachedMegaSyncPrivate.get();
    }

    mCachedMegaSyncPrivate.reset(new MegaSyncPrivate(config, client));
    return mCachedMegaSyncPrivate.get();
}

void MegaApiImpl::syncupdate_stateconfig(const SyncConfig& config)
{
    mCachedMegaSyncPrivate.reset();

    if (auto megaSync = cachedMegaSyncPrivateByBackupId(config))
    {
        fireOnSyncStateChanged(megaSync);
    }
}

void MegaApiImpl::syncupdate_stats(handle backupId, const PerSyncStats& stats)
{
    MegaSyncStatsPrivate msp(backupId, stats);
    fireOnSyncStatsUpdated(&msp);
}
void MegaApiImpl::syncupdate_scanning(bool scanning)
{
    if(client)
    {
        client->abortbackoff(false);
        client->syncscanstate = scanning;
    }
    fireOnGlobalSyncStateChanged();
}

void MegaApiImpl::syncupdate_syncing(bool syncing)
{
    fireOnGlobalSyncStateChanged();
}

void MegaApiImpl::syncupdate_treestate(const SyncConfig &config, const LocalPath& lp, treestate_t ts, nodetype_t)
{
    if (auto megaSync = cachedMegaSyncPrivateByBackupId(config))
    {
        string s = lp.platformEncoded();

        fireOnFileSyncStateChanged(megaSync, &s, (int)ts);
    }
}

bool MegaApiImpl::sync_syncable(Sync *sync, const char *name, LocalPath& localpath, Node *node)
{
    if (!sync || (node->type == FILENODE && !is_syncable(node->size)))
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    return is_syncable(sync, name, localpath);
}

bool MegaApiImpl::sync_syncable(Sync *sync, const char *name, LocalPath& localpath)
{
    {
        std::lock_guard<std::mutex> g(mSyncable_fa_mutex);
        if (!mSyncable_fa)
        {
            mSyncable_fa = fsAccess->newfileaccess();
        }
        if (!sync || ((syncLowerSizeLimit || syncUpperSizeLimit)
                && mSyncable_fa->fopen(localpath, FSLogging::logOnError) && !is_syncable(mSyncable_fa->size)))
        {
            return false;
        }
    }

    SdkMutexGuard g(sdkMutex);
    return is_syncable(sync, name, localpath);
}

void MegaApiImpl::sync_removed(const SyncConfig& config)
{
    auto msp_ptr = ::mega::make_unique<MegaSyncPrivate>(config, client);
    fireOnSyncDeleted(msp_ptr.get());
}

void MegaApiImpl::sync_added(const SyncConfig& config)
{
    mCachedMegaSyncPrivate.reset();

    auto megaSync = cachedMegaSyncPrivateByBackupId(config);

    fireOnSyncAdded(megaSync);
}

void MegaApiImpl::syncupdate_remote_root_changed(const SyncConfig &config)
{
    mCachedMegaSyncPrivate.reset();

    if (auto megaSync = cachedMegaSyncPrivateByBackupId(config))
    {
        fireOnSyncStateChanged(megaSync);
    }
}

void MegaApiImpl::syncs_restored(SyncError syncError)
{
    mCachedMegaSyncPrivate.reset();
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_SYNCS_RESTORED);
    event->setNumber(syncError);
    fireOnEvent(event);
}

void MegaApiImpl::syncs_disabled(SyncError syncError)
{
    mCachedMegaSyncPrivate.reset();
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_SYNCS_DISABLED);
    event->setNumber(syncError);
    fireOnEvent(event);
}

void MegaApiImpl::syncupdate_local_lockretry(bool waiting)
{
    if (waiting)
    {
        LOG_debug << "Sync - waiting for local filesystem lock";
    }
    else
    {
        LOG_debug << "Sync - local filesystem lock issue resolved, continuing...";
        client->abortbackoff(false);
    }

    this->fireOnGlobalSyncStateChanged();
}
#endif

void MegaApiImpl::backupput_result(const Error& e, handle backupId)
{
    if (requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || (request->getType() != MegaRequest::TYPE_BACKUP_PUT)) return;

    request->setParentHandle(backupId);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

// user addition/update (users never get deleted)
void MegaApiImpl::users_updated(User** u, int count)
{
    if(!count)
    {
        return;
    }

    MegaUserList *userList = NULL;
    if(u != NULL)
    {
        userList = new MegaUserListPrivate(u, count);
        fireOnUsersUpdate(userList);
    }
    else
    {
        fireOnUsersUpdate(NULL);
    }
    delete userList;
}

void MegaApiImpl::useralerts_updated(UserAlert::Base** b, int count)
{
    if (count)
    {
        MegaUserAlertList *userAlertList = b ? new MegaUserAlertListPrivate(b, count, client) : NULL;
        fireOnUserAlertsUpdate(userAlertList);
        delete userAlertList;
    }
}

void MegaApiImpl::account_updated()
{
    fireOnAccountUpdate();
}

void MegaApiImpl::sets_updated(Set** sets, int count)
{
    LOG_debug << "Sets updated: " << count;
    if (!count)
    {
        return;
    }

    if (sets)
    {
        unique_ptr<MegaSetListPrivate> sList(new MegaSetListPrivate(sets, count));
        fireOnSetsUpdate(sList.get());
    }
    else
    {
        fireOnSetsUpdate(nullptr);
    }
}

void MegaApiImpl::setelements_updated(SetElement** elements, int count)
{
    LOG_debug << "Elements updated: " << count;
    if (!count)
    {
        return;
    }

    if (elements)
    {
        unique_ptr<MegaSetElementListPrivate> eList(new MegaSetElementListPrivate(elements, count));
        fireOnSetElementsUpdate(eList.get());
    }
    else
    {
        fireOnSetElementsUpdate(nullptr);
    }
}

void MegaApiImpl::pcrs_updated(PendingContactRequest **r, int count)
{
    if(!count)
    {
        return;
    }

    MegaContactRequestList *requestList = NULL;
    if(r != NULL)
    {
        requestList = new MegaContactRequestListPrivate(r, count);
        fireOnContactRequestsUpdate(requestList);
    }
    else
    {
        fireOnContactRequestsUpdate(NULL);
    }
    delete requestList;
}

void MegaApiImpl::unlink_result(handle h, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_REMOVE) &&
                    (request->getType() != MegaRequest::TYPE_MOVE)))
    {
        return;
    }

#ifdef ENABLE_SYNC
    client->syncdownrequired = true;
#endif

    if (request->getType() != MegaRequest::TYPE_MOVE)
    {
        request->setNodeHandle(h);
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::unlinkversions_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }

    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || request->getType() != MegaRequest::TYPE_REMOVE_VERSIONS)
    {
        return;
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::fetchnodes_result(const Error &e)
{
    MegaRequestPrivate* request = NULL;
    if (!client->restag)
    {
        for (map<int, MegaRequestPrivate *>::iterator it = requestMap.begin(); it != requestMap.end(); it++)
        {
            if (it->second->getType() == MegaRequest::TYPE_FETCH_NODES)
            {
                request = it->second;
                break;
            }
        }
        if (!request)
        {
            request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_NODES);
            //request->performRequest not required as not put to requestQueue
        }

        if (e == API_OK)
        {
            assert(!client->mNodeManager.getRootNodeFiles().isUndef());    // is folder link fetched properly?

            request->setNodeHandle(client->getFolderLinkPublicHandle());
            if (!client->isValidFolderLink())    // is the key for the folder link invalid?
            {
                request->setFlag(true);
            }
        }

        if (!e && client->loggedin() == FULLACCOUNT && client->isNewSession)
        {
            updatePwdReminderData(false, false, false, false, true);
            client->isNewSession = false;
        }

        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_FETCH_NODES) &&
                    (request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT)))
    {
        return;
    }

    if (request->getType() == MegaRequest::TYPE_FETCH_NODES)
    {
        if (e == API_OK)
        {
            assert(!client->mNodeManager.getRootNodeFiles().isUndef());    // is folder link fetched properly?

            request->setNodeHandle(client->getFolderLinkPublicHandle());
            if (!client->isValidFolderLink())    // is the key for the folder link invalid?
            {
                request->setFlag(true);
            }
        }

        if (!e && client->loggedin() == FULLACCOUNT && client->isNewSession)
        {
            updatePwdReminderData(false, false, false, false, true);
            client->isNewSession = false;
        }

        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
    }
    else    // TYPE_CREATE_ACCOUNT
    {
        if (e != API_OK || request->getParamType() == MegaApi::RESUME_ACCOUNT)   // resuming ephemeral session
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            return;
        }
        else    // new account has been created
        {
            // set names silently...
            int creqtag = client->reqtag;
            client->reqtag = 0;
            string firstname = request->getName() ? request->getName() : "";
            if (!firstname.empty())
            {
                client->putua(ATTR_FIRSTNAME, (const byte*) request->getName(), int(strlen(request->getName())), -1,
                              request->getNodeHandle(), request->getAccess(), request->getTransferredBytes());
            }
            string lastname = request->getText() ? request->getText() : "";
            if (!lastname.empty())
            {
                client->putua(ATTR_LASTNAME, (const byte*) request->getText(), int(strlen(request->getText())), -1);
            }
            client->reqtag = creqtag;   // restore current reqtag, for future requests

            // Ephemeral++ don't have an email when account is created, so cannot send a signup link -> account is ready
            if (request->getParamType() == MegaApi::CREATE_EPLUSPLUS_ACCOUNT)   // creation of account E++
            {
                // import the PDF silently... (not chained)
                client->getwelcomepdf();

                // The session id cannot follow the same pattern, since no password is provided (yet)
                // In consequence, the session resumption requires a regular session id (instead of the
                // usual one with the user's handle and the pwd cipher)
                string sid;
                client->dumpsession(sid);
                request->setPrivateKey(sid.c_str());

                fireOnRequestFinish(request,  make_unique<MegaErrorPrivate>(e));
                return;
            }

            // Ephemeral accounts have an email -> send signup link
            if (!request->getPrivateKey()) // ...and finally send confirmation link
            {
                string fullname = firstname + lastname;
                string derivedKey = client->sendsignuplink2(request->getEmail(), request->getPassword(), fullname.c_str(), client->restag); // Use the tag where the request belongs
                string b64derivedKey;
                Base64::btoa(derivedKey, b64derivedKey);
                request->setPrivateKey(b64derivedKey.c_str());

                char buf[SymmCipher::KEYLENGTH * 4 / 3 + 3];
                Base64::btoa((byte*) &client->me, sizeof client->me, buf);
                string sid;
                sid.append(buf);
                sid.append("#");
                Base64::btoa((byte *)derivedKey.data(), SymmCipher::KEYLENGTH, buf);
                sid.append(buf);
                request->setSessionKey(sid.c_str());
                return;
            }
        }
    }
}

void MegaApiImpl::putnodes_result(const Error& inputErr, targettype_t t, vector<NewNode>& nn, bool targetOverride, int tag)
{
    handle h = UNDEF;
    Node *n = NULL;
    Error e = inputErr;

    if (!e && t != USER_HANDLE)
    {
        assert(!nn.empty() && nn.front().added && nn.front().mAddedHandle != UNDEF);
        n = client->nodebyhandle(nn.front().mAddedHandle);

        if(n)
        {
            n->applykey();
            n->setattr();
            h = n->nodehandle;
        }
    }

    MegaTransferPrivate* transfer = getMegaTransferPrivate(tag);
    if (transfer)
    {
        if (transfer->getType() == MegaTransfer::TYPE_DOWNLOAD)
        {
            return;
        }

        if(pendingUploads > 0)
        {
            pendingUploads--;
        }

        //scale to get the handle of the new node
        Node *ntmp;
        if (n)
        {
            handle ph = transfer->getParentHandle();
            for (ntmp = n; ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != ph) ); ntmp = ntmp->parent);
            if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == ph))
            {
                h = ntmp->nodehandle;
            }
        }

        transfer->setNodeHandle(h);
        transfer->setTargetOverride(targetOverride);
        LOG_verbose << "Set transferred bytes to transfer total bytes: " << transfer->getTotalBytes() << " (prev transferredBytes = " << transfer->getTransferredBytes() << ")";
        transfer->setTransferredBytes(transfer->getTotalBytes());

        if (!e)
        {
            transfer->setState(MegaTransfer::STATE_COMPLETED);
        }
        else
        {
            transfer->setState(MegaTransfer::STATE_FAILED);
            transfer->setForeignOverquota(e == API_EOVERQUOTA && client->isForeignNode(NodeHandle().set6byte(transfer->getParentHandle())));
        }

        fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(e));
        return;
    }

    auto reqIt = requestMap.find(tag);
    MegaRequestPrivate* request = reqIt == requestMap.end() ? nullptr : reqIt->second;
    if(!request || ((request->getType() != MegaRequest::TYPE_IMPORT_LINK) &&
                    (request->getType() != MegaRequest::TYPE_CREATE_FOLDER) &&
                    (request->getType() != MegaRequest::TYPE_COPY) &&
                    (request->getType() != MegaRequest::TYPE_MOVE) &&
                    (request->getType() != MegaRequest::TYPE_RESTORE) &&
                    (request->getType() != MegaRequest::TYPE_ADD_SYNC) &&
                    (request->getType() != MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD))) return;

#ifdef ENABLE_SYNC
    client->syncdownrequired = true;
#endif

    if (request->getType() == MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD)
    {
        request->setNodeHandle(h);
        request->setFlag(targetOverride);
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if (request->getType() == MegaRequest::TYPE_MOVE || request->getType() == MegaRequest::TYPE_COPY)
    {
        //scale to get the handle of the moved/copied node
        Node *ntmp;
        if (n)
        {
            for (ntmp = n; ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != request->getParentHandle()) ); ntmp = ntmp->parent);
            if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == request->getParentHandle()))
            {
                h = ntmp->nodehandle;
            }
        }
    }

    if (request->getType() != MegaRequest::TYPE_MOVE)
    {
        request->setNodeHandle(h);
        request->setFlag(targetOverride);
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
    }
    else
    {
        if (!e)
        {
            Node * node = client->nodebyhandle(request->getNodeHandle());
            if (!node)
            {
                e = API_ENOENT;
            }
            else
            {
                request->setNodeHandle(h);
                request->setFlag(targetOverride);
                e = client->unlink(node, false, request->getTag(), false);
            }
        }

        if (e)
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::setpcr_result(handle h, error e, opcactions_t action)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || request->getType() != MegaRequest::TYPE_INVITE_CONTACT) return;

    if (e)
    {
        LOG_debug << "Outgoing pending contact request failed (" << MegaError::getErrorString(e) << ")";
    }
    else
    {
        switch (action)
        {
            case OPCA_DELETE:
                LOG_debug << "Outgoing pending contact request deleted successfully";
                break;
            case OPCA_REMIND:
                LOG_debug << "Outgoing pending contact request reminded successfully";
                break;
            case OPCA_ADD:
                char buffer[12];
                Base64::btoa((byte*)&h, MegaClient::PCRHANDLE, buffer);
                LOG_debug << "Outgoing pending contact request succeeded, id: " << buffer;
                break;
        }
    }

    request->setNodeHandle(h);
    request->setNumber(action);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::updatepcr_result(error e, ipcactions_t action)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || request->getType() != MegaRequest::TYPE_REPLY_CONTACT_REQUEST) return;

    if (e)
    {
        LOG_debug << "Incoming pending contact request update failed (" << MegaError::getErrorString(e) << ")";
    }
    else
    {
        string labels[3] = {"accepted", "denied", "ignored"};
        LOG_debug << "Incoming pending contact request successfully " << labels[(int)action];
    }

    request->setNumber(action);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::fa_complete(handle, fatype, const char* data, uint32_t len)
{
    int tag = client->restag;
    while(tag)
    {
        if(requestMap.find(tag) == requestMap.end()) return;
        MegaRequestPrivate* request = requestMap.at(tag);
        if(!request || (request->getType() != MegaRequest::TYPE_GET_ATTR_FILE)) return;

        tag = int(request->getNumber());

        auto f = client->fsaccess->newfileaccess();
        string filePath(request->getFile());
        auto localPath = LocalPath::fromAbsolutePath(filePath);
        fsAccess->unlinklocal(localPath);

        bool success = f->fopen(localPath, false, true, FSLogging::logOnError)
                    && f->fwrite((const byte*)data, len, 0);

        f.reset();

        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(success ? API_OK : API_EWRITE));
    }
}

int MegaApiImpl::fa_failed(handle, fatype, int retries, error e)
{
    int tag = client->restag;
    while(tag)
    {
        if(requestMap.find(tag) == requestMap.end()) return 1;
        MegaRequestPrivate* request = requestMap.at(tag);
        if(!request || (request->getType() != MegaRequest::TYPE_GET_ATTR_FILE))
            return 1;

        tag = int(request->getNumber());
        if(retries >= 2)
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
        else
        {
            fireOnRequestTemporaryError(request, make_unique<MegaErrorPrivate>(e));
        }
    }

    return (retries >= 2);
}

void MegaApiImpl::putfa_result(handle h, fatype, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || request->getType() != MegaRequest::TYPE_SET_ATTR_FILE)
        return;

    if (e == API_OK && request->getMegaBackgroundMediaUploadPtr())
    {
        request->setNodeHandle(h);
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::enumeratequotaitems_result(unsigned type, handle product, unsigned prolevel, int gbstorage, int gbtransfer, unsigned months, unsigned amount, unsigned amountMonth, unsigned localPrice, const char* description, const char* iosid, const char* androidid, std::unique_ptr<BusinessPlan> bizPlan)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) &&
                    (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) &&
                    (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) &&
                    (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)))
    {
        return;
    }

    request->addProduct(type, product, prolevel, gbstorage, gbtransfer, months, amount, amountMonth, localPrice, description, iosid, androidid, move(bizPlan));
}

void MegaApiImpl::enumeratequotaitems_result(unique_ptr<CurrencyData> currencyData)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) &&
                    (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) &&
                    (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) &&
                    (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)))
    {
        return;
    }

    request->setCurrency(move(currencyData));
}

void MegaApiImpl::enumeratequotaitems_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) &&
                    (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) &&
                    (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) &&
                    (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)))
    {
        return;
    }

    if (request->getType() == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)
    {
        if (e != API_OK)
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            return;
        }
        unique_ptr<MegaAccountDetails> details(request->getMegaAccountDetails());
        unique_ptr<MegaPricing> pricing(request->getPricing());
        int recommended = calcRecommendedProLevel(*pricing.get(), *details.get());
        request->setNumber(recommended);
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if(request->getType() == MegaRequest::TYPE_GET_PRICING)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
    }
    else
    {
        MegaPricing *pricing = request->getPricing();
        MegaCurrency *currency = request->getCurrency();
        int i;
        for(i = 0; i < pricing->getNumProducts(); i++)
        {
            if (pricing->getHandle(i) == request->getNodeHandle())
            {
                int phtype = request->getParamType();
                int64_t ts = request->getTransferredBytes();
                requestMap.erase(request->getTag());
                int nextTag = client->nextreqtag();
                request->setTag(nextTag);
                requestMap[nextTag]=request;
                client->purchase_additem(0, request->getNodeHandle(), pricing->getAmount(i),
                                         currency->getCurrencySymbol(), 0, NULL, request->getParentHandle(),
                                         phtype, ts);
                break;
            }
        }

        if (i == pricing->getNumProducts())
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
        }

        delete pricing;
        delete currency;
    }
}

void MegaApiImpl::additem_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) &&
                    (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT))) return;

    if(e != API_OK)
    {
        client->purchase_begin();
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if(request->getType() == MegaRequest::TYPE_GET_PAYMENT_ID)
    {
        char saleid[16];
        Base64::btoa((byte *)&client->purchase_basket.back(), 8, saleid);
        request->setLink(saleid);
        client->purchase_begin();
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
        return;
    }

    //MegaRequest::TYPE_UPGRADE_ACCOUNT
    int method = int(request->getNumber());

    int creqtag = client->reqtag;
    client->reqtag = client->restag;
    client->purchase_checkout(method);
    client->reqtag = creqtag;
}

void MegaApiImpl::checkout_result(const char *errortype, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT)) return;

    if(!errortype)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if(!strcmp(errortype, "FP"))
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e - 100));
        return;
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::PAYMENT_EGENERIC));
    return;
}

void MegaApiImpl::submitpurchasereceipt_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::creditcardquerysubscriptions_result(int number, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS)) return;

    request->setNumber(number);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::creditcardcancelsubscriptions_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}
void MegaApiImpl::getpaymentmethods_result(int methods, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_PAYMENT_METHODS)) return;

    request->setNumber(methods);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::userfeedbackstore_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SUBMIT_FEEDBACK)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::sendevent_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SEND_EVENT)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::supportticket_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SUPPORT_TICKET)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::creditcardstore_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_STORE)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::copysession_result(string *session, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_SESSION_TRANSFER_URL)) return;

    if(e == API_OK)
    {
        const char *path = request->getText();
        string data = client->sessiontransferdata(path, session);
        data.insert(0, MegaClient::MEGAURL+"/#sitetransfer!");

        request->setLink(data.c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::clearing()
{
#ifdef ENABLE_SYNC
    mCachedMegaSyncPrivate.reset();
#endif
}

void MegaApiImpl::notify_retry(dstime dsdelta, retryreason_t reason)
{
#ifdef ENABLE_SYNC
    retryreason_t previousFlag = waitingRequest;
#endif

    if(!dsdelta)
        waitingRequest = RETRY_NONE;
    else if(dsdelta > 40)
        waitingRequest = reason;

#ifdef ENABLE_SYNC
    if(previousFlag != waitingRequest)
        fireOnGlobalSyncStateChanged();
#endif

    if (dsdelta && requestMap.size() == 1)
    {
        MegaRequestPrivate *request = requestMap.begin()->second;
        fireOnRequestTemporaryError(request, make_unique<MegaErrorPrivate>(API_EAGAIN, reason));
    }
}

void MegaApiImpl::notify_dbcommit()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_COMMIT_DB);
    event->setText(client->scsn.text());
    fireOnEvent(event);
}

void MegaApiImpl::notify_storage(int storageEvent)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_STORAGE);
    event->setNumber(storageEvent);
    fireOnEvent(event);
}

void MegaApiImpl::notify_change_to_https()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_CHANGE_TO_HTTPS);
    fireOnEvent(event);
}

void MegaApiImpl::notify_confirmation(const char *email)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_ACCOUNT_CONFIRMATION);
    event->setText(email);
    fireOnEvent(event);
}

void MegaApiImpl::notify_disconnect()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_DISCONNECT);
    fireOnEvent(event);
}

void MegaApiImpl::notify_business_status(BizStatus status)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_BUSINESS_STATUS);
    event->setNumber(status);
    fireOnEvent(event);
}

void MegaApiImpl::http_result(error e, int httpCode, byte *data, int size)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }

    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_QUERY_DNS
                    && request->getType() != MegaRequest::TYPE_CHAT_STATS
                    && request->getType() != MegaRequest::TYPE_DOWNLOAD_FILE))
    {
        return;
    }

    request->setNumber(httpCode);
    request->setTotalBytes(size);
    if (request->getType() == MegaRequest::TYPE_CHAT_STATS
            || request->getType() == MegaRequest::TYPE_QUERY_DNS)
    {
        string result;
        result.assign((const char *)data, size);
        request->setText(result.c_str());
    }
    else if (request->getType() == MegaRequest::TYPE_DOWNLOAD_FILE)
    {
        const char *file = request->getFile();
        if (file && e == API_OK)
        {
            auto f = client->fsaccess->newfileaccess();
            string filePath(file);
            auto localPath = LocalPath::fromAbsolutePath(filePath);

            fsAccess->unlinklocal(localPath);
            if (!f->fopen(localPath, false, true, FSLogging::logOnError))
            {
                e = API_EWRITE;
            }
            else if (size)
            {
                if (!f->fwrite((const byte*)data, size, 0))
                {
                    e = API_EWRITE;
                }
            }
        }
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}


void MegaApiImpl::timer_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }

    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_TIMER))
    {
        return;
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

// callback for non-EAGAIN request-level errors
// retrying is futile
// this can occur e.g. with syntactically malformed requests (due to a bug) or due to an invalid application key
void MegaApiImpl::request_error(error e)
{
    if (e == API_EBLOCKED && client->sid.size())
    {
        whyAmIBlocked(true);
        return;
    }

    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT);
    bool keepSyncConfigsFile = true;
    request->setFlag(false);
    request->setTransferTag(keepSyncConfigsFile);
    request->setParamType(e);

    if (e == API_ESSL && client->sslfakeissuer.size())
    {
        request->setText(client->sslfakeissuer.c_str());
    }

    if (e == API_ESID)
    {
        client->locallogout(true, keepSyncConfigsFile);
    }

    request->performRequest = [this, request]()
    {
        return performRequest_logout(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::request_response_progress(m_off_t currentProgress, m_off_t totalProgress)
{
    LOG_verbose << "Request response progress: current progress = " << currentProgress << ", total progress = " << totalProgress;
    if (!client->isFetchingNodesPendingCS())
    {
        return;
    }
    for (std::map<int,MegaRequestPrivate*>::iterator it = requestMap.begin(); it != requestMap.end(); it++)
    {
        MegaRequestPrivate *request = it->second;
        if (request && request->getType() == MegaRequest::TYPE_FETCH_NODES)
        {
            request->setTransferredBytes(currentProgress);
            if (totalProgress != -1)
            {
                request->setTotalBytes(totalProgress);
            }
            fireOnRequestUpdate(request);
        }
    }
}

void MegaApiImpl::prelogin_result(int version, string* email, string *salt, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_LOGIN)
                    && (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK)))
    {
        return;
    }

    if (e)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        return;
    }

    if (request->getType() == MegaRequest::TYPE_LOGIN)
    {
        const char* pin = request->getText();
        if (version == 1)
        {
            const char *password = request->getPassword();
            const char* base64pwkey = request->getPrivateKey();
            if (base64pwkey)
            {
                byte pwkey[SymmCipher::KEYLENGTH];
                Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey);
                if (password)
                {
                    uint64_t emailhash;
                    Base64::atob(password, (byte *)&emailhash, sizeof emailhash);

                    int creqtag = client->reqtag;
                    client->reqtag = client->restag;
                    client->fastlogin(email->c_str(), pwkey, emailhash);
                    client->reqtag = creqtag;
                }
                else
                {
                    int creqtag = client->reqtag;
                    client->reqtag = client->restag;
                    client->login(email->c_str(), pwkey, pin);
                    client->reqtag = creqtag;
                }
            }
            else
            {
                error err;
                byte pwkey[SymmCipher::KEYLENGTH];
                if ((err = client->pw_key(password, pwkey)))
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(err));
                    return;
                }

                int creqtag = client->reqtag;
                client->reqtag = client->restag;
                client->login(email->c_str(), pwkey, pin);
                client->reqtag = creqtag;
            }
        }
        else if (version == 2 && salt)
        {
            const char *password = request->getPassword();
            const char* base64pwkey = request->getPrivateKey();
            if (base64pwkey)
            {
                byte derivedKey[2 * SymmCipher::KEYLENGTH];
                Base64::atob(base64pwkey, derivedKey, sizeof derivedKey);

                int creqtag = client->reqtag;
                client->reqtag = client->restag;
                client->login2(email->c_str(), derivedKey, pin);
                client->reqtag = creqtag;
            }
            else
            {
                int creqtag = client->reqtag;
                client->reqtag = client->restag;
                client->login2(email->c_str(), password, salt, pin);
                client->reqtag = creqtag;
            }
        }
        else
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EINTERNAL));
        }
    }
    else if (request->getType() == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK)
    {
        request->setParamType(version);
        const char *link = request->getLink();
        const char* code;
        const char *mk64;

        if ((code = strstr(link, MegaClient::recoverLinkPrefix())))
        {
            code += strlen(MegaClient::recoverLinkPrefix());
        }
        else
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
            return;
        }

        long long type = request->getNumber();
        switch (type)
        {
            case RECOVER_WITH_MASTERKEY:
            {
                mk64 = request->getPrivateKey();
                if (!mk64)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
                    return;
                }

                int creqtag = client->reqtag;
                client->reqtag = client->restag;
                client->getprivatekey(code);
                client->reqtag = creqtag;
                break;
            }
            case RECOVER_WITHOUT_MASTERKEY:
            {
                int creqtag = client->reqtag;
                client->reqtag = client->restag;
                client->confirmrecoverylink(code, email->c_str(), request->getPassword(), NULL, version);
                client->reqtag = creqtag;
                break;
            }

        default:
            LOG_debug << "Unknown type of recovery link";

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EARGS));
            return;
        }
    }
}

// login result
void MegaApiImpl::login_result(error result)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_LOGIN && request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT)) return;

    // if login with user+pwd succeed, update lastLogin timestamp
    if (result == API_OK && request->getEmail() &&
            (request->getPassword() || request->getPrivateKey()))
    {
        client->isNewSession = true;
        client->tsLogin = m_time();
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(result));
}

void MegaApiImpl::logout_result(error e, MegaRequestPrivate* request)
{
    if(!e || e == API_ESID)
    {
        requestMap.erase(request->getTag());

        error preverror = (error)request->getParamType();
        abortPendingActions(preverror);

        pendingUploads = 0;
        pendingDownloads = 0;
        totalUploads = 0;
        totalDownloads = 0;
        waitingRequest = RETRY_NONE;
        excludedNames.clear();
        excludedPaths.clear();
        syncLowerSizeLimit = 0;
        syncUpperSizeLimit = 0;

        delete mPushSettings;
        mPushSettings = NULL;
        delete mTimezones;
        mTimezones = NULL;

#ifdef ENABLE_SYNC
        mCachedMegaSyncPrivate.reset();
#endif

        mLastReceivedLoggedInState = NOTLOGGEDIN;
        mLastReceivedLoggedInMeHandle = UNDEF;
        mLastKnownRootNode.reset();
        mLastKnownVaultNode.reset();
        mLastKnownRubbishNode.reset();
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::userdata_result(string *name, string* pubk, string* privk, Error result)
{
    // notify apps about the availability/update of user-flags, such as `aplvp`
    // (note that usually the API command is triggered internally, so no request is associated)
    if (result == API_OK)
    {
        MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MISC_FLAGS_READY);
        fireOnEvent(event);
    }

    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_USER_DATA)) return;

    if(result == API_OK)
    {
        request->setPassword(pubk->c_str());
        request->setPrivateKey(privk->c_str());
        request->setName(name->c_str());
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(result));
}

void MegaApiImpl::pubkey_result(User *u)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_GET_USER_DATA)) return;

    if(!u)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
        return;
    }

    if(!u->pubk.isvalid())
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EACCESS));
        return;
    }

    string key;
    u->pubk.serializekey(&key, AsymmCipher::PUBKEY);
    char pubkbuf[AsymmCipher::MAXKEYLENGTH * 4 / 3 + 4];
    Base64::btoa((byte *)key.data(), int(key.size()), pubkbuf);
    request->setPassword(pubkbuf);

    char jid[16];
    Base32::btoa((byte *)&u->userhandle, MegaClient::USERHANDLE, jid);
    request->setText(jid);

    if(u->email.size())
    {
        request->setEmail(u->email.c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
}

// password change result
void MegaApiImpl::changepw_result(error result)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || request->getType() != MegaRequest::TYPE_CHANGE_PW) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(result));
}

// the requested link could not be opened
void MegaApiImpl::openfilelink_result(const Error& result)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_IMPORT_LINK) &&
                    (request->getType() != MegaRequest::TYPE_GET_PUBLIC_NODE))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(result));
}

// the requested link was opened successfully
// (it is the application's responsibility to delete n!)
void MegaApiImpl::openfilelink_result(handle ph, const byte* key, m_off_t size, string* a, string* fa, int)
{
    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
            || ( request->getType() != MegaRequest::TYPE_IMPORT_LINK
                 && request->getType() != MegaRequest::TYPE_GET_PUBLIC_NODE)) return;

    if (!client->loggedin() && (request->getType() == MegaRequest::TYPE_IMPORT_LINK))
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_EACCESS));
        return;
    }

    // no key provided --> check only that the nodehandle is valid
    if (!key && (request->getType() == MegaRequest::TYPE_GET_PUBLIC_NODE))
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_EINCOMPLETE));
        return;
    }

    AttrMap attrs;
    string fileName;
    string validName;
    string fingerprint;
    string originalfingerprint;
    FileFingerprint ffp;
    m_time_t mtime = 0;

    string attrstring;
    attrstring.resize(a->length()*4/3+4);
    attrstring.resize(Base64::btoa((const byte *)a->data(), int(a->length()), (char *)attrstring.data()));

    bool isNodeKeyDecrypted;
    string keystring;
    SymmCipher nodeKey;
    keystring.assign((char*)key, FILENODEKEYLENGTH);
    nodeKey.setkey(key, FILENODE);

    byte *buf = Node::decryptattr(&nodeKey, attrstring.c_str(), attrstring.size());
    if (buf)
    {
        Node::parseattr(buf, attrs, size, mtime, fileName, fingerprint, ffp);

        // Normalize node name to UTF-8 string
        attr_map::iterator it = attrs.map.find('n');
        if (it != attrs.map.end() && !it->second.empty())
        {
            LocalPath::utf8_normalize(&(it->second));
            fileName = it->second.c_str();
            validName = fileName;
        }
        delete [] buf;

        isNodeKeyDecrypted = true;
    }
    else
    {
        fileName = "CRYPTO_ERROR";
        request->setFlag(true);
        isNodeKeyDecrypted = false;
    }

    if (request->getType() == MegaRequest::TYPE_IMPORT_LINK)
    {
        auto parenthandle = NodeHandle().set6byte(request->getParentHandle());
        Node *target = client->nodeByHandle(parenthandle);
        if (!target)
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_EARGS));
            return;
        }

        NodeHandle ovhandle;
        Node *ovn = client->childnodebyname(target, validName.c_str(), true);
        if (ovn)
        {
            if (ffp.isvalid && ovn->isvalid && ffp == *(FileFingerprint*)ovn)
            {
                request->setNodeHandle(ovn->nodehandle);
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                return;
            }

            ovhandle = ovn->nodeHandle();
        }

        vector<NewNode> newnodes(1);
        auto newnode = &newnodes[0];

        // set up new node as folder node
        newnode->source = NEW_PUBLIC;
        newnode->type = FILENODE;
        newnode->nodehandle = ph;
        newnode->parenthandle = UNDEF;
        newnode->nodekey.assign((char*)key,FILENODEKEYLENGTH);
        newnode->attrstring.reset(new string(*a));
        newnode->ovhandle = ovhandle;

        // add node
        requestMap.erase(request->getTag());
        int nextTag = client->nextreqtag();
        request->setTag(nextTag);
        requestMap[nextTag]=request;
        client->putnodes(parenthandle, UseLocalVersioningFlag, move(newnodes), nullptr, nextTag, false);
    }
    else
    {
        MegaNodePrivate *megaNodePrivate = new MegaNodePrivate(fileName.c_str(), FILENODE, size, 0, mtime, ph, &keystring,
                                                           fa, fingerprint.size() ? fingerprint.c_str() : NULL,
                                                           originalfingerprint.size() ? originalfingerprint.c_str() : NULL, INVALID_HANDLE, INVALID_HANDLE,
                                                               nullptr, nullptr, true, false, nullptr, isNodeKeyDecrypted);
        request->setPublicNode(megaNodePrivate);
        delete megaNodePrivate;
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_OK));
    }
}

// reload needed
void MegaApiImpl::notifyError(const char* reason, ErrorReason errorReason)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_FATAL_ERROR);
    event->setText(reason);
    event->setNumber(static_cast<int64_t>(errorReason));

    fireOnEvent(event);
}

void MegaApiImpl::reloading()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_RELOADING);
    fireOnEvent(event);
}

// nodes have been modified
// (nodes with their removed flag set will be deleted immediately after returning from this call,
// at which point their pointers will become invalid at that point.)
void MegaApiImpl::nodes_updated(Node** n, int count)
{
    LOG_debug << "Nodes updated: " << count;
    if (!count)
    {
        return;
    }

    MegaNodeList *nodeList = NULL;
    if (n != NULL)
    {
        nodeList = new MegaNodeListPrivate(n, count);
        fireOnNodesUpdate(nodeList);
    }
    else
    {
        fireOnNodesUpdate(NULL);
    }
    delete nodeList;
}

void MegaApiImpl::account_details(AccountDetails*, bool, bool, bool, bool, bool, bool)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_ACCOUNT_DETAILS && request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)) return;

    if (request->getType() == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)
    {
        // only ever one message
        client->purchase_enumeratequotaitems();
        return;
    }

    long long numPending = request->getNumber();
    numPending--;
    request->setNumber(numPending);
    if(!numPending)
    {
        bool storage_requested = request->getNumDetails() & 0x01;
        if (storage_requested && !request->getAccountDetails()->storage_max)
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_EACCESS));
        else
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_OK));
    }
}

void MegaApiImpl::account_details(AccountDetails*, error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_ACCOUNT_DETAILS && request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::querytransferquota_result(int code)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_QUERY_TRANSFER_QUOTA)) return;

    // pre-warn about a possible overquota for codes 2 and 3, like in the webclient
    request->setFlag((code == 2 || code == 3) ? true : false);

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
}

void MegaApiImpl::removecontact_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_REMOVE_CONTACT)) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::putua_result(error e)
{
    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
        || (request->getType() != MegaRequest::TYPE_SET_ATTR_USER &&
            request->getType() != MegaRequest::TYPE_SET_MY_BACKUPS &&
            request->getType() != MegaRequest::TYPE_VERIFY_CREDENTIALS))
    {
        return;
    }

    // if user just set the preferred language... change the GET param to the new language
    if (request->getParamType() == MegaApi::USER_ATTR_LANGUAGE && e == API_OK)
    {
        setLanguage(request->getText());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getua_result(error e)
{
    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
           || (request->getType() != MegaRequest::TYPE_GET_ATTR_USER && request->getType() != MegaRequest::TYPE_SET_ATTR_USER)) return;

    // if attempted to get ^!prd attribute but not exists yet...
    if (e == API_ENOENT)
    {
        if (request->getParamType() == MegaApi::USER_ATTR_PWD_REMINDER)
        {
            if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER)
            {
                string newValue;
                User::mergePwdReminderData(request->getNumDetails(), NULL, 0, &newValue);
                request->setText(newValue.c_str());

                // set the attribute using same request tag
                client->putua(ATTR_PWD_REMINDER, (byte*) newValue.data(), unsigned(newValue.size()), client->restag);
                return;
            }
            else if (request->getType() == MegaRequest::TYPE_GET_ATTR_USER)
            {
                m_time_t currenttime = m_time();
                if ((currenttime - client->accountsince) > User::PWD_SHOW_AFTER_ACCOUNT_AGE
                        && (currenttime - client->tsLogin) > User::PWD_SHOW_AFTER_LASTLOGIN)
                {
                    request->setFlag(true); // the password reminder dialog should be shown
                }
            }
        }
        else if (request->getParamType() == MegaApi::USER_ATTR_RICH_PREVIEWS &&
                 request->getType() == MegaRequest::TYPE_GET_ATTR_USER)
        {
            if (request->getNumDetails() == 0)  // used to check if rich-links are enabled
            {
                request->setFlag(false);
            }
            else if (request->getNumDetails() == 1) // used to check if should show warning
            {
                request->setFlag(true);
            }
        }
        else if ((request->getParamType() == MegaApi::USER_ATTR_ALIAS
                  || request->getParamType() == MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER
                  || request->getParamType() == MegaApi::USER_ATTR_DEVICE_NAMES
                  || request->getParamType() == MegaApi::USER_ATTR_APPS_PREFS)
                    && request->getType() == MegaRequest::TYPE_SET_ATTR_USER)
        {
            // The attribute doesn't exists so we have to create it
            TLVstore tlv;
            MegaStringMap *stringMap = request->getMegaStringMap();
            std::unique_ptr<MegaStringList> keys(stringMap->getKeys());
            attr_t type = static_cast<attr_t>(request->getParamType());
            bool keyPrefixModifier = request->getParamType() == MegaApi::USER_ATTR_DEVICE_NAMES && request->getFlag();
            string keyPrefix = User::attributePrefixInTLV(type, keyPrefixModifier);
            for (int i = 0; i < keys->size(); i++)
            {
                const char *key = keys->get(i);
                tlv.set(keyPrefix + key, Base64::atob(stringMap->get(key)));
            }

            // serialize and encrypt the TLV container
            std::unique_ptr<string> container(tlv.tlvRecordsToContainer(client->rng, &client->key));
            client->putua(type, (byte *)container->data(), unsigned(container->size()), client->restag);
            return;
        }
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getua_result(byte* data, unsigned len, attr_t type)
{
    error e = API_OK;

    // update cached notification settings for filtering
    std::unique_ptr<MegaPushNotificationSettingsPrivate> pushSettings;
    if (type == ATTR_PUSH_SETTINGS)
    {
        string settingsJson((const char*)data, len);
        pushSettings.reset(new MegaPushNotificationSettingsPrivate(settingsJson));
        if (pushSettings->isValid())
        {
            delete mPushSettings;
            mPushSettings = pushSettings->copy();
        }
        else
        {
            e = API_EINTERNAL;
            client->sendevent(99448, "Invalid JSON for received notification settings");
        }
    }

    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
        || (request->getType() != MegaRequest::TYPE_GET_ATTR_USER &&
            request->getType() != MegaRequest::TYPE_SET_ATTR_USER))
    {
        return;
    }

    assert(type == request->getParamType());

    if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER)
    {
        static_assert(int(ATTR_PWD_REMINDER) == int(MegaApi::USER_ATTR_PWD_REMINDER), "User Attribute Enum Mismatch");
        if (int(type) == MegaApi::USER_ATTR_PWD_REMINDER)
        {
            // merge received value with updated items
            string newValue;
            bool changed = User::mergePwdReminderData(request->getNumDetails(), (const char*) data, len, &newValue);
            request->setText(newValue.data());

            if (changed)
            {
                // set the attribute using same request tag
                client->putua(ATTR_PWD_REMINDER, (byte*) newValue.data(), unsigned(newValue.size()), client->restag);
            }
            else
            {
                LOG_debug << "Password-reminder data not changed, already up to date";
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
        }
        return;
    }

    // only for TYPE_GET_ATTR_USER
    switch (type)
    {
        case MegaApi::USER_ATTR_AVATAR:
            if (len)
            {

                auto f = client->fsaccess->newfileaccess();
                string filePath(request->getFile());
                auto localPath = LocalPath::fromAbsolutePath(filePath);

                fsAccess->unlinklocal(localPath);

                bool success = f->fopen(localPath, false, true, FSLogging::logOnError)
                            && f->fwrite((const byte*)data, len, 0);

                f.reset();

                if (!success)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EWRITE));
                    return;
                }
            }
            else    // no data for the avatar
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
                return;
            }

            break;

        // null-terminated char arrays
        case MegaApi::USER_ATTR_FIRSTNAME:
        case MegaApi::USER_ATTR_LASTNAME:
        case MegaApi::USER_ATTR_LANGUAGE:   // it's a c-string in binary format, want the plain data
        case MegaApi::USER_ATTR_PWD_REMINDER:
        case MegaApi::USER_ATTR_DISABLE_VERSIONS:
        case MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION:
            {
                string str((const char*)data,len);
                request->setText(str.c_str());

                static_assert(int(MegaApi::USER_ATTR_DISABLE_VERSIONS) == ATTR_DISABLE_VERSIONS, "User Attribute Enum Mismatch");
                static_assert(int(MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION) == ATTR_CONTACT_LINK_VERIFICATION, "User Attribute Enum Mismatch");
                static_assert(int(MegaApi::USER_ATTR_PWD_REMINDER) == ATTR_PWD_REMINDER, "User Attribute Enum Mismatch");

                if (int(type) == MegaApi::USER_ATTR_DISABLE_VERSIONS
                        || int(type) == MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION)
                {
                    request->setFlag(str == "1");
                }
                else if (int(type) == MegaApi::USER_ATTR_PWD_REMINDER)
                {
                    m_time_t currenttime = m_time();
                    bool isMasterKeyExported = User::getPwdReminderData(User::PWD_MK_EXPORTED, (const char*)data, len);
                    bool isLogout = request->getNumber();
                    bool pwdDontShow = User::getPwdReminderData(User::PWD_DONT_SHOW, (const char*)data, len);
                    if ((!isMasterKeyExported
                            && !pwdDontShow
                            && (currenttime - client->accountsince) > User::PWD_SHOW_AFTER_ACCOUNT_AGE
                            && (currenttime - User::getPwdReminderData(User::PWD_LAST_SUCCESS, (const char*)data, len)) > User::PWD_SHOW_AFTER_LASTSUCCESS
                            && (currenttime - User::getPwdReminderData(User::PWD_LAST_LOGIN, (const char*)data, len)) > User::PWD_SHOW_AFTER_LASTLOGIN
                            && (currenttime - User::getPwdReminderData(User::PWD_LAST_SKIPPED, (const char*)data, len)) > (request->getNumber() ? User::PWD_SHOW_AFTER_LASTSKIP_LOGOUT : User::PWD_SHOW_AFTER_LASTSKIP)
                            && (currenttime - client->tsLogin) > User::PWD_SHOW_AFTER_LASTLOGIN)
                            || (isLogout && !pwdDontShow))
                    {
                        request->setFlag(true); // the password reminder dialog should be shown
                    }
                    request->setAccess(isMasterKeyExported ? 1 : 0);
                }
            }
            break;

        // numbers
        case MegaApi::USER_ATTR_RUBBISH_TIME:
        case MegaApi::USER_ATTR_STORAGE_STATE:
            {
                char *endptr;
                string str((const char*)data, len);
                m_off_t value = strtoll(str.c_str(), &endptr, 10);
                if (endptr == str.c_str() || *endptr != '\0' || value == LLONG_MAX || value == LLONG_MIN)
                {
                    value = -1;
                }

                request->setNumber(value);

                static_assert(int(MegaApi::USER_ATTR_STORAGE_STATE) == ATTR_STORAGE_STATE, "User Attribute Enum Mismatch");
                if (int(type) == MegaApi::USER_ATTR_STORAGE_STATE && (value < MegaApi::STORAGE_STATE_GREEN || value > MegaApi::STORAGE_STATE_RED))
                {
                    e = API_EINTERNAL;
                }
            }
            break;

        case MegaApi::USER_ATTR_COOKIE_SETTINGS:
            {
                e = getCookieSettings_getua_result(data, len, request);
            }
            break;

        case MegaApi::USER_ATTR_PUSH_SETTINGS:
        {
            request->setMegaPushNotificationSettings(pushSettings.get());
        }
        break;

        case MegaApi::USER_ATTR_MY_BACKUPS_FOLDER:
        {
            handle h = 0;
            if (len != MegaClient::NODEHANDLE)
            {
                LOG_err << "Wrong received data size for 'My Backups' node handle: " << len << "; expected " << MegaClient::NODEHANDLE;
                assert(false);
            }
            memcpy(&h, data, MegaClient::NODEHANDLE);
            if (!client->nodebyhandle(h))
            {
                LOG_warn << "'My Backups' node was missing, or invalid handle in USER_ATTR_MY_BACKUPS_FOLDER";
                e = API_ENOENT;
            }
            else
            {
                request->setNodeHandle(h);
            }
        }
        break;

        // byte arrays with possible nulls in the middle --> to Base64
        case MegaApi::USER_ATTR_ED25519_PUBLIC_KEY: // fall-through
        {
            if (request->getFlag()) // asking for the fingerprint
            {
                string fingerprint = AuthRing::fingerprint(string((const char*)data, len), true);
                request->setPassword(fingerprint.c_str());
                break;
            }
        }
        // fall through
        case MegaApi::USER_ATTR_CU25519_PUBLIC_KEY:
        case MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY:
        case MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY:
        default:
        {
            string str;
            str.resize(len * 4 / 3 + 4);
            str.resize(Base64::btoa(data, len, (char*)str.data()));
            request->setText(str.c_str());
        }
        break;
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getua_result(TLVstore *tlv, attr_t type)
{
    error e = API_OK;
    MegaRequestPrivate* request = NULL;
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end() || !(request = it->second)
           || (request->getType() != MegaRequest::TYPE_GET_ATTR_USER
               && request->getType() != MegaRequest::TYPE_SET_ATTR_USER)) return;

    assert(type == static_cast<attr_t>(request->getParamType()));

    if (tlv)
    {
        if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER)
        {
            const string_map *newValuesMap = static_cast<MegaStringMapPrivate*>(request->getMegaStringMap())->getMap();
            unique_ptr<string_map> prefixedValueMap;

            if (type == ATTR_DEVICE_NAMES)
            {
                // allow only unique names for Devices and Drives
                if (haveDuplicatedValues(*tlv->getMap(), *newValuesMap)) // ignores keys
                {
                    e = API_EEXIST;
                    LOG_err << "Attribute " << User::attr2string(type) << " attempted to add duplicated value (2): "
                        << Base64::atob(newValuesMap->begin()->second); // will only have a single value
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    return;
                }

                if (request->getFlag()) // external drive
                {
                    string prefix = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true);
                    prefixedValueMap = mega::make_unique<string_map>();
                    for_each(newValuesMap->begin(), newValuesMap->end(),
                        [&prefixedValueMap, &prefix](const string_map::value_type& a) { prefixedValueMap->emplace(prefix + a.first, a.second); });
                    newValuesMap = prefixedValueMap.get();
                }
            }

            if (User::mergeUserAttribute(type, *newValuesMap, *tlv))
            {
                // serialize and encrypt the TLV container
                std::unique_ptr<string> container(tlv->tlvRecordsToContainer(client->rng, &client->key));
                client->putua(type, (byte *)container->data(), unsigned(container->size()), client->restag);
            }
            else
            {
                LOG_debug << "Attribute " << User::attr2string(type) << " not changed, already up to date";
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }

            return;
        }   // end of get+set

        // TLV data usually includes byte arrays with zeros in the middle, so values
        // must be converted into Base64 strings to avoid problems
        std::unique_ptr<MegaStringMap> stringMap(new MegaStringMapPrivate(tlv->getMap(), true));
        request->setMegaStringMap(stringMap.get());
        switch (request->getParamType())
        {
            // prepare request params to know if a warning should show or not
            case MegaApi::USER_ATTR_RICH_PREVIEWS:
            {
                const char *num = stringMap->get("num");

                if (request->getNumDetails() == 0)  // used to check if rich-links are enabled
                {
                    if (num)
                    {
                        string sValue = num;
                        string bValue;
                        Base64::atob(sValue, bValue);
                        request->setFlag(bValue == "1");
                    }
                    else
                    {
                        request->setFlag(false);
                    }
                }
                else if (request->getNumDetails() == 1) // used to check if should show warning
                {
                    request->setFlag(!num);
                    // it doesn't matter the value, just if it exists

                    const char *value = stringMap->get("c");
                    if (value)
                    {
                        string sValue = value;
                        string bValue;
                        Base64::atob(sValue, bValue);
                        request->setNumber(atoi(bValue.c_str()));
                    }
                }
                break;
            }
            case MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER:
            case MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER:
            {
                // If attr is CAMERA_UPLOADS_FOLDER determine if we want to retrieve primary or secondary folder
                // If attr is MY_CHAT_FILES_FOLDER, there's no secondary folder
                const char *key = request->getParamType() == MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER
                        && request->getFlag()
                            ? "sh"
                            : "h";

                const char *value = stringMap->get(key);
                if (!value)
                {
                    e = API_ENOENT;
                    break;
                }
                else
                {
                   handle nodehandle = 0;  // make sure top two bytes are 0
                   Base64::atob(value, (byte*) &nodehandle, MegaClient::NODEHANDLE);
                   request->setNodeHandle(nodehandle);
                }
                break;
            }
            case MegaApi::USER_ATTR_ALIAS:
            {
                // If a handle was set in the request, we have to find it in the corresponding map and return it
                const char *h = request->getText();
                if (h)
                {
                    string key{h};
                    string buffer;

                    if (!tlv || !tlv->get(key, buffer))
                    {
                        e = API_ENOENT;
                        break;
                    }
                    else
                    {
                        request->setName(buffer.c_str());
                    }
                }
                break;
            }
            case MegaApi::USER_ATTR_DEVICE_NAMES:
            {
                const char* buf = nullptr;

                if (request->getFlag()) // external drive
                {
                    handle driveId = request->getNodeHandle();
                    string key = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true) + string(Base64Str<MegaClient::DRIVEHANDLE>(driveId));
                    buf = stringMap->get(key.c_str());
                }
                else
                {
                    buf = stringMap->get(client->getDeviceidHash().c_str());
                }

                if (!buf)
                {
                    e = API_ENOENT;
                    break;
                }
                request->setName(Base64::atob(buf).c_str());
                break;
            }

            default:
                break;
        }
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

#ifdef DEBUG
void MegaApiImpl::delua_result(error)
{
}

void MegaApiImpl::senddevcommand_result(int value)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || (request->getType() != MegaRequest::TYPE_SEND_DEV_COMMAND)) return;

    error e = static_cast<error>(value);
    std::string command = request->getName() ? request->getName() :"";
    if (!command.compare("aodq") && value > 0)
    {
        e = API_OK;
        request->setNumber(value);
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}
#endif

void MegaApiImpl::getuseremail_result(string *email, error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || (request->getType() != MegaRequest::TYPE_GET_USER_EMAIL))
    {
        return;
    }

    if (e == API_OK && email)
    {
        request->setEmail(email->c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
    return;
}

// user attribute update notification
void MegaApiImpl::userattr_update(User*, int, const char*)
{
}

void MegaApiImpl::nodes_current()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_NODES_CURRENT);
    fireOnEvent(event);
}

void MegaApiImpl::catchup_result()
{
    // sc requests are sent sequentially, it must be the one at front and already started (tag == 1)
    MegaRequestPrivate *request = scRequestQueue.front();
    if (!request || (request->getType() != MegaRequest::TYPE_CATCHUP) || !request->getTag()) return;
    request = scRequestQueue.pop();

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));

    // if there are more sc requests in the queue, send the next one
    if (scRequestQueue.front())
    {
        waiter->notify();
    }
}

void MegaApiImpl::key_modified(handle userhandle, attr_t attribute)
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_KEY_MODIFIED);
    switch (attribute)
    {
    case ATTR_CU25519_PUBK:
        event->setNumber(0);
        break;
    case ATTR_ED25519_PUBK:
        event->setNumber(1);
        break;
    case ATTR_UNKNOWN: // used internally for RSA
        event->setNumber(2);
        break;
    case ATTR_SIG_CU255_PUBK:
        event->setNumber(3);
        break;
    case ATTR_SIG_RSA_PUBK:
        event->setNumber(4);
        break;
    default:
        event->setNumber(-1);
        break;
    }
    event->setHandle(userhandle);
    fireOnEvent(event);
}

void MegaApiImpl::upgrading_security()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_UPGRADE_SECURITY);
    fireOnEvent(event);
}

void MegaApiImpl::downgrade_attack()
{
    MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_DOWNGRADE_ATTACK);
    fireOnEvent(event);
}

void MegaApiImpl::ephemeral_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::ephemeral_result(handle uh, const byte* pw)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return;

    // save uh and pwcipher for session resumption of ephemeral accounts
    string sid;
    if (client->loggedin() == EPHEMERALACCOUNT)
    {
        char buf[SymmCipher::KEYLENGTH * 4 / 3 + 3];
        Base64::btoa((byte*) &uh, sizeof uh, buf);
        sid.append(buf);
        sid.append("#");
        Base64::btoa(pw, SymmCipher::KEYLENGTH, buf);
        sid.append(buf);
    }
    else // ephemeral++
    {
        string session;
        client->dumpsession(session);
        sid = Base64::btoa(session);
    }
    request->setSessionKey(sid.c_str());

    // chain a fetchnodes to get waitlink for ephemeral account
    int creqtag = client->reqtag;
    client->reqtag = client->restag;
    client->fetchnodes();
    client->reqtag = creqtag;
}

void MegaApiImpl::cancelsignup_result(error e)
{
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end()) return;
    MegaRequestPrivate *request = it->second;
    if (!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return;

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::whyamiblocked_result(int code)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_WHY_AM_I_BLOCKED)))
    {
        return;
    }

    if (code <= 0)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(code));
    }
    else    // code > 0
    {
        string reason = "Your account was terminated due to a breach of Mega's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse.";

        if (code == MegaApi::ACCOUNT_BLOCKED_EXCESS_DATA_USAGE)    // deprecated
        {
            reason = "You have been suspended due to excess data usage.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT)
        {
            reason = "Your account has been suspended due to copyright violations. Please check your email inbox.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT)
        {
            reason = "Your account was terminated due to a breach of MEGA's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED)
        {
            reason = "Your account has been disabled by your administrator. You may contact your business account administrator for further details.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED)
        {
            reason = "Your account has been removed by your administrator. You may contact your business account administrator for further details.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS)
        {
            reason = "Your account has been blocked pending verification via SMS.";
        }
        else if (code == MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL)
        {
            reason = "Your account has been temporarily suspended for your safety. Please verify your email and follow its steps to unlock your account.";
        }
        //else if (code == ACCOUNT_BLOCKED_DEFAULT) --> default reason

        bool logoutAllowed = request->getFlag();
        request->setNumber(code);
        request->setText(reason.c_str());
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));

        MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_ACCOUNT_BLOCKED);
        event->setNumber(code);
        event->setText(reason.c_str());
        fireOnEvent(event);

        // (don't log out if we can be unblocked by email or sms)
        if (logoutAllowed
                && code != MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS
                && code != MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL)
        {
            bool keepSyncConfigsFile = true;
            client->locallogout(true, keepSyncConfigsFile);

            MegaRequestPrivate *logoutRequest = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT);
            logoutRequest->setFlag(false);
            logoutRequest->setTransferTag(keepSyncConfigsFile);
            logoutRequest->setParamType(API_EBLOCKED);

            logoutRequest->performRequest = [this, logoutRequest]()
            {
                return performRequest_logout(logoutRequest);
            };

            requestQueue.push(logoutRequest);
            waiter->notify();
        }
    }
}

void MegaApiImpl::contactlinkcreate_result(error e, handle h)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_CREATE)))
    {
        return;
    }

    if (!e)
    {
        request->setNodeHandle(h);
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::contactlinkquery_result(error e, handle h, string *email, string *firstname, string *lastname, string *avatar)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_QUERY)))
    {
        return;
    }

    if (!e)
    {
        request->setParentHandle(h);
        request->setEmail(email->c_str());
        request->setName(Base64::atob(*firstname).c_str());
        request->setText(Base64::atob(*lastname).c_str());
        request->setFile(avatar->c_str());
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::contactlinkdelete_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_DELETE)))
    {
        return;
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::keepmealive_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }

    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_KEEP_ME_ALIVE)))
    {
        return;
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::getpsa_result(error e, int id, string *title, string *text, string *image, string *buttontext, string *buttonlink, std::string *url)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }

    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_GET_PSA)))
    {
        return;
    }

    if (!e)
    {
        request->setNumber(id);

        if (request->getFlag()) // supports URL retrieval
        {
            request->setEmail(url->c_str());
        }
        //else -> when `w` is provided, it's still possible to receive the old format
        request->setName(title->c_str());
        request->setText(text->c_str());
        request->setFile(image->c_str());
        request->setPassword(buttontext->c_str());
        request->setLink(buttonlink->c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::multifactorauthsetup_result(string *code, error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET) &&
                    (request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET)))
    {
        return;
    }

    if (request->getType() == MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET && !e)
    {
        if (!code)
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EINTERNAL));
            return;
        }
        request->setText(code->c_str());
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::multifactorauthcheck_result(int enabled)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK)))
    {
        return;
    }

    if (enabled < 0)
    {
        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(enabled));
        return;
    }

    request->setFlag(enabled);
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
}

void MegaApiImpl::multifactorauthdisable_result(error e)
{
    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET)))
    {
        return;
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::fetchtimezone_result(error e, vector<std::string> *timezones, vector<int> *timezoneoffsets, int defaulttz)
{
    unique_ptr<MegaTimeZoneDetails> tzDetails;
    if (!e)
    {
        tzDetails.reset(new MegaTimeZoneDetailsPrivate(timezones, timezoneoffsets, defaulttz));

        // update the cached timezones for notifications filtering
        delete mTimezones;
        mTimezones = tzDetails->copy();
    }

    if (requestMap.find(client->restag) == requestMap.end())
    {
        return;
    }
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_FETCH_TIMEZONE)))
    {
        return;
    }

    request->setTimeZoneDetails(tzDetails.get());
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::acknowledgeuseralerts_result(error e)
{
    map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag);
    if (it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if (request && ((request->getType() == MegaRequest::TYPE_USERALERT_ACKNOWLEDGE)))
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::smsverificationsend_result(error e)
{
    map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag);
    if (it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if (request && ((request->getType() == MegaRequest::TYPE_SEND_SMS_VERIFICATIONCODE)))
        {
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::smsverificationcheck_result(error e, string* phoneNumber)
{
    map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag);
    if (it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if (request && ((request->getType() == MegaRequest::TYPE_CHECK_SMS_VERIFICATIONCODE)))
        {
            if (e == API_OK && phoneNumber)
            {
                request->setName(phoneNumber->c_str());
            }
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::getregisteredcontacts_result(error e, vector<tuple<string, string, string>>* data)
{
    auto it = requestMap.find(client->restag);
    if (it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if (request && ((request->getType() == MegaRequest::TYPE_GET_REGISTERED_CONTACTS)))
        {
            if (data)
            {
                auto stringTable = std::unique_ptr<MegaStringTable>{MegaStringTable::createInstance()};
                for (const auto& row : *data)
                {
                    string_vector list;
                    list.emplace_back(std::get<0>(row));
                    list.emplace_back(std::get<1>(row));
                    list.emplace_back(std::get<2>(row));
                    auto stringList = new MegaStringListPrivate(move(list));
                    stringTable->append(stringList);
                }
                request->setMegaStringTable(stringTable.get());
            }
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::getcountrycallingcodes_result(error e, map<string, vector<string>>* data)
{
    auto it = requestMap.find(client->restag);
    if (it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if (request && ((request->getType() == MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES)))
        {
            if (data)
            {
                auto stringListMap = std::unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()};
                for (const auto& pair : *data)
                {
                    string_vector list;
                    for (const auto& value : pair.second)
                    {
                        list.emplace_back(value);
                    }
                    auto stringList = new MegaStringListPrivate(move(list));
                    stringListMap->set(pair.first.c_str(), stringList);
                }
                request->setMegaStringListMap(stringListMap.get());
            }
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
    }
}

void MegaApiImpl::sendsignuplink_result(error e)
{
    if(requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT) &&
                    (request->getType() != MegaRequest::TYPE_SEND_SIGNUP_LINK))) return;

    if ((request->getType() == MegaRequest::TYPE_CREATE_ACCOUNT)
            && (e == API_OK) && (request->getParamType() == MegaApi::CREATE_ACCOUNT))   // new account has been created
    {
        // import the PDF silently... (not chained)
        client->getwelcomepdf();
    }

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::confirmsignuplink2_result(handle, const char *name, const char *email, error e)
{
    if (requestMap.find(client->restag) == requestMap.end()) return;
    MegaRequestPrivate* request = requestMap.at(client->restag);
    if (!request || ((request->getType() != MegaRequest::TYPE_CONFIRM_ACCOUNT) &&
                    (request->getType() != MegaRequest::TYPE_QUERY_SIGNUP_LINK))) return;

    if (!e)
    {
        assert(strcmp(email, request->getEmail()) == 0);
        assert(strcmp(name, request->getName()) == 0);
        request->setName(name);
        request->setEmail(email);
        request->setFlag(true);
    }
    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
}

void MegaApiImpl::setkeypair_result(error)
{

}

void MegaApiImpl::getbanners_result(error e)
{
    auto it = requestMap.find(client->restag);

    if (it != requestMap.end() && it->second && (it->second->getType() == MegaRequest::TYPE_GET_BANNERS))
    {
        fireOnRequestFinish(it->second, make_unique<MegaErrorPrivate>(e));
    }
}

void MegaApiImpl::getbanners_result(vector< tuple<int, string, string, string, string, string, string> >&& banners)
{
    auto it = requestMap.find(client->restag);
    if (it == requestMap.end()) return;
    MegaRequestPrivate* request = it->second;
    if (!request || (request->getType() != MegaRequest::TYPE_GET_BANNERS)) return;

    request->setBanners(move(banners));

    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
}

void MegaApiImpl::dismissbanner_result(error e)
{
    auto itReq = requestMap.find(client->restag);

    if (itReq != requestMap.end() && itReq->second && (itReq->second->getType() == MegaRequest::TYPE_DISMISS_BANNER))
    {
        fireOnRequestFinish(itReq->second, make_unique<MegaErrorPrivate>(e));
    }
}

void MegaApiImpl::reqstat_progress(int permilprogress)
{
    MegaEventPrivate* event = new MegaEventPrivate(MegaEvent::EVENT_REQSTAT_PROGRESS);
    event->setNumber(permilprogress);
    fireOnEvent(event);
}

void MegaApiImpl::addListener(MegaListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    listeners.insert(listener);
}

void MegaApiImpl::addRequestListener(MegaRequestListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    requestListeners.insert(listener);
}

void MegaApiImpl::addTransferListener(MegaTransferListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    transferListeners.insert(listener);
}

void MegaApiImpl::addScheduledCopyListener(MegaScheduledCopyListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    backupListeners.insert(listener);
}

void MegaApiImpl::addGlobalListener(MegaGlobalListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    globalListeners.insert(listener);
}

void MegaApiImpl::removeListener(MegaListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    listeners.erase(listener);
}

void MegaApiImpl::removeRequestListener(MegaRequestListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    requestListeners.erase(listener);

    std::map<int, MegaRequestPrivate*>::iterator it = requestMap.begin();
    while(it != requestMap.end())
    {
        MegaRequestPrivate* request = it->second;
        if(request->getListener() == listener)
            request->setListener(NULL);

        it++;
    }

    requestQueue.removeListener(listener);
}

void MegaApiImpl::removeTransferListener(MegaTransferListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    transferListeners.erase(listener);

    std::map<int, MegaTransferPrivate*>::iterator it = transferMap.begin();
    while(it != transferMap.end())
    {
        MegaTransferPrivate* transfer = it->second;
        if(transfer->getListener() == listener)
            transfer->setListener(NULL);

        it++;
    }

    transferQueue.removeListener(listener);
}

void MegaApiImpl::removeScheduledCopyListener(MegaScheduledCopyListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    backupListeners.erase(listener);

    std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin();
    while(it != backupsMap.end())
    {
        MegaScheduledCopyController* backup = it->second;
        if(backup->getBackupListener() == listener)
            backup->setBackupListener(NULL);

        it++;
    }

    requestQueue.removeListener(listener);
}

void MegaApiImpl::removeGlobalListener(MegaGlobalListener* listener)
{
    if(!listener) return;

    SdkMutexGuard g(sdkMutex);
    globalListeners.erase(listener);
}

void MegaApiImpl::fireOnRequestStart(MegaRequestPrivate *request)
{
    assert(threadId == std::this_thread::get_id());
    LOG_info << client->clientname << "Request (" << request->getRequestString() << ") starting";
    for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;)
    {
        (*it++)->onRequestStart(api, request);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onRequestStart(api, request);
    }

    MegaRequestListener* listener = request->getListener();
    if(listener)
    {
        listener->onRequestStart(api, request);
    }
}


void MegaApiImpl::fireOnRequestFinish(MegaRequestPrivate *request, unique_ptr<MegaErrorPrivate> e, bool callbackIsFromSyncThread)
{
    assert(callbackIsFromSyncThread || threadId == std::this_thread::get_id());
#ifdef ENABLE_SYNC
    assert(!callbackIsFromSyncThread || client->syncs.onSyncThread());
#endif

    // call from other threads like sync thread. Push to requestQueue with performFireOnRequestFinish assigned,
    // all fireOneRequestFinish is therefore handled in sendPendingRequests processed by a single thread.
    if (threadId != std::this_thread::get_id())
    {
        auto ePtr = e.release();
        request->performFireOnRequestFinish = [this, request, ePtr]()
        {
            fireOnRequestFinish(request, std::unique_ptr<MegaErrorPrivate>(ePtr), false);
        };
        requestQueue.push(request);
        waiter->notify();
        return;
    }

    if(e->getErrorCode())
    {
        LOG_warn << (client ? client->clientname : "") << "Request (" << request->getRequestString() << ") finished with error: " << e->getErrorString();
    }
    else
    {
        LOG_info << (client ? client->clientname : "") << "Request (" << request->getRequestString() << ") finished";
    }

    for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;)
    {
        (*it++)->onRequestFinish(api, request, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onRequestFinish(api, request, e.get());
    }

    MegaRequestListener* listener = request->getListener();
    if(listener)
    {
        listener->onRequestFinish(api, request, e.get());
    }

    requestMap.erase(request->getTag());

    delete request;
}

void MegaApiImpl::fireOnRequestUpdate(MegaRequestPrivate *request)
{
    assert(threadId == std::this_thread::get_id());

    for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;)
    {
        (*it++)->onRequestUpdate(api, request);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onRequestUpdate(api, request);
    }

    MegaRequestListener* listener = request->getListener();
    if(listener)
    {
        listener->onRequestUpdate(api, request);
    }
}

void MegaApiImpl::fireOnRequestTemporaryError(MegaRequestPrivate *request, unique_ptr<MegaErrorPrivate> e)
{
    assert(threadId == std::this_thread::get_id());

    request->setNumRetry(request->getNumRetry() + 1);

    for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;)
    {
        (*it++)->onRequestTemporaryError(api, request, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onRequestTemporaryError(api, request, e.get());
    }

    MegaRequestListener* listener = request->getListener();
    if(listener)
    {
        listener->onRequestTemporaryError(api, request, e.get());
    }
}

void MegaApiImpl::fireOnTransferStart(MegaTransferPrivate *transfer)
{
    assert(threadId == std::this_thread::get_id());
    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);

    for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;)
    {
        (*it++)->onTransferStart(api, transfer);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onTransferStart(api, transfer);
    }

    MegaTransferListener* listener = transfer->getListener();
    if(listener)
    {
        listener->onTransferStart(api, transfer);
    }
}

void MegaApiImpl::fireOnTransferFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    assert(threadId == std::this_thread::get_id());
    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);
    transfer->setLastError(e.get());

    if(e->getErrorCode())
    {
        if (!(transfer->getState() == MegaTransfer::STATE_CANCELLED &&
              e->getErrorCode() == API_EINCOMPLETE &&
              transfer->getFolderTransferTag() > 0))
        {
            // avoid logging large numbers of lines for cancelled folder transfers with huge numbers of sub-transfers

            LOG_warn << "Transfer (" << transfer->getTransferString() << ") finished with error: " << e->getErrorString()
                        << " File: " << transfer->getFileName();

            if (e->hasExtraInfo() && e->getErrorCode() == API_ETOOMANY)
            {
                LOG_warn << "ETD affected: user status: " << e->getUserStatus() << "  link status: " << e->getLinkStatus();
            }
        }
    }
    else
    {
        LOG_info << "Transfer (" << transfer->getTransferString() << ") finished. File: " << transfer->getFileName();
    }

    for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;)
    {
        (*it++)->onTransferFinish(api, transfer, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onTransferFinish(api, transfer, e.get());
    }

    MegaTransferListener* listener = transfer->getListener();
    if (listener)
    {
        listener->onTransferFinish(api, transfer, e.get());
    }

    transferMap.erase(transfer->getTag());

    if (transfer->isStreamingTransfer())
    {
        client->removeAppData(transfer);
    }
    delete transfer;
}

void MegaApiImpl::fireOnTransferTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e)
{
    assert(threadId == std::this_thread::get_id());
    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);

    transfer->setNumRetry(transfer->getNumRetry() + 1);

    for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;)
    {
        (*it++)->onTransferTemporaryError(api, transfer, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onTransferTemporaryError(api, transfer, e.get());
    }

    MegaTransferListener* listener = transfer->getListener();
    if(listener)
    {
        listener->onTransferTemporaryError(api, transfer, e.get());
    }
}

MegaClient *MegaApiImpl::getMegaClient()
{
    return client;
}

void MegaApiImpl::fireOnTransferUpdate(MegaTransferPrivate *transfer)
{
    assert(threadId == std::this_thread::get_id());
    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);

    for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;)
    {
        (*it++)->onTransferUpdate(api, transfer);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onTransferUpdate(api, transfer);
    }

    MegaTransferListener* listener = transfer->getListener();
    if(listener)
    {
        listener->onTransferUpdate(api, transfer);
    }
}

void MegaApiImpl::fireOnFolderTransferUpdate(MegaTransferPrivate *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const LocalPath* currentFolder, const LocalPath* currentFileLeafname)
{
    // this occurs on worker thread for scanning stage (for uploads) and create tree (for downloads), and on SDK thread for the rest of calls
    assert((threadId != std::this_thread::get_id()
                && ((stage == MegaTransfer::STAGE_SCAN && transfer->getType() == MegaTransfer::TYPE_UPLOAD)
                        || (stage == MegaTransfer::STAGE_CREATE_TREE && transfer->getType() == MegaTransfer::TYPE_DOWNLOAD)))
            || threadId == std::this_thread::get_id());

    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);

    // This one is defined to only be called back on the listener for the transfer
    // not any of the global or megaapi listeners
    if (MegaTransferListener* listener = transfer->getListener())
    {
        listener->onFolderTransferUpdate(api, transfer, stage, foldercount, createdfoldercount, filecount,
                    currentFolder ? currentFolder->toPath(false).c_str() : nullptr,
                    currentFileLeafname ? currentFileLeafname->toPath(false).c_str() : nullptr);
    }
}

bool MegaApiImpl::fireOnTransferData(MegaTransferPrivate *transfer)
{
    assert(threadId == std::this_thread::get_id());
    notificationNumber++;
    transfer->setNotificationNumber(notificationNumber);

    bool result = false;
    MegaTransferListener* listener = transfer->getListener();
    if(listener)
    {
        result = listener->onTransferData(api, transfer, transfer->getLastBytes(), size_t(transfer->getDeltaSize()));
    }
    return result;
}

void MegaApiImpl::fireOnUsersUpdate(MegaUserList *users)
{
    assert(threadId == std::this_thread::get_id());

    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onUsersUpdate(api, users);
    }
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onUsersUpdate(api, users);
    }
}

void MegaApiImpl::fireOnUserAlertsUpdate(MegaUserAlertList *userAlerts)
{
    assert(threadId == std::this_thread::get_id());

    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onUserAlertsUpdate(api, userAlerts);
    }
    for (set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end();)
    {
        (*it++)->onUserAlertsUpdate(api, userAlerts);
    }
}

void MegaApiImpl::fireOnContactRequestsUpdate(MegaContactRequestList *requests)
{
    assert(threadId == std::this_thread::get_id());

    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onContactRequestsUpdate(api, requests);
    }
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onContactRequestsUpdate(api, requests);
    }
}

void MegaApiImpl::fireOnNodesUpdate(MegaNodeList *nodes)
{
    assert(threadId == std::this_thread::get_id());

    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onNodesUpdate(api, nodes);
    }
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onNodesUpdate(api, nodes);
    }
}

void MegaApiImpl::fireOnAccountUpdate()
{
    assert(threadId == std::this_thread::get_id());
    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onAccountUpdate(api);
    }
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onAccountUpdate(api);
    }
}

void MegaApiImpl::fireOnSetsUpdate(MegaSetList* sets)
{
    assert(threadId == std::this_thread::get_id());

    for (set<MegaGlobalListener*>::iterator it = globalListeners.begin(); it != globalListeners.end();)
    {
        (*it++)->onSetsUpdate(api, sets);
    }
    for (set<MegaListener*>::iterator it = listeners.begin(); it != listeners.end();)
    {
        (*it++)->onSetsUpdate(api, sets);
    }
}

void MegaApiImpl::fireOnSetElementsUpdate(MegaSetElementList* elements)
{
    assert(threadId == std::this_thread::get_id());

    for (set<MegaGlobalListener*>::iterator it = globalListeners.begin(); it != globalListeners.end();)
    {
        (*it++)->onSetElementsUpdate(api, elements);
    }
    for (set<MegaListener*>::iterator it = listeners.begin(); it != listeners.end();)
    {
        (*it++)->onSetElementsUpdate(api, elements);
    }
}

void MegaApiImpl::fireOnEvent(MegaEventPrivate *event)
{
    LOG_debug << "Sending " << event->getEventString() << " to app." << event->getValidDataToString();
    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onEvent(api, event);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onEvent(api, event);
    }

    delete event;
}

#ifdef ENABLE_SYNC
void MegaApiImpl::fireOnSyncStateChanged(MegaSyncPrivate *sync)
{
    assert(sync->getBackupId() != INVALID_HANDLE);
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onSyncStateChanged(api, sync);
    }
}

void MegaApiImpl::fireOnSyncStatsUpdated(MegaSyncStatsPrivate *stats)
{
    assert(stats->getBackupId() != INVALID_HANDLE);
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onSyncStatsUpdated(api, stats);
    }
}

void MegaApiImpl::fireOnSyncAdded(MegaSyncPrivate *sync)
{
    assert(sync->getBackupId() != INVALID_HANDLE);
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onSyncAdded(api, sync);
    }
}

void MegaApiImpl::fireOnSyncDeleted(MegaSyncPrivate *sync)
{
    assert(sync->getBackupId() != INVALID_HANDLE);
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onSyncDeleted(api, sync);
    }
}

void MegaApiImpl::fireOnGlobalSyncStateChanged()
{
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onGlobalSyncStateChanged(api);
    }

    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onGlobalSyncStateChanged(api);
    }
}

void MegaApiImpl::fireOnFileSyncStateChanged(MegaSyncPrivate *sync, string *localPath, int newState)
{
    assert(sync->getBackupId() != INVALID_HANDLE);
    assert(client->syncs.onSyncThread());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onSyncFileStateChanged(api, sync, localPath, newState);
    }
}

#endif

void MegaApiImpl::fireOnBackupStateChanged(MegaScheduledCopyController *backup)
{
    assert(threadId == std::this_thread::get_id());
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onBackupStateChanged(api, backup);
    }

    for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;)
    {
        (*it++)->onBackupStateChanged(api, backup);
    }

    MegaScheduledCopyListener* listener = backup->getBackupListener();
    if(listener)
    {
        listener->onBackupStateChanged(api, backup);
    }
}


void MegaApiImpl::fireOnBackupStart(MegaScheduledCopyController *backup)
{
    for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;)
    {
        (*it++)->onBackupStart(api, backup);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onBackupStart(api, backup);
    }

    MegaScheduledCopyListener* listener = backup->getBackupListener();
    if(listener)
    {
        listener->onBackupStart(api, backup);
    }

}

void MegaApiImpl::fireOnBackupFinish(MegaScheduledCopyController *backup, unique_ptr<MegaErrorPrivate> e)
{
    for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;)
    {
        (*it++)->onBackupFinish(api, backup, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onBackupFinish(api, backup, e.get());
    }

    MegaScheduledCopyListener* listener = backup->getBackupListener();
    if(listener)
    {
        listener->onBackupFinish(api, backup, e.get());
    }
}

void MegaApiImpl::fireOnBackupTemporaryError(MegaScheduledCopyController *backup, unique_ptr<MegaErrorPrivate> e)
{
    for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;)
    {
        (*it++)->onBackupTemporaryError(api, backup, e.get());
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onBackupTemporaryError(api, backup, e.get());
    }

    MegaScheduledCopyListener* listener = backup->getBackupListener();
    if(listener)
    {
        listener->onBackupTemporaryError(api, backup, e.get());
    }
}

void MegaApiImpl::fireOnBackupUpdate(MegaScheduledCopyController *backup)
{
    assert(threadId == std::this_thread::get_id());
//    notificationNumber++; //TODO: should we use notificationNumber for backups??

    for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;)
    {
        (*it++)->onBackupUpdate(api, backup);
    }

    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onBackupUpdate(api, backup);
    }

    MegaScheduledCopyListener* listener = backup->getBackupListener();
    if(listener)
    {
        listener->onBackupUpdate(api, backup);
    }
}


#ifdef ENABLE_CHAT

void MegaApiImpl::fireOnChatsUpdate(MegaTextChatList *chats)
{
    assert(threadId == std::this_thread::get_id());
    for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;)
    {
        (*it++)->onChatsUpdate(api, chats);
    }
    for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;)
    {
        (*it++)->onChatsUpdate(api, chats);
    }
}

#endif

void MegaApiImpl::processTransferPrepare(Transfer *t, MegaTransferPrivate *transfer)
{
    transfer->setTotalBytes(t->size);
    transfer->setState(t->state);
    transfer->setPriority(t->priority);
    LOG_info << "Transfer (" << transfer->getTransferString() << ") starting. File: " << transfer->getFileName();
}

void MegaApiImpl::processTransferUpdate(Transfer *tr, MegaTransferPrivate *transfer)
{
    dstime currentTime = Waiter::ds;
    if (tr->slot)
    {
        m_off_t prevTransferredBytes = transfer->getTransferredBytes();
        m_off_t deltaSize = tr->slot->progressreported - prevTransferredBytes;
        LOG_verbose << "Transfer update: progress to update = " << deltaSize << ", transfer size = " << tr->size << ", transferred bytes = " << transfer->getTransferredBytes() << ", progress reported = " << tr->slot->progressreported;
        transfer->setStartTime(currentTime);
        transfer->setTransferredBytes(tr->slot->progressreported);
        transfer->setDeltaSize(deltaSize);
        transfer->setSpeed(tr->slot->speed);
        transfer->setMeanSpeed(tr->slot->meanSpeed);

        if (tr->type == GET)
        {
            totalDownloadedBytes += deltaSize;
        }
        else
        {
            totalUploadedBytes += deltaSize;
        }
    }
    else
    {
        LOG_verbose << "No TransferSlot. Reset last progress, speed and mean speed.";
        transfer->setDeltaSize(0);
        transfer->setSpeed(0);
        transfer->setMeanSpeed(0);
    }

    transfer->setState(tr->state);
    transfer->setPriority(tr->priority);
    transfer->setUpdateTime(currentTime);
    fireOnTransferUpdate(transfer);
}

void MegaApiImpl::processTransferComplete(Transfer *tr, MegaTransferPrivate *transfer)
{
    dstime currentTime = Waiter::ds;
    m_off_t deltaSize = tr->size - transfer->getTransferredBytes();
    LOG_verbose << "Transfer complete: final progress to update = " << deltaSize << ", transfer size = " << tr->size << ", transferred bytes = " << transfer->getTransferredBytes();
    transfer->setStartTime(currentTime);
    transfer->setUpdateTime(currentTime);
    transfer->setTransferredBytes(tr->size);
    transfer->setPriority(tr->priority);
    transfer->setDeltaSize(deltaSize);
    transfer->setSpeed(tr->slot ? tr->slot->speed : 0);
    transfer->setMeanSpeed(tr->slot ? tr->slot->meanSpeed : 0);

    if (tr->type == GET)
    {
        totalDownloadedBytes += deltaSize;

        if (pendingDownloads > 0)
        {
            pendingDownloads--;
        }

        transfer->setState(MegaTransfer::STATE_COMPLETED);
        fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_OK));
    }
    else
    {
        totalUploadedBytes += deltaSize;

        transfer->setState(MegaTransfer::STATE_COMPLETING);
        transfer->setTransfer(NULL);
        fireOnTransferUpdate(transfer);
    }
}

void MegaApiImpl::processTransferFailed(Transfer *tr, MegaTransferPrivate *transfer, const Error& e, dstime timeleft)
{
    auto megaError = make_unique<MegaErrorPrivate>(e, timeleft / 10);
    transfer->setStartTime(Waiter::ds);
    transfer->setUpdateTime(Waiter::ds);
    transfer->setDeltaSize(0);
    transfer->setSpeed(0);
    transfer->setMeanSpeed(0);
    transfer->setLastError(megaError.get());
    transfer->setPriority(tr->priority);
    if (e == API_ETOOMANY && e.hasExtraInfo())
    {
        transfer->setState(MegaTransfer::STATE_FAILED);
        transfer->setForeignOverquota(false);
        fireOnTransferFinish(transfer, std::move(megaError));
    }
    else
    {
        transfer->setState(MegaTransfer::STATE_RETRYING);
        LOG_verbose << "processTransferFailed checking handle " << transfer->getParentHandle();
        transfer->setForeignOverquota(e == API_EOVERQUOTA && client->isForeignNode(NodeHandle().set6byte(transfer->getParentHandle())));
        fireOnTransferTemporaryError(transfer, std::move(megaError));
    }

}

void MegaApiImpl::processTransferRemoved(Transfer *tr, MegaTransferPrivate *transfer, const Error& e)
{
    if (tr)
    {
        m_off_t deltaSize = tr->size - transfer->getTransferredBytes();
        if (tr->type == GET)
        {
            totalDownloadedBytes += deltaSize;

            if (pendingDownloads > 0)
            {
                pendingDownloads--;
            }

            if (totalDownloads > 0)
            {
                totalDownloads--;
            }
        }
        else
        {
            totalUploadedBytes += deltaSize;

            if (pendingUploads > 0)
            {
                pendingUploads--;
            }

            if (totalUploads > 0)
            {
                totalUploads--;
            }
        }

        transfer->setPriority(tr->priority);
    }

    transfer->setStartTime(Waiter::ds);
    transfer->setUpdateTime(Waiter::ds);
    transfer->setState(e == API_EINCOMPLETE ? MegaTransfer::STATE_CANCELLED : MegaTransfer::STATE_FAILED);
    fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(e));
}

MegaError MegaApiImpl::checkAccess(MegaNode* megaNode, int level)
{
    std::unique_ptr<MegaError> megaError(checkAccessErrorExtended(megaNode, level));
    return megaError->getErrorCode();
}

MegaError *MegaApiImpl::checkAccessErrorExtended(MegaNode *megaNode, int level)
{
    if(!megaNode || level < MegaShare::ACCESS_UNKNOWN || level > MegaShare::ACCESS_OWNER)
    {
        return new MegaErrorPrivate(API_EARGS);
    }

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    if(!node)
    {
        return new MegaErrorPrivate(API_ENOENT);
    }

    accesslevel_t a = OWNER;
    switch(level)
    {
        case MegaShare::ACCESS_UNKNOWN:
        case MegaShare::ACCESS_READ:
            a = RDONLY;
            break;
        case MegaShare::ACCESS_READWRITE:
            a = RDWR;
            break;
        case MegaShare::ACCESS_FULL:
            a = FULL;
            break;
        case MegaShare::ACCESS_OWNER:
            a = OWNER;
            break;
    }

    return client->checkaccess(node, a) ? new MegaErrorPrivate(API_OK) : new MegaErrorPrivate(API_EACCESS);
}

MegaError MegaApiImpl::checkMove(MegaNode* megaNode, MegaNode* targetNode)
{
    std::unique_ptr<MegaError> megaError(checkMoveErrorExtended(megaNode, targetNode));
    return megaError->getErrorCode();
}

MegaError *MegaApiImpl::checkMoveErrorExtended(MegaNode *megaNode, MegaNode *targetNode)
{
    if(!megaNode || !targetNode) return new MegaErrorPrivate(API_EARGS);

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(megaNode->getHandle());
    Node *target = client->nodebyhandle(targetNode->getHandle());
    if(!node || !target)
    {
        return new MegaErrorPrivate(API_ENOENT);
    }

    return new MegaErrorPrivate(client->checkmove(node,target));
}

bool MegaApiImpl::isFilesystemAvailable()
{
    SdkMutexGuard g(sdkMutex);
    return client->nodeByHandle(client->mNodeManager.getRootNodeFiles()) != NULL;
}

bool isDigit(const char *c)
{
    return (*c >= '0' && *c <= '9');
}

// returns 0 if i==j, +1 if i goes first, -1 if j goes first.
int naturalsorting_compare (const char *i, const char *j)
{
    static uint64_t maxNumber = (ULONG_MAX - 57) / 10; // 57 --> ASCII code for '9'

    bool stringMode = true;

    while (*i && *j)
    {
        if (stringMode)
        {
            char char_i, char_j;
            while ( (char_i = *i) && (char_j = *j) )
            {
                bool char_i_isDigit = isDigit(i);
                bool char_j_isDigit = isDigit(j);

                if (char_i_isDigit && char_j_isDigit)
                {
                    stringMode = false;
                    break;
                }

                if(char_i_isDigit)
                {
                    return -1;
                }

                if(char_j_isDigit)
                {
                    return 1;
                }

                int difference = strncasecmp((char *)&char_i, (char *)&char_j, 1);
                if (difference)
                {
                    return difference;
                }

                ++i;
                ++j;
            }
        }
        else    // we are comparing numbers on both strings
        {
            uint64_t number_i = 0;
            unsigned int i_overflow_count = 0;
            while (*i && isDigit(i))
            {
                number_i = number_i * 10 + (*i - 48); // '0' ASCII code is 48
                ++i;

                // check the number won't overflow upon addition of next char
                if (number_i >= maxNumber)
                {
                    number_i -= maxNumber;
                    i_overflow_count++;
                }
            }

            uint64_t number_j = 0;
            unsigned int j_overflow_count = 0;
            while (*j && isDigit(j))
            {
                number_j = number_j * 10 + (*j - 48);
                ++j;

                // check the number won't overflow upon addition of next char
                if (number_j >= maxNumber)
                {
                    number_j -= maxNumber;
                    j_overflow_count++;
                }
            }

            int difference = i_overflow_count - j_overflow_count;
            if (difference)
            {
                return difference;
            }

            if (number_i != number_j)
            {
                return number_i > number_j ? 1 : -1;
            }

            stringMode = true;
        }
    }

    if (*j)
    {
        return -1;
    }

    if (*i)
    {
        return 1;
    }

    return 0;
}

std::function<bool (Node*, Node*)> MegaApiImpl::getComparatorFunction(int order, MegaClient& mc)
{
    switch (order)
    {
        case MegaApi::ORDER_NONE: return nullptr;
        case MegaApi::ORDER_DEFAULT_ASC: return MegaApiImpl::nodeComparatorDefaultASC;
        case MegaApi::ORDER_DEFAULT_DESC: return MegaApiImpl::nodeComparatorDefaultDESC;
        case MegaApi::ORDER_SIZE_ASC: return MegaApiImpl::nodeComparatorSizeASC;
        case MegaApi::ORDER_SIZE_DESC: return MegaApiImpl::nodeComparatorSizeDESC;
        case MegaApi::ORDER_CREATION_ASC: return MegaApiImpl::nodeComparatorCreationASC;
        case MegaApi::ORDER_CREATION_DESC: return MegaApiImpl::nodeComparatorCreationDESC;
        case MegaApi::ORDER_MODIFICATION_ASC: return MegaApiImpl::nodeComparatorModificationASC;
        case MegaApi::ORDER_MODIFICATION_DESC: return MegaApiImpl::nodeComparatorModificationDESC;
        case MegaApi::ORDER_ALPHABETICAL_ASC: return MegaApiImpl::nodeComparatorDefaultASC;
        case MegaApi::ORDER_ALPHABETICAL_DESC: return MegaApiImpl::nodeComparatorDefaultDESC;
        case MegaApi::ORDER_LINK_CREATION_ASC: return MegaApiImpl::nodeComparatorPublicLinkCreationASC;
        case MegaApi::ORDER_LINK_CREATION_DESC: return MegaApiImpl::nodeComparatorPublicLinkCreationDESC;
        case MegaApi::ORDER_PHOTO_ASC: return [&mc](Node* i, Node*j) { return MegaApiImpl::nodeComparatorPhotoASC(i, j, mc); };
        case MegaApi::ORDER_PHOTO_DESC: return [&mc](Node* i, Node*j) { return MegaApiImpl::nodeComparatorPhotoDESC(i, j, mc); };
        case MegaApi::ORDER_VIDEO_ASC: return [&mc](Node* i, Node*j) { return MegaApiImpl::nodeComparatorVideoASC(i, j, mc); };
        case MegaApi::ORDER_VIDEO_DESC: return [&mc](Node* i, Node*j) { return MegaApiImpl::nodeComparatorVideoDESC(i, j, mc); };
        case MegaApi::ORDER_LABEL_ASC: return MegaApiImpl::nodeComparatorLabelASC;
        case MegaApi::ORDER_LABEL_DESC: return MegaApiImpl::nodeComparatorLabelDESC;
        case MegaApi::ORDER_FAV_ASC: return MegaApiImpl::nodeComparatorFavASC;
        case MegaApi::ORDER_FAV_DESC: return MegaApiImpl::nodeComparatorFavDESC;
    }
    assert(false);
    return nullptr;
}

void MegaApiImpl::sortByComparatorFunction(node_vector& v, int order, MegaClient& mc)
{
    if (auto f = getComparatorFunction(order, mc))
    {
        std::sort(v.begin(), v.end(), f);
    }
}

bool MegaApiImpl::nodeNaturalComparatorASC(Node *i, Node *j)
{
    int r = naturalsorting_compare(i->displayname(), j->displayname());
    if (r < 0)
    {
        return 1;
    }
    return 0;
}

bool MegaApiImpl::nodeNaturalComparatorDESC(Node *i, Node *j)
{
    int r = naturalsorting_compare(i->displayname(), j->displayname());
    if (r <= 0)
    {
        return 0;
    }
    return 1;
}

bool MegaApiImpl::nodeComparatorDefaultASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    return nodeNaturalComparatorASC(i, j);
}

bool MegaApiImpl::nodeComparatorDefaultDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    return nodeNaturalComparatorDESC(i, j);
}

bool MegaApiImpl::nodeComparatorSizeASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    m_off_t r = sizeDifference(i, j);
    if (r < 0)
    {
        return 1;
    }
    if (r > 0)
    {
        return 0;
    }
    return nodeNaturalComparatorASC(i, j);
}

bool MegaApiImpl::nodeComparatorSizeDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    m_off_t r = sizeDifference(i, j);
    if (r < 0)
    {
        return 0;
    }
    if (r > 0)
    {
        return 1;
    }
    return nodeNaturalComparatorDESC(i, j);
}

bool MegaApiImpl::nodeComparatorCreationASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }
    if (i->ctime < j->ctime)
    {
        return 1;
    }
    if (i->ctime > j->ctime)
    {
        return 0;
    }
    return nodeNaturalComparatorASC(i, j);
}

bool MegaApiImpl::nodeComparatorCreationDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }
    if (i->ctime < j->ctime)
    {
        return 0;
    }
    if (i->ctime > j->ctime)
    {
        return 1;
    }
    return nodeNaturalComparatorDESC(i, j);
}

bool MegaApiImpl::nodeComparatorModificationASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    if (i->type != FILENODE) // Only file nodes have last modified date
    {
        // If node doesn't have mtime, order alphabetically ascending
        return nodeNaturalComparatorASC(i, j);
    }

    m_time_t r = i->mtime - j->mtime;
    if (r < 0)
    {
        return 1;
    }
    if (r > 0)
    {
        return 0;
    }
    return nodeNaturalComparatorASC(i, j);
}

bool MegaApiImpl::nodeComparatorModificationDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    if (i->type != FILENODE)
    {
        // If node doesn't have mtime, order alphabetically ascending
        return nodeNaturalComparatorASC(i, j);
    }

    m_time_t r = i->mtime - j->mtime;
    if (r < 0)
    {
        return 0;
    }
    if (r > 0)
    {
        return 1;
    }

    return nodeNaturalComparatorDESC(i, j);
}

bool MegaApiImpl::nodeComparatorPublicLinkCreationASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }
    if (!i->plink || !j->plink)
    {
        return nodeNaturalComparatorASC(i, j);
    }
    if (i->plink->cts < j->plink->cts)
    {
        return 1;
    }
    if (i->plink->cts > j->plink->cts)
    {
        return 0;
    }
    return nodeNaturalComparatorASC(i, j);
}

bool MegaApiImpl::nodeComparatorPublicLinkCreationDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }
    if (!i->plink || !j->plink)
    {
        return nodeNaturalComparatorDESC(i, j);
    }
    if (i->plink->cts < j->plink->cts)
    {
        return 0;
    }
    if (i->plink->cts > j->plink->cts)
    {
        return 1;
    }
    return nodeNaturalComparatorDESC(i, j);
}

bool MegaApiImpl::nodeComparatorLabelASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    nameid labelId = AttrMap::string2nameid("lbl");
    int iLabel = MegaNode::NODE_LBL_UNKNOWN;
    auto iAttrIt = i->attrs.map.find(labelId);
    if (iAttrIt != i->attrs.map.end())
    {
       iLabel = std::atoi(iAttrIt->second.c_str());
    }

    int jLabel = MegaNode::NODE_LBL_UNKNOWN;
    auto jAttrIt = j->attrs.map.find(labelId);
    if (jAttrIt != j->attrs.map.end())
    {
       jLabel = std::atoi(jAttrIt->second.c_str());
    }

    if (iLabel == MegaNode::NODE_LBL_UNKNOWN && jLabel ==  MegaNode::NODE_LBL_UNKNOWN)
    {
        return nodeComparatorDefaultASC(i, j);
    }
    if (iLabel == MegaNode::NODE_LBL_UNKNOWN)
    {
        return 0;
    }
    if (jLabel == MegaNode::NODE_LBL_UNKNOWN)
    {
        return 1;
    }

    if (iLabel < jLabel)
    {
        return 1;
    }
    if (iLabel > jLabel)
    {
        return 0;
    }
    return nodeComparatorDefaultASC(i, j);
}

bool MegaApiImpl::nodeComparatorLabelDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    nameid labelId = AttrMap::string2nameid("lbl");
    int iLabel = MegaNode::NODE_LBL_UNKNOWN;
    auto iAttrIt = i->attrs.map.find(labelId);
    if (iAttrIt != i->attrs.map.end())
    {
       iLabel = std::atoi(iAttrIt->second.c_str());
    }

    int jLabel = MegaNode::NODE_LBL_UNKNOWN;
    auto jAttrIt = j->attrs.map.find(labelId);
    if (jAttrIt != j->attrs.map.end())
    {
       jLabel = std::atoi(jAttrIt->second.c_str());
    }

    if (iLabel == MegaNode::NODE_LBL_UNKNOWN && jLabel == MegaNode::NODE_LBL_UNKNOWN)
    {
        return nodeComparatorDefaultASC(i, j);
    }
    if (iLabel == MegaNode::NODE_LBL_UNKNOWN)
    {
        return 0;
    }
    if (jLabel == MegaNode::NODE_LBL_UNKNOWN)
    {
        return 1;
    }

    if (iLabel < jLabel)
    {
        return 0;
    }
    if (iLabel > jLabel)
    {
        return 1;
    }
    return nodeComparatorDefaultASC(i, j);
}


bool MegaApiImpl::nodeComparatorFavASC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    nameid favId = AttrMap::string2nameid("fav");
    bool iFav = (i->attrs.map.find(favId) != i->attrs.map.end());
    bool jFav = (j->attrs.map.find(favId) != j->attrs.map.end());

    if (!(iFav ^ jFav))
    {
        // if both or none of them, have the same attribute value, order default ASC
        return nodeComparatorDefaultASC(i, j);
    }
    else if (iFav)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

bool MegaApiImpl::nodeComparatorFavDESC(Node *i, Node *j)
{
    int t = typeComparator(i, j);
    if (t >= 0)
    {
        return t;
    }

    nameid favId = AttrMap::string2nameid("fav");
    bool iFav = (i->attrs.map.find(favId) != i->attrs.map.end());
    bool jFav = (j->attrs.map.find(favId) != j->attrs.map.end());

    if (!(iFav ^ jFav))
    {
        // if both or none of them, have the same attribute value, order default ASC
        return nodeComparatorDefaultASC(i, j);
    }
    else if (iFav)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

// Compare node types. Returns -1 if i==j, 0 if i goes first, +1 if j goes first.
int MegaApiImpl::typeComparator(Node *i, Node *j)
{
    if (i->type < j->type)
    {
        return 0;
    }
    if (i->type > j->type)
    {
        return 1;
    }
    return -1;
}

bool MegaApiImpl::nodeComparatorPhotoASC(Node *i, Node *j, MegaClient& mc)
{
    bool i_photo = false, i_video = false, j_photo = false, j_video = false;
    bool i_media = mc.nodeIsMedia(i, &i_photo, &i_video);
    bool j_media = mc.nodeIsMedia(j, &j_photo, &j_video);

    if (i_media != j_media)
    {
        return i_media;  // media go first
    }

    if (i_photo != j_photo)
    {
        return i_photo; // photos before videos
    }

    // within photos or videos or non-media, order by date
    return nodeComparatorModificationASC(i, j);
}

bool MegaApiImpl::nodeComparatorPhotoDESC(Node *i, Node *j, MegaClient& mc)
{
    bool i_photo = false, i_video = false, j_photo = false, j_video = false;
    bool i_media = mc.nodeIsMedia(i, &i_photo, &i_video);
    bool j_media = mc.nodeIsMedia(j, &j_photo, &j_video);

    if (i_media != j_media)
    {
        return i_media;  // media go first
    }

    if (i_photo != j_photo)
    {
        return i_photo; // photos before videos
    }

    // within photos or videos or non-media, order by date
    return nodeComparatorModificationDESC(i, j);
}

bool MegaApiImpl::nodeComparatorVideoASC(Node *i, Node *j, MegaClient& mc)
{
    bool i_photo = false, i_video = false, j_photo = false, j_video = false;
    bool i_media = mc.nodeIsMedia(i, &i_photo, &i_video);
    bool j_media = mc.nodeIsMedia(j, &j_photo, &j_video);

    if (i_media != j_media)
    {
        return i_media;  // media go first
    }

    if (i_video != j_video)
    {
        return i_video; // videos before photos
    }

    // within photos or videos or non-media, order by date
    return nodeComparatorModificationASC(i, j);
}

bool MegaApiImpl::nodeComparatorVideoDESC(Node *i, Node *j, MegaClient& mc)
{
    bool i_photo = false, i_video = false, j_photo = false, j_video = false;
    bool i_media = mc.nodeIsMedia(i, &i_photo, &i_video);
    bool j_media = mc.nodeIsMedia(j, &j_photo, &j_video);

    if (i_media != j_media)
    {
        return i_media;  // media go first
    }

    if (i_video != j_video)
    {
        return i_video; // videos before photos
    }

    // within photos or videos or non-media, order by date
    return nodeComparatorModificationDESC(i, j);
}

int MegaApiImpl::getNumChildren(MegaNode* p)
{
    if (!p || p->getType() == MegaNode::TYPE_FILE)
    {
        return 0;
    }

    SdkMutexGuard lock(sdkMutex);
    return static_cast<int>(client->getNumberOfChildren(NodeHandle().set6byte(p->getHandle())));
}

int MegaApiImpl::getNumChildFiles(MegaNode* p)
{
    if (!p || p->getType() == MegaNode::TYPE_FILE)
    {
        return 0;
    }

    SdkMutexGuard lock(sdkMutex);
    Node *parent = client->nodebyhandle(p->getHandle());
    if (!parent || parent->type == FILENODE)
    {
        return 0;
    }

    return static_cast<int>(client->mNodeManager.getNumberOfChildrenByType(parent->nodeHandle(), FILENODE));
}

int MegaApiImpl::getNumChildFolders(MegaNode* p)
{
    if (!p || p->getType() == MegaNode::TYPE_FILE)
    {
        return 0;
    }

    SdkMutexGuard lock(sdkMutex);
    Node *parent = client->nodebyhandle(p->getHandle());
    if (!parent || parent->type == FILENODE)
    {
        return 0;
    }

    return static_cast<int>(client->mNodeManager.getNumberOfChildrenByType(parent->nodeHandle(), FOLDERNODE));
}


MegaNodeList *MegaApiImpl::getChildren(MegaNode* p, int order, CancelToken cancelToken)
{
    if (!p || p->getType() == MegaNode::TYPE_FILE)
    {
        return new MegaNodeListPrivate();
    }

    node_vector childrenNodes;

    SdkMutexGuard guard(sdkMutex);

    Node *parent = client->nodebyhandle(p->getHandle());
    if (parent && parent->type != FILENODE)
    {
        node_list nodeList = client->getChildren(parent, cancelToken);
        childrenNodes.reserve(nodeList.size());
        for (node_list::iterator it = nodeList.begin(); it != nodeList.end(); )
        {
            childrenNodes.push_back(*it++);
        }
        if (std::function<bool(Node*, Node*)> comparatorFunction = getComparatorFunction(order, *client))
        {
            std::sort(childrenNodes.begin(), childrenNodes.end(), comparatorFunction);
        }
    }
    return new MegaNodeListPrivate(childrenNodes.data(), int(childrenNodes.size()));
}

MegaNodeList *MegaApiImpl::getChildren(MegaNodeList *parentNodes, int order)
{
    SdkMutexGuard guard(sdkMutex);

    // prepare a vector with children of every parent node all together
    node_vector childrenNodes;
    for (int i = 0; i < parentNodes->size(); i++)
    {
        MegaNode *p = parentNodes->get(i);
        if (!p || p->getType() == MegaNode::TYPE_FILE)
        {
            continue;
        }

        Node *parent = client->nodebyhandle(p->getHandle());
        if (parent && parent->type != FILENODE)
        {
            node_list nodeChildrenList = client->getChildren(parent);
            childrenNodes.reserve(childrenNodes.size() + nodeChildrenList.size());
            for (Node* node : nodeChildrenList)
            {
                childrenNodes.push_back(node);
            }
        }
    }

    // sort all the children together
    if (std::function<bool(Node*, Node*)> comparatorFunction = getComparatorFunction(order, *client))
    {
        std::sort(childrenNodes.begin(), childrenNodes.end(), comparatorFunction);
    }

    return new MegaNodeListPrivate(childrenNodes.data(), int(childrenNodes.size()));
}

MegaNodeList *MegaApiImpl::getVersions(MegaNode *node)
{
    if (!node || node->getType() != MegaNode::TYPE_FILE)
    {
        return new MegaNodeListPrivate();
    }

    SdkMutexGuard g(sdkMutex);
    Node *current = client->nodebyhandle(node->getHandle());
    if (!current || current->type != FILENODE)
    {
        return new MegaNodeListPrivate();
    }

    vector<Node*> versions;
    versions.push_back(current);
    bool lookingFor = true;
    while (lookingFor)
    {
        node_list nodeList = client->getChildren(current);
        if (nodeList.empty())
        {
            lookingFor = false;
        }
        else
        {
            assert(nodeList.back()->parent == current);
            current = nodeList.back();
            assert(current->type == FILENODE);
            versions.push_back(current);
        }
    }

    return new MegaNodeListPrivate(versions.data(), int(versions.size()));
}

int MegaApiImpl::getNumVersions(MegaNode *node)
{
    if (!node || node->getType() != MegaNode::TYPE_FILE)
    {
        return 0;
    }

    SdkMutexGuard guard(sdkMutex);
    return client->mNodeManager.getNumVersions(NodeHandle().set6byte(node->getHandle()));
}

bool MegaApiImpl::hasVersions(MegaNode *node)
{
    if (!node || node->getType() != MegaNode::TYPE_FILE)
    {
        return false;
    }

    SdkMutexGuard guard(sdkMutex);
    return client->mNodeManager.hasVersion(NodeHandle().set6byte(node->getHandle()));
}

MegaNodeList* MegaApiImpl::getChildrenFromType(MegaNode* p, int type, int order, CancelToken cancelToken)
{
    if (!p || p->getType() == MegaNode::TYPE_FILE || type < nodetype_t::FILENODE || type > nodetype_t::FOLDERNODE)
    {
        return new MegaNodeListPrivate();
    }

    SdkMutexGuard guard(sdkMutex);

    node_vector childrenNodes;
    Node *parent = client->nodebyhandle(p->getHandle());
    if (parent && parent->type != FILENODE)
    {
        childrenNodes = client->mNodeManager.getChildrenFromType(parent, static_cast<nodetype_t>(type), cancelToken);

        if (std::function<bool(Node*, Node*)> comparatorFunction = getComparatorFunction(order, *client))
        {
            std::sort(childrenNodes.begin(), childrenNodes.end(), comparatorFunction);
        }
    }

    return new MegaNodeListPrivate(childrenNodes.data(), int(childrenNodes.size()));
}

bool MegaApiImpl::hasChildren(MegaNode *parent)
{
    if (!parent || parent->getType() == MegaNode::TYPE_FILE)
    {
        return false;
    }

    SdkMutexGuard g(sdkMutex);
    Node *p = client->nodebyhandle(parent->getHandle());
    if (!p || p->type == FILENODE)
    {
        return false;
    }

    return !!client->getNumberOfChildren(p->nodeHandle());
}

MegaNode *MegaApiImpl::getChildNode(MegaNode *parent, const char* name)
{
    if (!parent || !name || parent->getType() == MegaNode::TYPE_FILE)
    {
        return NULL;
    }

    SdkMutexGuard guard(sdkMutex);
    Node *parentNode = client->nodebyhandle(parent->getHandle());
    if (!parentNode || parentNode->type == FILENODE)
    {
        return NULL;
    }

    return MegaNodePrivate::fromNode(client->childnodebyname(parentNode, name));
}

MegaNode* MegaApiImpl::getChildNodeOfType(MegaNode *parent, const char *name, int type)
{
    if (!name || !parent || (type != MegaNode::TYPE_FILE && type != MegaNode::TYPE_FOLDER))
    {
        return nullptr;
    }

    SdkMutexGuard guard(sdkMutex);
    Node *parentNode = client->nodebyhandle(parent->getHandle());
    if (!parentNode || parentNode->type == FILENODE)
    {
        return nullptr;
    }

    return MegaNodePrivate::fromNode(client->childnodebynametype(parentNode, name, static_cast<nodetype_t>(type)));
}

Node *MegaApiImpl::getNodeByFingerprintInternal(const char *fingerprint)
{
    unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
    if (!fp)
    {
        return NULL;
    }

    SdkMutexGuard g(sdkMutex);
    return client->mNodeManager.getNodeByFingerprint(*fp);
}

Node *MegaApiImpl::getNodeByFingerprintInternal(const char *fingerprint, Node *parent)
{
    unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint));
    if (!fp)
    {
        return NULL;
    }

    Node *n = NULL;
    SdkMutexGuard g(sdkMutex);
    node_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp);
    if (nodes.size())
    {
        n = nodes.at(0);
    }

    if (n && parent && n->parent != parent)
    {
        for (unsigned int i = 1; i < nodes.size(); i++)
        {
            Node* node = nodes.at(i);
            if (node->parent == parent)
            {
                n = node;
                break;
            }
        }
    }
    return n;
}

FileFingerprint *MegaApiImpl::getFileFingerprintInternal(const char *fingerprint)
{
    if(!fingerprint || !fingerprint[0])
    {
        return NULL;
    }

    m_off_t size = 0;
    unsigned int fsize = unsigned(strlen(fingerprint));
    unsigned int ssize = fingerprint[0] - 'A';
    if(ssize > (sizeof(size) * 4 / 3 + 4) || fsize <= (ssize + 1))
    {
        return NULL;
    }

    int len =  sizeof(size) + 1;
    byte *buf = new byte[len];
    Base64::atob(fingerprint + 1, buf, len);
    int l = Serialize64::unserialize(buf, len, (uint64_t *)&size);
    delete [] buf;
    if(l <= 0)
    {
        return NULL;
    }

    string sfingerprint = fingerprint + ssize + 1;

    FileFingerprint *fp = new FileFingerprint;
    if(!fp->unserializefingerprint(&sfingerprint))
    {
        delete fp;
        return NULL;
    }

    fp->size = size;

    return fp;
}

char *MegaApiImpl::getMegaFingerprintFromSdkFingerprint(const char *sdkFingerprint)
{
    if (!sdkFingerprint || !sdkFingerprint[0])
    {
        return NULL;
    }

    unsigned int sizelen = sdkFingerprint[0] - 'A';
    if (sizelen > (sizeof(m_off_t) * 4 / 3 + 4) || strlen(sdkFingerprint) <= (sizelen + 1))
    {
        return NULL;
    }

    FileFingerprint ffp;
    string result = sdkFingerprint + sizelen + 1;
    if (!ffp.unserializefingerprint(&result))
    {
        return NULL;
    }
    return MegaApi::strdup(result.c_str());
}

char *MegaApiImpl::getSdkFingerprintFromMegaFingerprint(const char *megaFingerprint, m_off_t size)
{
    if (!megaFingerprint || !megaFingerprint[0] || size < 0)
    {
        return NULL;
    }

    FileFingerprint ffp;
    string sMegaFingerprint = megaFingerprint;
    if (!ffp.unserializefingerprint(&sMegaFingerprint))
    {
        return NULL;
    }

    char bsize[sizeof(size) + 1];
    int l = Serialize64::serialize((byte *)bsize, size);
    char *buf = new char[l * 4 / 3 + 4];
    char sizelen = static_cast<char>('A' + Base64::btoa((const byte *)bsize, l, buf));
    string result(1, sizelen);
    result.append(buf);
    result.append(megaFingerprint);
    delete [] buf;

    return MegaApi::strdup(result.c_str());
}

MegaNode* MegaApiImpl::getParentNode(MegaNode* n)
{
    if(!n) return NULL;

    SdkMutexGuard g(sdkMutex);
    Node *node = client->nodebyhandle(n->getHandle());
    if(!node)
    {
        return NULL;
    }

    return MegaNodePrivate::fromNode(node->parent);
}

char* MegaApiImpl::getNodePath(MegaNode *node)
{
    if(!node) return nullptr;

    SdkMutexGuard guard(sdkMutex);
    Node *n = client->nodebyhandle(node->getHandle());
    if(!n)
    {
        return nullptr;
    }
    return MegaApi::strdup(n->displaypath().c_str());
}

char* MegaApiImpl::getNodePathByNodeHandle(MegaHandle handle)
{
    SdkMutexGuard guard(sdkMutex);
    Node *n = client->nodebyhandle(handle);
    if(!n)
    {
        return nullptr;
    }
    return MegaApi::strdup(n->displaypath().c_str());
}

MegaNode* MegaApiImpl::getNodeByPath(const char *path, MegaNode* node)
{
    SdkMutexGuard guard(sdkMutex);

    Node* root = nullptr;

    if (node)
    {
        root = client->nodebyhandle(node->getHandle());
        if (!root)
        {
            return nullptr;
        }
    }

    Node* result = client->nodeByPath(path, root);

    return MegaNodePrivate::fromNode(result);
}

MegaNode* MegaApiImpl::getNodeByPathOfType(const char* path, MegaNode* node, int type)
{
    SdkMutexGuard guard(sdkMutex);

    Node* root = nullptr;

    if (node)
    {
        root = client->nodebyhandle(node->getHandle());
        if (!root)
        {
            return nullptr;
        }
    }

    nodetype_t t = (type == MegaNode::TYPE_FILE ? FILENODE :
                   (type == MegaNode::TYPE_FOLDER ? FOLDERNODE : TYPE_UNKNOWN));
    Node* result = client->nodeByPath(path, root, t);

    return MegaNodePrivate::fromNode(result);
}

MegaNode* MegaApiImpl::getNodeByHandle(handle handle)
{
    if(handle == UNDEF) return NULL;
    SdkMutexGuard g(sdkMutex);
    return MegaNodePrivate::fromNode(client->nodebyhandle(handle));
}

MegaContactRequest *MegaApiImpl::getContactRequestByHandle(MegaHandle handle)
{
    SdkMutexGuard guard(sdkMutex);
    auto it = client->pcrindex.find(handle);
    return it == client->pcrindex.end() ?
        nullptr :
        MegaContactRequestPrivate::fromContactRequest(it->second.get());
}

void MegaApiImpl::updateBackups()
{
    for (std::map<int, MegaScheduledCopyController *>::iterator it = backupsMap.begin(); it != backupsMap.end(); ++it)
    {
        MegaScheduledCopyController *backupController=it->second;
        backupController->update();
    }
}

void MegaApiImpl::executeOnThread(shared_ptr<ExecuteOnce> f)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_EXECUTE_ON_THREAD, nullptr);
    request->functionToExecute = std::move(f);
    requestQueue.push_front(request);  // these operations are part of requests that already queued, and should occur before other requests; queue at front
    waiter->notify();
}

unsigned MegaApiImpl::sendPendingTransfers(TransferQueue *queue, MegaRecursiveOperation* recursiveTransfer)
{
    CodeCounter::ScopeTimer ccst(client->performanceStats.megaapiSendPendingTransfers);

    auto t0 = std::chrono::steady_clock::now();
    unsigned count = 0;

    SdkMutexGuard guard(sdkMutex);
    TransferDbCommitter committer(client->tctable);

    TransferQueue &auxQueue = queue
            ? *queue            // custom transferQueue used for folder uploads/downloads
            : transferQueue;    // transfer queue of class MegaApiImpl

    // if we are processing a folder transfer custom queue, we need to process in one shot
    // if the folder transfer cancel token is activated, each of the remaining
    // transfers will be straight away called back for start/finish rather than
    // passed to the SDK.
    bool canSplit = !queue;

    while (MegaTransferPrivate *transfer = auxQueue.pop())
    {
        error e = API_OK;
        int nextTag = client->nextreqtag();
        transfer->setState(MegaTransfer::STATE_QUEUED);

        if (transfer->accessCancelToken().isCancelled())
        {
            if (queue && recursiveTransfer && recursiveTransfer->isCancelledByFolderTransferToken())
            {
                // shortcut in case we have a huge queue
                // millions of listener callbacks, logging etc takes a while
                LOG_debug << "Folder transfer is cancelled, skipping remaining subtransfers: " << auxQueue.size();
                recursiveTransfer->setTransfersTotalCount(recursiveTransfer->getTransfersTotalCount() - auxQueue.size());

                auxQueue.clear();
                // let the pop'd transfer notify the parent, we may be completely finished
                // otherwise the folder completes when transfers already established in the SDK core finish
            }

            transferMap[nextTag] = transfer;
            transfer->setTag(nextTag);
            transfer->setState(MegaTransfer::STATE_QUEUED);
            fireOnTransferStart(transfer);
            transfer->setStartTime(Waiter::ds);
            transfer->setUpdateTime(Waiter::ds);
            transfer->setState(MegaTransfer::STATE_CANCELLED);
            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_EINCOMPLETE));
            continue;
        }

        switch(transfer->getType())
        {
            case MegaTransfer::TYPE_UPLOAD:
            {
                const char* localPath = transfer->getPath();
                const char* fileName = transfer->getFileName();
                int64_t mtime = transfer->getTime();
                bool isSourceTemporary = transfer->isSourceFileTemporary();
                Node *parent = client->nodebyhandle(transfer->getParentHandle());
                bool startFirst = transfer->shouldStartFirst();

                bool uploadToInbox = ISUNDEF(transfer->getParentHandle()) && transfer->getParentPath() && (strchr(transfer->getParentPath(), '@') || (strlen(transfer->getParentPath()) == 11));
                const char *inboxTarget = uploadToInbox ? transfer->getParentPath() : nullptr;

                if (!localPath || !fileName || !(*fileName)
                        || (!uploadToInbox && (!parent || parent->type == FILENODE) ) )
                {
                    e = API_EARGS;
                    break;
                }

                if (parent && parent->inshare && !client->checkaccess(parent, RDWR))
                {
                    e = API_EACCESS;
                    break;
                }

                string tmpString = localPath;
                auto wLocalPath = LocalPath::fromAbsolutePath(tmpString);

                if (transfer->fingerprint_error != API_OK)
                {
                    LOG_debug << "Upload had already failed before queueing";
                    e = transfer->fingerprint_error;
                    break;
                }

                if (transfer->fingerprint_filetype == FILENODE && !transfer->fingerprint_onDisk.isvalid)
                {
                    LOG_debug << "Upload had already failed to be fingerprinted before queueing";
                    e = API_EREAD;
                    break;
                }

                assert(transfer->fingerprint_filetype == FILENODE ||
                       transfer->fingerprint_filetype == FOLDERNODE);

                if (transfer->fingerprint_filetype == FILENODE)
                {

                    FileFingerprint fp_forCloud = transfer->fingerprint_onDisk;
                    // don't clone an existing node unless it also already has the overridden mtime
                    // don't think that an existing file at this path is the right file unless it also has the overridden mtime
                    if (mtime != MegaApi::INVALID_CUSTOM_MOD_TIME)
                    {
                        fp_forCloud.mtime = mtime;
                    }

                    Node *previousNode = client->childnodebyname(parent, fileName, false);

                    bool forceToUpload = false;
                    if (previousNode)
                    {
                        if (previousNode->type == FOLDERNODE)
                        {
                            if (!recursiveTransfer) // file upload, but not sub-file inside a folder
                            {
                                e = API_EARGS;
                                break;
                            }
                            /* else => in case we found a folder (in cloud drive) with a duplicate name for any subfile of the folder we are trying to upload
                             * SDK core will resolve the name conflict
                             *   - If versioning is enabled, it creates a new version.
                             *   - If versioning is disabled, it overwrites the file (old one is deleted permanently).
                             */
                        }
                        else if (fp_forCloud.isvalid && previousNode->isvalid && fp_forCloud == *((FileFingerprint *)previousNode))
                        {
                            forceToUpload= hasToForceUpload(*previousNode, *transfer);
                            if (!forceToUpload)
                            {
                                LOG_debug << "Previous node exists and the upload is not forced: copy node handle";
                                pendingUploads++;
                                totalUploads++;
                                transfer->setState(MegaTransfer::STATE_QUEUED);
                                transferMap[nextTag] = transfer;
                                transfer->setTag(nextTag);
                                transfer->setTotalBytes(transfer->fingerprint_onDisk.size);
                                transfer->setTransferredBytes(0);
                                transfer->setStartTime(Waiter::ds);
                                transfer->setUpdateTime(Waiter::ds);
                                fireOnTransferStart(transfer);
                                transfer->setNodeHandle(previousNode->nodehandle);
                                transfer->setDeltaSize(transfer->fingerprint_onDisk.size);
                                transfer->setSpeed(0);
                                transfer->setMeanSpeed(0);
                                transfer->setState(MegaTransfer::STATE_COMPLETED);
                                pendingUploads--;
                                fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_OK));
                                break;
                            }
                        }
                    }

                    // If has been found by name and it's necessary force upload, it isn't necessary look for it again
                    if (!forceToUpload)
                    {
                        Node *samenode = client->mNodeManager.getNodeByFingerprint(fp_forCloud);
                        if (samenode && samenode->nodekey().size() && !hasToForceUpload(*samenode, *transfer))
                        {
                            pendingUploads++;
                            totalUploads++;
                            transfer->setState(MegaTransfer::STATE_QUEUED);
                            transferMap[nextTag] = transfer;
                            transfer->setTag(nextTag);
                            transfer->setTotalBytes(transfer->fingerprint_onDisk.size);
                            transfer->setStartTime(Waiter::ds);
                            transfer->setUpdateTime(Waiter::ds);
                            fireOnTransferStart(transfer);

                            TreeProcCopy tc;
                            client->proctree(samenode, &tc, false, true);
                            tc.allocnodes();
                            client->proctree(samenode, &tc, false, true);
                            tc.nn[0].parenthandle = UNDEF;

                            SymmCipher key;
                            AttrMap attrs;
                            string attrstring;
                            key.setkey((const byte*)tc.nn[0].nodekey.data(), samenode->type);
                            attrs = samenode->attrs;
                            string sname = fileName;
                            LocalPath::utf8_normalize(&sname);
                            attrs.map['n'] = sname;
                            attrs.getjson(&attrstring);
                            client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str());
                            if (tc.nn[0].type == FILENODE)
                            {
                                if (Node* ovn = client->getovnode(parent, &sname))
                                {
                                    tc.nn[0].ovhandle = ovn->nodeHandle();
                                }
                            }

                            if (uploadToInbox)
                            {
                                // obsolete feature, kept for sending logs to helpdesk
                                client->putnodes(inboxTarget, move(tc.nn), nextTag);
                            }
                            else
                            {
                                client->putnodes(parent->nodeHandle(), UseLocalVersioningFlag, move(tc.nn), nullptr, nextTag, false);
                            }

                            transfer->setDeltaSize(transfer->fingerprint_onDisk.size);
                            transfer->setSpeed(0);
                            transfer->setMeanSpeed(0);
                            transfer->setState(MegaTransfer::STATE_COMPLETING);
                            fireOnTransferUpdate(transfer);
                            break;
                        }

                    }

                    currentTransfer = transfer;
                    string wFileName = fileName;
                    MegaFilePut *f = new MegaFilePut(client, std::move(wLocalPath), &wFileName,
                            NodeHandle().set6byte(transfer->getParentHandle()),
                            uploadToInbox ? inboxTarget : "", mtime, isSourceTemporary, previousNode);
                    *static_cast<FileFingerprint*>(f) = transfer->fingerprint_onDisk;  // deliberate slicing - startxfer would re-fingerprint if we don't supply this info

                    f->setTransfer(transfer); // sets internal `megaTransfer`, different from internal `transfer`!
                    f->cancelToken = transfer->accessCancelToken();

                    error result = API_OK;
                    bool started = client->startxfer(PUT, f, committer, true, startFirst, transfer->isBackupTransfer(), UseLocalVersioningFlag, &result, nextTag);
                    if (!started)
                    {
                        transfer->setState(MegaTransfer::STATE_QUEUED);
                        if (!f->isvalid)
                        {
                            //Unable to read the file
                            transferMap[nextTag] = transfer;
                            transfer->setTag(nextTag);
                            fireOnTransferStart(transfer);
                            transfer->setStartTime(Waiter::ds);
                            transfer->setUpdateTime(Waiter::ds);
                            transfer->setState(MegaTransfer::STATE_FAILED);
                            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_EREAD));
                        }
                        else
                        {
                            MegaTransferPrivate* prevTransfer = NULL;
                            auto range = client->multi_transfers[PUT].equal_range(f);
                            for (auto it = range.first; it != range.second; ++it)
                            {
                                Transfer *t = it->second;
                                for (file_list::iterator fi = t->files.begin(); fi != t->files.end(); fi++)
                                {
                                    if (f->h != UNDEF && f->h == (*fi)->h && !f->targetuser.size()
                                            && !(*fi)->targetuser.size() && f->name == (*fi)->name)
                                    {
                                        prevTransfer = getMegaTransferPrivate((*fi)->tag);
                                        break;
                                    }
                                }
                            }

                            if (prevTransfer && transfer->getAppData())
                            {
                                string appData = prevTransfer->getAppData() ? string(prevTransfer->getAppData()) + "!" : string();
                                appData.append(transfer->getAppData());
                                prevTransfer->setAppData(appData.c_str());
                            }

                            //Already existing transfer
                            transferMap[nextTag] = transfer;
                            transfer->setTag(nextTag);
                            transfer->setTotalBytes(f->size);
                            fireOnTransferStart(transfer);
                            transfer->setStartTime(Waiter::ds);
                            transfer->setUpdateTime(Waiter::ds);
                            transfer->setState(MegaTransfer::STATE_CANCELLED);
                            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(result));
                        }

                        delete f; // `started` was false, `f` wasn't stored at Transfer::files
                    }
                    currentTransfer = NULL;
                }
                else
                {
                    transferMap[nextTag] = transfer;
                    transfer->setTag(nextTag);

                    // todo:  use make_shared after jenkins node is updated to VS2019
                    transfer->startRecursiveOperation(make_unique<MegaFolderUploadController>(this, transfer), nullptr);
                }
                break;
            }
            case MegaTransfer::TYPE_DOWNLOAD:
            {
                Node *node = NULL;
                MegaNode *publicNode = transfer->getPublicNode();
                const char *parentPath = transfer->getParentPath();
                const char *fileName = transfer->getFileName();
                bool startFirst = transfer->shouldStartFirst();

                if (!publicNode)
                {
                    handle nodehandle = transfer->getNodeHandle();
                    node = client->nodebyhandle(nodehandle);
                }

                if (!node && !publicNode)
                {
                    e = API_ENOENT;
                    break;
                }

                if (!transfer->isStreamingTransfer() && !parentPath && !fileName)
                {
                    e = API_EARGS;
                    break;
                }

                if (!transfer->isStreamingTransfer() && ((node && node->type != FILENODE) || (publicNode && publicNode->getType() != FILENODE)) )
                {
                    // Folder download
                    transferMap[nextTag] = transfer;
                    transfer->setTag(nextTag);

                    // todo:  use make_shared after jenkins node is updated to VS2019
                    transfer->startRecursiveOperation(make_unique<MegaFolderDownloadController>(this, transfer), publicNode);
                    break;
                }

                // File download
                if (!transfer->isStreamingTransfer())
                {
                    LocalPath name;
                    LocalPath wLocalPath;

                    if (parentPath)
                    {
                        wLocalPath = LocalPath::fromAbsolutePath(parentPath);
                    }
                    else
                    {
                        // Using the Absolute form, and passing "." means an absolute path of the current folder
                        // (ideally client apps would only pass absolute paths)
                        wLocalPath = LocalPath::fromAbsolutePath(".");
                        wLocalPath.appendWithSeparator(LocalPath::fromRelativePath(""), true);
                    }

                    FileSystemType fsType = fsAccess->getlocalfstype(wLocalPath);

                    if (node)
                    {
                        if (!fileName)
                        {
                            attr_map::iterator ait = node->attrs.map.find('n');
                            if (ait == node->attrs.map.end())
                            {
                                name = LocalPath::fromRelativePath("CRYPTO_ERROR");
                            }
                            else if(!ait->second.size())
                            {
                                name = LocalPath::fromRelativePath("BLANK");
                            }
                            else
                            {
                                name =  LocalPath::fromRelativeName(ait->second, *fsAccess, fsType);
                            }
                        }
                        else
                        {
                            name = LocalPath::fromRelativeName(fileName, *fsAccess, fsType);
                        }
                    }
                    else
                    {
                        if (!transfer->getFileName())
                        {
                            name = LocalPath::fromRelativeName(publicNode->getName(), *fsAccess, fsType);
                        }
                        else
                        {
                            name = LocalPath::fromRelativeName(transfer->getFileName(), *fsAccess, fsType);
                        }
                    }
                    wLocalPath.appendWithSeparator(name, true);

                    FileFingerprint nodeFingerprint;
                    if (node)
                    {
                        nodeFingerprint = *node;
                    }
                    else
                    {
                        unique_ptr<FileFingerprint> fp(getFileFingerprintInternal(publicNode->getFingerprint()));
                        if (fp) nodeFingerprint = *fp;
                    }

                    // Is there already a file at the download location that matches that node?
                    // If so, report the transfer as complete and successful, instantly (as it did before)
                    string ext;
                    if (nodeFingerprint.isvalid &&
                        fsAccess->getextension(wLocalPath, ext))
                    {
                        if (!ext.empty() && ext[0] == '.') ext.erase(0, 1);

                        if (client->treatAsIfFileDataEqual(nodeFingerprint, wLocalPath, ext))
                        {
                            transfer->setState(MegaTransfer::STATE_QUEUED);
                            transferMap[nextTag] = transfer;
                            transfer->setTag(nextTag);
                            transfer->setTotalBytes(nodeFingerprint.size);
                            transfer->setTransferredBytes(0);
                            transfer->setPath(wLocalPath.toPath(false).c_str());
                            transfer->setStartTime(Waiter::ds);
                            transfer->setUpdateTime(Waiter::ds);
                            totalDownloads++;
                            pendingDownloads++;
                            fireOnTransferStart(transfer);
                            if (node)
                            {
                                transfer->setNodeHandle(node->nodehandle);
                            }
                            else
                            {
                                transfer->setNodeHandle(publicNode->getHandle());
                            }
                            transfer->setDeltaSize(nodeFingerprint.size);
                            transfer->setSpeed(0);
                            transfer->setMeanSpeed(0);
                            transfer->setState(MegaTransfer::STATE_COMPLETED);
                            pendingDownloads--;
                            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_OK));
                            break;
                        }
                    }
                    else
                    {
                        LOG_debug << "Could not get fingerprint or extension to evaluate whether the on-disk file is the same as the node";
                    }

                    currentTransfer = transfer;

                    unique_ptr<MegaFileGet> f;
                    if (node)
                    {
                        f.reset(new MegaFileGet(client, node, wLocalPath, fsType));
                    }
                    else
                    {
                        f.reset(new MegaFileGet(client, publicNode, wLocalPath));
                    }

                    transfer->setPath(wLocalPath.toPath(false).c_str());

                    f->setTransfer(transfer);
                    f->cancelToken = transfer->accessCancelToken();

                    bool skipDuplicates = transfer->getFolderTransferTag() <= 0; //Let folder subtransfer have duplicates, so that repeated downloads can co-exist and progress accordingly
                    mega::error cause;
                    bool ok = client->startxfer(GET, f.release(), committer, skipDuplicates, startFirst, false, UseLocalVersioningFlag, &cause, nextTag);
                    if (!ok)
                    {
                        //Already existing transfer
                        transfer->setState(MegaTransfer::STATE_QUEUED);
                        transferMap[nextTag]=transfer;
                        transfer->setTag(nextTag);
                        fireOnTransferStart(transfer);

                        long long overquotaDelay = getBandwidthOverquotaDelay();
                        if (overquotaDelay)
                        {
                            fireOnTransferTemporaryError(transfer, make_unique<MegaErrorPrivate>(API_EOVERQUOTA, overquotaDelay));
                        }

                        transfer->setStartTime(Waiter::ds);
                        transfer->setUpdateTime(Waiter::ds);
                        transfer->setState(MegaTransfer::STATE_FAILED);
                        fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(cause));
                    }
                }
                else
                {
                    currentTransfer = transfer;
                    m_off_t startPos = transfer->getStartPos();
                    m_off_t endPos = transfer->getEndPos();
                    if (startPos < 0 || endPos < 0 || startPos > endPos)
                    {
                        e = API_EARGS;
                        break;
                    }

                    if (node)
                    {
                        transfer->setFileName(node->displayname());
                        if (startPos >= node->size || endPos >= node->size)
                        {
                            e = API_EARGS;
                            break;
                        }

                        m_off_t totalBytes = endPos - startPos + 1;
                        transferMap[nextTag]=transfer;
                        transfer->setTotalBytes(totalBytes);
                        transfer->setTag(nextTag);
                        transfer->setState(MegaTransfer::STATE_QUEUED);

                        fireOnTransferStart(transfer);
                        client->pread(node, startPos, totalBytes, transfer);
                        waiter->notify();
                    }
                    else
                    {
                        transfer->setFileName(publicNode->getName());
                        if (startPos >= publicNode->getSize() || endPos >= publicNode->getSize())
                        {
                            e = API_EARGS;
                            break;
                        }

                        m_off_t totalBytes = endPos - startPos + 1;
                        transferMap[nextTag]=transfer;
                        transfer->setTotalBytes(totalBytes);
                        transfer->setTag(nextTag);
                        transfer->setState(MegaTransfer::STATE_QUEUED);
                        fireOnTransferStart(transfer);
                        SymmCipher cipher;
                        cipher.setkey(publicNode->getNodeKey());
                        client->pread(publicNode->getHandle(), &cipher,
                            MemAccess::get<int64_t>((const char*)publicNode->getNodeKey()->data() + SymmCipher::KEYLENGTH),
                                      startPos, totalBytes, transfer, publicNode->isForeign(),
                                      publicNode->getPrivateAuth()->c_str(),
                                      publicNode->getPublicAuth()->c_str(),
                                      publicNode->getChatAuth());
                        waiter->notify();
                    }
                }

                currentTransfer = NULL;
                break;
            }
        }

        if (e)
        {
            transferMap[nextTag] = transfer;
            transfer->setTag(nextTag);
            transfer->setState(MegaTransfer::STATE_QUEUED);
            fireOnTransferStart(transfer);
            transfer->setStartTime(Waiter::ds);
            transfer->setUpdateTime(Waiter::ds);
            transfer->setState(MegaTransfer::STATE_FAILED);
            fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(e));
        }

        if (canSplit && (++count > 100 || std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0).count() > 100))
        {
            break;
        }
    }
    return count;
}

void MegaApiImpl::removeRecursively(const char *path)
{
#ifndef _WIN32
    auto localpath = LocalPath::fromPlatformEncodedAbsolute(path);
    FSACCESS_CLASS::emptydirlocal(localpath);
#else
    auto localpath = LocalPath::fromAbsolutePath(path);
    WinFileSystemAccess::emptydirlocal(localpath);
#endif
}

error MegaApiImpl::processAbortBackupRequest(MegaRequestPrivate *request)
{
    int tag = int(request->getNumber());

    map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(tag) ;
    if (itr != backupsMap.end())
    {
        MegaScheduledCopyController *backup = itr->second;

        bool flag = request->getFlag();
        if (!flag)
        {
            if (backup->getState() == MegaScheduledCopy::SCHEDULED_COPY_ONGOING)
            {
                for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++)
                {
                    MegaTransferPrivate *t = it->second;
                    if (t->getFolderTransferTag() == backup->getFolderTransferTag())
                    {
                        api->cancelTransferByTag(t->getTag()); //what if any of these fail? (Although I don't think that's possible)
                    }
                }
                request->setFlag(true);
                requestQueue.push(request);
            }
            else
            {
                LOG_debug << "Abort failed: no ongoing backup";
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
            }
        }
        else
        {
            backup->abortCurrent(); //TODO: THIS MAY CAUSE NEW REQUESTS, should we consider them before fireOnRequestFinish?!!!
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
        }
        return API_OK;
    }
    else
    {
        return API_ENOENT;
    }
}

void MegaApiImpl::yield()
{
#if __cplusplus >= 201100L
    std::this_thread::yield();
#elif !defined(_WIN32)
    sched_yield();
#endif
}

static void appendFileAttribute(string& s, int n, MegaHandle h)
{
    if (h != INVALID_HANDLE)
    {
        if (!s.empty())
        {
            s += "/";
        }

        char buf[64];
        snprintf(buf, sizeof(buf), "%u*", n);
        Base64::btoa((byte*)&h, sizeof(h), strchr(buf + 2, 0));
        s += buf;
    }
}

static error updateAttributesMapWithCoordinates(attr_map& attrUpdates, int latitude, int longitude, bool unshareable, MegaClient* client)
{
    static const nameid coordsNameShareable = AttrMap::string2nameid("l");
    static const nameid coordsNameUnshareable = AttrMap::string2nameid("gp");

    if (longitude == MegaNode::INVALID_COORDINATE && latitude == MegaNode::INVALID_COORDINATE)
    {
        attrUpdates[coordsNameShareable] = "";
        attrUpdates[coordsNameUnshareable] = "";
    }
    else
    {
        if (longitude < 0 || longitude >= 0x01000000
            || latitude < 0 || latitude >= 0x01000000)
        {
            return API_EARGS;
        }

        Base64Str<3> latEncoded((const byte*)&latitude);
        Base64Str<3> lonEncoded((const byte*)&longitude);
        string coordsValue = string(latEncoded) + lonEncoded.chars;
        if (coordsValue.size() != 8)
        {
            return API_EARGS;
        }

        if (unshareable)
        {
            if (client->unshareablekey.size() != Base64Str<SymmCipher::KEYLENGTH>::STRLEN)
            {
                return API_EKEY;
            }

            SymmCipher c;
            byte data[SymmCipher::BLOCKSIZE] = { 0 };
            memcpy(data, "unshare/", 8);
            memcpy(data + 8, (void*)coordsValue.data(), coordsValue.size());
            client->setkey(&c, client->unshareablekey.data());
            c.ctr_crypt(data, unsigned(8 + coordsValue.size()), 0, 0, NULL, true);
            attrUpdates[coordsNameUnshareable] = Base64Str<SymmCipher::BLOCKSIZE>(data);
            attrUpdates[coordsNameShareable] = "";
        }
        else
        {
            attrUpdates[coordsNameShareable] = coordsValue;
            attrUpdates[coordsNameUnshareable] = "";
        }
    }
    return API_OK;
}

void MegaApiImpl::sendPendingScRequest()
{
    MegaRequestPrivate *request = scRequestQueue.front();
    if (!request || request->getTag())
    {
        return;
    }
    assert(request->getType() == MegaRequest::TYPE_CATCHUP);

    SdkMutexGuard g(sdkMutex);;

    request->setTag(1);
    fireOnRequestStart(request);
    client->catchup();
}

void MegaApiImpl::sendPendingRequests()
{
    SdkMutexGuard g(sdkMutex);

    // For multiple consecutive requests of the same type (eg. remove transfer) this committer will put all the database activity into a single commit
    TransferDbCommitter committer(client->tctable);
    int lastRequestType = -1;
    int lastRequestConsecutive = 0;

    error e = API_OK;
    MegaRequestPrivate *request = nullptr;
    bool firstIteration = true;

    while(1)
    {

        // Report the error from the last loop iteration (if any). (success must be reported directly by each case)
        // Having this code here is good for in-place conversions of case statements
        // to the `performRequest` system and reduce the switch size
        // And also shares this block of code between the switch and performRequest cases.
        if (e && request)
        {
            LOG_err << "Error starting request: " << e;
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        }
#ifdef ENABLE_SYNC
        client->syncs.syncConfigStoreFlush();
#endif

        e = API_OK;

        if (!(request = requestQueue.pop()))
        {
            break;
        }

        if (request->performFireOnRequestFinish)
        {
            request->performFireOnRequestFinish();
            request = nullptr;
            continue;
        }

        // also we avoid yielding for consecutive transaction cancel operations (we used to yeild every time, but we need to keep the sdkMutex lock while the database transaction is ongoing)
        if ((lastRequestType == -1 || lastRequestType == request->getType()) && lastRequestConsecutive < 1024)
        {
            ++lastRequestConsecutive;
        }
        else
        {
            committer.commitNow();
            g.unlock();
            yield();
            g.lock();
            lastRequestConsecutive = 0;
        }

        lastRequestType = request->getType();

        // only try this in the 1st iteration
        if (firstIteration && request->getType() != MegaRequest::TYPE_LOGOUT)
        {
            client->abortbackoff(false);
        }
        firstIteration = false;

        if (request->getType() != MegaRequest::TYPE_EXECUTE_ON_THREAD)
        {
            if (!request->getTag())
            {
                int nextTag = client->nextreqtag();
                request->setTag(nextTag);
                requestMap[nextTag] = request;
                fireOnRequestStart(request);
            }
            else
            {
                // this case happens when we queue requests already started
            }
        }

        if (request->performRequest)
        {
            // the action should result in request destruction via fireOnRequestFinish
            // or a requeue of another step, etc.
            e = request->performRequest();
            continue;
        }

        if (request->performTransferRequest)
        {
            // the action has same requirement as the above performRequest
            e = request->performTransferRequest(committer);
            continue;
        }

        switch (request->getType())
        {
        default:
        {
            // Default case if not performRequest and not implemented below.
            // Keeping this at the top means we can convert the last case
            // to a performRequest while keeping the code in-place for diffs
            e = API_EINTERNAL;
            break;
        }
        case MegaRequest::TYPE_FETCH_GOOGLE_ADS:    // fall-through
        case MegaRequest::TYPE_QUERY_GOOGLE_ADS:
        {
            // deprecated
            e = API_EEXPIRED;
            break;
        }
#ifdef ENABLE_SYNC
        case MegaRequest::TYPE_REMOVE_SYNCS:
        {
            e = API_EARGS;
            assert(false); // this function deprecated, it wasn't used (and, questions about which error to report if multiple occur)
            break;
        }
#endif
        case MegaRequest::TYPE_DELETE:
        {
#ifdef HAVE_LIBUV
            g.unlock();
            httpServerStop();
            ftpServerStop();
            g.lock();
#endif
            abortPendingActions();
            threadExit = 1;
            break;
        }
        case MegaRequest::TYPE_EXECUTE_ON_THREAD:
        {
            request->functionToExecute->exec();
            //requestMap.erase(request->getTag());  // per the test for TYPE_EXECUTE_ON_THREAD above, we didn't add it to the map or assign it a tag
            delete request;
            request = nullptr;
            break;
        }
        }
    }
}

void MegaApiImpl::putSet(MegaHandle sid, int optionFlags, const char* name, MegaHandle cover, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET, listener);
    request->setParentHandle(sid);
    request->setParamType(optionFlags);
    request->setText(name);
    request->setNodeHandle(cover);

    request->performRequest = [this, request]()
        {
            Set s;
            s.setId(request->getParentHandle());
            if (request->getParamType() & MegaApi::OPTION_SET_NAME)
            {
                s.setName(request->getText() ? request->getText() : string());
            }
            if (request->getParamType() & MegaApi::OPTION_SET_COVER)
            {
                s.setCover(request->getNodeHandle());
            }
            client->putSet(move(s),
                [this, request](Error e, const Set* s)
                {
                    if (request->getParentHandle() == UNDEF && s)
                    {
                        request->setMegaSet(::mega::make_unique<MegaSetPrivate>(*s));
                    }
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeSet(MegaHandle sid, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET, listener);
    request->setParentHandle(sid);

    request->performRequest = [this, request]()
        {
            client->removeSet(request->getParentHandle(),
                [this, request](Error e)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::putSetElement(MegaHandle sid, MegaHandle eid, MegaHandle node, int optionFlags, int64_t order, const char* name, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET_ELEMENT, listener);
    request->setTotalBytes(sid);
    request->setParentHandle(eid);
    request->setNodeHandle(node);
    request->setParamType(optionFlags);
    request->setNumber(order);
    request->setText(name);

    request->performRequest = [this, request]()
        {
            SetElement el;
            el.setSet(request->getTotalBytes());
            el.setId(request->getParentHandle());
            el.setNode(request->getNodeHandle());
            if (request->getParamType() & MegaApi::OPTION_ELEMENT_ORDER)
            {
                el.setOrder(request->getNumber());
            }
            if (request->getParamType() & MegaApi::OPTION_ELEMENT_NAME)
            {
                el.setName(request->getText() ? request->getText() : string());
            }
            client->putSetElement(move(el),
                [this, request](Error e, const SetElement* el)
                {
                    if (e == API_OK) // only return SetElement upon create, not update
                    {
                        bool isNew = request->getParentHandle() == UNDEF;
                        assert(!isNew || el);
                        if (isNew && el)    // return the Element only when is created, not when updated
                        {
                            request->setMegaSetElementList(::mega::make_unique<MegaSetElementListPrivate>(&el, 1));
                        }
                    }
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeSetElement(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET_ELEMENT, listener);
    request->setTotalBytes(sid);
    request->setParentHandle(eid);

    request->performRequest = [this, request]()
        {
            client->removeSetElement(request->getTotalBytes(), request->getParentHandle(),
                [this, request](Error e)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_login(MegaRequestPrivate* request)
{
            const char *login = request->getEmail();
            const char *password = request->getPassword();
            const char* megaFolderLink = request->getLink();
            const char* base64pwkey = request->getPrivateKey();
            const char* sessionKey = request->getSessionKey();

            if (!megaFolderLink && (!(login && password)) && !sessionKey && (!(login && base64pwkey)))
            {
                return API_EARGS;
            }

            string slogin;
            if (login)
            {
                slogin = login;
                slogin.erase(slogin.begin(), std::find_if(slogin.begin(), slogin.end(), char_is_not_space));
                slogin.erase(std::find_if(slogin.rbegin(), slogin.rend(), char_is_not_space).base(), slogin.end());
            }

            requestMap.erase(request->getTag());

            abortPendingActions();

            requestMap[request->getTag()]=request;

            error e = API_OK;
            client->locallogout(false, true);
            if (sessionKey)
            {
                client->login(Base64::atob(string(sessionKey)));
            }
            else if (login && (base64pwkey || password) && !megaFolderLink)
            {
                client->prelogin(slogin.c_str());
            }
            else
            {
                e = client->folderaccess(megaFolderLink, password);
                if(e == API_OK)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                }
            }

            return e;
}

void MegaApiImpl::multiFactorAuthCheck(const char* email, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK, listener);
    request->setEmail(email);

    request->performRequest = [this, request]()
        {
            const char *email = request->getEmail();
            if (!email)
            {
                return API_EARGS;
            }
            client->multifactorauthcheck(email);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::multiFactorAuthGetCode(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET, listener);

    request->performRequest = [this, request]()
        {
            client->multifactorauthsetup();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::multiFactorAuthEnableOrDisable(const char* pin, bool enable, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET, listener);
    request->setFlag(enable);
    request->setPassword(pin);

    request->performRequest = [this, request]()
        {
            bool flag = request->getFlag();
            const char *pin = request->getPassword();
            if (!pin)
            {
                return API_EARGS;
            }

            if (flag)
            {
                client->multifactorauthsetup(pin);
            }
            else
            {
                client->multifactorauthdisable(pin);
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fetchTimeZone(bool forceApiFetch, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_TIMEZONE, listener);
    request->setFlag(forceApiFetch);

    request->performRequest = [this, request]()
        {
            if (!mTimezones || request->getFlag() /*forceApiFetch*/)
            {
                client->fetchtimezone();
            }
            else
            {
                request->setTimeZoneDetails(mTimezones);
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::createFolder(const char* name, MegaNode* parent, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_FOLDER, listener);
    if (parent) request->setParentHandle(parent->getHandle());
    request->setName(name);

    request->performRequest = [this, request]()
        {
            Node *parent = client->nodebyhandle(request->getParentHandle());
            const char *name = request->getName();
            if (!name || !(*name) || !parent) { return API_EARGS;}

            // prevent to create a duplicate folder with same name in same path
            Node *folder = client->childnodebyname(parent, name, false);
            if (folder && folder->type == FOLDERNODE)
            {
                request->setNodeHandle(folder->nodehandle);
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                return API_OK;
            }

            vector<NewNode> newnodes(1);
            NewNode *newnode = &newnodes[0];
            SymmCipher key;
            string attrstring;
            byte buf[FOLDERNODEKEYLENGTH];

            // set up new node as folder node
            newnode->source = NEW_NODE;
            newnode->type = FOLDERNODE;
            newnode->nodehandle = 0;
            newnode->parenthandle = UNDEF;

            // generate fresh random key for this folder node
            client->rng.genblock(buf,FOLDERNODEKEYLENGTH);
            newnode->nodekey.assign((char*)buf,FOLDERNODEKEYLENGTH);
            key.setkey(buf);

            // generate fresh attribute object with the folder name
            AttrMap attrs;
            string sname = name;
            LocalPath::utf8_normalize(&sname);
            attrs.map['n'] = sname;

            // JSON-encode object and encrypt attribute string
            attrs.getjson(&attrstring);
            newnode->attrstring.reset(new string);
            client->makeattr(&key, newnode->attrstring, attrstring.c_str());

            // add the newly generated folder node
            client->putnodes(parent->nodeHandle(), NoVersioning, move(newnodes), nullptr, request->getTag(), false);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::moveNode(MegaNode* node, MegaNode* newParent, const char* newName, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE, listener);
    if (node) request->setNodeHandle(node->getHandle());
    if (newParent) request->setParentHandle(newParent->getHandle());
    request->setName(newName);

    request->performRequest = [this, request]()
        {
            Node *node = client->nodebyhandle(request->getNodeHandle());
            Node *newParent = client->nodebyhandle(request->getParentHandle());
            const char *name = request->getName();
            if (!node || !newParent)
            {
                return API_EARGS;
            }

            if (node->parent == newParent)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                return API_OK;
            }

            if (node->type == ROOTNODE
                    || node->type == VAULTNODE
                    || node->type == RUBBISHNODE
                    || !node->parent) // rootnodes cannot be moved
            {
                return API_EACCESS;
            }

            // old versions cannot be moved
            if (node->parent->type == FILENODE)
            {
                return API_EACCESS;
            }

            // target must be a folder with enough permissions
            if (newParent->type == FILENODE || !client->checkaccess(newParent, RDWR))
            {
                return API_EACCESS;
            }

            error e = API_OK;
            if ((e = client->checkmove(node, newParent)))
            {
                // If it's not possible to move the node, try copy-delete,
                // but only when it's not possible due to access rights
                // the node and the target are from different node trees,
                // it's possible to put nodes in the target folder
                // and also to remove the source node
                if (e != API_EACCESS)
                {
                    return e;
                }

                Node *nodeRoot = node;
                while (nodeRoot->parent)
                {
                    nodeRoot = nodeRoot->parent;
                }

                Node *parentRoot = newParent;
                while (parentRoot->parent)
                {
                    parentRoot = parentRoot->parent;
                }

                if ((nodeRoot == parentRoot)
                        || !client->checkaccess(node, FULL)
                        || !client->checkaccess(newParent, RDWR))
                {
                    return e;
                }

                unsigned nc;
                TreeProcCopy tc;
                NodeHandle ovhandle;

                if (node->type == FILENODE)
                {
                    string newName;
                    if (name)
                    {
                        newName.assign(name);
                        LocalPath::utf8_normalize(&newName);
                    }
                    else
                    {
                        attr_map::iterator it = node->attrs.map.find('n');
                        if (it != node->attrs.map.end())
                        {
                            newName = it->second;
                        }
                    }
                    if (!newName.empty())
                    {
                        Node *ovn = client->childnodebyname(newParent, newName.c_str(), true);
                        if (ovn)
                        {
                            if (node->isvalid && ovn->isvalid && *(FileFingerprint*)node == *(FileFingerprint*)ovn)
                            {
                                request->setNodeHandle(UNDEF);
                                e = client->unlink(node, false, request->getTag(), false);
                                return e;  // request finishes now if error, otherwise on unlink_result
                            }

                            ovhandle = ovn->nodeHandle();
                        }
                    }
                }

                // determine number of nodes to be copied
                client->proctree(node, &tc, !ovhandle.isUndef());
                tc.allocnodes();
                nc = tc.nc;

                // build new nodes array
                client->proctree(node, &tc, !ovhandle.isUndef());
                if (!nc)
                {
                    e = API_EARGS;
                    return e;
                }

                tc.nn[0].parenthandle = UNDEF;
                tc.nn[0].ovhandle = ovhandle;

                if (name)   // move and rename
                {
                    string newName(name);
                    LocalPath::utf8_normalize(&newName);

                    AttrMap attrs = node->attrs;
                    attrs.map['n'] = newName;

                    string attrstring;
                    attrs.getjson(&attrstring);

                    SymmCipher key;
                    key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type);
                    client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str());
                }

                // Mark node to be restored if moving to Rubbish Bin
                if (client->getrootnode(newParent)->type == RUBBISHNODE && client->getrootnode(node)->type != RUBBISHNODE)
                {
                    // "rr" attribute name and value
                    nameid rrname = AttrMap::string2nameid("rr");
                    Base64Str<MegaClient::NODEHANDLE> rrvalue(node->parent->nodehandle);
                    // Add attribute to a copy of old attributes
                    AttrMap attrs = node->attrs;
                    attrs.map[rrname] = rrvalue;

                    // Magic incantations for setting attributes
                    string attrstring;
                    attrs.getjson(&attrstring);

                    SymmCipher key;
                    key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type);
                    client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str());
                }

                client->putnodes(newParent->nodeHandle(), UseLocalVersioningFlag, move(tc.nn), nullptr, request->getTag(), false);
                e = API_OK;
                return e;
            }

            e = client->rename(node, newParent, SYNCDEL_NONE, NodeHandle(), name, false,
                [request, this](NodeHandle h, Error e)
                {
#ifdef ENABLE_SYNC
                    client->syncdownrequired = true;
#endif
                    request->setNodeHandle(h.as8byte());
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_copy(MegaRequestPrivate* request)
{
            Node *node = NULL;
            Node *target = client->nodebyhandle(request->getParentHandle());
            const char* email = request->getEmail();
            MegaNode *megaNode = request->getPublicNode();
            const char *newName = request->getName();
            NodeHandle ovhandle;

            if (!megaNode || (!target && !email)
                    || (newName && !(*newName))
                    || (target && target->type == FILENODE))
            {
                return API_EARGS;
            }

            if (!megaNode->isForeign() && !megaNode->isPublic())
            {
                node = client->nodebyhandle(request->getNodeHandle());
                if (!node)
                {
                    return API_ENOENT;
                }
            }

            if (!node)
            {
                if (!megaNode->getNodeKey()->size())
                {
                    return API_EKEY;
                }

                string sname = megaNode->getName();
                if (newName)
                {
                    MegaNodePrivate *privateNode = dynamic_cast<MegaNodePrivate *>(megaNode);
                    if (privateNode)
                    {
                        sname = newName;
                        LocalPath::utf8_normalize(&sname);
                        privateNode->setName(sname.c_str());
                    }
                    else
                    {
                        LOG_err << "Unknown node type";
                    }
                }

                if (target && megaNode->getType() == MegaNode::TYPE_FILE)
                {
                    Node *ovn = client->childnodebyname(target, sname.c_str(), true);
                    if (ovn)
                    {
                        FileFingerprint *fp = getFileFingerprintInternal(megaNode->getFingerprint());
                        if (fp)
                        {
                            if (fp->isvalid && ovn->isvalid && *fp == *(FileFingerprint*)ovn)
                            {
                                request->setNodeHandle(ovn->nodehandle);
                                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                                delete fp;
                                return API_OK;
                            }

                            delete fp;
                        }

                        ovhandle = ovn->nodeHandle();
                    }
                }

                MegaTreeProcCopy tc(client);

                processMegaTree(megaNode, &tc);
                tc.allocnodes();

                // build new nodes array
                processMegaTree(megaNode, &tc);

                tc.nn[0].parenthandle = UNDEF;
                tc.nn[0].ovhandle = ovhandle;

                if (target)
                {
                    client->putnodes(target->nodeHandle(), UseLocalVersioningFlag, std::move(tc.nn), megaNode->getChatAuth(), request->getTag(), false);
                }
                else
                {
                    client->putnodes(email, move(tc.nn), request->getTag());
                }
            }
            else
            {
                TreeProcCopy tc;

                if (!node->nodekey().size())
                {
                    return API_EKEY;
                }

                if (node->attrstring)
                {
                    node->applykey();
                    node->setattr();
                    if (node->attrstring)
                    {
                        return API_EKEY;
                    }
                }

                string sname;
                if (newName)
                {
                    sname = newName;
                    LocalPath::utf8_normalize(&sname);
                }
                else
                {
                    attr_map::iterator it = node->attrs.map.find('n');
                    if (it != node->attrs.map.end())
                    {
                        sname = it->second;
                    }
                }

                if (target && node->type == FILENODE)
                {
                    Node *ovn = client->childnodebyname(target, sname.c_str(), true);
                    if (ovn)
                    {
                        if (node->isvalid && ovn->isvalid && *(FileFingerprint*)node == *(FileFingerprint*)ovn)
                        {
                            request->setNodeHandle(ovn->nodehandle);
                            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                            return API_OK;
                        }

                        ovhandle = ovn->nodeHandle();
                    }
                }

                // determine number of nodes to be copied
                client->proctree(node, &tc, false, !ovhandle.isUndef());
                tc.allocnodes();

                // build new nodes array
                client->proctree(node, &tc, false, !ovhandle.isUndef());
                tc.nn[0].parenthandle = UNDEF;
                tc.nn[0].ovhandle = ovhandle;

                if (newName)
                {
                    SymmCipher key;
                    AttrMap attrs;
                    string attrstring;

                    key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type);
                    attrs = node->attrs;

                    attrs.map['n'] = sname;

                    attrs.getjson(&attrstring);
                    client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str());
                }

                if (target)
                {
                    client->putnodes(target->nodeHandle(), UseLocalVersioningFlag, move(tc.nn), nullptr, request->getTag(), false);
                }
                else
                {
                    client->putnodes(email, move(tc.nn), request->getTag());
                }
            }
            return API_OK;
}

void MegaApiImpl::restoreVersion(MegaNode* version, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESTORE, listener);
    if (version)
    {
        request->setNodeHandle(version->getHandle());
    }

    request->performRequest = [this, request]()
        {
            Node *version = client->nodebyhandle(request->getNodeHandle());
            if (!version)
            {
                return API_ENOENT;
            }

            if (version->type != FILENODE || !version->parent || version->parent->type != FILENODE)
            {
                return API_EARGS;
            }

            Node *current = version;
            while (current->parent && current->parent->type == FILENODE)
            {
                current = current->parent;
            }

            if (!current->parent)
            {
                return API_EINTERNAL;
            }

            vector<NewNode> newnodes(1);
            NewNode* newnode = &newnodes[0];
            string attrstring;
            SymmCipher key;

            newnode->source = NEW_NODE;
            newnode->type = FILENODE;
            newnode->nodehandle = version->nodehandle;
            newnode->parenthandle = UNDEF;
            newnode->ovhandle = current->nodeHandle();
            newnode->nodekey = version->nodekey();
            newnode->attrstring.reset(new string);
            if (newnode->nodekey.size())
            {
                key.setkey((const byte*)version->nodekey().data(), version->type);
                version->attrs.getjson(&attrstring);
                client->makeattr(&key, newnode->attrstring, attrstring.c_str());
            }

            client->putnodes(current->parent->nodeHandle(), ClaimOldVersion, move(newnodes), nullptr, request->getTag(), false);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::renameNode(MegaNode* node, const char* newName, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RENAME, listener);
    if (node) request->setNodeHandle(node->getHandle());
    request->setName(newName);

    request->performRequest = [this, request]()
        {
            if (client->ststatus == STORAGE_PAYWALL)
            {
                return API_EPAYWALL;
            }

            Node* node = client->nodebyhandle(request->getNodeHandle());
            const char* newName = request->getName();
            if (!node || !newName || !(*newName)) { return API_EARGS; }

            if (!client->checkaccess(node, FULL)) { return API_EACCESS; }

            string sname = newName;
            LocalPath::utf8_normalize(&sname);
            return client->setattr(node, attr_map('n', sname),
                [request, this](NodeHandle h, Error e)
                {
                    request->setNodeHandle(h.as8byte());
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                }, false);
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::remove(MegaNode* node, bool keepversions, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE, listener);
    if (node) request->setNodeHandle(node->getHandle());
    request->setFlag(keepversions);

    request->performRequest = [this, request]()
        {
            Node* node = client->nodebyhandle(request->getNodeHandle());
            bool keepversions = request->getFlag();

            if (!node)
            {
                return API_ENOENT;
            }

            if (keepversions && node->type != FILENODE)
            {
                return API_EARGS;
            }

            if (node->type == ROOTNODE
                    || node->type == VAULTNODE
                    || node->type == RUBBISHNODE) // rootnodes cannot be deleted
            {
                return API_EACCESS;
            }

            return client->unlink(node, keepversions, request->getTag(), false);
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeVersions(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_VERSIONS, listener);

    request->performRequest = [this, request]()
        {
            client->unlinkversions();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::share(MegaNode* node, const char* email, int access, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SHARE, listener);
    if (node) request->setNodeHandle(node->getHandle());
    request->setEmail(email);
    request->setAccess(access);

    request->performRequest = [this, request]()
        {
            Node *node = client->nodebyhandle(request->getNodeHandle());
            const char* email = request->getEmail();
            int access = request->getAccess();
            if(!node || !email || !strchr(email, '@'))
            {
                return API_EARGS;
            }
            error e = API_OK;
            accesslevel_t a = ACCESS_UNKNOWN;
            switch(access)
            {
                case MegaShare::ACCESS_UNKNOWN:
                    a = ACCESS_UNKNOWN;
                    break;
                case MegaShare::ACCESS_READ:
                    a = RDONLY;
                    break;
                case MegaShare::ACCESS_READWRITE:
                    a = RDWR;
                    break;
                case MegaShare::ACCESS_FULL:
                    a = FULL;
                    break;
                case MegaShare::ACCESS_OWNER:
                    a = OWNER;
                    break;
                default:
                    e = API_EARGS;
            }

            if (e == API_OK)
            {
                client->setshare(node, email, a, false, nullptr, request->getTag(), [this, request](Error e, bool){
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_importLink_getPublicNode(MegaRequestPrivate* request)
{
            Node *node = client->nodebyhandle(request->getParentHandle());
            const char* megaFileLink = request->getLink();

            if (!megaFileLink)
            {
                return API_EARGS;
            }
            if ((request->getType() == MegaRequest::TYPE_IMPORT_LINK) && !node)
            {
                return API_EARGS;
            }

            handle ph = UNDEF;
            byte key[FILENODEKEYLENGTH];
            error e = client->parsepubliclink(megaFileLink, ph, key, TypeOfLink::FILE);
            if (e == API_OK)
            {
                client->openfilelink(ph, key);
            }
            else if (e == API_EINCOMPLETE)  // no key provided, check only the existence of the node
            {
                client->openfilelink(ph, nullptr);
                e = API_OK;
            }
            return e;
}

void MegaApiImpl::getDownloadUrl(MegaNode* node, bool singleUrl, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_DOWNLOAD_URLS, listener);
    request->setFlag(singleUrl);
    if (node) request->setNodeHandle(node->getHandle());

    request->performRequest = [this, request]()
        {
            Node *node = client->nodebyhandle(request->getNodeHandle());
            bool singleUrl = request->getFlag();
            if(!node)
            {
                return API_EARGS;
            }

            client->reqs.add(new CommandGetFile(client, (const byte*)node->nodekey().data(), node->nodekey().size(),
                node->nodehandle, true, nullptr, nullptr, nullptr, singleUrl,
                [this, request](const Error &e, m_off_t /*size*/, m_time_t /*ts*/, m_time_t /*tm*/, dstime /*timeleft*/,
                std::string* /*filename*/, std::string* /*fingerprint*/, std::string* /*fileattrstring*/,
                const std::vector<std::string> &urls, const std::vector<std::string> &ips)
                {
                    if (e == API_OK && urls.size() && ips.size())
                    {
                        string surls, sipsv4, sipsv6;

                        auto delimFields=";";
                        for (auto &u : urls)
                        {
                            if (!surls.empty()) surls.append(delimFields);
                            surls.append(u);
                        }
                        bool ipv4 = true;
                        for (auto &ip : ips)
                        {
                            auto &w = ipv4 ? sipsv4 : sipsv6;
                            if (!w.empty()) w.append(delimFields);
                            w.append(ip);
                            ipv4 = !ipv4;
                        }

                        request->setName(surls.c_str());
                        request->setLink(sipsv4.c_str());
                        request->setText(sipsv6.c_str());
                    }

                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    return true;
                })
            );

            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_passwordLink(MegaRequestPrivate* request)
{
            const char *link = request->getLink();
            const char *pwd = request->getPassword();
            bool encryptLink = request->getFlag();

            error e = API_OK;
            string result;
            if (encryptLink)
            {
                e = client->encryptlink(link, pwd, &result);
            }
            else
            {
                e = client->decryptlink(link, pwd, &result);
            }

            if (!e)
            {
                request->setText(result.c_str());
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }
            return e;
}

error MegaApiImpl::performRequest_export(MegaRequestPrivate* request)
{
            Node* node = client->nodebyhandle(request->getNodeHandle());
            if (!node) { return API_EARGS; }

            bool writable = request->getFlag();
            bool megaHosted = request->getTransferTag() != 0;
            // exportnode() will take care of creating a share first, should it be a folder
            return client->exportnode(node, !request->getAccess(), request->getNumber(), writable, megaHosted, request->getTag(),
                [this, request](Error e, handle h, handle ph)
            {
                if (e || !request->getAccess()) // disable export doesn't return h and ph
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                }
                else
                {
                    // This block of code used to be inside exportnode_result
                    if (Node* n = client->nodebyhandle(h))
                    {
                        char key[FILENODEKEYLENGTH*4/3+3];

                        // the key
                        if (n->type == FILENODE)
                        {
                            if(n->nodekey().size() >= FILENODEKEYLENGTH)
                            {
                                Base64::btoa((const byte*)n->nodekey().data(), FILENODEKEYLENGTH, key);
                            }
                            else
                            {
                                key[0]=0;
                            }
                        }
                        else if (n->sharekey)
                        {
                            Base64::btoa(n->sharekey->key, FOLDERNODEKEYLENGTH, key);
                        }
                        else
                        {
                            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_EKEY));
                            return;
                        }

                        TypeOfLink lType = client->validTypeForPublicURL(n->type);
                        string link = client->publicLinkURL(client->mNewLinkFormat, lType, ph, key);
                        request->setLink(link.c_str());
                        if (n->plink && n->plink->mAuthKey.size())
                        {
                            request->setPrivateKey(n->plink->mAuthKey.c_str());
                        }

                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_OK));
                    }
                    else
                    {
                        request->setNodeHandle(UNDEF);
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(MegaError::API_ENOENT));
                    }
                }
            });
}

void MegaApiImpl::fetchNodes(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_NODES, listener);

    request->performRequest = [this, request]()
        {
            if (nocache)
            {
                client->opensctable();

                if (client->sctable)
                {
                    client->sctable->remove();
                    client->sctable.reset();
                    client->mNodeManager.reset();
                    client->pendingsccommit = false;
                    client->cachedscsn = UNDEF;
                }

                nocache = false;
            }

            client->fetchnodes();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getCloudStorageUsed(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CLOUD_STORAGE_USED, listener);

    request->performRequest = [this, request]()
        {
            if (client->loggedin() != FULLACCOUNT && !client->loggedIntoFolder())
            {
                return API_EACCESS;
            }

            NodeCounter nc = client->mNodeManager.getCounterOfRootNodes();
            request->setNumber(nc.storage + nc.versionStorage);
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getAccountDetails(bool storage, bool transfer, bool pro, bool sessions, bool purchases, bool transactions, int source, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ACCOUNT_DETAILS, listener);
    int numDetails = 0;
    if (storage) numDetails |= 0x01;
    if (transfer) numDetails |= 0x02;
    if (pro) numDetails |= 0x04;
    if (transactions) numDetails |= 0x08;
    if (purchases) numDetails |= 0x10;
    if (sessions) numDetails |= 0x20;
    request->setNumDetails(numDetails);
    request->setAccess(source);

    request->performRequest = [this, request]()
        {
            if (client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            int numDetails = request->getNumDetails();
            bool storage = (numDetails & 0x01) != 0;
            bool transfer = (numDetails & 0x02) != 0;
            bool pro = (numDetails & 0x04) != 0;
            bool transactions = (numDetails & 0x08) != 0;
            bool purchases = (numDetails & 0x10) != 0;
            bool sessions = (numDetails & 0x20) != 0;

            int numReqs = int(storage || transfer || pro) + int(transactions) + int(purchases) + int(sessions);
            if (numReqs == 0)
            {
                return API_EARGS;
            }
            request->setNumber(numReqs);

            client->getaccountdetails(request->getAccountDetails(), storage, transfer, pro, transactions, purchases, sessions, request->getAccess());
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::queryTransferQuota(long long size, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_TRANSFER_QUOTA, listener);
    request->setNumber(size);

    request->performRequest = [this, request]()
        {
            m_off_t size = request->getNumber();
            client->querytransferquota(size);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_changePw(MegaRequestPrivate* request)
{
            const char* oldPassword = request->getPassword();
            const char* newPassword = request->getNewPassword();
            const char* pin = request->getText();
            if (!newPassword)
            {
                return API_EARGS;
            }

            if (oldPassword && !checkPassword(oldPassword))
            {
                return API_EARGS;
            }

            return client->changepw(newPassword, pin);
}

error MegaApiImpl::performRequest_logout(MegaRequestPrivate* request)
{
            if (request->getParamType() == API_ESSL && client->retryessl)
            {
                return API_EINCOMPLETE;
            }

            if (request->getFlag())
            {
                bool keepSyncConfigsFile = request->getTransferTag();

                client->logout(keepSyncConfigsFile, [this, request](error result) {
                    LOG_debug << "executing logout completion, error: " << result;  // track possible lack of logout callbacks
                    logout_result(result, request);
                });
            }
            else
            {
                client->locallogout(false, true);
                client->restag = request->getTag();
                logout_result(API_OK, request);
            }
            return API_OK;
}

void MegaApiImpl::getNodeAttribute(MegaNode* node, int type, const char* dstFilePath, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_FILE, listener);
    if (dstFilePath)
    {
        string path(dstFilePath);

        int c = path[path.size() - 1];
        if ((c == '/') || (c == '\\'))
        {
            const char* base64Handle = node->getBase64Handle();
            path.append(base64Handle);
            path.push_back(static_cast<char>('0' + type));
            path.append(".jpg");
            delete[] base64Handle;
        }

        request->setFile(path.c_str());
    }

    request->setParamType(type);
    if (node)
    {
        request->setNodeHandle(node->getHandle());
        const char* fileAttributes = node->getFileAttrString();
        if (fileAttributes)
        {
            request->setText(fileAttributes);
            const char* nodekey = node->getBase64Key();
            request->setPrivateKey(nodekey);
            delete[] nodekey;
            delete[] fileAttributes;
        }
    }

    request->performRequest = [this, request]()
        {
            const char* dstFilePath = request->getFile();
            int type = request->getParamType();
            handle h = request->getNodeHandle();
            const char *fa = request->getText();
            const char *base64key = request->getPrivateKey();

            Node *node = client->nodebyhandle(h);

            if(!dstFilePath || (!fa && !node) || (fa && (!base64key || ISUNDEF(h))))
            {
                return API_EARGS;
            }

            string fileattrstring;
            string key;
            if (!fa)
            {
                fileattrstring = node->fileattrstring;
                key = node->nodekey();
            }
            else
            {
                fileattrstring = fa;

                byte nodekey[FILENODEKEYLENGTH];
                if (Base64::atob(base64key, nodekey, sizeof nodekey) != sizeof nodekey)
                {
                    return API_EKEY;
                }
                key.assign((const char *)nodekey, sizeof nodekey);
            }
            error e = client->getfa(h, &fileattrstring, key, (fatype)type);
            if(e == API_EEXIST)
            {
                e = API_OK;
                int prevtag = client->restag;
                MegaRequestPrivate* req = NULL;
                while(prevtag)
                {
                    if(requestMap.find(prevtag) == requestMap.end())
                    {
                        LOG_err << "Invalid duplicate getattr request";
                        req = NULL;
                        e = API_EINTERNAL;
                        break;
                    }

                    req = requestMap.at(prevtag);
                    if(!req || (req->getType() != MegaRequest::TYPE_GET_ATTR_FILE))
                    {
                        LOG_err << "Invalid duplicate getattr type";
                        req = NULL;
                        e = API_EINTERNAL;
                        break;
                    }

                    prevtag = int(req->getNumber());
                }

                if(req)
                {
                    LOG_debug << "Duplicate getattr detected";
                    req->setNumber(request->getTag());
                }
            }

            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getAttrUser(MegaRequestPrivate* request)
{
            const char* value = request->getFile();
            attr_t type = static_cast<attr_t>(request->getParamType());
            const char *email = request->getEmail();
            const char *ph = request->getSessionKey();

            string attrname = MegaApiImpl::userAttributeToString(type);
            char scope = MegaApiImpl::userAttributeToScope(type);

            if ((!client->loggedin() && ph == NULL) || (ph && !ph[0]))
            {
                return API_EARGS;
            }

            User *user = email ? client->finduser(email, 0) : client->finduser(client->me, 0);

            if (!user)  // email/handle not found among (ex)contacts
            {
                if (scope == '*' || scope == '#')
                {
                    LOG_warn << "Cannot retrieve private/protected attributes from users other than yourself.";
                    return API_EACCESS;
                }

                client->getua(email, type, ph);
                return API_OK;
            }

            bool externalDriveRequest = type == ATTR_DEVICE_NAMES && request->getFlag();
            if (attrname.empty()    // unknown attribute type
                    || (type == ATTR_AVATAR && !value)  // no destination file for avatar
                    || (externalDriveRequest && !value)) // no drive path to look for drive-id
            {
                return API_EARGS;
            }

            // if attribute is private and user is not logged in user...
            if (scope == '*' && user->userhandle != client->me)
            {
                return API_EACCESS;
            }

            if (externalDriveRequest)
            {
                // check if drive-id already exists
                handle driveId;
                error e = readDriveId(*client->fsaccess, value, driveId);
                if (e != API_OK)
                {
                    return e;
                }
                request->setNodeHandle(driveId);
            }

            client->getua(user, type);
            return API_OK;
}

error MegaApiImpl::performRequest_setAttrUser(MegaRequestPrivate* request)
{
            const char* file = request->getFile();
            const char* value = request->getText();
            attr_t type = static_cast<attr_t>(request->getParamType());

            char scope = MegaApiImpl::userAttributeToScope(type);
            string attrname = MegaApiImpl::userAttributeToString(type);
            if (attrname.empty())   // unknown attribute type
            {
                return API_EARGS;
            }

            User *ownUser = client->finduser(client->me);
            if (!ownUser)
            {
                assert("Setting attribute without having logged in");
                return API_EACCESS;
            }

            string attrvalue;

            if (type == ATTR_KEYRING                ||
                    type == ATTR_KEYS               ||
                    User::isAuthring(type)          ||
                    type == ATTR_CU25519_PUBK       ||
                    type == ATTR_ED25519_PUBK       ||
                    type == ATTR_SIG_CU255_PUBK     ||
                    type == ATTR_SIG_RSA_PUBK)
            {
                return API_EACCESS;
            }
            else if (type == ATTR_MY_BACKUPS_FOLDER)
            {
                LOG_err << "Cannot set 'My backups' folder attribute directly. Please, use MegaApi::setMyBackupsFolder";
                return API_EACCESS;
            }
            else if (type == ATTR_AVATAR)
            {
                // read the attribute value from file
                if (file)
                {
                    auto localpath = LocalPath::fromAbsolutePath(file);

                    auto f = fsAccess->newfileaccess();
                    if (!f->fopen(localpath, 1, 0, FSLogging::logOnError))
                    {
                        return API_EREAD;
                    }

                    if (!f->fread(&attrvalue, unsigned(f->size), 0, 0, FSLogging::logOnError))
                    {
                        return API_EREAD;
                    }
                    f.reset();

                    client->putua(type, (byte *)attrvalue.data(), unsigned(attrvalue.size()));
                    return API_OK;
                }
                else    // removing current attribute's value
                {
                    client->putua(type);
                    return API_OK;
                }
            }
            else if (scope == '*')   // private attribute
            {
                if (type == ATTR_DEVICE_NAMES && request->getFlag()) // external drive
                {
                    const char *pathToDrive = request->getFile();
                    if (!pathToDrive)
                    {
                        return API_EARGS;
                    }

                    // check if the drive id is already created
                    // read <pathToDrive>/.megabackup/drive-id
                    handle driveId;
                    error e = readDriveId(*client->fsaccess, pathToDrive, driveId);

                    if (e == API_ENOENT)
                    {
                        // generate new id
                        driveId = generateDriveId(client->rng);
                        // write <pathToDrive>/.megabackup/drive-id
                        e = writeDriveId(*client->fsaccess, pathToDrive, driveId);
                    }

                    if (e != API_OK)
                        return e;

                    MegaStringMapPrivate stringMap;
                    const char *driveName = request->getName();
                    string buf = driveName ? driveName : "";
                    stringMap.set(Base64Str<MegaClient::DRIVEHANDLE>(driveId), Base64::btoa(buf).c_str());
                    request->setMegaStringMap(&stringMap); // makes a copy
                }

                MegaStringMap *stringMap = request->getMegaStringMap();
                if (!stringMap)
                {
                    return API_EARGS;
                }

                std::unique_ptr<TLVstore> tlv;
                if (type == ATTR_ALIAS
                        || type == ATTR_CAMERA_UPLOADS_FOLDER
                        || type == ATTR_DEVICE_NAMES
                        || type == ATTR_APPS_PREFS)
                {
                    if (!ownUser->isattrvalid(type)) // not fetched yet or outdated
                    {
                        // always get updated value before update it
                        client->getua(ownUser, type);
                        return API_OK;
                    }
                    else
                    {
                        tlv.reset(TLVstore::containerToTLVrecords(ownUser->getattr(type), &client->key));
                    }
                }
                else
                {
                    tlv.reset(new TLVstore);
                }

                const string_map *newValuesMap = static_cast<MegaStringMapPrivate*>(stringMap)->getMap();
                unique_ptr<string_map> prefixedValueMap;

                if (type == ATTR_DEVICE_NAMES)
                {
                    // allow only unique names for Devices and Drives
                    if (haveDuplicatedValues(*tlv->getMap(), *newValuesMap))
                    {
                        LOG_err << "Attribute " << User::attr2string(type) << " attempted to add duplicated value (1): "
                            << Base64::atob(newValuesMap->begin()->second); // will only have a single value
                        return API_EEXIST;
                    }

                    if (request->getFlag()) // external drive
                    {
                        string prefix = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true);
                        prefixedValueMap = mega::make_unique<string_map>();
                        for_each(newValuesMap->begin(), newValuesMap->end(),
                            [&prefixedValueMap, &prefix](const string_map::value_type& a) {prefixedValueMap->emplace(prefix + a.first, a.second); });
                        newValuesMap = prefixedValueMap.get();
                    }
                }

                if (User::mergeUserAttribute(type, *newValuesMap, *tlv.get()))
                {
                    // serialize and encrypt the TLV container
                    std::unique_ptr<string> container(tlv->tlvRecordsToContainer(client->rng, &client->key));
                    client->putua(type, (byte *)container->data(), unsigned(container->size()));
                }
                else
                {
                    // no changes, current value equal to new value
                    LOG_debug << "Attribute " << User::attr2string(type) << " not changed, already up to date";
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                }
                return API_OK;
            }
            else if (scope == '^')
            {
                if (type == ATTR_LANGUAGE)
                {
                    if (!value)
                    {
                        return API_EARGS;
                    }

                    string code;
                    if (!getLanguageCode(value, &code))
                    {
                        return API_ENOENT;
                    }

                    client->putua(type, (byte *)code.data(), unsigned(code.length()));
                    return API_OK;
                }
                else if (type == ATTR_PWD_REMINDER)
                {
                    if (request->getNumDetails() == 0)  // nothing to be changed
                    {
                        return API_EARGS;
                    }

                    // always get updated value before update it
                    client->getua(ownUser, type);
                    return API_OK;
                }
                else if ((type == ATTR_DISABLE_VERSIONS)
                         || (type == ATTR_NO_CALLKIT)
                         || (type == ATTR_CONTACT_LINK_VERIFICATION))
                {
                    if (!value || strlen(value) != 1 || (value[0] != '0' && value[0] != '1'))
                    {
                        return API_EARGS;
                    }

                    client->putua(type, (byte*)value, 1);
                }
                else if (type == ATTR_RUBBISH_TIME || type == ATTR_LAST_PSA)
                {
                    if (!value || !value[0])
                    {
                        return API_EARGS;
                    }

                    char *endptr;
                    m_off_t number = strtoll(value, &endptr, 10);
                    if (endptr == value || *endptr != '\0' || number == LLONG_MAX || number == LLONG_MIN || number < 0)
                    {
                        return API_EARGS;
                    }

                    string tmp(value);
                    client->putua(type, (byte *)tmp.data(), unsigned(tmp.size()));
                }
                else if (type == ATTR_COOKIE_SETTINGS)
                {
                    setCookieSettings_sendPendingRequests(request);
                }
                else if (type == ATTR_PUSH_SETTINGS)
                {
                    const MegaPushNotificationSettingsPrivate *settings = (MegaPushNotificationSettingsPrivate*)(request->getMegaPushNotificationSettings());
                    if (!settings)
                    {
                        return API_EARGS;
                    }

                    string settingsJson = settings->generateJson();
                    if (settingsJson.empty())
                    {
                        return API_EARGS;
                    }

                    client->putua(type, (byte *)settingsJson.data(), unsigned(settingsJson.size()));
                }
                else
                {
                    return API_EARGS;
                }
            }
            else    // any other type of attribute
            {
                if (!value)
                {
                    return API_EARGS;
                }

                client->putua(type, (byte *)value, unsigned(strlen(value)));
                return API_OK;
            }

            return API_OK;
}

void MegaApiImpl::getUserEmail(MegaHandle h, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_EMAIL, listener);
    request->setNodeHandle(h);

    request->performRequest = [this, request]()
        {
            handle uh = request->getNodeHandle();
            if (uh == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            char uid[12];
            Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid);
            uid[11] = 0;

            client->getUserEmail(uid);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_setAttrFile(MegaRequestPrivate* request)
{
            const char* srcFilePath = request->getFile();
            int type = request->getParamType();
            Node *node = request->getNodeHandle() == INVALID_HANDLE ? nullptr : client->nodebyhandle(request->getNodeHandle());
            MegaHandle fileattrhandle = (uint64_t)request->getNumber();
            auto bu = static_cast<MegaBackgroundMediaUploadPrivate*>(request->getMegaBackgroundMediaUploadPtr());

            if (!srcFilePath)
            {
                if (!node || fileattrhandle == INVALID_HANDLE) { return API_EARGS; }

                string fileattr;
                appendFileAttribute(fileattr, type, fileattrhandle);

                client->reqs.add(new CommandAttachFA(client, node->nodehandle, fatype(type), fileattr, request->getTag()));
            }
            else
            {
                if (!node == !bu) { return API_EARGS; }

                auto localpath = LocalPath::fromAbsolutePath(srcFilePath);

                std::unique_ptr<string> attributedata(new string);
                std::unique_ptr<FileAccess> f(fsAccess->newfileaccess());
                if (!f->fopen(localpath, 1, 0, FSLogging::logOnError))
                {
                    return API_EREAD;
                }

                // make the string a little bit larger initially with SymmCipher::BLOCKSIZE to avoid heap activity growing it for the encryption
                attributedata->reserve(size_t(f->size + SymmCipher::BLOCKSIZE));
                if (!f->fread(attributedata.get(), unsigned(f->size), 0, 0, FSLogging::logOnError))
                {
                    return API_EREAD;
                }

                SymmCipher* cipher = bu ? bu->nodecipher(client) : node->nodecipher();
                if (!cipher)
                {
                    return API_EKEY;
                }

                client->putfa(NodeOrUploadHandle(node ? node->nodeHandle() : NodeHandle()), (fatype)type, cipher, request->getTag(), std::move(attributedata));
            }
            return API_OK;
}

error MegaApiImpl::performRequest_setAttrNode(MegaRequestPrivate* request)
{
            Node* node = client->nodebyhandle(request->getNodeHandle());
            bool isOfficial = request->getFlag();
            error e = API_OK;

            if (!node)
            {
                return API_EARGS;
            }

            if (!client->checkaccess(node, FULL))
            {
                return API_EACCESS;
            }

            attr_map attrUpdates;

            if (isOfficial)
            {
                int type = request->getParamType();
                if (type == MegaApi::NODE_ATTR_DURATION)
                {
                    int secs = int(request->getNumber());
                    if (node->type != FILENODE || secs < MegaNode::INVALID_DURATION)
                    {
                        return API_EARGS;
                    }

                    if (secs == MegaNode::INVALID_DURATION)
                    {
                        attrUpdates['d'] = "";
                    }
                    else
                    {
                        string attrVal;
                        Base64::itoa(secs, &attrVal);
                        if (attrVal.size())
                        {
                            attrUpdates['d'] = attrVal;
                        }
                    }
                }
                else if (type == MegaApi::NODE_ATTR_COORDINATES)
                {
                    if (node->type != FILENODE)
                    {
                        return API_EARGS;
                    }

                    int longitude = request->getNumDetails();
                    int latitude = request->getTransferTag();
                    int unshareable = request->getAccess();

                    e = updateAttributesMapWithCoordinates(attrUpdates, latitude, longitude, !!unshareable, client);
                    if (e != API_OK)
                    {
                        return e;
                    }
                }
                else if (type == MegaApi::NODE_ATTR_ORIGINALFINGERPRINT)
                {
                    nameid nid = AttrMap::string2nameid("c0");
                    if (!request->getText())
                    {
                        attrUpdates[nid] = "";
                    }
                    else
                    {
                        attrUpdates[nid] = request->getText();
                    }
                }
                else if (type == MegaApi::NODE_ATTR_LABEL || type == MegaApi::NODE_ATTR_FAV || type == MegaApi::NODE_ATTR_SEN)
                {
                    Node *current = node;
                    bool remove = false;
                    nameid nid = 0;
                    int value = 0;
                    if (type == MegaApi::NODE_ATTR_LABEL)
                    {
                        value = request->getNumDetails();
                        if (value < LBL_UNKNOWN || value > LBL_GREY)
                        {
                            return API_EARGS;
                        }

                        nid = AttrMap::string2nameid("lbl");
                        remove = (value == LBL_UNKNOWN);
                    }
                    else if (type == MegaApi::NODE_ATTR_SEN)
                    {
                        nid = AttrMap::string2nameid("sen");
                        remove = !request->getNumDetails();
                        value = 1;
                    }
                    else
                    {
                        nid = AttrMap::string2nameid("fav");
                        remove = !request->getNumDetails();
                        value = 1;
                    }

                    if (remove)
                    {
                        attrUpdates[nid] = "";
                    }
                    else
                    {
                        attrUpdates[nid] = std::to_string(value);
                    }

                    // update file versions if any
                    if (current->type == FILENODE)
                    {
                        node_list childrens = client->getChildren(current);
                        while (childrens.size())
                        {
                            assert(childrens.size() != 1);
                            Node* n = *childrens.begin();
                            client->setattr(n, attr_map(attrUpdates), nullptr, false); // no callback for these
                            childrens = client->getChildren(n);
                        }
                    }

                    return client->setattr(current, move(attrUpdates),
                        [request, this](NodeHandle h, Error e)
                        {
                            request->setNodeHandle(h.as8byte());
                            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                        }, false);
                }
                else if (type == MegaApi::NODE_ATTR_S4)
                {
                    const char* attrValue = request->getText();
                    attrUpdates[AttrMap::string2nameid("s4")] = attrValue ? attrValue : "";
                }
                else
                {
                    return API_EARGS;
                }
            }
            else    // custom attribute, not official
            {
                const char* attrName = request->getName();
                const char* attrValue = request->getText();

                if (!attrName || !attrName[0] || strlen(attrName) > 7)
                {
                    return API_EARGS;
                }

                string sname = attrName;
                LocalPath::utf8_normalize(&sname);
                sname.insert(0, "_");
                nameid attr = AttrMap::string2nameid(sname.c_str());

                if (attrValue)
                {
                    string svalue = attrValue;
                    LocalPath::utf8_normalize(&svalue);
                    attrUpdates[attr] = svalue;
                }
                else
                {
                    attrUpdates[attr] = "";
                }
            }

            if (!e && !attrUpdates.empty())
            {
                e = client->setattr(node, std::move(attrUpdates),
                    [request, this](NodeHandle h, Error e)
                    {
                        request->setNodeHandle(h.as8byte());
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    }, false);
            }

            return e;
}

void MegaApiImpl::getFavourites(MegaNode* node, int count, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_NODE, listener);
    if (node)
    {
        request->setNodeHandle(node->getHandle());
    }
    else
    {
        request->setNodeHandle(UNDEF);
    }

    request->setParamType(MegaApi::NODE_ATTR_FAV);
    request->setNumDetails(count);

    request->performRequest = [this, request]()
        {
            int type = request->getParamType();

            if (type == MegaApi::NODE_ATTR_FAV)
            {
                int count = request->getNumDetails();
                MegaHandle folderHandle = request->getNodeHandle();
                Node *node = nullptr;
                if (!ISUNDEF(folderHandle))
                {
                    node = client->nodebyhandle(folderHandle);
                    if (!node)
                    {
                        return API_ENOENT;
                    }
                    if (node->type != FOLDERNODE)
                    {
                        return API_EARGS;
                    }
                }
                else
                {
                    node = client->nodeByHandle(client->mNodeManager.getRootNodeFiles());
                    if (!node)
                    {
                        LOG_debug << "Lookup of files root node failed";
                        return API_ENOENT;
                    }
                }

                // Check if 'node' is favourite, DB query starts at 'node' children
                std::vector<NodeHandle> favouriteNodes;
                nameid nid = AttrMap::string2nameid("fav");
                auto attrMapIterator = node->attrs.map.find(nid);
                if (attrMapIterator != node->attrs.map.end() && attrMapIterator->second == "1")
                {
                    favouriteNodes.push_back(node->nodeHandle());
                }

                if (count != 1 || favouriteNodes.empty())
                {
                   std::vector<NodeHandle> favs = client->mNodeManager.getFavouritesNodeHandles(node->nodeHandle(), count);
                   favouriteNodes.insert(favouriteNodes.end(), favs.begin(), favs.end());
                }

                std::vector<handle> handles;
                for (const NodeHandle& nodeHandle : favouriteNodes)
                {
                    handles.push_back(nodeHandle.as8byte());
                }

                request->setMegaHandleList(handles);
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::cancelGetNodeAttribute(MegaNode* node, int type, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_ATTR_FILE, listener);
    request->setParamType(type);
    if (node)
    {
        request->setNodeHandle(node->getHandle());
        const char* fileAttributes = node->getFileAttrString();
        if (fileAttributes)
        {
            request->setText(fileAttributes);
            delete[] fileAttributes;
        }
    }

    request->performRequest = [this, request]()
        {
            int type = request->getParamType();
            handle h = request->getNodeHandle();
            const char *fa = request->getText();

            Node *node = client->nodebyhandle(h);

            if((!fa && !node) || (fa && ISUNDEF(h)))
            {
                return API_EARGS;
            }

            string fileattrstring = fa ? string(fa) : node->fileattrstring;

            error e = client->getfa(h, &fileattrstring, string(), (fatype)type, 1);
            if (!e)
            {
                std::map<int, MegaRequestPrivate*>::iterator it = requestMap.begin();
                while(it != requestMap.end())
                {
                    MegaRequestPrivate *r = it->second;
                    it++;
                    if (r->getType() == MegaRequest::TYPE_GET_ATTR_FILE &&
                        r->getParamType() == request->getParamType() &&
                        r->getNodeHandle() == request->getNodeHandle())
                    {
                        fireOnRequestFinish(r, make_unique<MegaErrorPrivate>(API_EINCOMPLETE));
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_retryPendingConnections(MegaRequestPrivate* request)
{
            bool disconnect = request->getFlag();
            bool includexfers = request->getNumber();
            const char *dnsservers = request->getText();

            client->abortbackoff(includexfers);
            if (disconnect)
            {
                client->disconnect();

                string servers;
                if (dnsservers && dnsservers[0])
                {
                    servers = dnsservers;
                }
#ifndef __MINGW32__
#ifdef MEGA_USE_C_ARES
                if (servers.size())
                {
                    LOG_debug << "Using DNS servers " << servers;
                    httpio->setdnsservers(servers.c_str());
                }
#endif
#endif
            }

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
}

void MegaApiImpl::inviteContact(const char* email, const char* message, int action, MegaHandle contactLink, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_INVITE_CONTACT, listener);
    request->setNumber(action);
    request->setEmail(email);
    request->setText(message);
    request->setNodeHandle(contactLink);

    request->performRequest = [this, request]()
        {
            const char *email = request->getEmail();
            const char *message = request->getText();
            int action = int(request->getNumber());
            MegaHandle contactLink = request->getNodeHandle();

            if(client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            if(!email || !client->finduser(client->me)->email.compare(email))
            {
                return API_EARGS;
            }

            if (action != OPCA_ADD && action != OPCA_REMIND && action != OPCA_DELETE)
            {
                return API_EARGS;
            }

            client->setpcr(email, (opcactions_t)action, message, NULL, contactLink);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::replyContactRequest(MegaContactRequest* r, int action, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REPLY_CONTACT_REQUEST, listener);
    if (r)
    {
        request->setNodeHandle(r->getHandle());
    }

    request->setNumber(action);

    request->performRequest = [this, request]()
        {
            handle h = request->getNodeHandle();
            int action = int(request->getNumber());

            if(h == INVALID_HANDLE || action < 0 || action > MegaContactRequest::REPLY_ACTION_IGNORE)
            {
                return API_EARGS;
            }

            client->updatepcr(h, (ipcactions_t)action);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeContact(MegaUser* user, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_CONTACT, listener);
    if (user)
    {
        request->setEmail(user->getEmail());
    }

    request->performRequest = [this, request]()
        {
            const char *email = request->getEmail();
            User *u = client->finduser(email);
            if(!u || u->show == HIDDEN || u->userhandle == client->me) { return API_EARGS; }
            if (client->mBizMode == BIZ_MODE_SUBUSER && u->mBizMode != BIZ_MODE_UNKNOWN)
            {
                return API_EMASTERONLY;
            }

            return client->removecontact(email, HIDDEN);
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_createAccount(MegaRequestPrivate* request)
{
            const char *email = request->getEmail();
            const char *password = request->getPassword();
            const char *name = request->getName();
            const char *lastname = request->getText();
            const char *pwkey = request->getPrivateKey();
            const char *sid = request->getSessionKey();
            bool resumeProcess = (request->getParamType() == MegaApi::RESUME_ACCOUNT);   // resume existing ephemeral account
            bool cancelProcess = (request->getParamType() == MegaApi::CANCEL_ACCOUNT);
            bool createEphemeralPlusPlus = (request->getParamType() == MegaApi::CREATE_EPLUSPLUS_ACCOUNT);   // create ephemeral++ account
            bool resumeEphemeralPlusPlus = (request->getParamType() == MegaApi::RESUME_EPLUSPLUS_ACCOUNT);   // resume existing ephemeral++ account
            handle lastPublicHandle = request->getNodeHandle();
            int lastPublicHandleType = request->getAccess();
            int64_t lastAccessTimestamp =request->getTransferredBytes();

            if (!ISUNDEF(lastPublicHandle)
                    && ((lastPublicHandleType <= mega::MegaApi::AFFILIATE_TYPE_INVALID
                            || lastPublicHandleType > mega::MegaApi::AFFILIATE_TYPE_CONTACT)
                        || !lastAccessTimestamp))
            {
                return API_EARGS;
            }

            if ((!resumeProcess && !cancelProcess && !resumeEphemeralPlusPlus && !createEphemeralPlusPlus &&
                  (!email || !name || (!password && !pwkey))) ||
                 ((resumeProcess || resumeEphemeralPlusPlus) && !sid) ||
                 (createEphemeralPlusPlus && !(name && lastname)))
            {
                return API_EARGS;
            }

            byte pwbuf[SymmCipher::KEYLENGTH];
            handle uh = UNDEF;
            if (resumeProcess)
            {
                size_t pwkeyLen = strlen(sid);
                size_t pwkeyLenExpected = SymmCipher::KEYLENGTH * 4 / 3 + 3 + 10;
                if (pwkeyLen != pwkeyLenExpected ||
                        Base64::atob(sid, (byte*) &uh, sizeof uh) != sizeof uh ||
                        uh == UNDEF || sid[11] != '#' ||
                        Base64::atob(sid + 12, pwbuf, sizeof pwbuf) != sizeof pwbuf)
                {
                    return API_EARGS;
                }
            }

            if (cancelProcess)
            {
                client->cancelsignup();
            }
            else
            {
                int reqtag = request->getTag();
                requestMap.erase(reqtag);

                abortPendingActions();

                requestMap[reqtag] = request;

                client->locallogout(false, true);

                if (resumeProcess)
                {
                    client->resumeephemeral(uh, pwbuf);
                }
                else if (resumeEphemeralPlusPlus)
                {
                    client->resumeephemeralPlusPlus(Base64::atob(string(sid)));
                }
                else if (createEphemeralPlusPlus)
                {
                    client->createephemeralPlusPlus();
                }
                else
                {
                    assert(request->getParamType() == MegaApi::CREATE_ACCOUNT);
                    client->createephemeral();
                }
            }

            return API_OK;
}

error MegaApiImpl::performRequest_sendSignupLink(MegaRequestPrivate* request)
{
            if (request->getPrivateKey())
            {
                // obsolete. Use registration flow v2: calling (re)sendSignupLink() instead of fastSendSignupLink()
                return API_EINTERNAL;
            }
            const char *email = request->getEmail();
            const char *name = request->getName();
            if (!email || !name)
            {
                return API_EARGS;
            }

            if (client->loggedin() != EPHEMERALACCOUNT && client->loggedin() != EPHEMERALACCOUNTPLUSPLUS)
            {
                return API_EACCESS;
            }

            if (client->accountversion != 2)
            {
                return API_EINTERNAL;
            }

            client->resendsignuplink2(email, name);
            return API_OK;
}

void MegaApiImpl::querySignupLink(const char* link, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_SIGNUP_LINK, listener);
    request->setLink(link);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();
            if(!link)
            {
                return API_EARGS;
            }

            const char* ptr = link;
            const char* tptr;

            // is it a link to confirm the account? ie. https://mega.nz/#confirm<code_in_B64>
            if ((tptr = strstr(ptr, MegaClient::confirmLinkPrefix())))
            {
                ptr = tptr + strlen(MegaClient::confirmLinkPrefix());

                string code = Base64::atob(string(ptr));
                if (!code.empty())
                {
                    if (code.find("ConfirmCodeV2") != string::npos)
                    {
                        // “ConfirmCodeV2” (13B) || Email Confirmation Token (15B) || Email (>=5B) || \t || Fullname || Hash (8B)
                        size_t posEmail = 13 + 15;
                        size_t endEmail = code.find("\t", posEmail);
                        if (endEmail != string::npos)
                        {
                            string email = code.substr(posEmail, endEmail - posEmail);
                            request->setEmail(email.c_str());
                            request->setName(code.substr(endEmail + 1, code.size() - endEmail - 9).c_str());

                            sessiontype_t session = client->loggedin();
                            if (session == FULLACCOUNT)
                            {
                                return (client->ownuser()->email == email) ? API_EEXPIRED : API_EACCESS;
                            }
                            else    // not-logged-in / ephemeral account / partially confirmed
                            {
                                client->confirmsignuplink2((const byte*)code.data(), unsigned(code.size()));
                                return API_OK;
                            }
                        }
                    }
                }
            }
            // is it a new singup link? ie. https://mega.nz/#newsignup<code_in_B64>
            else if ((tptr = strstr(ptr, MegaClient::newsignupLinkPrefix())))
            {
                ptr = tptr + strlen(MegaClient::newsignupLinkPrefix());

                unsigned len = unsigned((strlen(link)-(ptr-link))*3/4+4);
                byte *c = new byte[len];
                len = Base64::atob(ptr,c,len);

                if (len > 8)
                {
                    // extract email and email_hash from link
                    byte *email = c;
                    byte *sha512bytes = c+len-8;    // last 11 chars

                    // get the hash for the received email
                    Hash sha512;
                    sha512.add(email, len-8);
                    string sha512str;
                    sha512.get(&sha512str);

                    // and finally check it
                    if (memcmp(sha512bytes, sha512str.data(), 8) == 0)
                    {
                        email[len-8] = '\0';
                        request->setEmail((const char *)email);
                        delete[] c;

                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                        return API_OK;
                    }
                }

                delete[] c;
            }

            return API_EARGS;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_confirmAccount(MegaRequestPrivate* request)
{
            const char* link = request->getLink();
            const char* password = request->getPassword();

            if (!link || !password)
            {
                return API_EARGS;
            }

            if (request->getPrivateKey())
            {
                // obsolete. Use registration flow v2
                return API_EINTERNAL;
            }

            const char* ptr = link;
            const char* tptr;

            if ((tptr = strstr(ptr, MegaClient::confirmLinkPrefix()))) ptr = tptr + strlen(MegaClient::confirmLinkPrefix());

            error e = API_OK;
            string code = Base64::atob(string(ptr));
            if (code.find("ConfirmCodeV2") != string::npos)
            {
                // “ConfirmCodeV2” (13B) || Email Confirmation Token (15B) || Email (>=5B) || \t || Fullname || Hash (8B)
                size_t posEmail = 13 + 15;
                size_t endEmail = code.find("\t", posEmail);
                if (endEmail != string::npos)
                {
                    string email = code.substr(posEmail, endEmail - posEmail);
                    request->setEmail(email.c_str());
                    request->setName(code.substr(endEmail + 1, code.size() - endEmail - 9).c_str());

                    sessiontype_t session = client->loggedin();
                    if (session == FULLACCOUNT)
                    {
                        e = (client->ownuser()->email == email) ? API_EEXPIRED : API_EACCESS;
                    }
                    else    // not-logged-in / ephemeral account / partially confirmed
                    {
                        client->confirmsignuplink2((const byte*)code.data(), unsigned(code.size()));
                    }
                }
                else
                {
                    e = API_EARGS;
                }
            }
            else
            {
                e = API_EARGS;
            }
            return e;
}

void MegaApiImpl::resetPassword(const char* email, bool hasMasterKey, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECOVERY_LINK, listener);
    request->setEmail(email);
    request->setFlag(hasMasterKey);

    request->performRequest = [this, request]()
        {
            const char *email = request->getEmail();
            bool hasMasterKey = request->getFlag();

            if (!email || !email[0])
            {
                return API_EARGS;
            }

            client->getrecoverylink(email, hasMasterKey);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::queryRecoveryLink(const char* link, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_RECOVERY_LINK, listener);
    request->setLink(link);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();

            const char* code;
            if (link && (code = strstr(link, MegaClient::recoverLinkPrefix())))
            {
                code += strlen(MegaClient::recoverLinkPrefix());
            }
            else if (link && (code = strstr(link, MegaClient::verifyLinkPrefix())))
            {
                code += strlen(MegaClient::verifyLinkPrefix());
            }
            else if (link && (code = strstr(link, MegaClient::cancelLinkPrefix())))
            {
                code += strlen(MegaClient::cancelLinkPrefix());
            }
            else
            {
                return API_EARGS;
            }

            client->queryrecoverylink(code);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::confirmResetPasswordLink(const char* link, const char* newPwd, const char* masterKey, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_RECOVERY_LINK, listener);
    request->setLink(link);
    request->setPassword(newPwd);
    request->setPrivateKey(masterKey);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();
            const char *newPwd = request->getPassword();

            const char* code;
            if (newPwd && link && (code = strstr(link, MegaClient::recoverLinkPrefix())))
            {
                code += strlen(MegaClient::recoverLinkPrefix());
            }
            else
            {
                return API_EARGS;
            }

            // concatenate query + confirm requests
            client->queryrecoverylink(code);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::checkRecoveryKey(const char* link, const char* recoveryKey, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_RECOVERY_KEY, listener);
    request->setLink(link);
    request->setPrivateKey(recoveryKey);

    request->performRequest = [this, request]()
        {
            const char* link = request->getLink();

            const char* code;
            if (link && (code = strstr(link, MegaClient::recoverLinkPrefix())))
            {
                code += strlen(MegaClient::recoverLinkPrefix());
            }
            else
            {
                return API_EARGS;
            }

            // concatenate query + confirm requests
            client->getprivatekey(code);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getCancelLink(MegaRequestPrivate* request)
{
            if (client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            User *u = client->finduser(client->me);
            if (!u)
            {
                return API_ENOENT;
            }

            const char *pin = request->getText();
            client->getcancellink(u->email.c_str(), pin);
            return API_OK;
}

void MegaApiImpl::confirmCancelAccount(const char* link, const char* pwd, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_CANCEL_LINK, listener);
    request->setLink(link);
    request->setPassword(pwd);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();
            const char *pwd = request->getPassword();

            if (client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            const char* code;
            if (!pwd || !link || !(code = strstr(link, MegaClient::cancelLinkPrefix())))
            {
                return API_EARGS;
            }

            if (!checkPassword(pwd))
            {
                return API_ENOENT;
            }

            code += strlen(MegaClient::cancelLinkPrefix());
            client->confirmcancellink(code);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getChangeEmailLink(MegaRequestPrivate* request)
{
            if (client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            const char *email = request->getEmail();
            const char *pin = request->getText();
            if (!email)
            {
                return API_EARGS;
            }

            client->getemaillink(email, pin);
            return API_OK;
}

void MegaApiImpl::confirmChangeEmail(const char* link, const char* pwd, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK, listener);
    request->setLink(link);
    request->setPassword(pwd);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();
            const char *pwd = request->getPassword();

            if (client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            const char* code;
            if (pwd && link && (code = strstr(link, MegaClient::verifyLinkPrefix())))
            {
                code += strlen(MegaClient::verifyLinkPrefix());
            }
            else
            {
                return API_EARGS;
            }

            // concatenates query + validate pwd + confirm
            client->queryrecoverylink(code);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::pauseTransfers(bool pause, int direction, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PAUSE_TRANSFERS, listener);
    request->setFlag(pause);
    request->setNumber(direction);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
        {
            bool pause = request->getFlag();
            int direction = int(request->getNumber());
            if(direction != -1
                    && direction != MegaTransfer::TYPE_DOWNLOAD
                    && direction != MegaTransfer::TYPE_UPLOAD)
            {
                return API_EARGS;
            }

            if(direction == -1)
            {
                client->pausexfers(PUT, pause, false, committer);
                client->pausexfers(GET, pause, false, committer);
            }
            else if(direction == MegaTransfer::TYPE_DOWNLOAD)
            {
                client->pausexfers(GET, pause, false, committer);
            }
            else
            {
                client->pausexfers(PUT, pause, false, committer);
            }

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::pauseTransfer(int transferTag, bool pause, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PAUSE_TRANSFER, listener);
    request->setTransferTag(transferTag);
    request->setFlag(pause);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
        {
            bool pause = request->getFlag();
            int transferTag = request->getTransferTag();
            MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag);
            if (!megaTransfer)
            {
                return API_ENOENT;
            }

            error e = client->transferlist.pause(megaTransfer->getTransfer(), pause, committer);
            if (!e)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performTransferRequest_moveTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer)
{
            bool automove = request->getFlag();
            int transferTag = request->getTransferTag();
            int number = int(request->getNumber());

            if (!transferTag || !number)
            {
                return API_EARGS;
            }

            MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag);
            if (!megaTransfer)
            {
                return API_ENOENT;
            }

            Transfer *transfer = megaTransfer->getTransfer();
            if (!transfer)
            {
                return API_ENOENT;
            }

            if (automove)
            {
                switch (number)
                {
                    case MegaTransfer::MOVE_TYPE_UP:
                        client->transferlist.moveup(transfer, committer);
                        break;
                    case MegaTransfer::MOVE_TYPE_DOWN:
                        client->transferlist.movedown(transfer, committer);
                        break;
                    case MegaTransfer::MOVE_TYPE_TOP:
                        client->transferlist.movetofirst(transfer, committer);
                        break;
                    case MegaTransfer::MOVE_TYPE_BOTTOM:
                        client->transferlist.movetolast(transfer, committer);
                        break;
                    default:
                        return API_EARGS;
                }
            }
            else
            {
                MegaTransferPrivate* prevMegaTransfer = getMegaTransferPrivate(number);
                if (!prevMegaTransfer)
                {
                    return API_ENOENT;
                }

                Transfer *prevTransfer = prevMegaTransfer->getTransfer();
                if (!prevTransfer)
                {
                    client->transferlist.movetransfer(transfer, client->transferlist.transfers[transfer->type].begin(), committer);
                }
                else
                {
                    if (transfer->type != prevTransfer->type)
                    {
                        return API_EARGS;
                    }
                    else
                    {
                        client->transferlist.movetransfer(transfer, prevTransfer, committer);
                    }
                }
            }

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
}

void MegaApiImpl::setMaxConnections(int direction, int connections, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_MAX_CONNECTIONS, listener);
    request->setParamType(direction);
    request->setNumber(connections);

    request->performRequest = [this, request]()
        {
            int direction = request->getParamType();
            int connections = int(request->getNumber());

            if (connections <= 0 || (direction != -1
                    && direction != MegaTransfer::TYPE_DOWNLOAD
                    && direction != MegaTransfer::TYPE_UPLOAD))
            {
                return API_EARGS;
            }

            if ((unsigned int) connections > MegaClient::MAX_NUM_CONNECTIONS)
            {
                return API_ETOOMANY;
            }

            if (direction == -1)
            {
                client->setmaxconnections(GET, connections);
                client->setmaxconnections(PUT, connections);
            }
            else if (direction == MegaTransfer::TYPE_DOWNLOAD)
            {
                client->setmaxconnections(GET, connections);
            }
            else
            {
                client->setmaxconnections(PUT, connections);
            }

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performTransferRequest_cancelTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer)
{
            int transferTag = request->getTransferTag();
            MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag);
            if (!megaTransfer)
            {
                return API_ENOENT;
            }

            if (megaTransfer->getType() == MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD)
            {
                return API_EACCESS;
            }

            if (megaTransfer->isFolderTransfer())
            {
                // folder upload or folder download.
                // we can do this by cancel token
                // all sub-transfers must have the same cancel token
                if (!megaTransfer->getCancelToken())
                {
                    LOG_warn << "Cancel requested for folder transfer, but it has lost its cancel token";
                    return API_EARGS;
                }
                else
                {
                    // there is a race to this point, in theory the operation could have just completed successfully on the thread
                    megaTransfer->stopRecursiveOperationThread();

                    // mark this transfer and any/all sub-transfers as cancelled
                    megaTransfer->getCancelToken()->cancel();

                    // report cancellation signalled
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                    return API_OK;
                }
            }

            if (!megaTransfer->isStreamingTransfer())
            {
                Transfer *transfer = megaTransfer->getTransfer();
                if (!transfer)
                {
                    return API_ENOENT;
                }
                #ifdef _WIN32
                    if (transfer->type == GET)
                    {
                        RemoveHiddenFileAttribute(transfer->localfilename);
                    }
                #endif

                MegaErrorPrivate megaError(API_EINCOMPLETE);
                megaTransfer->setLastError(&megaError);

                bool found = false;
                file_list files = transfer->files;
                file_list::iterator iterator = files.begin();
                while (iterator != files.end())
                {
                    File *file = *iterator;
                    iterator++;
                    if (file->tag == transferTag)
                    {
                        found = true;
                        if (!file->syncxfer)
                        {
                            client->stopxfer(file, &committer);
                            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                        }
                        else
                        {
                            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EACCESS));
                        }
                        break;
                    }
                }

                if (!found)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_ENOENT));
                }
            }
            else
            {
                m_off_t startPos = megaTransfer->getStartPos();
                m_off_t endPos = megaTransfer->getEndPos();
                m_off_t totalBytes = endPos - startPos + 1;

                MegaNode *publicNode = megaTransfer->getPublicNode();
                if (publicNode)
                {
                    client->preadabort(publicNode->getHandle(), startPos, totalBytes);
                }
                else
                {
                    Node *node = client->nodebyhandle(megaTransfer->getNodeHandle());
                    if (node)
                    {
                        client->preadabort(node, startPos, totalBytes);
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
            return API_OK;
}

void MegaApiImpl::cancelTransfers(int direction, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFERS, listener);
    request->setParamType(direction);

    request->performRequest = [this, request]()
        {
            int direction = request->getParamType();

            if ((direction != MegaTransfer::TYPE_DOWNLOAD) && (direction != MegaTransfer::TYPE_UPLOAD))
            {
                return API_EARGS;
            }

            CancelToken cancelled(true);

            // 1. Set all intermediate layer MegaTransfer object cancel tokens
            transferQueue.setAllCancelled(cancelled, direction);
            for (auto& t : transferMap)
            {
                if (t.second->getType() == direction
                   && !t.second->isSyncTransfer()
                   && !t.second->isStreamingTransfer())
                {
                    t.second->setCancelToken(cancelled);
                }
            }

            // 2. cancel transfers that were already startxfer'd
            for (auto& it : client->multi_transfers[direction])
            {
                for (auto& it2 : it.second->files)
                {
                    if (!it2->syncxfer)
                    {
                        it2->cancelToken = cancelled;
                    }
                }
            }

            LOG_verbose << "Marked all non-sync non-streaming transfers as cancelled. direction: " << direction;
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setScheduledCopy(const char* localFolder, MegaNode* parent, bool attendPastBackups, int64_t period, string periodstring, int numBackups, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_SCHEDULED_COPY, listener);
    if (parent) request->setNodeHandle(parent->getHandle());
    if (localFolder)
    {
        request->setFile(localFolder);
    }

    request->setNumRetry(numBackups);
    request->setNumber(period);
    request->setText(periodstring.c_str());
    request->setFlag(attendPastBackups);

    request->performRequest = [this, request]()
        {
            Node *parent = client->nodebyhandle(request->getNodeHandle());
            const char *localPath = request->getFile();
            if(!parent || (parent->type==FILENODE) || !localPath)
            {
                return API_EARGS;
            }

            string utf8name(localPath);
            MegaScheduledCopyController *mbc = NULL;
            int tagexisting = 0;
            bool existing = false;
            for (std::map<int, MegaScheduledCopyController *>::iterator it = backupsMap.begin(); it != backupsMap.end(); ++it)
            {
                if (!strcmp(it->second->getLocalFolder(), utf8name.c_str()) && it->second->getMegaHandle() == request->getNodeHandle())
                {
                    existing = true;
                    mbc = it->second;
                    tagexisting = it->first;
                }
            }

            if (existing){
                LOG_debug << "Updating existing backup parameters: " <<  utf8name.c_str() << " to " << request->getNodeHandle();
                mbc->setPeriod(request->getNumber());
                mbc->setPeriodstring(request->getText());
                mbc->setMaxBackups(request->getNumRetry());
                mbc->setAttendPastBackups(request->getFlag());

                request->setTransferTag(tagexisting);
                if (!mbc->isValid())
                {
                    LOG_err << "Failed to update backup parameters: Invalid parameters";
                    return API_EARGS;
                }
            }
            else
            {
                int tag = request->getTag();
                int tagForFolderTansferTag = client->nextreqtag();
                string speriod = request->getText();
                bool attendPastBackups= request->getFlag();
                //TODO: add existence of local folder check (optional??)

                MegaScheduledCopyController *mbc = new MegaScheduledCopyController(this, tag, tagForFolderTansferTag, request->getNodeHandle(),
                                                                     utf8name.c_str(), attendPastBackups, speriod.c_str(),
                                                                     request->getNumber(), request->getNumRetry());
                mbc->setBackupListener(request->getBackupListener()); //TODO: should we add this in setScheduledCopy?
                if (mbc->isValid())
                {
                    backupsMap[tag] = mbc;
                    request->setTransferTag(tag);
                }
                else
                {
                    delete mbc;
                    return API_EARGS;
                }
            }

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));

            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeScheduledCopy(int tag, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SCHEDULED_COPY, listener);
    request->setNumber(tag);

    request->performRequest = [this, request]()
        {
            int backuptag = int(request->getNumber());
            bool found = false;
            bool flag = request->getFlag();
            error e   = API_OK;

            map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(backuptag) ;
            if (itr != backupsMap.end())
            {
                found = true;
            }

            if (found)
            {
                if (!flag)
                {
                    MegaRequestPrivate *requestabort = new MegaRequestPrivate(MegaRequest::TYPE_ABORT_CURRENT_SCHEDULED_COPY);
                    requestabort->setNumber(backuptag);
                    requestabort->performRequest = [this, requestabort]()
                    {
                        return processAbortBackupRequest(requestabort);
                    };

                    int nextTag = client->nextreqtag();
                    requestabort->setTag(nextTag);
                    requestMap[nextTag]=requestabort;
                    fireOnRequestStart(requestabort);

                    e = processAbortBackupRequest(requestabort);
                    if (e)
                    {
                        LOG_err << "Failed to abort backup upon remove request";
                        fireOnRequestFinish(requestabort, make_unique<MegaErrorPrivate>(API_OK));
                    }
                    else
                    {
                        request->setFlag(true);
                        requestQueue.push(request);
                    }
                }
                else
                {
                    MegaScheduledCopyController * todelete = itr->second;
                    backupsMap.erase(backuptag);
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
                    delete todelete;
                }
            }
            else
            {
                e = API_ENOENT;
            }

            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::startTimer(int64_t period, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_TIMER, listener);
    request->setNumber(period);

    request->performRequest = [this, request]()
        {
            int delta = int(request->getNumber());
            TimerWithBackoff *twb = new TimerWithBackoff(client->rng, request->getTag());
            twb->backoff(delta);
            return client->addtimer(twb);
        };

    requestQueue.push(request);
    waiter->notify();
}

#ifdef ENABLE_SYNC
void MegaApiImpl::syncFolder(const char* localFolder, const char* name, MegaHandle megaHandle, SyncConfig::Type type, const char* driveRootIfExternal, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_SYNC, listener);
    request->setNodeHandle(megaHandle);
    request->setFile(localFolder);

    if (name || type == SyncConfig::TYPE_BACKUP)
    {
        request->setName(name);  // for TYPE_BACKUP, if empty, replacement name will be created when sending request
    }
    else if (localFolder)
    {
        request->setName(request->getFile());
    }
    request->setParamType(type);
    request->setLink(driveRootIfExternal);

    request->performRequest = [this, request]()
        {
            SyncConfig::Type type = static_cast<SyncConfig::Type>(request->getParamType());

            const string& localPath = request->getFile() ? request->getFile() : string();
            if (localPath.empty())
            {
                LOG_debug << "Error: empty local path";
                return API_EARGS;
            }

            Node* node = client->nodebyhandle(request->getNodeHandle());
            if (type != SyncConfig::TYPE_BACKUP &&
                (!node || (node->type == FILENODE)))
            {
                LOG_debug << "Node not found for sync add";
                return API_EARGS;
            }

            LocalPath localPathLP(LocalPath::fromAbsolutePath(localPath));
            const char* name = request->getName();
            const string& syncName = name ? name : localPathLP.leafOrParentName();
            const auto& drivePath = request->getLink() ? LocalPath::fromAbsolutePath(request->getLink()) : LocalPath();

            SyncConfig syncConfig(localPathLP,
                syncName, NodeHandle(), "",
                0, drivePath, true, type);


            if (type == SyncConfig::TYPE_BACKUP)
            {
                // for backups, we create the node to sync to, then call addsync()
                client->preparebackup(syncConfig, [this, request](Error e, SyncConfig backupConfig, MegaClient::UndoFunction revertOnError){
                    if (e)
                    {
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    }
                    else
                    {
                        request->setNodeHandle(backupConfig.mRemoteNode.as8byte());
                        addSyncByRequest(request, backupConfig, revertOnError);
                    }
                });
            }
            else
            {
                // for syncs, the node to sync to already exists
                syncConfig.mRemoteNode = NodeHandle().set6byte(request->getNodeHandle());
                syncConfig.mOriginalPathOfRemoteRootNode = node->displaypath();
                addSyncByRequest(request, syncConfig, nullptr);
            }

            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::loadExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_LOAD_EXTERNAL_DRIVE_BACKUPS, listener);
    request->setFile(externalDriveRoot);
    request->setListener(listener);

    request->performRequest = [this, request]()
        {
            const char* externalDrive = request->getFile();
            if (!externalDrive)
            {
                return API_EARGS;
            }
            else
            {
                client->syncs.backupOpenDrive(LocalPath::fromAbsolutePath(externalDrive), [this, request](Error e){
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e), true);
                });
                return API_OK;
            }
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::closeExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CLOSE_EXTERNAL_DRIVE_BACKUPS, listener);
    request->setFile(externalDriveRoot);
    request->setListener(listener);

    request->performRequest = [this, request]()
        {
            const char* externalDrive = request->getFile();
            if (!externalDrive)
            {
                return API_EARGS;
            }
            else
            {
                client->syncs.backupCloseDrive(LocalPath::fromAbsolutePath(externalDrive), [this, request](Error e){
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e), true);
                });
                return API_OK;
            }
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::importSyncConfigs(const char* configs, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_IMPORT_SYNC_CONFIGS, listener);
    request->setText(configs);

    request->performRequest = [this, request]()
        {
            if (const auto* configs = request->getText())
            {
                auto completion =
                  [request, this](error result)
                  {
                      auto error = make_unique<MegaErrorPrivate>(result);
                      fireOnRequestFinish(request, std::move(error));
                  };

                client->importSyncConfigs(configs, std::move(completion));
                return API_OK;
            }

            return API_EARGS;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::copySyncDataToCache(const char* localFolder, const char* name, MegaHandle megaHandle, const char* remotePath,
    long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_COPY_SYNC_CONFIG, listener);

    request->setNodeHandle(megaHandle);
    if (localFolder)
    {
        request->setFile(localFolder);
    }

    if (name)
    {
        request->setName(name);
    }
    else if (localFolder)
    {
        request->setName(request->getFile());
    }

    request->setLink(remotePath);
    request->setFlag(enabled);
    request->setNumDetails(temporaryDisabled);
    request->setNumber(localfp);

    request->performRequest = [this, request]()
        {
            const char *localPath = request->getFile();
            if(!localPath)
            {
                return API_EARGS;
            }

            const char *name = request->getName();
            const char *remotePath = request->getLink();

            using CType = CacheableStatus::Type;
            bool overStorage = client->mCachedStatus.lookup(CType::STATUS_STORAGE, MegaApi::STORAGE_STATE_UNKNOWN) >= MegaApi::STORAGE_STATE_RED;
            bool businessExpired = client->mCachedStatus.lookup(CType::STATUS_BUSINESS, BIZ_STATUS_UNKNOWN) == BIZ_STATUS_EXPIRED;
            bool blocked = client->mCachedStatus.lookup(CType::STATUS_BLOCKED, 0) == 1;

            auto syncError = NO_SYNC_ERROR;
            // the order is important here: a user needs to resolve blocked in order to resolve storage
            if (overStorage)
            {
                syncError = STORAGE_OVERQUOTA;
            }
            else if (businessExpired)
            {
                syncError = ACCOUNT_EXPIRED;
            }
            else if (blocked)
            {
                syncError = ACCOUNT_BLOCKED;
            }

            bool enabled = request->getFlag();
            bool temporaryDisabled = request->getNumDetails();

            if (!enabled && temporaryDisabled)
            {
                if (!syncError)
                {
                    syncError = UNKNOWN_TEMPORARY_ERROR;
                }
            }

            const auto& drivePath = request->getLink() ? LocalPath::fromAbsolutePath(request->getLink()) : LocalPath();

            SyncConfig syncConfig(LocalPath::fromAbsolutePath(localPath),
                                  name, NodeHandle().set6byte(request->getNodeHandle()), remotePath ? remotePath : "",
                                  static_cast<fsfp_t>(request->getNumber()),
                                  drivePath, enabled);

            if (temporaryDisabled)
            {
                syncConfig.mError = syncError;
            }

            client->ensureSyncUserAttributes([this, request, syncConfig](Error e){

                if (e != API_OK)
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    return;
                }

                client->copySyncConfig(syncConfig, [this, request](handle backupId, error e)
                {
                    if (e == API_OK)
                    {
                        request->setParentHandle(backupId);
                    }

                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                });
            });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::copyCachedStatus(int storageStatus, int blockStatus, int businessStatus, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_COPY_CACHED_STATUS, listener);

    if (blockStatus < 0) blockStatus = 999;
    if (storageStatus < 0) storageStatus = 999;
    if (businessStatus < 0) businessStatus = 999;
    request->setNumber(storageStatus + 1000 * blockStatus + 1000000 * businessStatus);

    request->performRequest = [this, request]()
        {
            auto number = request->getNumber();
            int businessStatusValue = static_cast<int>(number / 1000000);
            number = number % 1000000;
            int blockedStatusValue =  static_cast<int>(number / 1000);
            int storageStatusValue = static_cast<int>(number % 1000);

            using CType = CacheableStatus::Type;
            auto loadAndPersist = [this](CType type, int value) -> error
            {
                if (value == 999)
                {
                    LOG_verbose << "Ignoring not valid status in migration: " << CacheableStatus::typeToStr(type) << " = " << value;
                    return API_OK; //received invalid value: not to be used
                }

                if (int64_t oldValue = client->mCachedStatus.lookup(type, 999) != 999)
                {
                    LOG_verbose << "Ignoring already present status in migration: " << CacheableStatus::typeToStr(type) << " = " << value
                                << " existing = " << oldValue;
                    return API_OK;
                }

                client->mCachedStatus.loadCachedStatus(type, value);
                return API_OK;
            };

            error e = API_OK;
            auto subE = loadAndPersist(CType::STATUS_STORAGE, storageStatusValue);
            if (!e)
            {
                e = subE;
            }
            subE = loadAndPersist(CType::STATUS_BUSINESS, businessStatusValue);
            if (!e)
            {
                e = subE;
            }
            subE = loadAndPersist(CType::STATUS_BLOCKED, blockedStatusValue);
            if (!e)
            {
                e = subE;
            }

            if (!e)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeSyncById(handle backupId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SYNC, listener);
    request->setParentHandle(backupId);
    request->setFlag(true);

    request->performRequest = [this, request]()
        {
            auto backupId = request->getParentHandle();
            if (backupId == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            SyncConfig c;
            if (!client->syncs.syncConfigByBackupId(backupId, c))
            {
                LOG_err << "Backup id not found: " << Base64Str<MegaClient::BACKUPHANDLE>(backupId);
                return API_ENOENT;
            }

            request->setFile(c.mLocalPath.toPath(false).c_str());

            std::function<void(Error)> completion = [request, this](Error e) {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(error(e)));
            };

            client->syncs.deregisterThenRemoveSync(backupId, completion, false);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}
#endif  // ENABLE_SYNC

void MegaApiImpl::reportEvent(const char* details, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REPORT_EVENT, listener);
    request->setText(details);

    request->performRequest = [this, request]()
        {
            const char *details = request->getText();
            if(!details)
            {
                return API_EARGS;
            }

            string event = "A"; //Application event
            int size = int(strlen(details));
            char *base64details = new char[size * 4 / 3 + 4];
            Base64::btoa((byte *)details, size, base64details);
            client->reportevent(event.c_str(), base64details);
            delete [] base64details;
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_enumeratequotaitems(MegaRequestPrivate* request)
{
            if ((request->getType() == MegaRequest::TYPE_GET_PAYMENT_ID)
                && (request->getParamType() < mega::MegaApi::AFFILIATE_TYPE_INVALID
                    || request->getParamType() > mega::MegaApi::AFFILIATE_TYPE_CONTACT))
            {
                return API_EARGS;
            }

            int method = int(request->getNumber());
            if(method != MegaApi::PAYMENT_METHOD_BALANCE && method != MegaApi::PAYMENT_METHOD_CREDIT_CARD)
            {
                return API_EARGS;
            }

            client->purchase_enumeratequotaitems();
            return API_OK;
}

void MegaApiImpl::submitPurchaseReceipt(int gateway, const char* receipt, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT, listener);
    request->setNumber(gateway);
    request->setText(receipt);
    request->setNodeHandle(lastPublicHandle);
    request->setParamType(lastPublicHandleType);
    request->setTransferredBytes(lastAccessTimestamp);

    request->performRequest = [this, request]()
        {
            const char* receipt = request->getText();
            int type = int(request->getNumber());
            handle lph = request->getNodeHandle();
            int phtype = request->getParamType();
            int64_t ts = request->getTransferredBytes();

            if (request->getParamType() < mega::MegaApi::AFFILIATE_TYPE_INVALID
                || request->getParamType() > mega::MegaApi::AFFILIATE_TYPE_CONTACT)
            {
                return API_EARGS;
            }

            if(!receipt || (type != MegaApi::PAYMENT_METHOD_GOOGLE_WALLET
                            && type != MegaApi::PAYMENT_METHOD_ITUNES
                            && type != MegaApi::PAYMENT_METHOD_WINDOWS_STORE && type != MegaApi::PAYMENT_METHOD_HUAWEI_WALLET))
            {
                return API_EARGS;
            }

            if(type == MegaApi::PAYMENT_METHOD_ITUNES && client->loggedin() != FULLACCOUNT)
            {
                return API_EACCESS;
            }

            string base64receipt;
            if (type == MegaApi::PAYMENT_METHOD_GOOGLE_WALLET
                    || type == MegaApi::PAYMENT_METHOD_WINDOWS_STORE || type == MegaApi::PAYMENT_METHOD_HUAWEI_WALLET)
            {
                int len = int(strlen(receipt));
                base64receipt.resize(len * 4 / 3 + 4);
                base64receipt.resize(Base64::btoa((byte *)receipt, len, (char *)base64receipt.data()));
            }
            else // MegaApi::PAYMENT_METHOD_ITUNES
            {
                base64receipt = receipt;
            }

            client->submitpurchasereceipt(type, base64receipt.c_str(), lph, phtype, ts);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::creditCardStore(const char* address1, const char* address2, const char* city,
    const char* province, const char* country, const char* postalcode,
    const char* firstname, const char* lastname, const char* creditcard,
    const char* expire_month, const char* expire_year, const char* cv2,
    MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_STORE, listener);
    string email;

    {
        SdkMutexGuard g(sdkMutex);
        User* u = client->finduser(client->me);
        if (u)
        {
            email = u->email;
        }
    }

    if (email.size())
    {
        string saddress1, saddress2, scity, sprovince, scountry, spostalcode;
        string sfirstname, slastname, screditcard, sexpire_month, sexpire_year, scv2;

        if (address1)
        {
            saddress1 = address1;
        }

        if (address2)
        {
            saddress2 = address2;
        }

        if (city)
        {
            scity = city;
        }

        if (province)
        {
            sprovince = province;
        }

        if (country)
        {
            scountry = country;
        }

        if (postalcode)
        {
            spostalcode = postalcode;
        }

        if (firstname)
        {
            sfirstname = firstname;
        }

        if (lastname)
        {
            slastname = lastname;
        }

        if (creditcard)
        {
            screditcard = creditcard;
            screditcard.erase(std::remove_if(screditcard.begin(), screditcard.end(), char_is_not_digit), screditcard.end());
        }

        if (expire_month)
        {
            sexpire_month = expire_month;
        }

        if (expire_year)
        {
            sexpire_year = expire_year;
        }

        if (cv2)
        {
            scv2 = cv2;
        }

        int tam = int(256 + sfirstname.size() + slastname.size() + screditcard.size()
            + sexpire_month.size() + sexpire_year.size() + scv2.size() + saddress1.size()
            + saddress2.size() + scity.size() + sprovince.size() + spostalcode.size()
            + scountry.size() + email.size());

        char* ccplain = new char[tam];
        snprintf(ccplain, tam, "{\"first_name\":\"%s\",\"last_name\":\"%s\","
            "\"card_number\":\"%s\","
            "\"expiry_date_month\":\"%s\",\"expiry_date_year\":\"%s\","
            "\"cv2\":\"%s\",\"address1\":\"%s\","
            "\"address2\":\"%s\",\"city\":\"%s\","
            "\"province\":\"%s\",\"postal_code\":\"%s\","
            "\"country_code\":\"%s\",\"email_address\":\"%s\"}", sfirstname.c_str(), slastname.c_str(),
            screditcard.c_str(), sexpire_month.c_str(), sexpire_year.c_str(), scv2.c_str(), saddress1.c_str(),
            saddress2.c_str(), scity.c_str(), sprovince.c_str(), spostalcode.c_str(), scountry.c_str(), email.c_str());

        request->setText((const char*)ccplain);
        delete[] ccplain;
    }

    request->performRequest = [this, request]()
        {
            const char* ccplain = request->getText();
            return client->creditcardstore(ccplain);
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::creditCardQuerySubscriptions(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS, listener);

    request->performRequest = [this, request]()
        {
            client->creditcardquerysubscriptions();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::creditCardCancelSubscriptions(const char* reason, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS, listener);
    request->setText(reason);

    request->performRequest = [this, request]()
        {
            const char* reason = request->getText();
            client->creditcardcancelsubscriptions(reason);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPaymentMethods(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PAYMENT_METHODS, listener);

    request->performRequest = [this, request]()
        {
            client->getpaymentmethods();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::submitFeedback(int rating, const char* comment, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUBMIT_FEEDBACK, listener);
    request->setText(comment);
    request->setNumber(rating);

    request->performRequest = [this, request]()
        {
            int rating = int(request->getNumber());
            const char *message = request->getText();

            if(rating < 1 || rating > 5)
            {
                return API_EARGS;
            }

            if(!message)
            {
                message = "";
            }

            int size = int(strlen(message));
            char *base64message = new char[size * 4 / 3 + 4];
            Base64::btoa((byte *)message, size, base64message);

            char base64uhandle[12];
            Base64::btoa((const byte*)&client->me, MegaClient::USERHANDLE, base64uhandle);

            string feedback;
            feedback.resize(128 + strlen(base64message));

            snprintf((char *)feedback.data(), feedback.size(), "{\\\"r\\\":\\\"%d\\\",\\\"m\\\":\\\"%s\\\",\\\"u\\\":\\\"%s\\\"}", rating, base64message, base64uhandle);
            client->userfeedbackstore(feedback.c_str());
            delete [] base64message;
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::sendEvent(int eventType, const char* message, bool addJourneyId, const char* viewId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_EVENT, listener);
    request->setNumber(eventType);
    request->setText(message);
    request->setFlag(addJourneyId);
    request->setSessionKey(viewId);

    request->performRequest = [this, request]()
        {
            int number = int(request->getNumber());
            const char *text = request->getText();

            if(number < 98900 || (number >= 99150 && (number < 99200 || number >= 99600)) || !text)
            {
                return API_EARGS;
            }

            const char* viewId = request->getSessionKey();
            bool addJourneyId = request->getFlag();
            client->sendevent(number, text, viewId, addJourneyId);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::createSupportTicket(const char* message, int type, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUPPORT_TICKET, listener);
    request->setParamType(type);
    request->setText(message);

    request->performRequest = [this, request]()
        {
            int type = request->getParamType();
            const char *message = request->getText();

            if ((type < 0 || type > 10) || !message)
            {
                return API_EARGS;
            }

            client->supportticket(message, type);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getUserData(MegaRequestPrivate* request)
{
            const char* email = request->getEmail();
            if (request->getFlag() && !email)
            {
                return API_EARGS;
            }

            if(!request->getFlag())
            {
                client->getuserdata(client->reqtag);
            }
            else
            {
                client->getpubkey(email);
            }

            return API_OK;
}

void MegaApiImpl::sendDevCommand(const char* command, const char* email, long long quota, int businessStatus, int userStatus, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_DEV_COMMAND, listener);
    request->setName(command);
    request->setEmail(email);
    request->setTotalBytes(quota);
    request->setAccess(businessStatus);
    request->setNumDetails(userStatus);

    request->performRequest = [this, request]()
        {
            const char *command = request->getName();
            if (!command)
            {
                return API_EARGS;
            }

    #ifdef DEBUG
            const char* email = request->getEmail();
            long long q = request->getTotalBytes();
            int bs = request->getAccess();
            int us = request->getNumDetails();

            bool isOdqSubcmd = !strcmp(command, "aodq");
            bool isTqSubcmd = !strcmp(command, "tq");
            bool isBsSubcmd = !strcmp(command, "bs");
            bool isUsSubcmd = !strcmp(command, "us");
            bool isFrSubcmd = !strcmp(command, "fr");   // force reload via -6 on sc channel

            if (!isOdqSubcmd && !isTqSubcmd && !isBsSubcmd && !isUsSubcmd && !isFrSubcmd)
            {
                return API_EARGS;
            }

            if (isTqSubcmd)
            {
                if (q < 0)
                {
                    return API_EARGS;
                }
            }
            else if (isBsSubcmd)
            {
                if (bs < -1 || bs > 2)
                {
                    return API_EARGS;
                }
            }
            else if (isUsSubcmd)
            {
                if (us == 1 || us < 0 || us > 9)
                {
                    return API_EARGS;
                }
            }
            client->senddevcommand(command, email, q, bs, us);
            return API_OK;
    #else
            return API_EACCESS;
    #endif
        };


    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::killSession(MegaHandle sessionHandle, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_KILL_SESSION, listener);
    request->setNodeHandle(sessionHandle);

    request->performRequest = [this, request]()
        {
            MegaHandle handle = request->getNodeHandle();
            if (handle == INVALID_HANDLE)
            {
                client->killallsessions();
            }
            else
            {
                client->killsession(handle);
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getSessionTransferURL(const char* path, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SESSION_TRANSFER_URL, listener);
    request->setText(path);

    request->performRequest = [this, request]()
        {
            error e = client->copysession();

            if (e == API_ENOENT)    // no session to copy because not logged in
            {
                string url = MegaClient::MEGAURL + "/#";
                auto path = request->getText();
                if (path) url.append(path);
                request->setLink(url.c_str());

                e = API_OK;
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                return e;
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resendVerificationEmail(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL, listener);

    request->performRequest = [this, request]()
        {
            client->resendverificationemail();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::resetSmsVerifiedPhoneNumber(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER, listener);

    request->performRequest = [this, request]()
        {
            client->resetSmsVerifiedPhoneNumber();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::cleanRubbishBin(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CLEAN_RUBBISH_BIN, listener);

    request->performRequest = [this, request]()
        {
            client->cleanrubbishbin();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::useHttpsOnly(bool usehttps, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_USE_HTTPS_ONLY, listener);
    request->setFlag(usehttps);

    request->performTransferRequest = [this, request](TransferDbCommitter& committer)
        {
            bool usehttps = request->getFlag();
            if (client->usehttps != usehttps)
            {
                client->usehttps = usehttps;
                for (int d = GET; d == GET || d == PUT; d += PUT - GET)
                {
                    for (auto it = client->multi_transfers[d].begin(); it != client->multi_transfers[d].end(); )
                    {
                        Transfer *t = it->second;
                        it++; // in case the failed() call deletes the transfer (which removes it from the list)
                        if (t->slot)
                        {
                            t->failed(API_EAGAIN, committer);
                        }
                    }
                }
            }
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setProxySettings(MegaProxy* proxySettings, MegaRequestListener* listener)
{
    Proxy* localProxySettings = new Proxy();
    localProxySettings->setProxyType(proxySettings->getProxyType());

    string url;
    if (proxySettings->getProxyURL())
        url = proxySettings->getProxyURL();

    string localurl;

#if defined(_WIN32) && defined(USE_CURL)
    localurl = url;
#else
    LocalPath::path2local(&url, &localurl);
#endif

    localProxySettings->setProxyURL(&localurl);

    if (proxySettings->credentialsNeeded())
    {
        string username;
        if (proxySettings->getUsername())
            username = proxySettings->getUsername();

        string localusername;

#if defined(_WIN32) && defined(USE_CURL)
        localusername = username;
#else
        LocalPath::path2local(&username, &localusername);
#endif

        string password;
        if (proxySettings->getPassword())
            password = proxySettings->getPassword();

        string localpassword;

#if defined(_WIN32) && defined(USE_CURL)
        localpassword = password;
#else
        LocalPath::path2local(&password, &localpassword);
#endif

        localProxySettings->setCredentials(&localusername, &localpassword);
    }

    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PROXY, listener);
    request->setProxy(localProxySettings);

    request->performRequest = [this, request]()
        {
            Proxy *proxy = request->getProxy();
            httpio->setproxy(proxy);
            delete proxy;
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getLastAvailableVersion(const char* appKey, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_APP_VERSION, listener);
    request->setText(appKey);

    request->performRequest = [this, request]()
        {
            const char *appKey = request->getText();
            if (!appKey)
            {
                appKey = this->appKey.c_str();
            }
            client->getlastversion(appKey);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getLocalSSLCertificate(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_LOCAL_SSL_CERT, listener);

    request->performRequest = [this, request]()
        {
            client->getlocalsslcertificate();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::queryDNS(const char* hostname, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_DNS, listener);
    request->setName(hostname);

    request->performRequest = [this, request]()
        {
            const char *hostname = request->getName();
            if (!hostname)
            {
                return API_EARGS;
            }

            client->dnsrequest(hostname);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::downloadFile(const char* url, const char* dstpath, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DOWNLOAD_FILE, listener);
    request->setLink(url);
    request->setFile(dstpath);

    request->performRequest = [this, request]()
        {
            const char *url = request->getLink();
            const char *file = request->getFile();
            if (!url || !file)
            {
                return API_EARGS;
            }

            client->httprequest(url, METHOD_GET, true);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

#ifdef ENABLE_CHAT
void MegaApiImpl::createChat(bool group, bool publicchat, MegaTextChatPeerList* peers, const MegaStringMap* userKeyMap, const char* title, bool meetingRoom, int chatOptions, const MegaScheduledMeeting* scheduledMeeting, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_CREATE, listener);
    request->setFlag(group);
    request->setAccess(publicchat ? 1 : 0);
    request->setMegaTextChatPeerList(peers);
    request->setText(title);
    request->setMegaStringMap(userKeyMap);
    request->setNumber(meetingRoom);
    request->setParamType(chatOptions);
    if (scheduledMeeting)
    {
        std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
        l->insert(scheduledMeeting->copy());
        request->setMegaScheduledMeetingList(l.get());
    }

    request->performRequest = [this, request]()
        {
            MegaTextChatPeerList *chatPeers = request->getMegaTextChatPeerList();
            bool group = request->getFlag();
            const char *title = request->getText();
            bool publicchat = (request->getAccess() == 1);
            MegaStringMap *userKeyMap = request->getMegaStringMap();

            if (!chatPeers) // empty groupchat
            {
                MegaTextChatPeerListPrivate tmp = MegaTextChatPeerListPrivate();
                request->setMegaTextChatPeerList(&tmp);
                chatPeers = request->getMegaTextChatPeerList();
            }

            int numPeers = chatPeers->size();
            const string_map *uhkeymap = NULL;
            if(publicchat)
            {
                if (!group || !userKeyMap
                        || (userKeyMap->size() != numPeers + 1))    // includes our own key
                {
                    return API_EARGS;
                }
                uhkeymap = ((MegaStringMapPrivate*)userKeyMap)->getMap();
            }
            else
            {
                if (!group && numPeers != 1)
                {
                    return API_EACCESS;
                }
            }

            bool meetingRoom = static_cast<bool>(request->getNumber());
            if (!group && request->getMegaStringList())
            {
                return API_EARGS;
            }

            MegaScheduledMeetingPrivate* schedMeeting = nullptr;
            if (request->getMegaScheduledMeetingList() && request->getMegaScheduledMeetingList()->size() == 1)
            {
                schedMeeting = static_cast<MegaScheduledMeetingPrivate*>(request->getMegaScheduledMeetingList()->at(0));
            }

            const userpriv_vector *userpriv = ((MegaTextChatPeerListPrivate*)chatPeers)->getList();
            // if 1:1 chat, peer is enforced to be moderator too
            if (!group && userpriv->at(0).second != PRIV_MODERATOR)
            {
                ((MegaTextChatPeerListPrivate*)chatPeers)->setPeerPrivilege(userpriv->at(0).first, PRIV_MODERATOR);
            }

            client->createChat(group, publicchat, userpriv, uhkeymap, title, meetingRoom, request->getParamType() /*chat options value*/, schedMeeting ? schedMeeting->scheduledMeeting() : nullptr);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setChatOption(MegaHandle chatid, int option, bool enabled, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_CHAT_OPTIONS, listener);
    request->setNodeHandle(chatid);
    request->setAccess(option);
    request->setFlag(enabled);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            if (chatid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            TextChat* chat = it->second;
            if (!chat->group)
            {
                return API_EARGS;
            }

            client->reqs.add(new CommandSetChatOptions(client, chatid, request->getAccess() /*option*/, request->getFlag() /*enabled*/,
            [request, this] (Error e)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::inviteToChat(MegaHandle chatid, MegaHandle uh, int privilege, bool openMode, const char* unifiedKey, const char* title, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_INVITE, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(uh);
    request->setAccess(privilege);
    request->setText(title);
    request->setFlag(openMode);
    request->setSessionKey(unifiedKey);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle uh = request->getParentHandle();
            int access = request->getAccess();
            const char *title = request->getText();
            bool publicMode = request->getFlag();
            const char *unifiedKey = request->getSessionKey();

            if (publicMode && !unifiedKey)
            {
                return API_EINCOMPLETE;
            }

            if (chatid == INVALID_HANDLE || uh == INVALID_HANDLE)
            {
                return API_ENOENT;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            TextChat *chat = it->second;
            if (chat->publicchat != publicMode)
            {
                return API_EACCESS;
            }

            // new participants of private chats require the title to be encrypted to them
            if (!chat->publicchat && (!chat->title.empty() && (!title || title[0] == '\0')))
            {
                return API_EINCOMPLETE;
            }

            ChatOptions chatOptions(static_cast<ChatOptions_t>(chat->chatOptions));
            if (!chat->group
                    || chat->priv < PRIV_STANDARD
                    || (chat->priv != PRIV_MODERATOR && !chatOptions.openInvite()))
            {
                // only allowed moderators or participants with standard permissions just if openInvite is enabled
                return API_EACCESS;
            }

            client->inviteToChat(chatid, uh, access, unifiedKey, title);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeFromChat(MegaHandle chatid, MegaHandle uh, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_REMOVE, listener);
    request->setNodeHandle(chatid);
    if (uh != INVALID_HANDLE)   // if not provided, it removes oneself from the chat
    {
        request->setParentHandle(uh);
    }

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle uh = request->getParentHandle();

            if (chatid == INVALID_HANDLE)
            {
                return API_ENOENT;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;

            // user is optional. If not provided, command apply to own user
            if (uh != INVALID_HANDLE)
            {
                if (!chat->group || (uh != client->me && chat->priv != PRIV_MODERATOR))
                {
                    return API_EACCESS;
                }
                client->removeFromChat(chatid, uh);
            }
            else
            {
                request->setParentHandle(client->me);
                client->removeFromChat(chatid, client->me);
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getUrlChat(MegaHandle chatid, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_URL, listener);
    request->setNodeHandle(chatid);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            if (chatid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            client->getUrlChat(chatid);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::grantAccessInChat(MegaHandle chatid, MegaNode* n, MegaHandle uh, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_GRANT_ACCESS, listener);
    request->setParentHandle(chatid);
    request->setNodeHandle(n->getHandle());

    char uid[12];
    Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid);
    uid[11] = 0;

    request->setEmail(uid);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getParentHandle();
            handle h = request->getNodeHandle();
            const char *uid = request->getEmail();

            if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || !uid)
            {
                return API_ENOENT;
            }

            client->grantAccessInChat(chatid, h, uid);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeAccessInChat(MegaHandle chatid, MegaNode* n, MegaHandle uh, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_REMOVE_ACCESS, listener);
    request->setParentHandle(chatid);
    request->setNodeHandle(n->getHandle());

    char uid[12];
    Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid);
    uid[11] = 0;

    request->setEmail(uid);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getParentHandle();
            handle h = request->getNodeHandle();
            const char *uid = request->getEmail();

            if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || !uid)
            {
                return API_ENOENT;
            }

            client->removeAccessInChat(chatid, h, uid);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::updateChatPermissions(MegaHandle chatid, MegaHandle uh, int privilege, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(uh);
    request->setAccess(privilege);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle uh = request->getParentHandle();
            int access = request->getAccess();

            if (chatid == INVALID_HANDLE || uh == INVALID_HANDLE)
            {
                return API_ENOENT;
            }
            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (!chat->group || chat->priv != PRIV_MODERATOR)
            {
                return API_EACCESS;
            }

            client->updateChatPermissions(chatid, uh, access);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::truncateChat(MegaHandle chatid, MegaHandle messageid, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_TRUNCATE, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(messageid);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            handle messageid = request->getParentHandle();
            if (chatid == INVALID_HANDLE || messageid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (chat->priv != PRIV_MODERATOR)
            {
                return API_EACCESS;
            }

            client->truncateChat(chatid, messageid);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setChatTitle(MegaHandle chatid, const char* title, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_SET_TITLE, listener);
    request->setNodeHandle(chatid);
    request->setText(title);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            const char *title = request->getText();
            if (chatid == INVALID_HANDLE || title == NULL)
            {
                return API_EARGS;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (!chat->group || chat->priv != PRIV_MODERATOR)
            {
                return API_EACCESS;
            }

            client->setChatTitle(chatid, title);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getChatPresenceURL(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_PRESENCE_URL, listener);

    request->performRequest = [this, request]()
        {
            client->getChatPresenceUrl();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::registerPushNotification(int deviceType, const char* token, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION, listener);
    request->setNumber(deviceType);
    request->setText(token);

    request->performRequest = [this, request]()
        {
            int deviceType = int(request->getNumber());
            const char* token = request->getText();

            if ((deviceType != MegaApi::PUSH_NOTIFICATION_ANDROID &&
                deviceType != MegaApi::PUSH_NOTIFICATION_IOS_VOIP &&
                deviceType != MegaApi::PUSH_NOTIFICATION_IOS_STD &&
                deviceType != MegaApi::PUSH_NOTIFICATION_ANDROID_HUAWEI)
                || token == NULL)
            {
                return API_EARGS;
            }

            client->registerPushNotification(deviceType, token);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::archiveChat(MegaHandle chatid, int archive, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_ARCHIVE, listener);
    request->setNodeHandle(chatid);
    request->setFlag(archive);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            bool archive = request->getFlag();
            if (chatid == INVALID_HANDLE)
            {
                return API_ENOENT;
            }

            client->archiveChat(chatid, archive);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setChatRetentionTime(MegaHandle chatid, unsigned period, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_RETENTION_TIME, listener);
    request->setNodeHandle(chatid);
    request->setTotalBytes(period);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            unsigned period = static_cast <unsigned>(request->getTotalBytes());

            if (chatid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (chat->priv != PRIV_MODERATOR)
            {
                return API_EACCESS;
            }

            client->setchatretentiontime(chatid, period);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_chatStats(MegaRequestPrivate* request)
{
            const char* json = request->getName();
            if (!json)
            {
                return API_EARGS;
            }

            int port = int(request->getNumber());
            if (port < 0 || port > 65535)
            {
                return API_EARGS;
            }

            int type = request->getParamType();
            if (type == 1)
            {
               client->sendchatstats(json, port);
            }
            else if (type == 2)
            {
                handle userid = request->getNodeHandle();
                if (userid == UNDEF)
                {
                    return API_EARGS;
                }

                handle callid = request->getParentHandle();

                client->sendchatlogs(json, userid, callid, port);
            }
            else
            {
                return API_EARGS;
            }
            return API_OK;
}

void MegaApiImpl::requestRichPreview(const char* url, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RICH_LINK, listener);
    request->setLink(url);

    request->performRequest = [this, request]()
        {
            const char *url = request->getLink();
            if (!url)
            {
                return API_EARGS;
            }

            client->richlinkrequest(url);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::chatLinkHandle(MegaHandle chatid, bool del, bool createifmissing, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_LINK_HANDLE, listener);
    request->setNodeHandle(chatid);
    request->setFlag(del);
    request->setAccess(createifmissing ? 1 : 0);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            bool del = request->getFlag();
            bool createifmissing = request->getAccess();
            if (del && createifmissing)
            {
                return API_EARGS;
            }
            if (chatid == INVALID_HANDLE)
            {
                return API_ENOENT;
            }
            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (!chat->group || !chat->publicchat || chat->priv == PRIV_RM
                    || ((del || createifmissing) && chat->priv != PRIV_MODERATOR))
            {
                return API_EACCESS;
            }

            client->chatlink(chatid, del, createifmissing);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getChatLinkURL(MegaHandle publichandle, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_LINK_URL, listener);
    request->setNodeHandle(publichandle);

    request->performRequest = [this, request]()
        {
            MegaHandle publichandle = request->getNodeHandle();
            if (publichandle == INVALID_HANDLE)
            {
                return API_ENOENT;
            }
            client->chatlinkurl(publichandle);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::chatLinkClose(MegaHandle chatid, const char* title, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PRIVATE_MODE, listener);
    request->setNodeHandle(chatid);
    request->setText(title);

    request->performRequest = [this, request]()
        {
            MegaHandle chatid = request->getNodeHandle();
            const char *title = request->getText();
            if (chatid == INVALID_HANDLE)
            {
                return API_ENOENT;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }
            TextChat *chat = it->second;
            if (!chat->publicchat)
            {
                return API_EEXIST;
            }
            if (!chat->group || chat->priv != PRIV_MODERATOR)
            {
                return API_EACCESS;
            }
            if (!chat->title.empty() && (!title || title[0] == '\0'))
            {
                return API_EARGS;
            }

            client->chatlinkclose(chatid, title);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::chatLinkJoin(MegaHandle publichandle, const char* unifiedkey, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_AUTOJOIN_PUBLIC_CHAT, listener);
    request->setNodeHandle(publichandle);
    request->setSessionKey(unifiedkey);

    request->performRequest = [this, request]()
        {
            MegaHandle publichandle = request->getNodeHandle();
            const char *unifiedkey = request->getSessionKey();

            if (publichandle == INVALID_HANDLE)
            {
                return API_ENOENT;
            }

            if (unifiedkey == NULL)
            {
                return API_EINCOMPLETE;
            }
            client->chatlinkjoin(publichandle, unifiedkey);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}
#endif

void MegaApiImpl::whyAmIBlocked(bool logout, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_WHY_AM_I_BLOCKED, listener);
    request->setFlag(logout);

    request->performRequest = [this, request]()
        {
            client->whyamiblocked();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::contactLinkCreate(bool renew, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_CREATE, listener);
    request->setFlag(renew);

    request->performRequest = [this, request]()
        {
            client->contactlinkcreate(request->getFlag());
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::contactLinkQuery(MegaHandle h, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_QUERY, listener);
    request->setNodeHandle(h);

    request->performRequest = [this, request]()
        {
            handle h = request->getNodeHandle();
            if (ISUNDEF(h))
            {
                return API_EARGS;
            }

            client->contactlinkquery(h);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::contactLinkDelete(MegaHandle h, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_DELETE, listener);
    request->setNodeHandle(h);

    request->performRequest = [this, request]()
        {
            handle h = request->getNodeHandle();
            client->contactlinkdelete(h);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::keepMeAlive(int type, bool enable, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_KEEP_ME_ALIVE, listener);
    request->setParamType(type);
    request->setFlag(enable);

    request->performRequest = [this, request]()
        {
            int type = request->getParamType();
            bool enable = request->getFlag();

            if (type != MegaApi::KEEP_ALIVE_CAMERA_UPLOADS)
            {
                return API_EARGS;
            }

            client->keepmealive(type, enable);
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPSA(bool urlSupported, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PSA, listener);
    request->setFlag(urlSupported);

    request->performRequest = [this, request]()
        {
            client->getpsa(request->getFlag());
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getFolderInfo(MegaNode* node, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FOLDER_INFO, listener);
    if (node)
    {
        request->setNodeHandle(node->getHandle());
    }

    request->performRequest = [this, request]()
        {
            MegaHandle h = request->getNodeHandle();
            if (ISUNDEF(h))
            {
                return API_EARGS;
            }

            Node *node = client->nodebyhandle(h);
            if (!node)
            {
                return API_ENOENT;
            }

            if (node->type == FILENODE)
            {
                return API_EARGS;
            }

            NodeCounter nc = node->getCounter();
            std::unique_ptr<MegaFolderInfo> folderInfo = make_unique<MegaFolderInfoPrivate>((int)nc.files, (int)nc.folders, (int)nc.versions, nc.storage, nc.versionStorage);
            request->setMegaFolderInfo(folderInfo.get());

            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getAchievements(MegaRequestPrivate* request)
{
            if (request->getFlag())
            {
                client->getmegaachievements(request->getAchievementsDetails());
            }
            else
            {
                client->getaccountachievements(request->getAchievementsDetails());
            }
            return API_OK;
}

void MegaApiImpl::acknowledgeUserAlerts(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_USERALERT_ACKNOWLEDGE, listener);

    request->performRequest = [this, request]()
        {
            client->acknowledgeuseralerts();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getPublicLinkInformation(const char* megaFolderLink, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUBLIC_LINK_INFORMATION, listener);
    request->setLink(megaFolderLink);

    request->performRequest = [this, request]()
        {
            const char *link = request->getLink();
            if (!link)
            {
                return API_EARGS;
            }

            handle h = UNDEF;
            byte folderkey[FOLDERNODEKEYLENGTH];
            error e = client->parsepubliclink(link, h, folderkey, TypeOfLink::FOLDER);
            if (e == API_OK)
            {
                request->setNodeHandle(h);
                Base64Str<FOLDERNODEKEYLENGTH> folderkeyB64(folderkey);
                request->setPrivateKey(folderkeyB64.chars);
                client->getpubliclinkinfo(h);
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getFileAttributeUploadURL(MegaHandle nodehandle, int64_t fullFileSize, int faType, bool forceSSL, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_FA_UPLOAD_URL, listener);
    request->setNodeHandle(nodehandle);
    request->setNumber(fullFileSize);
    request->setFlag(forceSSL);
    request->setParamType(faType);

    request->performRequest = [this, request]()
        {
            bool getIp = true;
            uint64_t nodeHandle = request->getNodeHandle();
            int intFaType = request->getParamType();
            assert(intFaType >= 0 && (intFaType < (1 << (sizeof(fatype)*8)))); // Value of intFaType <= (2^(fatype_numbits) - 1)
            fatype faType = static_cast<fatype>(intFaType); // if the assert above is true, int should fit fine into a fatype (uint16_t)
            bool forceSSL = request->getFlag();
            long long fullSize = request->getNumber();

            NodeOrUploadHandle nuh(NodeHandle().set6byte(nodeHandle));

            client->reqs.add(new CommandPutFA(std::move(nuh), faType, forceSSL, -1, fullSize, getIp,
            [this, request](Error e, const std::string &url, const std::vector<std::string> &ips)
            {
                assert(e != API_OK || !url.empty());
                if (e == API_OK && !url.empty())
                {
                    request->setName(url.c_str());
                    if (!ips.empty())
                    {
                        request->setLink(ips.at(0).c_str());
                    }
                    if (ips.size() > 1)
                    {
                        request->setText(ips.at(1).c_str());
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_getBackgroundUploadURL(MegaRequestPrivate* request)
{
            //if not using MegaBackgroundMediaUpload, ask for IPs
            bool getIp = !request->getMegaBackgroundMediaUploadPtr();

            client->reqs.add(new CommandGetPutUrl(request->getNumber(), client->putmbpscap, request->getFlag() | client->usehttps, getIp,
                                                  [this, request](Error e, const std::string &url, const std::vector<std::string> &ips)
            {
                assert(e != API_OK || !url.empty());
                if (e == API_OK && !url.empty())
                {
                    if (request->getMegaBackgroundMediaUploadPtr())
                    {
                        MegaBackgroundMediaUploadPrivate* mu = static_cast<MegaBackgroundMediaUploadPrivate*>(request->getMegaBackgroundMediaUploadPtr());
                        mu->url = url;
                    }
                    else
                    {
                        request->setName(url.c_str());
                        if (!ips.empty())
                        {
                            request->setLink(ips.at(0).c_str());
                        }
                        if (ips.size() > 1)
                        {
                            request->setText(ips.at(1).c_str());
                        }
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
}

error MegaApiImpl::performRequest_completeBackgroundUpload(MegaRequestPrivate* request)
{
            MegaBackgroundMediaUploadPrivate* bgMediaUpload = static_cast<MegaBackgroundMediaUploadPrivate*>(request->getMegaBackgroundMediaUploadPtr());
            const char * base64fileKey = request->getPrivateKey();

            if (!base64fileKey && !bgMediaUpload)
            {
                return API_EINCOMPLETE;
            }

            const char* utf8Name = request->getName();
            auto parentHandle = NodeHandle().set6byte(request->getParentHandle());
            const char *uploadToken = request->getSessionKey();
            const char* fingerprintOriginal = request->getPassword();
            const char* fingerprint = request->getNewPassword();

            if (!fingerprint || !utf8Name || !uploadToken || parentHandle.isUndef())
            {
                return API_EINCOMPLETE;
            }

            UploadToken ulToken;
            auto binTokSize = Base64::atob(uploadToken, &ulToken[0], sizeof(ulToken));
            if (binTokSize != 36 || binTokSize != sizeof(ulToken))
            {
                LOG_err << "Invalid upload token: " << uploadToken;
                return API_EARGS;
            }

            byte *theFileKey;
            std::unique_ptr<byte[]> filekey;
            if (bgMediaUpload) //using MegaBackgroundMediaUploadPrivate key
            {
                theFileKey = bgMediaUpload->filekey;
            }
            else //app driven upload (key provided as parameter)
            {
                theFileKey = new byte[FILENODEKEYLENGTH];
                auto binFileKeySize = Base64::atob(base64fileKey, theFileKey, FILENODEKEYLENGTH);
                filekey.reset(theFileKey);

                if (binFileKeySize != FILENODEKEYLENGTH)
                {
                    LOG_err << "Invalid file key";
                    return API_EARGS;
                }
            }

            Node *parentNode = client->nodeByHandle(parentHandle);
            if (!parentNode)
            {
                LOG_err << "Parent node doesn't exist anymore";
                return API_ENOENT;
            }

            std::unique_ptr<char[]> megafingerprint(getMegaFingerprintFromSdkFingerprint(fingerprint));
            if (!megafingerprint)
            {
                LOG_err << "Bad fingerprint";
                return API_EARGS;
            }

            std::function<error(string *)> addFileAttrsFunc;
            std::function<error(AttrMap& attrs)> addNodeAttrsFunc;

            if (bgMediaUpload)
            {
                addFileAttrsFunc = [bgMediaUpload](string *fileattributes)
                {
                    appendFileAttribute(*fileattributes, GfxProc::THUMBNAIL, bgMediaUpload->thumbnailFA);
                    appendFileAttribute(*fileattributes, GfxProc::PREVIEW, bgMediaUpload->previewFA);

    #ifdef USE_MEDIAINFO
                    if (bgMediaUpload->mediaproperties.isPopulated())
                    {
                        if (!fileattributes->empty())
                        {
                            fileattributes->append("/");
                        }
                        fileattributes->append(MediaProperties::encodeMediaPropertiesAttributes(bgMediaUpload->mediaproperties,
                                                                (uint32_t*)(bgMediaUpload->filekey + FILENODEKEYLENGTH / 2)));
                    }
    #endif
                    return API_OK;
                };

                addNodeAttrsFunc = [this, bgMediaUpload](AttrMap& attrs)
                {
                    error e = API_OK;
                    int lat, lon;
                    encodeCoordinates(bgMediaUpload->latitude, bgMediaUpload->longitude, lat, lon);
                    attr_map updates;
                    if (API_OK != (e = updateAttributesMapWithCoordinates(updates, lat, lon, bgMediaUpload->unshareableGPS, client)))
                    {
                        return e;
                    }
                    attrs.applyUpdates(updates);
                    return e;
                };
            }

            vector<NewNode> newnodes(1);
            NewNode* newnode = &newnodes[0];
            error e = client->putnodes_prepareOneFile(newnode, parentNode, utf8Name, ulToken,
                                                theFileKey, megafingerprint.get(), fingerprintOriginal,
                                                std::move(addNodeAttrsFunc),
                                                std::move(addFileAttrsFunc));
            if (e != API_OK)
            {
                return e;
            }

            client->reqs.add(new CommandPutNodes(client, parentHandle, NULL, UseLocalVersioningFlag, move(newnodes), request->getTag(), PUTNODES_APP, nullptr, nullptr, false));
            return e;
}

error MegaApiImpl::performRequest_verifyCredentials(MegaRequestPrivate* request)
{
            handle uh = request->getNodeHandle();
            bool isReset = request->getFlag();
            if (isReset)
            {
                return client->resetCredentials(uh);
            }
            else
            {
                return client->verifyCredentials(uh);
            }
}

void MegaApiImpl::sendSMSVerificationCode(const char* phoneNumber, MegaRequestListener* listener, bool reverifying_whitelisted)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SMS_VERIFICATIONCODE, listener);
    request->setText(phoneNumber);
    request->setFlag(reverifying_whitelisted);

    request->performRequest = [this, request]()
        {
            string phoneNumber = request->getText();
            bool reverifying_whitelisted = request->getFlag();

            return client->smsverificationsend(phoneNumber, reverifying_whitelisted);
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::checkSMSVerificationCode(const char* verificationCode, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_SMS_VERIFICATIONCODE, listener);
    request->setText(verificationCode);

    request->performRequest = [this, request]()
        {
            string code = request->getText();
            error e = client->smsverificationcheck(code);
            // FIXME: if the API returned the new state and the verified phone number in
            // the response to the code's verification, the following block can be deleted
            if (e == API_OK)
            {
                client->reqs.add(new CommandGetUserData(client, client->reqtag, nullptr));
            }
            return e;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getRegisteredContacts(const MegaStringMap* contacts, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_REGISTERED_CONTACTS, listener);
    request->setMegaStringMap(contacts);

    request->performRequest = [this, request]()
        {
            const auto contacts = request->getMegaStringMap();
            if (contacts)
            {
                map<const char*, const char*> contactsMap; // non-owning
                const auto contactsKeys = std::unique_ptr<MegaStringList>{ contacts->getKeys() };
                for (int i = 0; i < contactsKeys->size(); ++i)
                {
                    const auto key = contactsKeys->get(i);
                    contactsMap[key] = contacts->get(key);
                }
                client->reqs.add(new CommandGetRegisteredContacts{ client, contactsMap });
            }
            else
            {
                assert(false && "contacts must be valid");
                return API_EARGS;
            }
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getCountryCallingCodes(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES, listener);

    request->performRequest = [this, request]()
        {
            client->reqs.add(new CommandGetCountryCallingCodes{ client });
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getMiscFlags(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_MISC_FLAGS, listener);

    request->performRequest = [this, request]()
        {
            if (client->loggedin())
            {
                // it only returns not-user-related flags (ie. server-sider-rubbish scheduler is missing)
                return API_EACCESS;
            }
            client->getmiscflags();
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getBanners(MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_BANNERS, listener);

    request->performRequest = [this, request]()
        {
            client->reqs.add(new CommandGetBanners(client));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::dismissBanner(int id, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DISMISS_BANNER, listener);
    request->setParamType(id); // banner id
    request->setNumber(m_time(nullptr)); // timestamp

    request->performRequest = [this, request]()
        {
            client->reqs.add(new CommandDismissBanner(client, request->getParamType(), request->getNumber()));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::performRequest_backupPut(MegaRequestPrivate* request)
{
            NodeHandle remoteNode = NodeHandle().set6byte(request->getNodeHandle());
            const char* backupName = request->getName();
            const char* localFolder = request->getFile();
            int backupType = static_cast<int>(request->getTotalBytes());
            BackupType bType = static_cast<BackupType>(backupType);
            handle backupId = request->getParentHandle();

            CommandBackupPut::BackupInfo info;
            info.backupId = backupId;
            info.type = bType;
            info.backupName = backupName ? backupName : "";
            info.nodeHandle = remoteNode;
            info.localFolder = localFolder ? LocalPath::fromAbsolutePath(localFolder) : LocalPath();
            info.deviceId = client->getDeviceidHash();
            info.state = CommandBackupPut::SPState(request->getAccess());
            info.subState = request->getNumDetails();

            bool isNew = request->getFlag();
            if (isNew) // Register a new sync/backup
            {
                if ((backupType != MegaApi::BACKUP_TYPE_CAMERA_UPLOADS
                     && backupType != MegaApi::BACKUP_TYPE_MEDIA_UPLOADS)
                        || !localFolder
                        || !backupName
                        || remoteNode.isUndef()
                        || !ISUNDEF(backupId))  // new backups don't have a backup id yet
                {
                    return API_EARGS;
                }

                client->reqs.add(new CommandBackupPut(client, info, nullptr));
            }
            else // update an existing sync/backup
            {
                if ((backupType != MegaApi::BACKUP_TYPE_CAMERA_UPLOADS
                    && backupType != MegaApi::BACKUP_TYPE_MEDIA_UPLOADS
                    && backupType != MegaApi::BACKUP_TYPE_INVALID)
                    || ISUNDEF(backupId))   // existing backups must have a backup id
                {
                    return API_EARGS;
                }

                client->reqs.add(new CommandBackupPut(client, info, nullptr));
            }
            return API_OK;
}

void MegaApiImpl::removeBackup(MegaHandle backupId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_REMOVE, listener);
    request->setParentHandle(backupId);

    request->performRequest = [this, request]()
        {
            client->reqs.add(new CommandBackupRemove(client, request->getParentHandle(),
                [request, this](Error e) { fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e)); }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::sendBackupHeartbeat(MegaHandle backupId, int status, int progress, int ups, int downs, long long ts, MegaHandle lastNode, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT_HEART_BEAT, listener);
    request->setParentHandle(backupId);
    request->setAccess(status);
    request->setNumDetails(progress);
    request->setParamType(ups);
    request->setTransferTag(downs);
    request->setNumber(ts);
    request->setNodeHandle(lastNode);

    request->performRequest = [this, request]()
        {
            client->reqs.add(new CommandBackupPutHeartBeat(client,
                                                           (MegaHandle)request->getParentHandle(),
                                                           CommandBackupPutHeartBeat::SPHBStatus(request->getAccess()),
                                                           (uint8_t)request->getNumDetails(),
                                                           (uint32_t)request->getParamType(),
                                                           (uint32_t)request->getTransferTag(),
                                                           request->getNumber(),
                                                           request->getNodeHandle(),
                                                           [this, request](Error e) {
                                                               fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                                                           }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

#ifdef ENABLE_CHAT
void MegaApiImpl::startChatCall(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_START_CHAT_CALL, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(schedId);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            if (chatid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            handle schedId = request->getParentHandle();

            client->reqs.add(new CommandMeetingStart(client, chatid, schedId, [request, this](Error e, std::string sfuUrl, handle callid)
            {
                if (e == API_OK)
                {
                    request->setParentHandle(callid);
                    request->setText(sfuUrl.c_str());
                }

                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::joinChatCall(MegaHandle chatid, MegaHandle callid, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_JOIN_CHAT_CALL, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(callid);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle callid = request->getParentHandle();
            if (chatid == INVALID_HANDLE || callid == INVALID_HANDLE)
            {
                return API_EARGS;
            }

            client->reqs.add(new CommandMeetingJoin(client, chatid, callid, [request, this](Error e, std::string sfuUrl)
            {
                if (e == API_OK)
                {
                    request->setText(sfuUrl.c_str());
                }

                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::endChatCall(MegaHandle chatid, MegaHandle callid, int reason, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_END_CHAT_CALL, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(callid);
    request->setAccess(reason);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle callid = request->getParentHandle();
            int reason = request->getAccess();
            if (chatid == INVALID_HANDLE
                    || callid == INVALID_HANDLE
                    || !client->isValidEndCallReason(reason))
            {
                return API_EARGS;
            }

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            TextChat* chat = it->second;
            if (reason == END_CALL_REASON_BY_MODERATOR && !chat->group)
            {
                return API_EACCESS;
            }

            client->reqs.add(new CommandMeetingEnd(client, chatid, callid, reason, [request, this](Error e)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}
#endif

void MegaApiImpl::setMyBackupsFolder(const char* localizedName, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_MY_BACKUPS, listener);
    request->setText(localizedName);

    request->performRequest = [this, request]()
        {
            auto addua_completion = [request, this](Error e)
            {
                if (e == API_OK)
                {
                    const string* buf = client->ownuser()->getattr(ATTR_MY_BACKUPS_FOLDER);
                    handle h = 0;
                    memcpy(&h, buf->data(), MegaClient::NODEHANDLE);

                    request->setNodeHandle(h);
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            };

            return client->setbackupfolder(request->getText(), request->getTag(), addua_completion);
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::getRecentActionsAsync(unsigned days, unsigned maxnodes, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECENT_ACTIONS, listener);
    request->setNumber(days);
    request->setParamType(maxnodes);

    request->performRequest = [this, request]()
        {
           int maxnodes = request->getParamType();
           if (maxnodes <= 0)
           {
               return API_EARGS;
           }

           int days = static_cast<int>(request->getNumber());
           if (days <= 0)
           {
               return API_EARGS;
           }

           m_time_t since = m_time() - days * 86400;
           recentactions_vector v = client->getRecentActions(maxnodes, since);
           std::unique_ptr<MegaRecentActionBucketList> recentActions(new MegaRecentActionBucketListPrivate(v, client));
           request->setRecentActions(std::move(recentActions));
           fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_OK));
           return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

#ifdef ENABLE_CHAT
void MegaApiImpl::createOrUpdateScheduledMeeting(const MegaScheduledMeeting* scheduledMeeting, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING, listener);
    assert(scheduledMeeting);
    if (scheduledMeeting)
    {
        std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
        l->insert(scheduledMeeting->copy());
        request->setMegaScheduledMeetingList(l.get());
    }

    request->performRequest = [this, request]()
        {
            if (!request->getMegaScheduledMeetingList() || request->getMegaScheduledMeetingList()->size() != 1)
            {
                return API_EARGS;
            }

            MegaScheduledMeetingPrivate* schedMeeting = static_cast<MegaScheduledMeetingPrivate*>(request->getMegaScheduledMeetingList()->at(0));
            handle chatid = schedMeeting->chatid();
            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            client->reqs.add(new CommandScheduledMeetingAddOrUpdate(client, schedMeeting->scheduledMeeting(), [chatid, request, this] (Error e, const ScheduledMeeting* sm)
            {
                if (sm)
                {
                    std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
                    l->insert(new MegaScheduledMeetingPrivate(sm));
                    request->setMegaScheduledMeetingList(l.get());
                }

                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DEL_SCHEDULED_MEETING, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(schedId);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle schedId = request->getParentHandle();

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            client->reqs.add(new CommandScheduledMeetingRemove(client, chatid, schedId, [request, this] (Error e)
            {
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fetchScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SCHEDULED_MEETING, listener);
    request->setNodeHandle(chatid);
    request->setParentHandle(schedId);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            handle schedId = request->getParentHandle();

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            client->reqs.add(new CommandScheduledMeetingFetch(client, chatid, schedId, [request, this](Error e, const std::vector<std::unique_ptr<ScheduledMeeting>> *result)
            {
                if (result && !result->empty())
                {
                    std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
                    for (auto const &sm : *result)
                    {
                        l->insert(new MegaScheduledMeetingPrivate(sm.get()));
                    }
                    request->setMegaScheduledMeetingList(l.get());
                }

                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fetchScheduledMeetingEvents(MegaHandle chatid, MegaTimeStamp since, MegaTimeStamp until, unsigned int count, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES, listener);
    request->setNodeHandle(chatid);
    request->setNumber(since);
    request->setTotalBytes(until);
    request->setTransferredBytes(count);

    request->performRequest = [this, request]()
        {
            handle chatid = request->getNodeHandle();
            m_time_t since = request->getNumber();
            m_time_t until = request->getTotalBytes();
            unsigned int count = static_cast<unsigned int>(request->getTransferredBytes());

            textchat_map::iterator it = client->chats.find(chatid);
            if (it == client->chats.end())
            {
                return API_ENOENT;
            }

            client->reqs.add(new CommandScheduledMeetingFetchEvents(client, chatid, since, until, count, true /*byDemand*/, [request, this] (Error e, const std::vector<std::unique_ptr<ScheduledMeeting>>* result)
            {
                if (result && !result->empty())
                {
                    std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance());
                    for (auto const& sm: *result)
                    {
                        l->insert(new MegaScheduledMeetingPrivate(sm.get()));
                    }
                    request->setMegaScheduledMeetingList(l.get());
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }));
            return API_OK;
        };

    requestQueue.push(request);
    waiter->notify();
}
#endif

#ifdef ENABLE_SYNC
void MegaApiImpl::addSyncByRequest(MegaRequestPrivate* request, SyncConfig syncConfig, MegaClient::UndoFunction revertOnError)
{
    client->addsync(move(syncConfig), false,
        [this, request, revertOnError](error e, SyncError se, handle backupId)
        {
            request->setNumDetails(se);

            SyncConfig createdConfig;
            bool found = client->syncs.syncConfigByBackupId(backupId, createdConfig);

            if (!found)
            {
                if (!e)
                {
                    LOG_debug << "Correcting error to API_ENOENT for sync add";
                    e = API_ENOENT;
                }

                if (revertOnError)
                {
                    // deletes backup root node, if we created one but then couldn't finish configuring
                    revertOnError([this, request, e](){
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                    });
                }
                else
                {
                    fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                }
            }
            else
            {
                request->setNumber(createdConfig.mFilesystemFingerprint);
                request->setParentHandle(backupId);

                auto sync = ::mega::make_unique<MegaSyncPrivate>(createdConfig, client);

                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            }
        }, "");
}
#endif

void MegaApiImpl::updateStats()
{
    SdkMutexGuard g(sdkMutex);
    if (pendingDownloads && !client->multi_transfers[GET].size())
    {
        LOG_warn << "Incorrect number of pending downloads: " << pendingDownloads;
        pendingDownloads = 0;
    }

    if (pendingUploads && !client->multi_transfers[PUT].size())
    {
        LOG_warn << "Incorrect number of pending uploads: " << pendingUploads;
        pendingUploads = 0;
    }
}

long long MegaApiImpl::getNumNodes()
{
    return client->totalNodes;
}

long long MegaApiImpl::getTotalDownloadedBytes()
{
    return totalDownloadedBytes;
}

long long MegaApiImpl::getTotalUploadedBytes()
{
    return totalUploadedBytes;
}

long long MegaApiImpl::getTotalDownloadBytes()
{
    return totalDownloadBytes;
}

long long MegaApiImpl::getTotalUploadBytes()
{
    return totalUploadBytes;
}

void MegaApiImpl::update()
{
#ifdef ENABLE_SYNC
    SdkMutexGuard g(sdkMutex);

    LOG_debug << "PendingCS? " << (client->pendingcs != NULL);
    LOG_debug << "PendingFA? " << client->activefa.size() << " active, " << client->queuedfa.size() << " queued";
    LOG_debug << "FLAGS: " << client->syncactivity
              << " " << client->syncdownrequired << " " << client->syncdownretry
              << " " << client->syncfslockretry << " " << client->syncfsopsfailed
              << " " << client->syncnagleretry << " " << client->syncscanfailed
              << " " << client->syncops << " " << client->syncscanstate
              << " " << client->fileAttributesUploading.size() << " " << client->synccreateForVault.size() << " " << client->synccreateGeneral.size()
              << " " << client->fetchingnodes
              << " " << client->xferpaused[0] << " " << client->xferpaused[1]
              << " " << client->multi_transfers[0].size() << " " << client->multi_transfers[1].size()
              << " " << client->syncscanstate << " " << client->statecurrent
              << " " << client->syncadding << " " << client->syncdebrisadding
              << " " << client->umindex.size() << " " << client->uhindex.size();
    LOG_debug << "UL speed: " << httpio->uploadSpeed << "  DL speed: " << httpio->downloadSpeed;
#endif

    waiter->notify();
}

int MegaApiImpl::isWaiting()
{
#ifdef ENABLE_SYNC
    if (client->syncfslockretry || client->syncfsopsfailed)
    {
        LOG_debug << "SDK waiting for a blocked file: " << client->blockedfile;
        return RETRY_LOCAL_LOCK;
    }
#endif

    if (waitingRequest)
    {
        LOG_debug << "SDK waiting for a request. Reason: " << waitingRequest;
    }
    return waitingRequest;
}

int MegaApiImpl::areServersBusy()
{
    return isWaiting();
}

void MegaApiImpl::lockMutex()
{
    sdkMutex.lock();
}

void MegaApiImpl::unlockMutex()
{
    sdkMutex.unlock();
}

bool MegaApiImpl::tryLockMutexFor(long long time)
{
    if (time <= 0)
    {
        return sdkMutex.try_lock();
    }
    else
    {
        return sdkMutex.try_lock_for(std::chrono::milliseconds(time));
    }
}

void MegaApiImpl::setBackup(int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT, listener);
    request->setTotalBytes(backupType);
    request->setNodeHandle(targetNode);
    request->setFile(localFolder);
    request->setName(backupName);
    request->setAccess(state);
    request->setNumDetails(subState);
    request->setFlag(true); // indicates it's a new backup

    request->performRequest = [this, request]()
    {
        return performRequest_backupPut(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::updateBackup(MegaHandle backupId, int backupType, MegaHandle targetNode, const char* localFolder,  const char* backupName, int state, int subState, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT, listener);
    request->setParentHandle(backupId);

    if (backupType != BackupType::INVALID)
    {
        request->setTotalBytes(backupType);
    }

    if (targetNode != UNDEF)
    {
        request->setNodeHandle(targetNode);
    }

    if (localFolder)
    {
        request->setFile(localFolder);
    }

    if (backupName)
    {
        request->setName(backupName);
    }

    if (state >= 0)
    {
        request->setAccess(state);
    }

    if (subState >= 0)
    {
        request->setNumDetails(subState);
    }

    request->performRequest = [this, request]()
    {
        return performRequest_backupPut(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::fetchGoogleAds(int adFlags, MegaStringList *adUnits, MegaHandle publicHandle, MegaRequestListener *listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_GOOGLE_ADS, listener);
    request->setNumber(adFlags);
    request->setMegaStringList(adUnits);
    request->setNodeHandle(publicHandle);
    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::queryGoogleAds(int adFlags, MegaHandle publicHandle, MegaRequestListener *listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_GOOGLE_ADS, listener);
    request->setNumber(adFlags);
    request->setNodeHandle(publicHandle);
    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setCookieSettings(int settings, MegaRequestListener *listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_COOKIE_SETTINGS);
    request->setNumDetails(settings);

    request->performRequest = [this, request]()
    {
        return performRequest_setAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::setCookieSettings_sendPendingRequests(MegaRequestPrivate* request)
{
    auto tmp = std::to_string(request->getNumDetails());
    client->putua(ATTR_COOKIE_SETTINGS, (byte *)tmp.data(), unsigned(tmp.size()));
}

void MegaApiImpl::getCookieSettings(MegaRequestListener *listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener);
    request->setParamType(MegaApi::USER_ATTR_COOKIE_SETTINGS);

    request->performRequest = [this, request]()
    {
        return performRequest_getAttrUser(request);
    };

    requestQueue.push(request);
    waiter->notify();
}

error MegaApiImpl::getCookieSettings_getua_result(byte* data, unsigned len, MegaRequestPrivate* request)
{
    // make a copy to make sure we don't read too much from a non-null terminated string
    unique_ptr<char[]> buff(new char[len + 1]);
    buff[len] = 0;
    char* startptr = buff.get();
    strncpy(startptr, (const char*)data, len);

    // get the value
    char* endptr;
    long value = strtol(startptr, &endptr, 10);

    // validate the value
    error e = API_OK;
    if (endptr == startptr || *endptr != '\0' || value == LONG_MAX || value == LONG_MIN)
    {
        value = -1;
        LOG_err << "Invalid value for Cookie Settings bitmap";
        e = API_EINTERNAL;
    }

    request->setNumDetails(int(value));

    return e;
}

bool MegaApiImpl::cookieBannerEnabled()
{
    return client->mCookieBannerEnabled;
}

bool MegaApiImpl::startDriveMonitor()
{
    SdkMutexGuard g(sdkMutex);
    return client->startDriveMonitor();
}

void MegaApiImpl::stopDriveMonitor()
{
    SdkMutexGuard g(sdkMutex);
    client->stopDriveMonitor();
}

bool MegaApiImpl::driveMonitorEnabled()
{
    SdkMutexGuard g(sdkMutex);
    return client->driveMonitorEnabled();
}

void MegaApiImpl::enableRequestStatusMonitor(bool enable)
{
    SdkMutexGuard g(sdkMutex);
    enable ? client->startRequestStatusMonitor() : client->stopRequestStatusMonitor();
}

bool MegaApiImpl::requestStatusMonitorEnabled()
{
    SdkMutexGuard g(sdkMutex);
    return client->requestStatusMonitorEnabled();
}

#ifdef USE_DRIVE_NOTIFICATIONS
void MegaApiImpl::drive_presence_changed(bool appeared, const LocalPath& driveRoot)
{
    for (auto it = globalListeners.begin(); it != globalListeners.end(); ++it)
    {
        (*it)->onDrivePresenceChanged(api, appeared, driveRoot.platformEncoded().c_str());
    }
}
#endif

//
// Sets and Elements
//

void MegaApiImpl::putSetElements(MegaHandle sid, const MegaHandleList* nodes, const MegaStringList* names, MegaRequestListener* listener)
{
    assert(nodes && nodes->size() && (!names || names->size() == static_cast<int>(nodes->size())));
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET_ELEMENTS, listener);
    request->setTotalBytes(sid);
    request->setMegaHandleList(nodes);
    request->setMegaStringList(names);

    request->performRequest = [this, request]()
    {
        MegaHandleList* nodes = request->getMegaHandleList();
        MegaStringList* names = request->getMegaStringList();
        vector<SetElement> els(nodes->size());
        for (size_t i = 0u; i < els.size(); ++i)
        {
            SetElement& el = els[i];
            el.setSet(request->getTotalBytes());
            el.setNode(nodes->get(static_cast<int>(i)));
            if (names)
            {
                el.setName(names->get(static_cast<int>(i)));
            }
        }

        client->putSetElements(move(els), [this, request](Error e, const vector<const SetElement*>* retEls, const vector<int64_t>* elErrs)
            {
                if (e == API_OK)
                {
                    if (retEls)
                    {
                        request->setMegaSetElementList(::mega::make_unique<MegaSetElementListPrivate>(retEls->data(), static_cast<int>(retEls->size())));
                    }
                    if (elErrs)
                    {
                        request->setMegaIntegerList(::mega::make_unique<MegaIntegerListPrivate>(*elErrs));
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            });

        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::removeSetElements(MegaHandle sid, const MegaHandleList* eids, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET_ELEMENTS, listener);
    request->setTotalBytes(sid);
    request->setMegaHandleList(eids);

    request->performRequest = [this, request]()
    {
        const MegaHandleList* eidsList = request->getMegaHandleList();
        if (!eidsList)
        {
            return API_ENOENT;
        }

        std::vector<handle> eids(eidsList->size());
        for (size_t i = 0u; i < eids.size(); ++i)
        {
            eids[i] = eidsList->get(static_cast<int>(i));
        }

        client->removeSetElements(request->getTotalBytes(), move(eids),
            [this, request](Error e, const vector<int64_t>* elErrs)
            {
                if (e == API_OK && elErrs)
                {
                    request->setMegaIntegerList(::mega::make_unique<MegaIntegerListPrivate>(*elErrs));
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            });

        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

MegaSetList* MegaApiImpl::getSets()
{
    SdkMutexGuard g(sdkMutex);

    MegaSetListPrivate* sList = new MegaSetListPrivate(client->getSets());

    return sList;
}

MegaSet* MegaApiImpl::getSet(MegaHandle sid)
{
    SdkMutexGuard g(sdkMutex);

    const Set* s = client->getSet(sid);
    return s ? (new MegaSetPrivate(*s)) : nullptr;
}

MegaHandle MegaApiImpl::getSetCover(MegaHandle sid)
{
    SdkMutexGuard g(sdkMutex);

    const Set* s = client->getSet(sid);
    return s ? s->cover() : INVALID_HANDLE;
}

bool MegaApiImpl::nodeInRubbishCheck(handle h) const
{
    Node* n = client->nodebyhandle(h);
    bool inRubbish = n && n->firstancestor()->type == RUBBISHNODE;
    return inRubbish;
}

unsigned MegaApiImpl::getSetElementCount(MegaHandle sid, bool includeElementsInRubbishBin)
{
    SdkMutexGuard g(sdkMutex);

    if (includeElementsInRubbishBin)
    {
        return client->getSetElementCount(sid);
    }
    else
    {
        auto els = client->getSetElements(sid);
        auto ret = count_if(begin(*els), end(*els), [this](const pair<const handle, SetElement>& e)
                            { return !nodeInRubbishCheck(e.second.node()); });
        return static_cast<unsigned>(ret);
    }
}

MegaSetElementList* MegaApiImpl::getSetElements(MegaHandle sid, bool includeElementsInRubbishBin)
{
    SdkMutexGuard g(sdkMutex);

    auto* elements = client->getSetElements(sid);
    std::function<bool(handle)> filterOut;
    if (!includeElementsInRubbishBin)
    {
        filterOut = std::bind(&MegaApiImpl::nodeInRubbishCheck, this, std::placeholders::_1);
    }

    MegaSetElementListPrivate* eList = new MegaSetElementListPrivate(elements, filterOut);
    return eList;
}

MegaSetElement* MegaApiImpl::getSetElement(MegaHandle sid, MegaHandle eid)
{
    SdkMutexGuard g(sdkMutex);

    const SetElement* el = client->getSetElement(sid, eid);

    return el ? (new MegaSetElementPrivate(*el)) : nullptr;
}

MegaSetListPrivate::MegaSetListPrivate(const Set *const* sets, int count)
{
    if (sets && count)
    {
        mSets.reserve(count);
        for (int i = 0; i < count; ++i)
        {
            const Set& s = *sets[i];
            add(MegaSetPrivate(s));
        }
    }
}

MegaSetListPrivate::MegaSetListPrivate(const map<handle, Set>& sets)
{
    mSets.reserve(sets.size());
    for (const auto& sp : sets)
    {
        const Set& s = sp.second;
        add(MegaSetPrivate(s));
    }
}

void MegaSetListPrivate::add(MegaSetPrivate&& s)
{
    mSets.emplace_back(move(s));
}


MegaSetElementListPrivate::MegaSetElementListPrivate(const SetElement* const* elements, int count)
{
    if (elements && count)
    {
        mElements.reserve(count);
        for (int i = 0; i < count; ++i)
        {
            const SetElement& el = *elements[i];
            add(MegaSetElementPrivate(el));
        }
    }
}

MegaSetElementListPrivate::MegaSetElementListPrivate(const elementsmap_t* elements, const std::function<bool(handle)>& filterOut)
{
    if (elements)
    {
        mElements.reserve(elements->size());
        for (const auto& e : *elements)
        {
            if (!filterOut || !filterOut(e.second.node()))
            {
                const SetElement& el = e.second;
                add(MegaSetElementPrivate(el));
            }
        }
        mElements.shrink_to_fit();
    }
}

void MegaSetElementListPrivate::add(MegaSetElementPrivate&& el)
{
    mElements.emplace_back(move(el));
}

bool MegaApiImpl::isExportedSet(MegaHandle sid)
{
    SdkMutexGuard g(sdkMutex);

    return client->isExportedSet(sid);
}

void MegaApiImpl::exportSet(MegaHandle sid, bool create, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT_SET, listener);
    request->setNodeHandle(sid);
    request->setFlag(create);
    request->performRequest = [this, request]()
    {
        client->exportSet(request->getNodeHandle(), request->getFlag(), [this, request](Error e)
        {
            if (e == API_OK)
            {
                const bool isExport = request->getFlag();
                const auto sid = request->getNodeHandle();
                const Set* updatedSet = client->getSet(sid);
                if (!updatedSet)
                {
                    LOG_err << "Sets: Set to be updated not found for " << (isExport ? "en" : "dis")
                            << "able export operation. Set id " << toHandle(sid);
                    assert(false);
                }
                if((isExport && !updatedSet->isExported())
                   || (!isExport && updatedSet->isExported()))
                {
                    LOG_err << "Sets: Set " << (isExport ? "en" : "dis") << "able operation with"
                            << " incoherent result state isExported()==" << updatedSet->isExported()
                            << ". Set id " << toHandle(sid);
                    assert(false);
                }

                string url;
                if (isExport)
                {
                    std::tie(e, url) = client->getPublicSetLink(updatedSet->id());
                }
                if (e == API_OK)
                {
                    request->setLink(url.c_str());
                    request->setMegaSet(unique_ptr<MegaSet>(new MegaSetPrivate(*updatedSet)));
                    unique_ptr<MegaSetListPrivate> updatedSetList(new MegaSetListPrivate(&updatedSet, 1));
                    fireOnSetsUpdate(updatedSetList.get());
                }
            }
            fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
        });

        return API_OK;
    };
    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::exportSet(MegaHandle sid, MegaRequestListener* listener)
{
    exportSet(sid, true, listener);
}

void MegaApiImpl::disableExportSet(MegaHandle sid, MegaRequestListener* listener)
{
    exportSet(sid, false, listener);
}

void MegaApiImpl::fetchPublicSet(const char* publicSetLink, MegaRequestListener* listener)
{
    MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SET, listener);
    request->setLink(publicSetLink);
    request->performRequest = [this, request]() -> ErrorCodes
    {
        const auto e = client->fetchPublicSet(
            request->getLink(),
            [this, request](Error e, Set* s, elementsmap_t* els)
            {
                unique_ptr<Set> sp(s);
                unique_ptr<elementsmap_t> elsp(els);

                if (e == API_OK)
                {
                    assert(sp && elsp);
                    if (sp && elsp)
                    {
                        request->setMegaSet(mega::make_unique<MegaSetPrivate>(*sp));
                        request->setMegaSetElementList(mega::make_unique<MegaSetElementListPrivate>(elsp.get()));
                    }
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
            });

        return e;
    };

    requestQueue.push(request);
    waiter->notify();
}

void MegaApiImpl::stopPublicSetPreview()
{
    SdkMutexGuard g(sdkMutex);

    client->stopSetPreview();
}

bool MegaApiImpl::inPublicSetPreview()
{
    SdkMutexGuard g(sdkMutex);

    return client->inPublicSetPreview();
}

MegaSet* MegaApiImpl::getPublicSetInPreview()
{
    SdkMutexGuard g(sdkMutex);

    const auto s = client->getPreviewSet();

    return s ? new MegaSetPrivate(*s) : nullptr;
}

MegaSetElementList* MegaApiImpl::getPublicSetElementsInPreview()
{
    SdkMutexGuard g(sdkMutex);

    const auto els = client->getPreviewSetElements();

    return els ? new MegaSetElementListPrivate(els) : nullptr;
}

void MegaApiImpl::getPreviewElementNode(MegaHandle eid, MegaRequestListener* listener)
{
    MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_EXPORTED_SET_ELEMENT, listener);

    request->performRequest = [eid, this, request]()
    {
        const string paramErr = "Error failed to get MegaNode for Set Element " + toHandle(eid) + ". ";
        if (!client->inPublicSetPreview())
        {
            LOG_err << paramErr << "Public Set preview mode disable";
            return API_EACCESS;
        }

        auto element = client->getPreviewSetElement(eid);
        if (!element)
        {
            LOG_err << paramErr << "Element not found in preview mode Set "
                    << toHandle(client->getPreviewSet()->id());
            return API_EARGS;
        }

        std::array<byte, FILENODEKEYLENGTH> ekey;
        handle enode = element->node();
        memcpy(ekey.data(), element->key().c_str(), ekey.size());
        auto commandCB =
            [ekey, enode, request, this] (const Error &e, m_off_t size, m_time_t ts, m_time_t tm,
            dstime /*timeleft*/, std::string* filename, std::string* fingerprint,
            std::string* fileattrstring, const std::vector<std::string> &/*tempurls*/,
            const std::vector<std::string> &/*ips*/)
            {
                if (!fingerprint) // failed processing the command
                {
                    LOG_err << "Sets: Link check failed: " << e;
                    if (e == API_OK)
                    {
                        fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(API_EINTERNAL));
                        return true;
                    }
                }

                if (e)
                {
                    LOG_err << "Sets: Not available: " << e;
                }
                else
                {
                    auto ekeyStr = string((char*)ekey.data(), ekey.size());
                    unique_ptr<MegaNodePrivate>ret(new MegaNodePrivate(filename->c_str(), FILENODE, size, ts, tm,
                                                                       enode, &ekeyStr, fileattrstring, fingerprint->c_str(),
                                                                       nullptr, INVALID_HANDLE, INVALID_HANDLE, nullptr, nullptr,
                                                                       false /*isPublic*/, true /*isForeign*/));
                    request->setPublicNode(ret.get());
                }
                fireOnRequestFinish(request, make_unique<MegaErrorPrivate>(e));
                return true;
            };

        client->reqs.add(new CommandGetFile(client, (byte*)ekey.data(), ekey.size(), enode,
                                            true /*private*/, nullptr, nullptr, nullptr, false,
                                            commandCB));

        return API_OK;
    };

    requestQueue.push(request);
    waiter->notify();
}

const char* MegaApiImpl::getPublicLinkForExportedSet(MegaHandle sid)
{
    string retStr;
    error e;
    {
        SdkMutexGuard g(sdkMutex);
        std::tie(e, retStr) = client->getPublicSetLink(sid);
    }

    char* link = nullptr;
    if (e == API_OK)
    {
        auto sz = retStr.size() + 1;
        link = new char[sz];
        std::strncpy(link, retStr.c_str(), sz);
        LOG_verbose << "Successfully created public link " << retStr << "for Set " << toHandle(sid);
    }
    else
    {
        LOG_err << "Failing to create a public link for Set " << toHandle(sid) << " with error code "
                << e << "(" << MegaError::getErrorString(e) << ")";
    }

    return link;
}

void TreeProcCopy::allocnodes()
{
    nn.resize(nc);
    allocated = true;
}

// determine node tree size (nn = NULL) or write node tree to new nodes array
void TreeProcCopy::proc(MegaClient* client, Node* n)
{
    if (allocated)
    {
        string attrstring;
        SymmCipher key;
        assert(nc > 0);
        NewNode* t = &nn[--nc];

        // copy node
        t->source = NEW_NODE;
        t->type = n->type;
        t->nodehandle = n->nodehandle;
        t->parenthandle = n->parent ? n->parent->nodehandle : UNDEF;

        // copy key (if file) or generate new key (if folder)
        if (n->type == FILENODE) t->nodekey = n->nodekey();
        else
        {
            byte buf[FOLDERNODEKEYLENGTH];
            client->rng.genblock(buf,sizeof buf);
            t->nodekey.assign((char*)buf,FOLDERNODEKEYLENGTH);
        }

        t->attrstring.reset(new string);
        if(t->nodekey.size())
        {
            key.setkey((const byte*)t->nodekey.data(),n->type);

            AttrMap tattrs;
            tattrs.map = n->attrs.map;
            nameid rrname = AttrMap::string2nameid("rr");
            attr_map::iterator it = tattrs.map.find(rrname);
            if (it != tattrs.map.end())
            {
                LOG_debug << "Removing rr attribute";
                tattrs.map.erase(it);
            }

            tattrs.getjson(&attrstring);
            client->makeattr(&key, t->attrstring, attrstring.c_str());
        }
    }
    else nc++;
}

int TransferQueue::getLastPushedTag() const
{
    return lastPushedTransferTag;
}

TransferQueue::TransferQueue()
{
}

void TransferQueue::push(MegaTransferPrivate *transfer)
{
    std::lock_guard<std::mutex> g(mutex);
    transfers.push_back(transfer);
    transfer->setPlaceInQueue(++lastPushedTransferTag);
}

void TransferQueue::push_front(MegaTransferPrivate *transfer)
{
    std::lock_guard<std::mutex> g(mutex);
    transfers.push_front(transfer);
}

bool TransferQueue::empty()
{
    std::lock_guard<std::mutex> g(mutex);
    return transfers.empty();
}

size_t TransferQueue::size()
{
    std::lock_guard<std::mutex> g(mutex);
    return transfers.size();
}

void TransferQueue::clear()
{
    std::lock_guard<std::mutex> g(mutex);
    return transfers.clear();
}

MegaTransferPrivate *TransferQueue::pop()
{
    std::lock_guard<std::mutex> g(mutex);
    if(transfers.empty())
    {
        return NULL;
    }
    MegaTransferPrivate *transfer = transfers.front();
    transfers.pop_front();
    return transfer;
}

std::vector<MegaTransferPrivate *> TransferQueue::popUpTo(int lastQueuedTransfer, int direction)
{
    std::lock_guard<std::mutex> g(mutex);
    std::vector<MegaTransferPrivate*> toret;
    for (auto it = transfers.begin(); it != transfers.end();)
    {
        MegaTransferPrivate *transfer = *it;
        if (transfer->getPlaceInQueue() > lastQueuedTransfer)
        {
            break;
        }

        if (!transfer->isSyncTransfer() && transfer->getType() == direction)
        {
            toret.push_back(transfer);
            it = transfers.erase(it);
        }
        else
        {
            it++;
        }
    }
    return toret;
}

void TransferQueue::removeWithFolderTag(int folderTag, std::function<void(MegaTransferPrivate *)> callback)
{
    // We need to lock the TransferQueue's mutex or it's not safe to iterate transfers.
    // However this is risky because we are making callbacks with it locked
    // We shouldn't cause a deadlock wih the impl mutex because that one is always locked before calling here.
    // However the callback (including its calls to fireOnXYZ() ) must be careful not to lock any mutex which
    // may have been locked during other MegaApi function calls.
    std::lock_guard<std::mutex> g(mutex);

    for (auto it = transfers.begin(); it != transfers.end();)
    {
        if ((*it)->getFolderTransferTag() == folderTag)
        {
            if (callback)
            {
                callback(*it);
            }
            it = transfers.erase(it);
        }
        else
        {
            it++;
        }
    }
}

void TransferQueue::removeListener(MegaTransferListener *listener)
{
    std::lock_guard<std::mutex> g(mutex);

    std::deque<MegaTransferPrivate *>::iterator it = transfers.begin();
    while(it != transfers.end())
    {
        MegaTransferPrivate *transfer = (*it);
        if(transfer->getListener() == listener)
            transfer->setListener(NULL);
        it++;
    }
}

void TransferQueue::setAllCancelled(CancelToken cancelled, int direction)
{
    std::lock_guard<std::mutex> g(mutex);
    for (auto& t : transfers)
    {
        if (t->getType() == direction
            && !t->isSyncTransfer()
            && !t->isStreamingTransfer())
        {
            t->setCancelToken(cancelled);
        }
    }
}

RequestQueue::RequestQueue()
{
}

void RequestQueue::push(MegaRequestPrivate *request)
{
    std::lock_guard<std::mutex> g(mutex);
    requests.push_back(request);
}

void RequestQueue::push_front(MegaRequestPrivate *request)
{
    std::lock_guard<std::mutex> g(mutex);
    requests.push_front(request);
}

MegaRequestPrivate *RequestQueue::pop()
{
    std::lock_guard<std::mutex> g(mutex);
    if(requests.empty())
    {
        return NULL;
    }
    MegaRequestPrivate *request = requests.front();
    requests.pop_front();
    return request;
}

MegaRequestPrivate *RequestQueue::front()
{
    std::lock_guard<std::mutex> g(mutex);
    if(requests.empty())
    {
        return NULL;
    }
    MegaRequestPrivate *request = requests.front();
    return request;
}

void RequestQueue::removeListener(MegaRequestListener *listener)
{
    std::lock_guard<std::mutex> g(mutex);

    std::deque<MegaRequestPrivate *>::iterator it = requests.begin();
    while(it != requests.end())
    {
        MegaRequestPrivate *request = (*it);
        if(request->getListener()==listener)
            request->setListener(NULL);
        it++;
    }
}

void RequestQueue::removeListener(MegaScheduledCopyListener *listener)
{
    std::lock_guard<std::mutex> g(mutex);

    std::deque<MegaRequestPrivate *>::iterator it = requests.begin();
    while(it != requests.end())
    {
        MegaRequestPrivate *request = (*it);
        if(request->getBackupListener()==listener)
            request->setBackupListener(NULL);
        it++;
    }
}

MegaHashSignatureImpl::MegaHashSignatureImpl(const char *base64Key)
{
    hashSignature = new HashSignature(new Hash());
    asymmCypher = new AsymmCipher();

    string pubks;
    int len = int(strlen(base64Key)/4*3+3);
    pubks.resize(len);
    pubks.resize(Base64::atob(base64Key, (byte *)pubks.data(), len));
    asymmCypher->setkey(AsymmCipher::PUBKEY,(byte*)pubks.data(), int(pubks.size()));
}

MegaHashSignatureImpl::~MegaHashSignatureImpl()
{
    delete hashSignature;
    delete asymmCypher;
}

void MegaHashSignatureImpl::init()
{
    hashSignature->get(asymmCypher, NULL, 0);
}

void MegaHashSignatureImpl::add(const char *data, unsigned size)
{
    hashSignature->add((const byte *)data, size);
}

bool MegaHashSignatureImpl::checkSignature(const char *base64Signature)
{
    char signature[512];
    int l = Base64::atob(base64Signature, (byte *)signature, sizeof(signature));
    if(l != sizeof(signature))
        return false;

    return hashSignature->checksignature(asymmCypher, (const byte *)signature, sizeof(signature));
}

int MegaAccountDetailsPrivate::getProLevel()
{
    return details.pro_level;
}

int64_t MegaAccountDetailsPrivate::getProExpiration()
{
    return details.pro_until;
}

int MegaAccountDetailsPrivate::getSubscriptionStatus()
{
    if(details.subscription_type == 'S')
    {
        return MegaAccountDetails::SUBSCRIPTION_STATUS_VALID;
    }

    if(details.subscription_type == 'R')
    {
        return MegaAccountDetails::SUBSCRIPTION_STATUS_INVALID;
    }

    return MegaAccountDetails::SUBSCRIPTION_STATUS_NONE;
}

int64_t MegaAccountDetailsPrivate::getSubscriptionRenewTime()
{
    return details.subscription_renew;
}

char *MegaAccountDetailsPrivate::getSubscriptionMethod()
{
    return MegaApi::strdup(details.subscription_method.c_str());
}

int MegaAccountDetailsPrivate::getSubscriptionMethodId()
{
    return details.subscription_method_id;
}

char *MegaAccountDetailsPrivate::getSubscriptionCycle()
{
    return MegaApi::strdup(details.subscription_cycle);
}

long long MegaAccountDetailsPrivate::getStorageMax()
{
    return details.storage_max;
}

long long MegaAccountDetailsPrivate::getStorageUsed()
{
    return details.storage_used;
}

long long MegaAccountDetailsPrivate::getVersionStorageUsed()
{
    long long total = 0;

    handlestorage_map::iterator it;
    for (it = details.storage.begin(); it != details.storage.end(); it++)
    {
        total += it->second.version_bytes;
    }

    return total;
}

long long MegaAccountDetailsPrivate::getTransferMax()
{
    return details.transfer_max;
}

long long MegaAccountDetailsPrivate::getTransferOwnUsed()
{
    return details.transfer_own_used;
}

long long MegaAccountDetailsPrivate::getTransferSrvUsed()
{
    return details.transfer_srv_used;
}

long long MegaAccountDetailsPrivate::getTransferUsed()
{
    long long total = details.transfer_srv_used + details.transfer_own_used + getTemporalBandwidth();
    // in case the total exceed the maximum allowance (due to the free IP-based quota)...
    if (details.transfer_max && total > details.transfer_max) //do not limit for free user (no max allowance configured)
    {
        total = details.transfer_max;
    }
    return total;
}

int MegaAccountDetailsPrivate::getNumUsageItems()
{
    return int(details.storage.size());
}

long long MegaAccountDetailsPrivate::getStorageUsed(MegaHandle handle)
{
    handlestorage_map::iterator it = details.storage.find(handle);
    if (it != details.storage.end())
    {
        return it->second.bytes;
    }
    else
    {
        return 0;
    }
}

long long MegaAccountDetailsPrivate::getNumFiles(MegaHandle handle)
{
    handlestorage_map::iterator it = details.storage.find(handle);
    if (it != details.storage.end())
    {
        return it->second.files;
    }
    else
    {
        return 0;
    }
}

long long MegaAccountDetailsPrivate::getNumFolders(MegaHandle handle)
{
    handlestorage_map::iterator it = details.storage.find(handle);
    if (it != details.storage.end())
    {
        return it->second.folders;
    }
    else
    {
        return 0;
    }
}

long long MegaAccountDetailsPrivate::getVersionStorageUsed(MegaHandle handle)
{
    handlestorage_map::iterator it = details.storage.find(handle);
    if (it != details.storage.end())
    {
        return it->second.version_bytes;
    }
    else
    {
        return 0;
    }
}

long long MegaAccountDetailsPrivate::getNumVersionFiles(MegaHandle handle)
{
    handlestorage_map::iterator it = details.storage.find(handle);
    if (it != details.storage.end())
    {
        return it->second.version_files;
    }
    else
    {
        return 0;
    }
}

MegaAccountDetails* MegaAccountDetailsPrivate::copy()
{
    return new MegaAccountDetailsPrivate(&details);
}

int MegaAccountDetailsPrivate::getNumBalances() const
{
    return int(details.balances.size());
}

MegaAccountBalance *MegaAccountDetailsPrivate::getBalance(int i) const
{
    if ((unsigned int)i < details.balances.size())
    {
        return MegaAccountBalancePrivate::fromAccountBalance(&(details.balances[(unsigned int)i]));
    }
    return NULL;
}

int MegaAccountDetailsPrivate::getNumSessions() const
{
    return int(details.sessions.size());
}

MegaAccountSession *MegaAccountDetailsPrivate::getSession(int i) const
{
    if ((unsigned int)i < details.sessions.size())
    {
        return MegaAccountSessionPrivate::fromAccountSession(&(details.sessions[(unsigned int)i]));
    }
    return NULL;
}

int MegaAccountDetailsPrivate::getNumPurchases() const
{
    return int(details.purchases.size());
}

MegaAccountPurchase *MegaAccountDetailsPrivate::getPurchase(int i) const
{
    if ((unsigned int)i < details.purchases.size())
    {
        return MegaAccountPurchasePrivate::fromAccountPurchase(&(details.purchases[(unsigned int)i]));
    }
    return NULL;
}

int MegaAccountDetailsPrivate::getNumTransactions() const
{
    return int(details.transactions.size());
}

MegaAccountTransaction *MegaAccountDetailsPrivate::getTransaction(int i) const
{
    if ((unsigned int)i < details.transactions.size())
    {
        return MegaAccountTransactionPrivate::fromAccountTransaction(&(details.transactions[(unsigned int)i]));
    }
    return NULL;
}

int MegaAccountDetailsPrivate::getTemporalBandwidthInterval()
{
    return int(details.transfer_hist.size());
}

long long MegaAccountDetailsPrivate::getTemporalBandwidth()
{
    long long result = 0;
    for (unsigned int i = 0; i < details.transfer_hist.size(); i++)
    {
        result += details.transfer_hist[i];
    }
    return result;
}

bool MegaAccountDetailsPrivate::isTemporalBandwidthValid()
{
    return details.transfer_hist_valid;
}

MegaErrorPrivate::MegaErrorPrivate(int errorCode)
    : MegaError(errorCode)
{
}

MegaErrorPrivate::MegaErrorPrivate(int errorCode, SyncError syncError)
: MegaError(errorCode, syncError)
{
}
#ifdef ENABLE_SYNC
MegaErrorPrivate::MegaErrorPrivate(int errorCode, MegaSync::Error syncError)
: MegaError(errorCode, syncError)
{
}
#endif

MegaErrorPrivate::MegaErrorPrivate(int errorCode, long long value)
    : MegaError(errorCode)
    , mValue(value)
{
}

MegaErrorPrivate::MegaErrorPrivate(const Error& err)
    : MegaError(err)
    , mValue(0)
    , mUserStatus(err.getUserStatus())
    , mLinkStatus(err.getLinkStatus())
{
}

MegaErrorPrivate::MegaErrorPrivate(const MegaError &megaError)
    : MegaError(megaError.getErrorCode())
    , mValue(megaError.getValue())
    , mUserStatus(megaError.getUserStatus())
    , mLinkStatus(megaError.getLinkStatus())
{
}

MegaErrorPrivate::~MegaErrorPrivate()
{

}

MegaError* MegaErrorPrivate::copy() const
{
    return new MegaErrorPrivate(*this);
}

int MegaErrorPrivate::getErrorCode() const
{
    return errorCode;
}

long long MegaErrorPrivate::getValue() const
{
    return mValue;
}

bool MegaErrorPrivate::hasExtraInfo() const
{
    return mUserStatus != MegaError::UserErrorCode::USER_ETD_UNKNOWN
            || mLinkStatus != MegaError::LinkErrorCode::LINK_UNKNOWN;
}

long long MegaErrorPrivate::getUserStatus() const
{
    return mUserStatus;
}

long long MegaErrorPrivate::getLinkStatus() const
{
    return mLinkStatus;
}

const char* MegaErrorPrivate::getErrorString() const
{
    return MegaError::getErrorString(errorCode);
}

const char* MegaErrorPrivate::toString() const
{
    return getErrorString();
}

const char* MegaErrorPrivate::__str__() const
{
    return getErrorString();
}

const char *MegaErrorPrivate::__toString() const
{
    return getErrorString();
}

MegaPricingPrivate::~MegaPricingPrivate()
{
    for(unsigned i = 0; i < description.size(); i++)
    {
        delete[] description[i];
    }

    for(unsigned i = 0; i < iosId.size(); i++)
    {
        delete[] iosId[i];
    }

    for(unsigned i = 0; i < androidId.size(); i++)
    {
        delete[] androidId[i];
    }
}

int MegaPricingPrivate::getNumProducts()
{
    return int(handles.size());
}

handle MegaPricingPrivate::getHandle(int productIndex)
{
    if((unsigned)productIndex < handles.size())
        return handles[productIndex];

    return UNDEF;
}

int MegaPricingPrivate::getProLevel(int productIndex)
{
    if((unsigned)productIndex < proLevel.size())
        return proLevel[productIndex];

    return 0;
}

int MegaPricingPrivate::getGBStorage(int productIndex)
{
    if (static_cast<size_t>(productIndex) < gbStorage.size())
    {
        return gbStorage[static_cast<size_t>(productIndex)];
    }

    return 0;
}

int MegaPricingPrivate::getGBTransfer(int productIndex)
{
    if (static_cast<size_t>(productIndex) < gbTransfer.size())
    {
        return gbTransfer[static_cast<size_t>(productIndex)];
    }

    return 0;
}

int MegaPricingPrivate::getMonths(int productIndex)
{
    if((unsigned)productIndex < months.size())
        return months[productIndex];

    return 0;
}

int MegaPricingPrivate::getAmount(int productIndex)
{
    if((unsigned)productIndex < amount.size())
        return amount[productIndex];

    return 0;
}

int mega::MegaPricingPrivate::getLocalPrice(int productIndex)
{
    if((unsigned)productIndex < mLocalPrice.size())
        return mLocalPrice[productIndex];

    return 0;
}

const char *MegaPricingPrivate::getDescription(int productIndex)
{
    if((unsigned)productIndex < description.size())
        return description[productIndex];

    return NULL;
}

const char *MegaPricingPrivate::getIosID(int productIndex)
{
    if((unsigned)productIndex < iosId.size())
        return iosId[productIndex];

    return NULL;
}

const char *MegaPricingPrivate::getAndroidID(int productIndex)
{
    if((unsigned)productIndex < androidId.size())
        return androidId[productIndex];

    return NULL;
}

bool MegaPricingPrivate::isBusinessType(int productIndex)
{
    if((unsigned)productIndex < type.size())
        return type[productIndex];

    return false;
}

int MegaPricingPrivate::getAmountMonth(int productIndex)
{
    if((unsigned)productIndex < amountMonth.size())
        return amountMonth[productIndex];

    return 0;
}

MegaPricing *MegaPricingPrivate::copy()
{
    MegaPricingPrivate *megaPricing = new MegaPricingPrivate();
    for(unsigned i=0; i<handles.size(); i++)
    {
        std::unique_ptr<BusinessPlan> bizPlan(mBizPlan[i] ? new BusinessPlan(*mBizPlan[i]) : nullptr);

        megaPricing->addProduct(type[i], handles[i], proLevel[i], gbStorage[i], gbTransfer[i],
                                months[i], amount[i], amountMonth[i],
                                mLocalPrice[i],
                                description[i], iosId[i], androidId[i],
                                move(bizPlan));
    }

    return megaPricing;
}

void MegaPricingPrivate::addProduct(unsigned int type, handle product, int proLevel, int gbStorage, int gbTransfer, int months, int amount, int amountMonth,
                                    unsigned localPrice, const char* description, const char* iosid, const char* androidid, std::unique_ptr<BusinessPlan> bizPlan)
{
    this->type.push_back(type);
    this->handles.push_back(product);
    this->proLevel.push_back(proLevel);
    this->gbStorage.push_back(gbStorage);
    this->gbTransfer.push_back(gbTransfer);
    this->months.push_back(months);
    this->amount.push_back(amount);
    this->amountMonth.push_back(amountMonth);
    mLocalPrice.push_back(localPrice);
    this->description.push_back(MegaApi::strdup(description));
    this->iosId.push_back(MegaApi::strdup(iosid));
    this->androidId.push_back(MegaApi::strdup(androidid));
    mBizPlan.push_back(move(bizPlan));
}


int MegaPricingPrivate::getGBStoragePerUser(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->gbStoragePerUser;
    }

    return 0;
}

int MegaPricingPrivate::getGBTransferPerUser(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->gbTransferPerUser;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getMinUsers(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->minUsers;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getPricePerUser(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->pricePerUser;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getLocalPricePerUser(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->localPricePerUser;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getPricePerStorage(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->pricePerStorage;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getLocalPricePerStorage(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->localPricePerStorage;
    }

    return 0;
}

int MegaPricingPrivate::getGBPerStorage(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->gbPerStorage;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getPricePerTransfer(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->pricePerTransfer;
    }

    return 0;
}

unsigned int MegaPricingPrivate::getLocalPricePerTransfer(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->localPricePerTransfer;
    }

    return 0;
}

int MegaPricingPrivate::getGBPerTransfer(int productIndex)
{
    if ((unsigned)productIndex < mBizPlan.size() && mBizPlan.at(productIndex)) // some Pro plans don't have a valid pointer, only business plans
    {
        return mBizPlan[productIndex]->gbPerTransfer;
    }

    return 0;
}

MegaCurrencyPrivate::~MegaCurrencyPrivate()
{
}

MegaCurrency *MegaCurrencyPrivate::copy()
{
    return new MegaCurrencyPrivate(*this);
}

const char *MegaCurrencyPrivate::getCurrencySymbol()
{
    return mCurrencyData.currencySymbol.c_str();
}

const char *MegaCurrencyPrivate::getCurrencyName()
{
    return mCurrencyData.currencyName.c_str();
}

const char *MegaCurrencyPrivate::getLocalCurrencySymbol()
{
    return mCurrencyData.localCurrencySymbol.c_str();
}

const char *MegaCurrencyPrivate::getLocalCurrencyName()
{
    return mCurrencyData.localCurrencyName.c_str();
}

void MegaCurrencyPrivate::setCurrency(std::unique_ptr<CurrencyData> data)
{
    assert(data);
    mCurrencyData = *data;
}

#ifdef ENABLE_SYNC
MegaSyncPrivate::MegaSyncPrivate(const SyncConfig& config, MegaClient* client /* never null */)
    : mRunState(MegaSync::SyncRunningState(config.mRunState))
    , mType(static_cast<SyncType>(config.getType()))
{
    this->megaHandle = config.mRemoteNode.as8byte();
    this->localFolder = NULL;
    setLocalFolder(config.getLocalPath().toPath(false).c_str());
    this->mName= NULL;
    if (!config.mName.empty())
    {
        setName(config.mName.c_str());
    }
    else
    {
        //using leaf name of localpath as name:
        setName(config.getLocalPath().leafName().toName(*client->fsaccess).c_str());
    }
    this->lastKnownMegaFolder = NULL;
    this->fingerprint = 0;

    setLocalFingerprint(static_cast<long long>(config.mFilesystemFingerprint));
    setLastKnownMegaFolder(config.mOriginalPathOfRemoteRootNode.c_str());

    setError(config.mError < 0 ? 0 : config.mError);
    setWarning(config.mWarning);
    setBackupId(config.mBackupId);
}

MegaSyncPrivate::MegaSyncPrivate(MegaSyncPrivate *sync)
    : mRunState(sync->mRunState)
    , mType(sync->mType)
{
    this->setBackupId(sync->mBackupId);
    this->localFolder = NULL;
    this->mName = NULL;
    this->setLocalFolder(sync->getLocalFolder());
    this->setName(sync->getName());
    this->lastKnownMegaFolder = NULL;
    this->setLastKnownMegaFolder(sync->getLastKnownMegaFolder());
    this->setMegaHandle(sync->getMegaHandle());
    this->setLocalFingerprint(sync->getLocalFingerprint());
    this->setError(sync->getError());
    this->setWarning(sync->getWarning());
}

MegaSyncPrivate::~MegaSyncPrivate()
{
    delete [] localFolder;
    delete [] mName;
    delete [] lastKnownMegaFolder;
}

MegaSync *MegaSyncPrivate::copy()
{
    return new MegaSyncPrivate(this);
}

MegaHandle MegaSyncPrivate::getMegaHandle() const
{
    return megaHandle;
}

void MegaSyncPrivate::setMegaHandle(MegaHandle handle)
{
    this->megaHandle = handle;
}

const char *MegaSyncPrivate::getLocalFolder() const
{
    return localFolder;
}

void MegaSyncPrivate::setLocalFolder(const char *path)
{
    if (localFolder)
    {
        delete [] localFolder;
    }
    localFolder =  MegaApi::strdup(path);
}

const char *MegaSyncPrivate::getName() const
{
    return mName;
}

void MegaSyncPrivate::setName(const char *name)
{
    if (mName)
    {
        delete [] mName;
    }
    mName =  MegaApi::strdup(name);
}

const char *MegaSyncPrivate::getLastKnownMegaFolder() const
{
    return lastKnownMegaFolder;
}

void MegaSyncPrivate::setLastKnownMegaFolder(const char *path)
{
    if (lastKnownMegaFolder)
    {
        delete [] lastKnownMegaFolder;
    }
    lastKnownMegaFolder = MegaApi::strdup(path);
}

long long MegaSyncPrivate::getLocalFingerprint() const
{
    return fingerprint;
}

void MegaSyncPrivate::setLocalFingerprint(long long fingerprint)
{
    this->fingerprint = fingerprint;
}

MegaHandle MegaSyncPrivate::getBackupId() const
{
    return mBackupId;
}

void MegaSyncPrivate::setBackupId(MegaHandle backupId)
{
    this->mBackupId = backupId;
}

int MegaSyncPrivate::getError() const
{
    return mError;
}

void MegaSyncPrivate::setError(int error)
{
    mError = error;
}

int MegaSyncPrivate::getWarning() const
{
    return mWarning;
}

void MegaSyncPrivate::setWarning(int warning)
{
    mWarning = warning;
}

int MegaSyncPrivate::getType() const
{
    return mType;
}

void MegaSyncPrivate::setType(MegaSync::SyncType type)
{
    mType = type;
}

int MegaSyncPrivate::getRunState() const
{
    return mRunState;
}

MegaSyncListPrivate::MegaSyncListPrivate()
{
    list = NULL;
    s = 0;
}

MegaSyncListPrivate::MegaSyncListPrivate(MegaSyncPrivate** newlist, int size)
{
    list = NULL; s = size;
    if(!size) return;

    list = new MegaSync*[size];
    for(int i=0; i<size; i++)
        list[i] = newlist[i]->copy();
}

MegaSyncListPrivate::MegaSyncListPrivate(const MegaSyncListPrivate *syncList)
{
    s = syncList->size();
    if (!s)
    {
        list = NULL;
        return;
    }

    list = new MegaSync*[s];
    for (int i = 0; i<s; i++)
    {
        list[i] = new MegaSyncPrivate(static_cast<MegaSyncPrivate *>(syncList->get(i)));
    }
}

MegaSyncListPrivate::~MegaSyncListPrivate()
{
    if(!list)
        return;

    for(int i=0; i<s; i++)
        delete list[i];
    delete [] list;
}

MegaSyncList *MegaSyncListPrivate::copy() const
{
    return new MegaSyncListPrivate(this);
}

MegaSync *MegaSyncListPrivate::get(int i) const
{
    if(!list || (i < 0) || (i >= s))
        return NULL;

    return list[i];
}

int MegaSyncListPrivate::size() const
{
    return s;
}

void MegaSyncListPrivate::addSync(MegaSync *sync)
{
    MegaSync** copyList = list;
    s = s + 1;
    list = new MegaSync*[s];
    for (int i = 0; i < s - 1; ++i)
    {
        list[i] = copyList[i];
    }

    list[s - 1] = sync->copy();

    if (copyList != NULL)
    {
        delete [] copyList;
    }
}
#endif


MegaAccountBalance *MegaAccountBalancePrivate::fromAccountBalance(const AccountBalance *balance)
{
    return new MegaAccountBalancePrivate(balance);
}

MegaAccountBalancePrivate::~MegaAccountBalancePrivate()
{

}

MegaAccountBalance *MegaAccountBalancePrivate::copy()
{
    return new MegaAccountBalancePrivate(&balance);
}

double MegaAccountBalancePrivate::getAmount() const
{
    return balance.amount;
}

char *MegaAccountBalancePrivate::getCurrency() const
{
    return MegaApi::strdup(balance.currency);
}

MegaAccountBalancePrivate::MegaAccountBalancePrivate(const AccountBalance *balance)
{
    this->balance = *balance;
}

MegaAccountSession *MegaAccountSessionPrivate::fromAccountSession(const AccountSession *session)
{
    return new MegaAccountSessionPrivate(session);
}

MegaAccountSessionPrivate::~MegaAccountSessionPrivate()
{

}

MegaAccountSession *MegaAccountSessionPrivate::copy()
{
    return new MegaAccountSessionPrivate(&session);
}

int64_t MegaAccountSessionPrivate::getCreationTimestamp() const
{
    return session.timestamp;
}

int64_t MegaAccountSessionPrivate::getMostRecentUsage() const
{
    return session.mru;
}

char *MegaAccountSessionPrivate::getUserAgent() const
{
    return MegaApi::strdup(session.useragent.c_str());
}

char *MegaAccountSessionPrivate::getIP() const
{
    return MegaApi::strdup(session.ip.c_str());
}

char *MegaAccountSessionPrivate::getCountry() const
{
    return MegaApi::strdup(session.country);
}

bool MegaAccountSessionPrivate::isCurrent() const
{
    return session.current;
}

bool MegaAccountSessionPrivate::isAlive() const
{
    return session.alive;
}

MegaHandle MegaAccountSessionPrivate::getHandle() const
{
    return session.id;
}

MegaAccountSessionPrivate::MegaAccountSessionPrivate(const AccountSession *session)
{
    this->session = *session;
}


MegaAccountPurchase *MegaAccountPurchasePrivate::fromAccountPurchase(const AccountPurchase *purchase)
{
    return new MegaAccountPurchasePrivate(purchase);
}

MegaAccountPurchasePrivate::~MegaAccountPurchasePrivate()
{

}

MegaAccountPurchase *MegaAccountPurchasePrivate::copy()
{
    return new MegaAccountPurchasePrivate(&purchase);
}

int64_t MegaAccountPurchasePrivate::getTimestamp() const
{
    return purchase.timestamp;
}

char *MegaAccountPurchasePrivate::getHandle() const
{
    return MegaApi::strdup(purchase.handle);
}

char *MegaAccountPurchasePrivate::getCurrency() const
{
    return MegaApi::strdup(purchase.currency);
}

double MegaAccountPurchasePrivate::getAmount() const
{
    return purchase.amount;
}

int MegaAccountPurchasePrivate::getMethod() const
{
    return purchase.method;
}

MegaAccountPurchasePrivate::MegaAccountPurchasePrivate(const AccountPurchase *purchase)
{
    this->purchase = *purchase;
}


MegaAccountTransaction *MegaAccountTransactionPrivate::fromAccountTransaction(const AccountTransaction *transaction)
{
    return new MegaAccountTransactionPrivate(transaction);
}

MegaAccountTransactionPrivate::~MegaAccountTransactionPrivate()
{

}

MegaAccountTransaction *MegaAccountTransactionPrivate::copy()
{
    return new MegaAccountTransactionPrivate(&transaction);
}

int64_t MegaAccountTransactionPrivate::getTimestamp() const
{
    return transaction.timestamp;
}

char *MegaAccountTransactionPrivate::getHandle() const
{
    return MegaApi::strdup(transaction.handle);
}

char *MegaAccountTransactionPrivate::getCurrency() const
{
    return MegaApi::strdup(transaction.currency);
}

double MegaAccountTransactionPrivate::getAmount() const
{
    return transaction.delta;
}

MegaAccountTransactionPrivate::MegaAccountTransactionPrivate(const AccountTransaction *transaction)
{
    this->transaction = *transaction;
}



ExternalInputStream::ExternalInputStream(MegaInputStream *inputStream)
{
    this->inputStream = inputStream;
}

m_off_t ExternalInputStream::size()
{
    return inputStream->getSize();
}

bool ExternalInputStream::read(byte *buffer, unsigned size)
{
    return inputStream->read((char *)buffer, size);
}


MegaTreeProcCopy::MegaTreeProcCopy(MegaClient *client)
{
    this->client = client;
}

void MegaTreeProcCopy::allocnodes()
{
    nn.resize(nc);
    allocated = true;
}
bool MegaTreeProcCopy::processMegaNode(MegaNode *n)
{
    if (allocated)
    {
        NewNode* t = &nn[--nc];

        // copy key (if file) or generate new key (if folder)
        if (n->getType() == FILENODE)
        {
            t->nodekey = *(n->getNodeKey());
        }
        else
        {
            byte buf[FOLDERNODEKEYLENGTH];
            client->rng.genblock(buf,sizeof buf);
            t->nodekey.assign((char*)buf, FOLDERNODEKEYLENGTH);
        }

        t->attrstring.reset(new string);
        if (n->isPublic())
        {
            t->source = NEW_PUBLIC;
        }
        else
        {
            t->source = NEW_NODE;
        }

        SymmCipher key;
        AttrMap attrs;

        key.setkey((const byte*)t->nodekey.data(),n->getType());
        string sname = n->getName();
        LocalPath::utf8_normalize(&sname);
        attrs.map['n'] = sname;

        const char *fingerprint = n->getFingerprint();
        if (fingerprint && fingerprint[0])
        {
            m_off_t size = 0;
            unsigned int fsize = unsigned(strlen(fingerprint));
            unsigned int ssize = fingerprint[0] - 'A';
            if (!(ssize > (sizeof(size) * 4 / 3 + 4) || fsize <= (ssize + 1)))
            {
                int len =  sizeof(size) + 1;
                byte *buf = new byte[len];
                Base64::atob(fingerprint + 1, buf, len);
                int l = Serialize64::unserialize(buf, len, (uint64_t *)&size);
                delete [] buf;
                if (l > 0)
                {
                    attrs.map['c'] = fingerprint + ssize + 1;
                }
            }
        }

        string attrstring;
        attrs.getjson(&attrstring);
        client->makeattr(&key, t->attrstring, attrstring.c_str());

        t->nodehandle = n->getHandle();
        t->type = (nodetype_t)n->getType();
        t->parenthandle = n->getParentHandle() ? n->getParentHandle() : UNDEF;
    }
    else
    {
        nc++;
    }

    return true;
}

MegaFolderUploadController::MegaFolderUploadController(MegaApiImpl *api, MegaTransferPrivate *t)
    : MegaRecursiveOperation(api->getMegaClient())
    , fsaccess(new FSACCESS_CLASS())
{
    megaApi = api;
    transfer = t;
    listener = t->getListener();
    recursive = 0;
    tag = transfer->getTag();
    mMainThreadId = std::this_thread::get_id();

    assert(mMainThreadId == megaApi->threadId);
}

void MegaFolderUploadController::start(MegaNode*)
{
    assert(mMainThreadId == std::this_thread::get_id());

    transfer->setFolderTransferTag(-1);
    transfer->setStartTime(Waiter::ds);
    transfer->setState(MegaTransfer::STATE_QUEUED);
    megaApi->fireOnTransferStart(transfer);

    unique_ptr<MegaNode> parent(megaApi->getNodeByHandle(transfer->getParentHandle()));
    if (!parent)
    {
        transfer->setState(MegaTransfer::STATE_FAILED);
        megaApi->fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_EARGS));
        return;
    }

    // set transfer target node as megaNode of root tree (mUploadTree)
    mUploadTree.megaNode.reset(parent.release());

    // create a subtree for the folder that we want to upload
    unique_ptr<Tree> newTreeNode(new Tree);
    LocalPath path = LocalPath::fromAbsolutePath(transfer->getPath());
    auto leaf = transfer->getFileName()
            ? transfer->getFileName()
            : path.leafName().toPath(true);

    // if folder node already exists in remote, set it as new subtree's megaNode, otherwise call putnodes_prepareOneFolder
    newTreeNode->megaNode.reset(megaApi->getChildNode(mUploadTree.megaNode.get(), leaf.c_str()));

    if (newTreeNode->megaNode && newTreeNode->megaNode->getType() == MegaNode::TYPE_FILE)
    {
        // there's a node (TYPE_FILE) with the same name in the destination path, we will make fail transfer
        transfer->setState(MegaTransfer::STATE_FAILED);
        megaApi->fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(API_EEXIST));
        return;
    }
    else if (!newTreeNode->megaNode) // there's no other node with the same name in the destination path
    {
        newTreeNode->folderName = leaf;
        newTreeNode->fsType = fsaccess->getlocalfstype(path);

        megaapiThreadClient()->putnodes_prepareOneFolder(&newTreeNode->newnode, leaf, false);
        newTreeNode->newnode.nodehandle = nextUploadId();
        newTreeNode->newnode.parenthandle = UNDEF;
    }
    // else => if there's another node (TYPE_FOLDER) with the same name, in the destination path, the content of both folders will be merged

    // add the tree above, to subtrees vector for root tree
    mUploadTree.subtrees.push_back(std::move(newTreeNode));

    // it's mandatory to notify stage change from MegaApiImpl's thread to avoid deadlocks and other issues
    notifyStage(MegaTransfer::STAGE_SCAN);

    mWorkerThread = std::thread ([this, path]() {
        // recurse all subfolders on disk, building up tree structure to match
        // not yet existing folders get a temporary upload id instead of a handle
        uint32_t foldercount = 0;
        uint32_t filecount = 0;
        LocalPath lp = path;
        scanFolder_result scanResult = scanFolder(*mUploadTree.subtrees.front(), lp, foldercount, filecount);

        // if the thread runs, we always queue a function to execute on MegaApi thread for onFinish()
        // we keep a pointer to it in case we need to execute it early and directly on cancel()
        mCompletionForMegaApiThread.reset(new ExecuteOnce([this, scanResult]() {

            // these next parts must run on MegaApiImpl's thread again, as
            // genUploadTransfersForFiles or checkCompletion may call the fireOnXYZ() functions
            assert(mMainThreadId == std::this_thread::get_id());

            // make sure the thread is joined.  This lets us add error-catching asserts elsewhere.
            if (mWorkerThread.joinable())
            {
                mWorkerThread.join();
            }

            if (scanResult == scanFolder_failed)
            {
                // scan stage could not finish properly, because some dir could not be accessed
                complete(API_EACCESS);
                return;
            }
            else if (scanResult == scanFolder_cancelled)
            {
                complete(API_EINCOMPLETE, true);
                return;
            }

            // create folders in batches, not too many at once
            // createNextFolderBatch is responsible for starting the transfers once all needed folders (if any) are created.
            notifyStage(MegaTransfer::STAGE_CREATE_TREE);
            vector<NewNode> newnodes;
#ifndef NDEBUG
            batchResult r =
#endif
            createNextFolderBatch(mUploadTree, newnodes, true);

            assert(r == batchResult_cancelled ||
                   r == batchResult_requestSent ||
                   r == batchResult_batchesComplete);
        }));

        // Queue that function.
        megaApi->executeOnThread(mCompletionForMegaApiThread);
    });
}

// this method provides a temporal handle useful to indicate putnodes()-local parent linkage
handle MegaFolderUploadController::nextUploadId()
{
    return mCurrUploadId = (mCurrUploadId + 1 <= 0xFFFFFFFFFFFF)
        ? mCurrUploadId + 1
        : 0;
}

void MegaRecursiveOperation::onTransferStart(MegaApi *, MegaTransfer *t)
{
    assert(mMainThreadId == std::this_thread::get_id());
    assert(transfer);

    ++transfersStartedCount;
    if (transfersStartedCount == transfersTotalCount &&
        !transfer->accessCancelToken().isCancelled() &&
        !startedTransferring)
    {
        // Apps expect this one called when all sub-transfers have
        // been queued in SDK core already
        notifyStage(MegaTransfer::STAGE_TRANSFERRING_FILES);
        megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_TRANSFERRING_FILES, 0, 0, unsigned(transfersTotalCount), nullptr, nullptr);
        startedTransferring = true;
    }

    if (transfer)
    {
        transfer->setState(t->getState());
        transfer->setPriority(t->getPriority());
        transfer->setTotalBytes(transfer->getTotalBytes() + t->getTotalBytes());
        transfer->setUpdateTime(Waiter::ds);
        megaApi->fireOnTransferUpdate(transfer);
    }
}

void MegaRecursiveOperation::onTransferUpdate(MegaApi *, MegaTransfer *t)
{
    assert(mMainThreadId == std::this_thread::get_id());
    assert(transfer);
    if (transfer)
    {
        LOG_verbose << "MegaRecursiveOperation: on transfer update -> adding new progress " << t->getDeltaSize() << " to previous transferred bytes " << transfer->getTransferredBytes() << " -> updated transferred bytes = " << (transfer->getTransferredBytes() + t->getDeltaSize());
        transfer->setState(t->getState());
        transfer->setPriority(t->getPriority());
        transfer->setTransferredBytes(transfer->getTransferredBytes() + t->getDeltaSize());
        transfer->setUpdateTime(Waiter::ds);
        transfer->setSpeed(t->getSpeed());
        transfer->setMeanSpeed(t->getMeanSpeed());
        megaApi->fireOnTransferUpdate(transfer);
    }
}

void MegaRecursiveOperation::onTransferFinish(MegaApi *, MegaTransfer *t, MegaError *e)
{
    assert(mMainThreadId == std::this_thread::get_id());
    ++transfersFinishedCount;
    assert(transfer);
    if (transfer)
    {
        LOG_verbose << "MegaRecursiveOperation: on transfer finish -> adding new progress " << t->getDeltaSize() << " to previous transferred bytes " << transfer->getTransferredBytes() << " -> updated transferred bytes = " << (transfer->getTransferredBytes() + t->getDeltaSize());
        transfer->setState(MegaTransfer::STATE_ACTIVE);
        transfer->setPriority(t->getPriority());
        transfer->setTransferredBytes(transfer->getTransferredBytes() + t->getDeltaSize());
        transfer->setUpdateTime(Waiter::ds);
        transfer->setSpeed(t->getSpeed());
        transfer->setMeanSpeed(t->getMeanSpeed());
        megaApi->fireOnTransferUpdate(transfer);
    }
    if (e->getErrorCode() != API_OK)
    {
        mIncompleteTransfers++;
    }
    LOG_debug << "MegaRecursiveOperation finished subtransfers: " << transfersFinishedCount << " of " << transfersTotalCount;
    if (allSubtransfersResolved())
    {
        setRootNodeHandleInTransfer();

        // Cancelled or not, there is always an onTransferFinish callback for the folder transfer.
        // If subtransfers were started, completion is always by the last subtransfer completing
        complete(mIncompleteTransfers ? API_EINCOMPLETE : API_OK);
    }
}

MegaFolderUploadController::~MegaFolderUploadController()
{
    assert(mMainThreadId == std::this_thread::get_id());
    LOG_debug << "MegaFolderUploadController dtor is being called from main thread";
    ensureThreadStopped();

    //we shouldn't need to detach as transfer listener: all listened transfer should have been cancelled/completed
}

MegaFolderUploadController::scanFolder_result MegaFolderUploadController::scanFolder(Tree& tree, LocalPath& localPath, uint32_t& foldercount, uint32_t& filecount)
{
    recursive++;
    unique_ptr<DirAccess> da(fsaccess->newdiraccess());
    if (!da->dopen(&localPath, nullptr, false))
    {
        LOG_err << "Can't open local directory" << localPath;
        recursive--;
        return scanFolder_failed;
    }

    megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, foldercount, 0, filecount, &localPath, nullptr);

    LocalPath localname;
    nodetype_t dirEntryType;
    while (da->dnext(localPath, localname, false, &dirEntryType))
    {

        if (isCancelledByFolderTransferToken())
        {
            LOG_debug << "MegaFolderUploadController::scanFolder thread stopped by cancel token";
            return scanFolder_cancelled;
        }
        if (mWorkerThreadStopFlag)
        {
            LOG_debug << "MegaFolderUploadController::scanFolder thread stopped by flag";
            return scanFolder_cancelled;
        }

        megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, foldercount, 0, filecount, &localPath, &localname);

        ScopedLengthRestore restoreLen(localPath);
        localPath.appendWithSeparator(localname, false);
        if (dirEntryType == FILENODE)
        {
            // Do the fingerprinting for uploads on the scan thread, so we don't lock the main mutex for so long
            FileFingerprint fp;
            auto fa = fsaccess->newfileaccess();
            if (fa->fopen(localPath, true, false, FSLogging::logOnError))
            {
                fp.genfingerprint(fa.get());
            }

            // if we couldn't get the fingerprint, !isvalid and we'll fail the transfer
            tree.files.emplace_back(localPath, fp);

            filecount += 1;
        }
        else if (dirEntryType == FOLDERNODE)
        {
            // generate new subtree
            unique_ptr<Tree> newTreeNode(new Tree);
            newTreeNode->folderName = localname.toName(*fsaccess);
            newTreeNode->fsType = fsaccess->getlocalfstype(localPath);

            // generate fresh random key and node attributes
            MegaClient::putnodes_prepareOneFolder(&newTreeNode->newnode, newTreeNode->folderName, rng, tmpnodecipher, false);

            // set nodeHandle
            newTreeNode->newnode.nodehandle = nextUploadId();
            newTreeNode->newnode.parenthandle = tree.newnode.nodehandle;

            scanFolder_result sr = scanFolder(*newTreeNode, localPath, foldercount, filecount);
            if (sr != scanFolder_succeeded)
            {
                recursive--;
                return sr;
            }
            tree.subtrees.push_back(std::move(newTreeNode));

            foldercount += 1;
        }
    }
    recursive--;
    return scanFolder_succeeded;
}

MegaFolderUploadController::batchResult MegaFolderUploadController::createNextFolderBatch(Tree& tree, vector<NewNode>& newnodes, bool isBatchRootLevel)
{
    assert(mMainThreadId == std::this_thread::get_id());

    // preload children for this level (optimization to speed up searches by name/type)
    // (done here instead of at scanFolder() to avoid locking the mutex from the worker)
    if (!tree.childrenLoaded && tree.megaNode)
    {
        Node* parent = megaApi->client->nodebyhandle(tree.megaNode->getHandle());
        assert(parent);
        megaApi->client->getChildren(parent);
        tree.childrenLoaded = true;
    }

    // recurse until we find nodes not yet created
    for (auto& t : tree.subtrees)
    {
        assert(newnodes.size() <= MAXNODESUPLOAD);
        if (newnodes.size() >= MAXNODESUPLOAD)
        {
           // avoid iterating through tree structure when a batch has reached the limit of nodes
           break;
        }

        if (!t->megaNode && tree.megaNode) // check if our last call created it (or it always existed)
        {
            t->megaNode.reset(megaApi->getChildNodeOfType(tree.megaNode.get(), t->folderName.c_str(), MegaNode::TYPE_FOLDER));
        }

        // if node doesn't exist yet and we haven't exceeded the limit per batch
        if (!t->megaNode && newnodes.size() < MAXNODESUPLOAD)
        {
            if (isBatchRootLevel)
            {
                /* the parent of the root newNode (for current batch) must already exist in remote,
                 * so parent handle for root newNode must be UNDEF */
                assert(tree.megaNode);
                t->newnode.parenthandle = UNDEF;
            }
            newnodes.push_back(std::move(t->newnode));
        }

        // if newnodes contains at least one newNode, isBatchRootLevel will be false
        batchResult br = createNextFolderBatch(*t, newnodes, newnodes.empty());
        if (br != batchResult_stillRecursing)
        {
            return br;
        }
    }

    if (isCancelledByFolderTransferToken())
    {
        complete(API_EINCOMPLETE, true);
        return batchResult_cancelled;
    }

    if (isBatchRootLevel && !newnodes.empty())
    {
        // the lambda will be exeuted on the MegaApiImpl's thread
        // use a weak_ptr in case this operation was cancelled, and 'this' object doesn't exist
        // anymore when the request completes
        weak_ptr<MegaFolderUploadController> weak_this = shared_from_this();
        megaapiThreadClient()->putnodes(NodeHandle().set6byte(tree.megaNode->getHandle()), UseLocalVersioningFlag, std::move(newnodes), nullptr, megaapiThreadClient()->nextreqtag(), false,
            [this, weak_this](const Error& e, targettype_t, vector<NewNode>&, bool, int tag)
            {
                // double check our object still exists on request completion
                if (!weak_this.lock()) return;
                assert(weak_this.lock().get() == this);
                assert(mMainThreadId == std::this_thread::get_id());

                // lambda function that will be executed as completion function in putnodes procresult
                if (e)
                {
                    complete(e);
                }
                else
                {
                    // start the next batch, if there are any left (or start transfers, if we are ready)
                    vector<NewNode> newnodes;
#ifndef NDEBUG
                    batchResult r =
#endif
                    createNextFolderBatch(mUploadTree, newnodes, true);

                    assert(r == batchResult_cancelled ||
                           r == batchResult_requestSent ||
                           r == batchResult_batchesComplete);

                }
            });

        unsigned existing = 0, total = 0;
        mUploadTree.recursiveCountFolders(existing, total);
        megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_CREATE_TREE, total, existing, 0, nullptr, nullptr);

        return batchResult_requestSent;
    }

    if (&tree == &mUploadTree)
    {
        // we recursed the entire tree without finding any more folder nodes to create.
        // time to set the file uploads in motion.

        TransferQueue transferQueue;
        if (!genUploadTransfersForFiles(mUploadTree, transferQueue))
        {
            complete(API_EINCOMPLETE, true);
        }
        else if (transferQueue.empty())
        {
            complete(API_OK);
        }
        else
        {
            // once we call sendPendingTransfers, we are guaranteed start/finish callbacks for each file transfer
            // the last callback of onFinish for one of these will also complete and destroy this MegaFolderUploadController
            transfersTotalCount = transferQueue.size();
            megaApi->sendPendingTransfers(&transferQueue, this);
            // no further code can be added here, this object may now be deleted (eg, due to cancel token activation)
        }

        return batchResult_batchesComplete;
    }

    return batchResult_stillRecursing;
}

bool MegaFolderUploadController::genUploadTransfersForFiles(Tree& tree, TransferQueue& transferQueue)
{
    for (const auto& localpath : tree.files)
    {
        MegaTransferPrivate *subTransfer = megaApi->createUploadTransfer(false, localpath.lp.toPath(false).c_str(),
                                                                      tree.megaNode.get(), nullptr, (const char*)NULL,
                                                                      MegaApi::INVALID_CUSTOM_MOD_TIME, tag, false, nullptr /*appdata*/, false, false, tree.fsType, transfer->accessCancelToken(), this, &localpath.fp);
        transferQueue.push(subTransfer);

        if (isCancelledByFolderTransferToken()) return false;
    }

    for (auto& t : tree.subtrees)
    {
        genUploadTransfersForFiles(*t, transferQueue);
        if (isCancelledByFolderTransferToken()) return false;
    }

    return true;
}

void MegaRecursiveOperation::setRootNodeHandleInTransfer()
{
    if (transfer && transfer->getType() == MegaTransfer::TYPE_UPLOAD)
    {
        // set root folder node handle in MegaTransfer
        LocalPath path = LocalPath::fromAbsolutePath(transfer->getPath());
        auto rootFolderName = transfer->getFileName()
                                  ? transfer->getFileName()
                                  : path.leafName().toPath(true);

        unique_ptr<MegaNode> parentRootNode(megaApi->getNodeByHandle(transfer->getParentHandle()));
        std::unique_ptr<MegaNode>root(megaApi->getChildNode(parentRootNode.get(), rootFolderName.c_str()));
        if (root)
        {
            if (transfer->getNodeHandle() != INVALID_HANDLE
                && transfer->getNodeHandle() != root->getHandle())
            {
                LOG_debug << "setRootNodeHandleInTransfer root nodehandle: " << Base64Str<MegaClient::NODEHANDLE>(root->getHandle())
                          << ": doesn't match with current one: " << Base64Str<MegaClient::NODEHANDLE>(transfer->getNodeHandle()) ;
            }
            transfer->setNodeHandle(root->getHandle());
        }
    }
}

void MegaRecursiveOperation::complete(Error e, bool cancelledByUser)
{
    assert(mMainThreadId == std::this_thread::get_id());

    // Completion only happens on this same thread
    // and only by two possible paths:
    // 1. the very last file sub-transfer has completed
    // 2. there were no files (either by cancel/fail or only folders were needed)
    // for both those cases, the thread must have completed already.
    assert(!mWorkerThread.joinable());

    std::string logMsg = "MegaRecursiveOperation";
    if (cancelledByUser) { logMsg.append(" (has been cancelled by user)"); }
    e ? logMsg.append(" finished with error [").append(std::to_string(e).c_str()).append("]") : logMsg.append(" finished successfully");
    LOG_debug << logMsg << " - bytes: " << transfer->getTransferredBytes() << " of " << transfer->getTotalBytes();

    if (allSubtransfersResolved())
    {
        setRootNodeHandleInTransfer();
    }

    transfer->setState(cancelledByUser ? MegaTransfer::STATE_CANCELLED : MegaTransfer::STATE_COMPLETED);
    megaApi->fireOnTransferFinish(transfer, make_unique<MegaErrorPrivate>(e));
}

MegaScheduledCopyController::MegaScheduledCopyController(MegaApiImpl *megaApi, int tag, int folderTransferTag, handle parenthandle, const char* filename, bool attendPastBackups, const char *speriod, int64_t period, int maxBackups)
{
    LOG_info << "Registering backup for folder " << filename << " period=" << period << " speriod=" << speriod << " Number-of-Backups=" << maxBackups;

    this->basepath = filename;
    size_t found = basepath.find_last_of("/\\");
    string aux = basepath;
    while (aux.size() && (found == (aux.size()-1)))
    {
        aux = aux.substr(0, found - 1);
        found = aux.find_last_of("/\\");
    }
    this->backupName = aux.substr((found == string::npos)?0:found+1);

    this->parenthandle = parenthandle;

    this->megaApi = megaApi;
    this->client = megaApi->getMegaClient();

    this->attendPastBackups = attendPastBackups;

    this->pendingTags = 0;

    clearCurrentBackupData();

    lastbackuptime = getLastBackupTime();

    this->backupListener = NULL;

    this->maxBackups = maxBackups;

    this->pendingremovals = 0;

    this->lastwakeuptime = 0;

    this->tag = tag;
    this->folderTransferTag = folderTransferTag;

    valid = true;
    this->setPeriod(period);
    this->setPeriodstring(speriod);
    if (!backupName.size())
    {
        valid = false;
    }

    if (valid)
    {
        megaApi->startTimer(this->startTime - Waiter::ds + 1); //wake the sdk when required
        this->state = MegaScheduledCopy::SCHEDULED_COPY_ACTIVE;
        megaApi->fireOnBackupStateChanged(this);
        removeexceeding(false);
    }
    else
    {
        this->state = MegaScheduledCopy::SCHEDULED_COPY_FAILED;
    }
}

MegaScheduledCopyController::MegaScheduledCopyController(MegaScheduledCopyController *backup)
{
    this->pendingremovals = backup->pendingremovals;
    this->setTag(backup->getTag());
    this->setLocalFolder(backup->getLocalFolder());
    this->setMegaHandle(backup->getMegaHandle());

    this->setFolderTransferTag(backup->getFolderTransferTag());

    this->megaApi = backup->megaApi;
    this->client = backup->client;
    this->setBackupListener(backup->getBackupListener());


    //copy currentBackup data
    this->recursive = backup->recursive;
    this->pendingTransfers = backup->pendingTransfers;
    this->pendingTags = backup->pendingTags;
    for (auto it = backup->pendingFolders.begin(); it != backup->pendingFolders.end(); it++)
    {
        this->pendingFolders.push_back(*it);
    }

    for (std::vector<MegaTransfer *>::iterator it = backup->failedTransfers.begin(); it != backup->failedTransfers.end(); it++)
    {
        this->failedTransfers.push_back(((MegaTransfer *)*it)->copy());
    }
    this->currentHandle = backup->currentHandle;
    this->currentBKStartTime = backup->currentBKStartTime;
    this->updateTime = backup->updateTime;
    this->transferredBytes = backup->transferredBytes;
    this->totalBytes = backup->totalBytes;
    this->speed = backup->speed;
    this->meanSpeed = backup->meanSpeed;
    this->numberFiles = backup->numberFiles;
    this->totalFiles = backup->totalFiles;
    this->numberFolders = backup->numberFolders;
    this->attendPastBackups = backup->attendPastBackups;

    this->offsetds=backup->getOffsetds();
    this->lastbackuptime=backup->getLastBackupTime();
    this->state=backup->getState();
    this->startTime=backup->getStartTime();
    this->period=backup->getPeriod();
    this->ccronexpr=backup->getCcronexpr();
    this->periodstring=backup->getPeriodString();
    this->valid=backup->isValid();

    this->setLocalFolder(backup->getLocalFolder());
    this->setBackupName(backup->getBackupName());
    this->setMegaHandle(backup->getMegaHandle());
    this->setMaxBackups(backup->getMaxBackups());
}


long long MegaScheduledCopyController::getNextStartTime(long long oldStartTimeAbsolute) const
{
    if (oldStartTimeAbsolute == -1)
    {
        return (getNextStartTimeDs() + this->offsetds)/10;
    }
    else
    {
        return (getNextStartTimeDs(oldStartTimeAbsolute*10 - this->offsetds) + this->offsetds)/10;
    }
}

long long MegaScheduledCopyController::getNextStartTimeDs(long long oldStartTimeds) const
{
    if (oldStartTimeds == -1)
    {
        return startTime;
    }
    if (period != -1)
    {
        return oldStartTimeds + period;
    }
    else
    {
        if (!valid)
        {
            return oldStartTimeds;
        }
        long long current_ds = oldStartTimeds + this->offsetds;  // 64 bit

        long long newt = cron_next(&ccronexpr, time_t(current_ds/10));  // time_t is 32 bit still on many systems
        long long newStarTimeds = newt*10-offsetds;  // 64 bit again
        return newStarTimeds;
    }
}

void MegaScheduledCopyController::update()
{
    if (!valid)
    {
        if (!isBusy())
        {
            state = SCHEDULED_COPY_FAILED;
        }
        return;
    }
    if (Waiter::ds > startTime)
    {
        if (!isBusy())
        {
            long long nextStartTime = getNextStartTimeDs(startTime);
            if (nextStartTime <= startTime)
            {
                LOG_err << "Invalid calculated NextStartTime" ;
                valid = false;
                state = SCHEDULED_COPY_FAILED;
                return;
            }

            if (nextStartTime > Waiter::ds)
            {
                start();
            }
            else
            {
                LOG_warn << " BACKUP discarded (too soon, time for the next): " << basepath;
                start(true);
                megaApi->startTimer(1); //wake sdk
            }

            startTime = nextStartTime;
        }
        else
        {
            LOG_verbose << "Backup busy: " << basepath <<
                           ". State=" << ((state==MegaScheduledCopy::SCHEDULED_COPY_ONGOING)?"On Going":"Removing exeeding") << ". Postponing ...";
            if ((lastwakeuptime+10) < Waiter::ds )
            {
                megaApi->startTimer(10); //give it a while
                lastwakeuptime = Waiter::ds+10;
            }
        }
    }
    else
    {
        if (lastwakeuptime < Waiter::ds || ((this->startTime + 1) < lastwakeuptime))
        {
            LOG_debug << " Waking in " << (this->startTime - Waiter::ds + 1) << " deciseconds to do backup";
            megaApi->startTimer(this->startTime - Waiter::ds + 1); //wake the sdk when required
            lastwakeuptime = this->startTime + 1;
        }
    }
}

void MegaScheduledCopyController::removeexceeding(bool currentoneOK)
{
    map<int64_t, MegaNode *> backupTimesNodes;
    int ncompleted=0;

    MegaNode * parentNode = megaApi->getNodeByHandle(parenthandle);

    if (parentNode)
    {
        MegaNodeList* children = megaApi->getChildren(parentNode, MegaApi::ORDER_NONE);
        if (children)
        {
            for (int i = 0; i < children->size(); i++)
            {
                MegaNode *childNode = children->get(i);
                string childname = childNode->getName();

                if (isBackup(childname, backupName) )
                {
                    const char *backstvalue = childNode->getCustomAttr("BACKST");

                    if ( ( !backstvalue || !strcmp(backstvalue,"ONGOING") ) && ( childNode->getHandle() != currentHandle ) )
                    {
                        LOG_err << "Found unexpected ONGOING backup (probably from previous executions). Changing status to MISCARRIED";
                        this->pendingTags++;
                        megaApi->setCustomNodeAttribute(childNode, "BACKST", "MISCARRIED", this);
                    }

                    if ( (backstvalue && !strcmp(backstvalue,"COMPLETE"))
                            || ( childNode->getHandle() == currentHandle && currentoneOK ) //either its completed or is the current one and it went ok (it might not have backstvalue yet set
                            )
                    {
                        ncompleted++;
                    }

                    int64_t timeofbackup = getTimeOfBackup(childname);
                    if (timeofbackup)
                    {
                        backupTimesNodes[timeofbackup]=childNode;
                    }
                    else
                    {
                        LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded.";
                    }
                }
            }
        }
        while (backupTimesNodes.size() > (unsigned int)maxBackups)
        {
            map<int64_t, MegaNode *>::iterator itr = backupTimesNodes.begin();
            const char *backstvalue = itr->second->getCustomAttr("BACKST");
            if ( (ncompleted == 1) && backstvalue && (!strcmp(backstvalue,"COMPLETE")) && backupTimesNodes.size() > 1)
            {
                itr++;
            }

            MegaNode * nodeToDelete = itr->second;
            int64_t timetodelete = itr->first;
            backstvalue = nodeToDelete->getCustomAttr("BACKST");
            if (backstvalue && !strcmp(backstvalue,"COMPLETE"))
            {
                ncompleted--;
            }

            char * nodepath = megaApi->getNodePath(nodeToDelete);
            LOG_info << " Removing exceeding backup " << nodepath;
            delete []nodepath;
            state = SCHEDULED_COPY_REMOVING_EXCEEDING;
            megaApi->fireOnBackupStateChanged(this);
            pendingremovals++;
            megaApi->remove(nodeToDelete, false, this);

            backupTimesNodes.erase(timetodelete);
        }

        delete children;
    }
    delete parentNode;
}

int64_t MegaScheduledCopyController::getLastBackupTime()
{
    map<int64_t, MegaNode *> backupTimesPaths;
    int64_t latesttime=0;

    MegaNode * parentNode = megaApi->getNodeByHandle(parenthandle);
    if (parentNode)
    {
        MegaNodeList* children = megaApi->getChildren(parentNode, MegaApi::ORDER_NONE);
        if (children)
        {
            for (int i = 0; i < children->size(); i++)
            {
                MegaNode *childNode = children->get(i);
                string childname = childNode->getName();
                if (isBackup(childname, backupName) )
                {
                    int64_t timeofbackup = getTimeOfBackup(childname);
                    if (timeofbackup)
                    {
                        backupTimesPaths[timeofbackup]=childNode;
                        latesttime = (std::max)(latesttime, timeofbackup);
                    }
                    else
                    {
                        LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded.";
                    }
                }
            }
            delete children;
        }
        delete parentNode;
    }
    return latesttime;
}

bool MegaScheduledCopyController::isBackup(string localname, string backupname) const
{
    return ( localname.compare(0, backupname.length(), backupname) == 0) && (localname.find("_bk_") != string::npos);
}

int64_t MegaScheduledCopyController::getTimeOfBackup(string localname) const
{
    size_t pos = localname.find("_bk_");
    if (pos == string::npos || ( (pos+4) >= (localname.size()-1) ) )
    {
        return 0;
    }
    string rest = localname.substr(pos + 4).c_str();

//    int64_t toret = atol(rest.c_str());
    int64_t toret = stringToTimestamp(rest, FORMAT_SCHEDULED_COPY);
    return toret;
}

bool MegaScheduledCopyController::getAttendPastBackups() const
{
    return attendPastBackups;
}

MegaTransferList *MegaScheduledCopyController::getFailedTransfers()
{
    MegaTransferList *result = new MegaTransferListPrivate(failedTransfers.data(), int(failedTransfers.size()));
    return result;
}

void MegaScheduledCopyController::setAttendPastBackups(bool value)
{
    attendPastBackups = value;
}

bool MegaScheduledCopyController::isValid() const
{
    return valid;
}

void MegaScheduledCopyController::setValid(bool value)
{
    valid = value;
}

cron_expr MegaScheduledCopyController::getCcronexpr() const
{
    return ccronexpr;
}

void MegaScheduledCopyController::setCcronexpr(const cron_expr &value)
{
    ccronexpr = value;
}

MegaScheduledCopyListener *MegaScheduledCopyController::getBackupListener() const
{
    return backupListener;
}

void MegaScheduledCopyController::setBackupListener(MegaScheduledCopyListener *value)
{
    backupListener = value;
}

long long MegaScheduledCopyController::getTotalFiles() const
{
    return totalFiles;
}

void MegaScheduledCopyController::setTotalFiles(long long value)
{
    totalFiles = value;
}

int64_t MegaScheduledCopyController::getCurrentBKStartTime() const
{
    return currentBKStartTime;
}

void MegaScheduledCopyController::setCurrentBKStartTime(const int64_t &value)
{
    currentBKStartTime = value;
}

int64_t MegaScheduledCopyController::getUpdateTime() const
{
    return updateTime;
}

void MegaScheduledCopyController::setUpdateTime(const int64_t &value)
{
    updateTime = value;
}

long long MegaScheduledCopyController::getTransferredBytes() const
{
    return transferredBytes;
}

void MegaScheduledCopyController::setTransferredBytes(long long value)
{
    transferredBytes = value;
}

long long MegaScheduledCopyController::getTotalBytes() const
{
    return totalBytes;
}

void MegaScheduledCopyController::setTotalBytes(long long value)
{
    totalBytes = value;
}

long long MegaScheduledCopyController::getSpeed() const
{
    return speed;
}

void MegaScheduledCopyController::setSpeed(long long value)
{
    speed = value;
}

long long MegaScheduledCopyController::getMeanSpeed() const
{
    return meanSpeed;
}

void MegaScheduledCopyController::setMeanSpeed(long long value)
{
    meanSpeed = value;
}

long long MegaScheduledCopyController::getNumberFiles() const
{
    return numberFiles;
}

void MegaScheduledCopyController::setNumberFiles(long long value)
{
    numberFiles = value;
}

long long MegaScheduledCopyController::getNumberFolders() const
{
    return numberFolders;
}

void MegaScheduledCopyController::setNumberFolders(long long value)
{
    numberFolders = value;
}

int64_t MegaScheduledCopyController::getLastbackuptime() const
{
    return lastbackuptime;
}

void MegaScheduledCopyController::setLastbackuptime(const int64_t &value)
{
    lastbackuptime = value;
}

void MegaScheduledCopyController::setState(int value)
{
    state = value;
}

bool MegaScheduledCopyController::isBusy() const
{
    return (state == SCHEDULED_COPY_ONGOING) || (state == SCHEDULED_COPY_REMOVING_EXCEEDING || (state == SCHEDULED_COPY_SKIPPING));
}

std::string MegaScheduledCopyController::epochdsToString(const int64_t rawtimeds) const
{
    struct tm dt;
    char buffer [40];
    time_t rawtime = rawtimeds/10;
    m_localtime(rawtime, &dt);

    strftime(buffer, sizeof( buffer ), "%Y%m%d%H%M%S", &dt);

    return std::string(buffer);
}

void MegaScheduledCopyController::clearCurrentBackupData()
{
    this->recursive = 0;
    this->pendingTransfers = 0;
    this->pendingFolders.clear();
    for (std::vector<MegaTransfer *>::iterator it = failedTransfers.begin(); it != failedTransfers.end(); it++)
    {
        delete *it;
    }
    this->failedTransfers.clear();
    this->currentHandle = UNDEF;
    this->currentBKStartTime = 0;
    this->updateTime = 0;
    this->transferredBytes = 0;
    this->totalBytes = 0;
    this->speed = 0;
    this->meanSpeed = 0;
    this->numberFiles = 0;
    this->totalFiles = 0;
    this->numberFolders = 0;
}


void MegaScheduledCopyController::start(bool skip)
{
    LOG_info << "starting backup of " << basepath << ". Next one will be in " << getNextStartTimeDs(startTime)-offsetds << " ds" ;
    clearCurrentBackupData();
    this->setCurrentBKStartTime(Waiter::ds); //notice: this is != StarTime

    size_t plastsep = basepath.find_last_of("\\/");
    if(plastsep == string::npos)
        plastsep = size_t(-1);
    string name = basepath.substr(plastsep+1);

    std::ostringstream ossremotename;
    ossremotename << name;
    ossremotename << "_bk_";
    ossremotename << epochdsToString(offsetds+startTime);
    string backupname = ossremotename.str();
    currentName = backupname;

    lastbackuptime = (std::max)(lastbackuptime,offsetds+startTime);

    megaApi->fireOnBackupStart(this);

    MegaNode *parent = megaApi->getNodeByHandle(parenthandle);
    if(!parent)
    {
        LOG_err << "Could not start backup: "<< name << ". Parent node not found";
        megaApi->fireOnBackupFinish(this, make_unique<MegaErrorPrivate>(API_ENOENT));

    }
    else
    {
        if (skip)
        {
            state = SCHEDULED_COPY_SKIPPING;
        }
        else
        {
            state = SCHEDULED_COPY_ONGOING;
        }
        megaApi->fireOnBackupStateChanged(this);

        auto localpath = LocalPath::fromAbsolutePath(basepath);

        MegaNode *child = megaApi->getChildNode(parent, backupname.c_str());

        if(!child || !child->isFolder())
        {
            pendingFolders.push_back(localpath);
            megaApi->createFolder(backupname.c_str(), parent, this);
        }
        else
        {
            LOG_err << "Could not start backup: "<< backupname << ". Backup already exists";
            megaApi->fireOnBackupFinish(this, make_unique<MegaErrorPrivate>(API_EEXIST));
            state = SCHEDULED_COPY_ACTIVE;

        }

        delete child;
        delete parent;
    }
}

void MegaScheduledCopyController::onFolderAvailable(MegaHandle handle)
{
    MegaNode *parent = megaApi->getNodeByHandle(handle);
    if(currentHandle == UNDEF)//main folder of the backup instance
    {
        currentHandle = handle;
        if (state == SCHEDULED_COPY_ONGOING)
        {
            this->pendingTags++;
            megaApi->setCustomNodeAttribute(parent, "BACKST", "ONGOING", this);
        }
        else
        {
            this->pendingTags++;
            megaApi->setCustomNodeAttribute(parent, "BACKST", "SKIPPED", this);
        }
    }
    else
    {
        numberFolders++;
    }
    recursive++;
    LocalPath localPath = pendingFolders.front();
    pendingFolders.pop_front();

    if (state == SCHEDULED_COPY_ONGOING)
    {
        LocalPath localname;
        auto da = client->fsaccess->newdiraccess();
        if (da->dopen(&localPath, NULL, false))
        {
            FileSystemType fsType = client->fsaccess->getlocalfstype(localPath);

            while (da->dnext(localPath, localname, false))
            {
                ScopedLengthRestore restoreLen(localPath);
                localPath.appendWithSeparator(localname, false);

                //TODO: add exclude filters here

                auto fa = client->fsaccess->newfileaccess();
                if(fa->fopen(localPath, true, false, FSLogging::logOnError))
                {
                    string name = localname.toName(*client->fsaccess);
                    if(fa->type == FILENODE)
                    {
                        pendingTransfers++;

                        totalFiles++;
                        megaApi->startUpload(false, localPath.toPath(false).c_str(),
                                                            parent, nullptr, nullptr, -1,folderTransferTag, true,
                                                            nullptr, false, false, fsType, CancelToken(), this);
                    }
                    else
                    {
                        MegaNode *child = megaApi->getChildNode(parent, name.c_str());
                        if(!child || !child->isFolder())
                        {
                            pendingFolders.push_back(localPath);
                            megaApi->createFolder(name.c_str(), parent, this);
                        }
                        else
                        {
                            pendingFolders.push_front(localPath);
                            onFolderAvailable(child->getHandle());
                        }
                        delete child;
                    }
                }
            }
        }
    }
    else if (state == SCHEDULED_COPY_SKIPPING)
    {
        //do nth
    }
    else
    {
        LOG_warn << " Backup folder created while not ONGOING: " << localPath;
    }

    delete parent;
    recursive--;

    checkCompletion();
}

bool MegaScheduledCopyController::checkCompletion()
{
    if(!recursive && !pendingFolders.size() && !pendingTransfers && !pendingTags)
    {
        error e = API_OK;
        LOG_debug << "Folder transfer finished - " << this->getTransferredBytes() << " of " << this->getTotalBytes();
        MegaNode *node = megaApi->getNodeByHandle(currentHandle);
        if (node)
        {
            if (failedTransfers.size())
            {
                this->pendingTags++;
                megaApi->setCustomNodeAttribute(node, "BACKST", "INCOMPLETE", this);
                e = API_EINCOMPLETE;
            }
            else if (state != SCHEDULED_COPY_SKIPPING)
            {
                this->pendingTags++;
                megaApi->setCustomNodeAttribute(node, "BACKST", "COMPLETE", this);
            }
            else
            {
                e = API_EEXPIRED;
            }
            delete node;
        }
        else
        {
            LOG_err << "Could not set backup attribute, node not found for: " << currentName;
            e = API_ENOENT;
        }

        state = SCHEDULED_COPY_ACTIVE;
        megaApi->fireOnBackupFinish(this, make_unique<MegaErrorPrivate>(e));
        megaApi->fireOnBackupStateChanged(this);

        removeexceeding(e == API_OK);

        return true;
    }
    return false;
}

int MegaScheduledCopyController::getFolderTransferTag() const
{
    return folderTransferTag;
}

void MegaScheduledCopyController::setFolderTransferTag(int value)
{
    folderTransferTag = value;
}

int64_t MegaScheduledCopyController::getOffsetds() const
{
    return offsetds;
}

void MegaScheduledCopyController::setOffsetds(const int64_t &value)
{
    offsetds = value;
}

void MegaScheduledCopyController::abortCurrent()
{
    LOG_debug << "Setting backup as aborted: " << currentName;

    if (state == SCHEDULED_COPY_ONGOING || state == SCHEDULED_COPY_SKIPPING)
    {
        megaApi->fireOnBackupFinish(this, make_unique<MegaErrorPrivate>(API_EINCOMPLETE));
    }

    state = SCHEDULED_COPY_ACTIVE;
    megaApi->fireOnBackupStateChanged(this);

    MegaNode *node = megaApi->getNodeByHandle(currentHandle);
    if (node)
    {
        this->pendingTags++;
        megaApi->setCustomNodeAttribute(node, "BACKST", "ABORTED", this);
        delete node;
    }
    else
    {
        LOG_err << "Could not set backup attribute, node not found for: " << currentName;
    }

    clearCurrentBackupData();

}

void MegaScheduledCopyController::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e)
{
    int type = request->getType();
    int errorCode = e->getErrorCode();

    if(type == MegaRequest::TYPE_CREATE_FOLDER)
    {
        if(!errorCode)
        {
            onFolderAvailable(request->getNodeHandle());
            megaApi->fireOnBackupUpdate(this);
        }
        else
        {
            pendingFolders.pop_front();
            megaApi->fireOnBackupUpdate(this);
            checkCompletion();
        }
    }
    else if(type == MegaRequest::TYPE_REMOVE)
    {
        pendingremovals--;
        if (!pendingremovals)
        {
            assert(pendingTags>=0);
            if (pendingTags <= 0)
            {
                state = SCHEDULED_COPY_ACTIVE;
            }
            megaApi->fireOnBackupStateChanged(this);
        }
    }
    else if(type == MegaRequest::TYPE_SET_ATTR_NODE)
    {
        pendingTags--;
        assert(pendingTags>=0);

        if (!pendingTags)
        {
            if (state == SCHEDULED_COPY_ONGOING || state == SCHEDULED_COPY_SKIPPING)
            {
                checkCompletion();
            }
            else // from REMOVING OR after abort
            {
                if (state != SCHEDULED_COPY_ACTIVE)
                {
                    state = SCHEDULED_COPY_ACTIVE;
                    megaApi->fireOnBackupStateChanged(this);
                }
            }
        }
    }

}

void MegaScheduledCopyController::onTransferStart(MegaApi *, MegaTransfer *t)
{
    LOG_verbose << " at MegaScheduledCopyController::onTransferStart: "+ string(t->getFileName());

    this->setTotalBytes(this->getTotalBytes() + t->getTotalBytes());
    this->setUpdateTime(Waiter::ds);

    megaApi->fireOnBackupUpdate(this);
}

void MegaScheduledCopyController::onTransferUpdate(MegaApi *, MegaTransfer *t)
{
    LOG_verbose << " at MegaScheduledCopyController::onTransferUpdate";

    this->setTransferredBytes(this->getTransferredBytes() + t->getDeltaSize());
    this->setUpdateTime(Waiter::ds);
    this->setSpeed(t->getSpeed());
    this->setMeanSpeed(t->getMeanSpeed());

    megaApi->fireOnBackupUpdate(this);
}

void MegaScheduledCopyController::onTransferTemporaryError(MegaApi *, MegaTransfer *t, MegaError *e)
{
    LOG_verbose << " at MegaScheduledCopyController::onTransferTemporaryError";

    unique_ptr<MegaErrorPrivate> errorPrivate;
    if (dynamic_cast<MegaErrorPrivate *>(e))
    {
        errorPrivate = unique_ptr<MegaErrorPrivate>(dynamic_cast<MegaErrorPrivate *>(e->copy()));
    }
    else
    {
         errorPrivate = make_unique<MegaErrorPrivate>(e->getErrorCode());
    }

    megaApi->fireOnBackupTemporaryError(this, std::move(errorPrivate));  // we received a non-owning pointer but we need to pass ownership to fireOnBackupTemporaryError
}

void MegaScheduledCopyController::onTransferFinish(MegaApi *, MegaTransfer *t, MegaError *e)
{
    LOG_verbose << " at MegaackupController::onTransferFinish";

    pendingTransfers--;
//    this->setTransferredBytes(this->getTransferredBytes() + t->getDeltaSize()); //TODO: THIS was in MegaUploaderController (which seems wrong)
    this->setUpdateTime(Waiter::ds);
    this->setSpeed(t->getSpeed());
    this->setMeanSpeed(t->getMeanSpeed());

    if (e->getErrorCode() != MegaError::API_OK)
    {
        failedTransfers.push_back(t->copy());
    }
    else
    {
        numberFiles++;
    }

    megaApi->fireOnBackupUpdate(this);

    checkCompletion();
}

MegaScheduledCopy *MegaScheduledCopyController::copy()
{
    return new MegaScheduledCopyController(this);
}


int MegaScheduledCopyController::getMaxBackups() const
{
    return maxBackups;
}

void MegaScheduledCopyController::setMaxBackups(int value)
{
    maxBackups = value;
}

string MegaScheduledCopyController::getBackupName() const
{
    return backupName;
}

void MegaScheduledCopyController::setBackupName(const string &value)
{
    backupName = value;
}

int64_t MegaScheduledCopyController::getPeriod() const
{
    return period;
}

const char *MegaScheduledCopyController::getPeriodString() const
{
    return periodstring.c_str();
}

void MegaScheduledCopyController::setPeriod(const int64_t &value)
{
    period = value;
    if (value != -1)
    {
        this->offsetds=m_time(NULL)*10 - Waiter::ds;
        this->startTime = lastbackuptime?(lastbackuptime+period-offsetds):Waiter::ds;
        if (this->startTime < Waiter::ds)
            this->startTime = Waiter::ds;
    }
}

void MegaScheduledCopyController::setPeriodstring(const string &value)
{
    periodstring = value;
    valid = true;
    if (value.size())
    {
        const char* err = NULL;
        memset((cron_expr *)&ccronexpr, 0, sizeof(ccronexpr));
        cron_parse_expr(periodstring.c_str(), &ccronexpr, &err);

        if (err != NULL)
        {
            valid = false;
            return;
        }

        this->offsetds=m_time(NULL)*10 - Waiter::ds;

        if (!lastbackuptime)
        {
            this->startTime = Waiter::ds;
        }
        else
        {
            this->startTime = this->getNextStartTimeDs(lastbackuptime-offsetds);
        }
        if (this->startTime < Waiter::ds)
        {
            //to avoid skipping (do empty backups with SKIPPED attr) for a long while (e.g: period too short or downtime too long)
            // we determine a max number of executions to skip.

            int maxBackupToSkip = maxBackups + 10;
            int64_t* starttimes = new int64_t[maxBackupToSkip];
            int64_t next = lastbackuptime-offsetds;
            int64_t previousnext = next;

            for (int i = 0; i < maxBackupToSkip; i++)
            {
                starttimes[i] = startTime;
            }

            int j = 0;

            do
            {
                previousnext = next;
                next = this->getNextStartTimeDs(next);
                starttimes[j] = next;
                j = (j==(maxBackupToSkip-1))?0:j+1;
            } while (next > previousnext && next < Waiter::ds);

            if (!attendPastBackups)
            {
                this->startTime = next;
            }
            else
            {
                this->startTime = starttimes[j]; //starttimes[j] should have the oldest time
            }
            delete [] starttimes;
        }
        LOG_debug << " Next Backup set in " << startTime - Waiter::ds << " deciseconds. At: " << epochdsToString((this->startTime+this->offsetds));
    }
}

int64_t MegaScheduledCopyController::getStartTime() const
{
    return startTime;
}

void MegaScheduledCopyController::setStartTime(const int64_t &value)
{
    startTime = value;
}

int MegaScheduledCopyController::getTag() const
{
    return this->tag;
}

void MegaScheduledCopyController::setTag(int value)
{
    tag = value;
}

MegaHandle MegaScheduledCopyController::getMegaHandle() const
{
    return this->parenthandle;
}

void MegaScheduledCopyController::setMegaHandle(const MegaHandle &value)
{
    parenthandle = value;
}

const char *MegaScheduledCopyController::getLocalFolder() const
{
    return this->basepath.c_str();
}

void MegaScheduledCopyController::setLocalFolder(const string &value)
{
    basepath = value;
}

int MegaScheduledCopyController::getState() const
{
    return state;
}

MegaScheduledCopyController::~MegaScheduledCopyController()
{
    megaApi->removeRequestListener(this);
    megaApi->removeTransferListener(this);

    for (std::vector<MegaTransfer *>::iterator it = failedTransfers.begin(); it != failedTransfers.end(); it++)
    {
        delete *it;
    }
}

// Note: it's mandatory to notify stage change from MegaApiImpl's thread
void MegaRecursiveOperation::notifyStage(uint8_t stage)
{
    // Make a copy of transfer and set stage in the temp transfer.
    // If we set stage in the original transfer, we may incur in a wrong order of notification.
    assert(mMainThreadId == std::this_thread::get_id());
    assert (stage > MegaTransfer::STAGE_NONE && stage <= MegaTransfer::STAGE_MAX);
    LOG_debug << "MegaRecursiveOperation: starting " << MegaTransfer::stageToString(stage);
    unique_ptr<MegaTransfer> tempTransfer(transfer->copy());
    MegaTransferPrivate *transferPtr = static_cast<MegaTransferPrivate*>(tempTransfer.get());
    transferPtr->setStage(stage);
    megaApi->fireOnTransferUpdate(transferPtr);
}

void MegaRecursiveOperation::ensureThreadStopped()
{
    mWorkerThreadStopFlag = true;
    if (mWorkerThread.joinable())
    {
        mWorkerThread.join();
    }
}

bool MegaRecursiveOperation::isCancelledByFolderTransferToken()
{
    return transfer->accessCancelToken().isCancelled();
}

MegaClient* MegaRecursiveOperation::megaapiThreadClient()
{
    assert(mMainThreadId == std::this_thread::get_id());
    return mMegaapiThreadClient;
}

MegaFolderDownloadController::MegaFolderDownloadController(MegaApiImpl *api, MegaTransferPrivate *t)
    : MegaRecursiveOperation(api->client)
    , fsaccess(new FSACCESS_CLASS())
{
    megaApi = api;
    fsaccess->setdefaultfilepermissions(megaApi->getDefaultFilePermissions()); // Grant default file permissions
    fsaccess->setdefaultfolderpermissions(megaApi->getDefaultFolderPermissions()); // Grant default folder permissions
    transfer = t;
    listener = t->getListener();
    recursive = 0;
    tag = t->getTag();
    mMainThreadId = std::this_thread::get_id();
}

MegaFolderDownloadController::~MegaFolderDownloadController()
{
    assert(mMainThreadId == std::this_thread::get_id());
    LOG_debug << "MegaFolderDownloadController dtor is being called from main thread";
    ensureThreadStopped();
}

void MegaFolderDownloadController::start(MegaNode *node)
{
    assert(mMainThreadId == std::this_thread::get_id());
    transfer->setFolderTransferTag(-1);
    transfer->setStartTime(Waiter::ds);
    transfer->setState(MegaTransfer::STATE_QUEUED);
    megaApi->fireOnTransferStart(transfer);

    unique_ptr<MegaNode> autoDelNode;
    if (!node)
    {
        node = megaApi->getNodeByHandle(transfer->getNodeHandle());
        autoDelNode.reset(node);
        if (!node)
        {
            LOG_debug << "Folder download failed. Node not found";
            complete(API_ENOENT);
            return;
        }
    }
    LocalPath path;
    if (transfer->getParentPath())
    {
        path = LocalPath::fromAbsolutePath(transfer->getParentPath());
    }
    else
    {
        path = LocalPath::fromAbsolutePath(".");
        path.appendWithSeparator(LocalPath::fromRelativePath(""), true);
    }

    FileSystemType fsType = megaapiThreadClient()->fsaccess->getlocalfstype(path);
    const LocalPath &name = (!transfer->getFileName() || !strlen(transfer->getFileName()))
         ? LocalPath::fromRelativeName(node->getName(), *megaapiThreadClient()->fsaccess, fsType)
         : LocalPath::fromRelativeName(transfer->getFileName(), *megaapiThreadClient()->fsaccess, fsType);

    path.appendWithSeparator(name, true);
    transfer->setPath(path.toPath(false).c_str());

    // check we are not overwriting file with folder:
    auto tmpfileaccess = fsaccess->newfileaccess();
    if (tmpfileaccess->isfile(path))
    {
        // destination path could not be open or there's a node (TYPE_FILE) with the same name in the destination path
        complete(API_EEXIST);
        return;
    }

    notifyStage(MegaTransfer::STAGE_SCAN);
    // for download scan is just checking nodes, we can do this all in one quick pass
    unsigned fileAddedCount = 0;
    scanFolder_result sr = scanFolder(node, path, fsType, fileAddedCount);

    if (sr != scanFolder_succeeded)
    {
        if (sr == scanFolder_cancelled)
        {
            complete(API_EINCOMPLETE, true);
        }
        else // inconsistent node state
        {
            complete(API_EINTERNAL);
        }
    }
    else
    {
        // it's mandatory to notify stage change from MegaApiImpl's thread to avoid deadlocks and other issues
        notifyStage(MegaTransfer::STAGE_CREATE_TREE);

        // start worker thread to create local folder tree
        mWorkerThread = std::thread([this, fsType](){

            // local folder creation runs on the download worker thread (and checks the cancelled flag)
            Error e = createFolder();

            // the thread always queues a function to execute on MegaApi thread for onFinish()
            // we keep a pointer to it in case we need to cancel()
            mCompletionForMegaApiThread.reset(new ExecuteOnce([this, fsType, e]() {

                // these next parts must run on MegaApiImpl's thread again, as
                // genUploadTransfersForFiles or checkCompletion may call the fireOnXYZ() functions
                assert(mMainThreadId == std::this_thread::get_id());

                // make sure the thread is joined.  This lets us add error-catching asserts elsewhere.
                if (mWorkerThread.joinable())
                {
                    mWorkerThread.join();
                }

                if (e)
                {
                    complete(e);
                }
                else
                {
                    // downloadFiles must run on the megaApi thread, as it may call fireOnTransferXYZ()
                    TransferQueue transferQueue;
                    if (!genDownloadTransfersForFiles(fsType, transferQueue))
                    {
                        complete(API_EINCOMPLETE, true);
                    }
                    else if (transferQueue.empty())
                    {
                        complete(API_OK);
                    }
                    else
                    {
                        // once we call sendPendingTransfers, we are guaranteed start/finish callbacks for each file transfer
                        // the last callback of onFinish for one of these will also complete and destroy this MegaFolderUploadController
                        transfersTotalCount = transferQueue.size();
                        megaApi->sendPendingTransfers(&transferQueue, this);
                        // no further code can be added here, this object may now be deleted (eg, due to cancel token activation)

                        // complete() will finally be called when the last sub-transfer finishes
                    }
                }
            }));

            // Queue that function.
            megaApi->executeOnThread(mCompletionForMegaApiThread);
        });
    }
}

MegaFolderDownloadController::scanFolder_result MegaFolderDownloadController::scanFolder(MegaNode *node, LocalPath& localpath, FileSystemType fsType, unsigned& fileAddedCount)
{
    assert(mMainThreadId == std::this_thread::get_id());

    if (isCancelledByFolderTransferToken())
    {
        return scanFolder_cancelled;
    }

    recursive++;
    size_t index = 0;
    if (node->getType() == FOLDERNODE || node->getType() == ROOTNODE)
    {
       // If node is a folder or root node, store it's localPath, along with a vector with it's children file nodes
       mLocalTree.emplace_back(LocalTree(localpath));
       index = mLocalTree.size() - 1;
    }

    megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, unsigned(mLocalTree.size()), 0, fileAddedCount, &localpath, nullptr);

    MegaNodeList *children = nullptr;
    unique_ptr<MegaNodeList> autoDelChildren;
    if (node->isForeign())
    {
        children = node->getChildren();
    }
    else
    {
        children = megaApi->getChildren(node, MegaApi::ORDER_NONE);  // no order is much faster for a very large folder (or nested folders with large subfolders)
        autoDelChildren.reset(children);
    }

    if (!children)
    {
        LOG_err << "Child nodes not found: " << localpath;
        recursive--;
        return scanFolder_failed;
    }

    for (int i = 0; i < children->size(); i++)
    {
        if (isCancelledByFolderTransferToken())
        {
            return scanFolder_cancelled;
        }

        MegaNode *child = children->get(i);
        if (child->getType() == MegaNode::TYPE_FILE)
        {
            // Add child node to vector in mLocalTree at index we have stored it's localPath
            ::mega::unique_ptr<MegaNode> childNode (child->copy());
            mLocalTree.at(index).childrenNodes.push_back(std::move(childNode));
            fileAddedCount += 1;
        }
        else
        {
            ScopedLengthRestore restoreLen(localpath);
            localpath.appendWithSeparator(LocalPath::fromRelativeName(child->getName(), *fsaccess, fsType), true);
            scanFolder_result result = scanFolder(child, localpath, fsType, fileAddedCount);

            if (result != scanFolder_succeeded)
            {
                recursive--;
                return result;
            }
        }
    }
    recursive--;
    return scanFolder_succeeded;
}

Error MegaFolderDownloadController::createFolder()
{
    // Create all local directories in one shot (on the download worker thread)
    unsigned created = 0;
    assert(mMainThreadId != std::this_thread::get_id());
    auto it = mLocalTree.begin();
    while (it != mLocalTree.end())
    {
        if (mWorkerThreadStopFlag)
        {
            LOG_debug << "MegaFolderDownloadController::createFolder thread stopped by flag";
            return API_EINCOMPLETE;
        }
        if (isCancelledByFolderTransferToken())
        {
            LOG_debug << "MegaFolderDownloadController::createFolder thread stopped by cancel token";
            return API_EINCOMPLETE;
        }

        LocalPath &localpath = it->localPath;

        megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_CREATE_TREE, unsigned(mLocalTree.size()), created, 0, nullptr, nullptr);

        Error e = MegaApiImpl::createLocalFolder_unlocked(localpath, *fsaccess);
        if (e && e != API_EEXIST)
        {
            mLocalTree.clear();
            return e;
        }
        ++it;
        ++created;
    }
    return API_OK;
}

bool MegaFolderDownloadController::genDownloadTransfersForFiles(FileSystemType fsType, TransferQueue& transferQueue)
{
    // Add all download transfers in one shot
    for (auto it = mLocalTree.begin(); it != mLocalTree.end(); it++)
    {
        if (isCancelledByFolderTransferToken())
        {
            return false;
        }

        LocalPath &localpath = it->localPath;
        const std::vector<unique_ptr<MegaNode>> &childrenNodes = it->childrenNodes;
        for (size_t i = 0; i < childrenNodes.size(); i++)
        {
            if (isCancelledByFolderTransferToken())
            {
                return false;
            }

             MegaNode &node = *(childrenNodes.at(i).get());
             ScopedLengthRestore restoreLen(localpath);
             localpath.appendWithSeparator(LocalPath::fromRelativeName(node.getName(), *fsaccess, fsType), true);
             string utf8path = localpath.toPath(false);
             MegaTransferPrivate *transferDownload = megaApi->createDownloadTransfer(false, &node, utf8path.c_str(), nullptr, tag, nullptr /*appData()*/, transfer->accessCancelToken(), this, fsType);
             transferQueue.push(transferDownload);
         }
    }
    return true;
}

#ifdef HAVE_LIBUV
StreamingBuffer::StreamingBuffer()
{
    this->capacity = 0;
    this->buffer = NULL;
    this->inpos = 0;
    this->outpos = 0;
    this->size = 0;
    this->free = 0;
    this->maxBufferSize = MAX_BUFFER_SIZE;
    this->maxOutputSize = MAX_OUTPUT_SIZE;
    this->fileSize = 0;
    this->duration = 0;
}

StreamingBuffer::~StreamingBuffer()
{
    delete [] buffer;
}

void StreamingBuffer::init(size_t capacity)
{
    assert(capacity > 0);
    // Recalculate maxBufferSize and maxOutputSize.
    calcMaxBufferAndMaxOutputSize();
    // Truncate capacity if needed
    if (capacity > maxBufferSize)
    {
        size_t bytesPerSecond = getBytesPerSecond();
        LOG_warn << "[Streaming] Truncating requested capacity due to being greater than maxBufferSize."
                 << " Capacity requested = " << capacity << " bytes"
                 << ", truncated to = " << maxBufferSize << " bytes"
                 << " [file size = " << fileSize << " bytes"
                 << ", total duration = " << (duration ? (std::to_string(duration).append(" secs")) : "not a media file")
                 << (duration ? std::string(", estimated duration in truncated buffer: ").append(std::to_string(maxBufferSize / bytesPerSecond)).append(" secs")
                                    .append(", max length to be served: ").append(std::to_string(maxOutputSize / bytesPerSecond)).append(" secs")
                                    .append(", bytes per second: ").append(std::to_string(bytesPerSecond / 1024)).append(" KB/s")
                                : "")
                 << "]";
        capacity = maxBufferSize;
    }
    else
    {
        LOG_debug << "[Streaming] Init StreamingBuffer."
                 << " Capacity requested = " << capacity << " bytes"
                 << " [file size = " << fileSize << " bytes"
                 << ", total duration = " << (duration ? (std::to_string(duration).append(" secs")) : "not a media file")
                 << (duration ? std::string(", estimated duration in buffer: ").append(std::to_string(partialDuration(capacity)).append(" secs")) : "")
                 << "]";
    }

    this->capacity = static_cast<unsigned>(capacity);
    this->buffer = new char[this->capacity];
    this->inpos = 0;
    this->outpos = 0;
    this->size = 0;
    this->free = this->capacity;
}

void StreamingBuffer::calcMaxBufferAndMaxOutputSize()
{
    size_t maxReadChunkSize = static_cast<size_t>(DirectReadSlot::MAX_DELIVERY_CHUNK);
#if defined(__ANDROID__) || defined(USE_IOS)
    size_t minNeededBufferSize = (maxReadChunkSize * 5) / 2; // Equivalent to x2.5 [x * (2 + 1/2) = x * 5/2]
#else
    constexpr size_t BUFFER_PAUSE_RESUME_FACTOR = 3; // Take into account pausing/resuming actions: we need that margin.
    size_t minNeededBufferSize = maxReadChunkSize * BUFFER_PAUSE_RESUME_FACTOR;
#endif
    size_t maxDeliveryChunksPerByteRate;
    if (duration)
    {
        size_t bytesPerSecond = getBytesPerSecond();
        // Desirable min buffer capacity: 10 seconds at least. Limit: 2x max buffer size requested by the client.
        size_t minDesirableBufferSize = std::min(10 * bytesPerSecond, maxBufferSize * 2);
        // Min buffer capacity needed to prevent data from shrinking: minNeededBufferSize at least.
        minNeededBufferSize = std::max(minNeededBufferSize, minDesirableBufferSize);
        // Calc multiplication factor for maxOutputSize depending on the byteRate and the maxReadChunkSize from DirectReadSlot.
        maxDeliveryChunksPerByteRate = std::max<size_t>((bytesPerSecond / maxReadChunkSize) + ((bytesPerSecond % maxReadChunkSize != 0) ? 1 : 0), 1ul);
    }
    else
    {
        // Default multiplication factor for maxOutputSize (there is no byteRate to adapt the value).
        maxDeliveryChunksPerByteRate = 1;
    }
     // Set maxBufferSize taking into account minNeededBufferSize, truncating the value to be divisible by maxReadChunkSize.
    maxBufferSize = (std::max(maxBufferSize, minNeededBufferSize) / maxReadChunkSize) * maxReadChunkSize;
    // Set max outputSize depending on maxDeliveryChunksPerByteRate. Limit is maxBufferSize.
    maxOutputSize = std::min(maxDeliveryChunksPerByteRate * maxReadChunkSize, maxBufferSize);
}

void StreamingBuffer::reset(bool freeData, size_t sizeToReset)
{
    if (!sizeToReset || sizeToReset > size)
    {
        sizeToReset = size;
    }
    LOG_warn << "[Streaming] Reset streaming buffer. Actual size: " << size << ", free: " << free << ", capacity = " << capacity << ", size to reset: " << sizeToReset << "] [inpos = " << inpos << ", outpos = " << outpos << "]";
    this->inpos = this->inpos >= sizeToReset ?
                        this->inpos - sizeToReset :
                        this->capacity - (sizeToReset - this->inpos);
    this->outpos = this->outpos >= sizeToReset ?
                        this->outpos - sizeToReset :
                        this->capacity - (sizeToReset - this->outpos);
    this->size -= sizeToReset;
    if (freeData)
    {
        this->free += sizeToReset;
    }
}

size_t StreamingBuffer::append(const char *buf, size_t len)
{
    if (!buffer)
    {
        // initialize the buffer if it's not initialized yet
        init(len);
    }

    if (free < len)
    {
        LOG_debug << "[Streaming] Not enough available space, len will be truncated. "
                  << " [requested = " << len
                  << ", buffered = " << free
                  << ", discarded = " << (len - free) << "]";
        len = free;
    }

    // update the internal state
    size_t currentIndex = inpos;
    inpos += len;
    int remaining = static_cast<int>(inpos - capacity);
    inpos %= capacity;
    size += len;
    free -= len;

    // append the new data
    if (remaining <= 0)
    {
        memcpy(buffer + currentIndex, buf, len);
    }
    else
    {
        size_t num = static_cast<size_t>(static_cast<int>(len) - remaining);
        LOG_debug << "[Streaming] Length exceeds limits of circular buffer. Writting a piece of " << num << " bytes to the end and the others " << remaining << " bytes from the beginning"
                    << " [current index = " << currentIndex << ", len = " << len << ", capacity = " << capacity << "]";
        memcpy(buffer + currentIndex, buf, num);
        memcpy(buffer, buf + num, static_cast<size_t>(remaining));
    }

    return len;
}

size_t StreamingBuffer::availableData() const
{
    return size;
}

size_t StreamingBuffer::availableSpace() const
{
    return free;
}

size_t StreamingBuffer::availableCapacity() const
{
    return capacity;
}

uv_buf_t StreamingBuffer::nextBuffer()
{
    if (!size)
    {
        // no data available
        return uv_buf_init(NULL, 0);
    }

    // prepare output buffer
    char *outbuf = buffer + outpos;
    size_t len = size < maxOutputSize ? size : maxOutputSize;
    if (outpos + len > capacity)
    {
        LOG_debug << "[Streaming] Available length exceeds limits of circular buffer: "
                    << "Truncating output buffer length to " << (capacity-outpos) << " bytes"
                    << " [outpos = " << outpos << ", len = " << len << ", capacity = " << capacity << "]";
        len = capacity - outpos;
    }

    // update the internal state
    size -= len;
    outpos += len;
    outpos %= capacity;

    // return the buffer
    return uv_buf_init(outbuf, (unsigned int)(len));
}

void StreamingBuffer::freeData(size_t len)
{
    LOG_verbose << "[Streaming] Streaming buffer free data: len = " << len << ", actual free = " << free << ", new free = " << (free+len) << ", size = " << size << " [capacity = " << capacity << "]"; 
    // update the internal state
    free += len;
}

void StreamingBuffer::setMaxBufferSize(unsigned int bufferSize)
{
    LOG_debug << "[Streaming] Set new max buffer size for StreamingBuffer: " << bufferSize;
    if (bufferSize)
    {
        this->maxBufferSize = bufferSize;
    }
    else
    {
        this->maxBufferSize = MAX_BUFFER_SIZE;
    }
}

void StreamingBuffer::setMaxOutputSize(unsigned int outputSize)
{
    LOG_debug << "[Streaming] Set new max output size for StreamingBuffer: " << outputSize;
    if (outputSize)
    {
        this->maxOutputSize = outputSize;
    }
    else
    {
        this->maxOutputSize = MAX_OUTPUT_SIZE;
    }
}

void StreamingBuffer::setFileSize(m_off_t fileSize)
{
    this->fileSize = fileSize;
    LOG_debug << "[Streaming] File size set to " << this->fileSize << " bytes";
}

void StreamingBuffer::setDuration(int duration)
{
    if (!duration)
    {
        // Param 'duration' is documented in MegaNode::getDuration() to be -1 if it's not defined.
        // Hence, if it's 0, the file should be a media file... which has a duration of 0 seconds.
        LOG_warn << "[Streaming] Duration value is 0 seconds for this media file!";
    }
    this->duration = duration > 0 ? duration : 0;
    LOG_debug << "[Streaming] File duration set to " << this->duration << " secs";
}

m_off_t StreamingBuffer::getBytesPerSecond() const
{
    if (fileSize < duration)
    {
        LOG_err << "[Streaming] File size is smaller than its duration in seconds!"
                << " [file size = " << fileSize << " bytes"
                << " , duration = " << duration << " secs]";
    }
    return duration ? (fileSize / duration) : 0;
}

m_off_t StreamingBuffer::partialDuration(m_off_t partialSize) const
{
    partialSize = std::min(partialSize, fileSize);
    m_off_t bytesPerSecond = getBytesPerSecond();
    return bytesPerSecond ? (partialSize / bytesPerSecond) : 0;
}

unsigned StreamingBuffer::getMaxBufferSize()
{
    return this->maxBufferSize ?
                static_cast<unsigned>(this->maxBufferSize) :
                StreamingBuffer::MAX_BUFFER_SIZE;
}

unsigned StreamingBuffer::getMaxOutputSize()
{
    return this->maxOutputSize ?
                static_cast<unsigned>(this->maxOutputSize) :
                StreamingBuffer::MAX_OUTPUT_SIZE;
}

std::string StreamingBuffer::bufferStatus() const
{
    std::string bufferState;
    bufferState.reserve(256);
    bufferState.append("[|Buffer status| buffered = ")
               .append(std::to_string(size));
    if (duration) bufferState.append(" (").append(std::to_string(partialDuration(size)).append( " secs)"));
    bufferState.append(", free = ")
               .append(std::to_string(free));
    if (duration) bufferState.append(" (").append(std::to_string(partialDuration(free)).append( " secs)"));
    bufferState.append(", capacity = ")
               .append(std::to_string(capacity));
    if (duration) bufferState.append(" (").append(std::to_string(partialDuration(capacity)).append( " secs)"));
    bufferState.append("]");
    return bufferState;
}

// http_parser settings
http_parser_settings MegaTCPServer::parsercfg;

MegaTCPServer::MegaTCPServer(MegaApiImpl *megaApi, string basePath, bool tls, string certificatepath, string keypath, bool ipv6)
    : useIPv6(ipv6)
#ifdef ENABLE_EVT_TLS
    , useTLS(tls)
#else
    , useTLS(false)
#endif
{
    this->megaApi = megaApi;
    this->localOnly = true;
    this->started = false;
    this->port = 0;
    this->maxBufferSize = 0;
    this->maxOutputSize = 0;
    this->restrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS;
    this->lastHandle = INVALID_HANDLE;
    this->remainingcloseevents = 0;
    this->closing = false;
    this->thread = new MegaThread();
#ifdef ENABLE_EVT_TLS
    this->certificatepath = certificatepath;
    this->keypath = keypath;
    this->closing = false;
    this->remainingcloseevents = 0;
    this->evtrequirescleaning = false;
#endif
    fsAccess = new MegaFileSystemAccess();

    if (basePath.size())
    {
        LocalPath lp = LocalPath::fromAbsolutePath(basePath);
        if (!lp.endsInSeparator())
        {
            lp.appendWithSeparator(LocalPath(), true);
        }
        string sBasePath = lp.toPath(false);
        this->basePath = sBasePath;
    }
    semaphoresdestroyed = false;
    uv_sem_init(&semaphoreEnd, 0);
    uv_sem_init(&semaphoreStartup, 0);
}

MegaTCPServer::~MegaTCPServer()
{
    stop();
    semaphoresdestroyed = true;
    uv_sem_destroy(&semaphoreStartup);
    uv_sem_destroy(&semaphoreEnd);
    delete fsAccess;

    LOG_verbose << " MegaTCPServer::~MegaTCPServer joining uv thread";
    thread->join();
    LOG_verbose << " MegaTCPServer::~MegaTCPServer deleting uv thread";
    delete thread;
}

bool MegaTCPServer::start(int port, bool localOnly)
{
    if (started && this->port == port && this->localOnly == localOnly)
    {
        LOG_verbose << "MegaTCPServer::start Alread started at that port, returning " << started;
        return true;
    }
    if (started)
    {
        stop();
    }

    this->port = port;
    this->localOnly = localOnly;

    thread->start(threadEntryPoint, this);
    uv_sem_wait(&semaphoreStartup);

    LOG_verbose << "MegaTCPServer::start. port = " << port << ", returning " << started;
    return started;
}

#ifdef ENABLE_EVT_TLS
int MegaTCPServer::uv_tls_writer(evt_tls_t *evt_tls, void *bfr, int sz)
{
    int rv = 0;
    uv_buf_t b;
    b.base = (char*)bfr;
    b.len = sz;

    MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data;
    assert(tcpctx != NULL);

    if (uv_is_writable((uv_stream_t*)(&tcpctx->tcphandle)))
    {
        uv_write_t *req = new uv_write_t();
        tcpctx->writePointers.push_back((char*)bfr);
        req->data = tcpctx;
        LOG_verbose << "Sending " << sz << " bytes of TLS data on port = " << tcpctx->server->port;
        if (int err = uv_write(req, (uv_stream_t*)&tcpctx->tcphandle, &b, 1, onWriteFinished_tls_async))
        {
            LOG_warn << "At uv_tls_writer: Finishing due to an error sending the response: " << err;
            tcpctx->writePointers.pop_back();
            delete [] (char*)bfr;
            delete req;

            closeTCPConnection(tcpctx);
        }
        rv = sz; //writer should return the written size
    }
    else
    {
        delete [] (char*)bfr;
        LOG_debug << " uv_is_writable returned false";
    }

    return rv;
}
#endif

// todo: a lot of this function is the same as initializeAndStartListening, we should factor them (maybe call that one from this one?)
void MegaTCPServer::run()
{
    LOG_debug << " Running tcp server: " << port << " TLS=" << useTLS;

#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
        if (evt_ctx_init_ex(&evtctx, certificatepath.c_str(), keypath.c_str()) != 1 )
        {
            LOG_err << "Unable to init evt ctx";
            port = 0;
            uv_sem_post(&semaphoreStartup);
            uv_sem_post(&semaphoreEnd);
            return;
        }
        evt_ctx_set_nio(&evtctx, NULL, uv_tls_writer);
    }
#endif

    uv_loop_init(&uv_loop);

    uv_async_init(&uv_loop, &exit_handle, onCloseRequested);
    exit_handle.data = this;

    uv_tcp_init(&uv_loop, &server);
    server.data = this;

    uv_tcp_keepalive(&server, 0, 0);

    union {
        struct sockaddr_in6 ipv6;
        struct sockaddr_in ipv4;
    } address;

    if (useIPv6)
    {
        if (localOnly)
        {
            uv_ip6_addr("::1", port, &address.ipv6);
        }
        else
        {
            uv_ip6_addr("::", port, &address.ipv6);
        }
    }
    else
    {
        if (localOnly)
        {
            uv_ip4_addr("127.0.0.1", port, &address.ipv4);
        }
        else
        {
            uv_ip4_addr("0.0.0.0", port, &address.ipv4);
        }
    }

    uv_connection_cb onNewClientCB;
#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
         onNewClientCB = onNewClient_tls;
    }
    else
    {
#endif
        onNewClientCB = onNewClient;
#ifdef ENABLE_EVT_TLS
    }
#endif

    if(uv_tcp_bind(&server, (const struct sockaddr*)&address, 0)
        || uv_listen((uv_stream_t*)&server, 32, onNewClientCB))
    {
        LOG_err << "TCP failed to bind/listen port = " << port;
        port = 0;

        uv_close((uv_handle_t *)&exit_handle,NULL);
        uv_close((uv_handle_t *)&server,NULL);
        uv_sem_post(&semaphoreStartup);
        uv_run(&uv_loop, UV_RUN_ONCE); // so that resources are cleaned peacefully
        uv_sem_post(&semaphoreEnd);
        return;
    }

    LOG_info << "TCP" << (useTLS ? "(tls)" : "") << " server started on port " << port;
    started = true;
    uv_sem_post(&semaphoreStartup);

    LOG_info << "Starting uv loop ...";
    uv_run(&uv_loop, UV_RUN_DEFAULT);

    LOG_info << "UV loop ended";
#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
        //evt_ctx_free(&evtctx); //This causes invalid free when called second time!! collides with memory allocated elsewhere (e.g: via curl_global_init!)
        SSL_CTX_free(evtctx.ctx);
    }
#endif
    uv_loop_close(&uv_loop);
    started = false;
    port = 0;
    LOG_debug << "UV loop thread exit";
}

void MegaTCPServer::initializeAndStartListening()
{
#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
        if (evt_ctx_init_ex(&evtctx, certificatepath.c_str(), keypath.c_str()) != 1 )
        {
            LOG_err << "Unable to init evt ctx";
            port = 0;
            uv_sem_post(&semaphoreStartup);
            uv_sem_post(&semaphoreEnd);
            return;
        }
        evtrequirescleaning = true;
        evt_ctx_set_nio(&evtctx, NULL, uv_tls_writer);
    }
#endif

    uv_loop_init(&uv_loop);

    uv_async_init(&uv_loop, &exit_handle, onCloseRequested);
    exit_handle.data = this;

    uv_tcp_init(&uv_loop, &server);
    server.data = this;

    uv_tcp_keepalive(&server, 0, 0);

    union {
        struct sockaddr_in6 ipv6;
        struct sockaddr_in ipv4;
    } address;

    if (useIPv6)
    {
        if (localOnly)
        {
            uv_ip6_addr("::1", port, &address.ipv6);
        }
        else
        {
            uv_ip6_addr("::", port, &address.ipv6);
        }
    }
    else
    {
        if (localOnly)
        {
            uv_ip4_addr("127.0.0.1", port, &address.ipv4);
        }
        else
        {
            uv_ip4_addr("0.0.0.0", port, &address.ipv4);
        }
    }

    uv_connection_cb onNewClientCB;
#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
         onNewClientCB = onNewClient_tls;
    }
    else
    {
#endif
        onNewClientCB = onNewClient;
#ifdef ENABLE_EVT_TLS
    }
#endif

    if(uv_tcp_bind(&server, (const struct sockaddr*)&address, 0)
        || uv_listen((uv_stream_t*)&server, 32, onNewClientCB))
    {
        LOG_err << "TCP failed to bind/listen port = " << port;
        port = 0;
        uv_async_send(&exit_handle);
        //This is required in case uv_loop was already running so as to free references to "this".
        // a uv_sem_post will be required there, so that we can delete the server accordingly
        return;
    }

    LOG_info << "TCP" << (useTLS ? "(tls)" : "") << " server started on port " << port;
    started = true;
    uv_sem_post(&semaphoreStartup);
    LOG_debug << "UV loop already alive!";
}

void MegaTCPServer::stop(bool doNotWait)
{
    if (!started)
    {
        LOG_verbose << "Stopping non started MegaTCPServer port=" << port;
        return;
    }

    LOG_debug << "Stopping MegaTCPServer port = " << port;
    uv_async_send(&exit_handle);
    if (!doNotWait)
    {
        LOG_verbose << "Waiting for sempahoreEnd to conclude server stop port = " << port;
        uv_sem_wait(&semaphoreEnd); //this is signaled when closed my last connection
    }
    LOG_debug << "Stopped MegaTCPServer port = " << port;
    started = false;
}

int MegaTCPServer::getPort()
{
    return port;
}

bool MegaTCPServer::isLocalOnly()
{
    return localOnly;
}

void MegaTCPServer::setMaxBufferSize(int bufferSize)
{
    this->maxBufferSize = bufferSize <= 0 ? 0 : bufferSize;
}

void MegaTCPServer::setMaxOutputSize(int outputSize)
{
    this->maxOutputSize = outputSize <= 0 ? 0 : outputSize;
}

int MegaTCPServer::getMaxBufferSize()
{
    if (maxBufferSize)
    {
        return maxBufferSize;
    }

    return StreamingBuffer::MAX_BUFFER_SIZE;
}

int MegaTCPServer::getMaxOutputSize()
{
    if (maxOutputSize)
    {
        return maxOutputSize;
    }

    return StreamingBuffer::MAX_OUTPUT_SIZE;
}

void MegaTCPServer::setRestrictedMode(int mode)
{
    this->restrictedMode = mode;
}

int MegaTCPServer::getRestrictedMode()
{
    return restrictedMode;
}

bool MegaTCPServer::isHandleAllowed(handle h)
{
    return restrictedMode == MegaApi::TCP_SERVER_ALLOW_ALL
            || (restrictedMode == MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS && allowedHandles.count(h))
            || (restrictedMode == MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK && h == lastHandle);
}

void MegaTCPServer::clearAllowedHandles()
{
    allowedHandles.clear();
    lastHandle = INVALID_HANDLE;
}

char *MegaTCPServer::getLink(MegaNode *node, string protocol)
{
    if (!node)
    {
        return NULL;
    }

    lastHandle = node->getHandle();
    allowedHandles.insert(lastHandle);

    string localhostIP = useIPv6 ? "[::1]" : "127.0.0.1";

    ostringstream oss;
    oss << protocol << (useTLS ? "s" : "") << "://" << localhostIP << ":" << port << "/";
    char *base64handle = node->getBase64Handle();
    oss << base64handle;
    delete [] base64handle;

    if (node->isPublic() || node->isForeign())
    {
        char *base64key = node->getBase64Key();
        oss << "!" << base64key;
        delete [] base64key;

        if (node->isForeign())
        {
            oss << "!" << node->getSize();
            string *publicAuth = node->getPublicAuth();
            string *privAuth = node->getPrivateAuth();
            const char *chatAuth = node->getChatAuth();
            if (privAuth->size())
            {
                oss << "!f" << *privAuth;
            }
            else if (publicAuth->size())
            {
                oss << "!p" << *publicAuth;
            }
            else if (chatAuth && chatAuth[0])
            {
                oss << "!c" << chatAuth;
            }
        }
    }

    oss << "/";

    string name = node->getName();
    string escapedName;
    URLCodec::escape(&name, &escapedName);
    oss << escapedName;
    string link = oss.str();
    return MegaApi::strdup(link.c_str());
}

set<handle> MegaTCPServer::getAllowedHandles()
{
    return allowedHandles;
}

void MegaTCPServer::removeAllowedHandle(MegaHandle handle)
{
    allowedHandles.erase(handle);
}

void *MegaTCPServer::threadEntryPoint(void *param)
{
#ifndef _WIN32
    struct sigaction noaction;
    memset(&noaction, 0, sizeof(noaction));
    noaction.sa_handler = SIG_IGN;
    ::sigaction(SIGPIPE, &noaction, 0);
#endif

    MegaTCPServer *tcpServer = (MegaTCPServer *)param;
    tcpServer->run();
    return NULL;
}

#ifdef ENABLE_EVT_TLS
void MegaTCPServer::evt_on_rd(evt_tls_t *evt_tls, char *bfr, int sz)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data;
    assert(tcpctx != NULL);

    uv_buf_t data;
    data.base = bfr;
    data.len = sz;

    if (!tcpctx->invalid)
    {
        tcpctx->server->processReceivedData(tcpctx, sz, &data);
    }
    else
    {
        LOG_debug << " Not procesing invalid data after failed evt_close";
    }
}

void MegaTCPServer::on_evt_tls_close(evt_tls_t *evt_tls, int status)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data;
    assert(tcpctx != NULL);

    LOG_debug << "TLS connection closed. status = " << status;

    if (status == 1)
    {
        closeTCPConnection(tcpctx);
    }
    else
    {
        LOG_debug << "TLS connection closed failed!!! status = " << status;
        tcpctx->invalid = true;
    }
}

void MegaTCPServer::on_hd_complete( evt_tls_t *evt_tls, int status)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data;
    LOG_debug << "TLS handshake finished in port = " << tcpctx->server->port << ". Status: " << status;

    if (status)
    {
        evt_tls_read(evt_tls, evt_on_rd); //this only establish callback
        if ( tcpctx->server->respondNewConnection(tcpctx) )
        {
            // we dont need to explicitally start reading. on_tcp_read will be called
        }
    }
    else
    {
        evt_tls_close(evt_tls, on_evt_tls_close);
    }
}

void MegaTCPServer::onNewClient_tls(uv_stream_t *server_handle, int status)
{
    if (status < 0)
    {
        LOG_warn << " onNewClient_tls unexpected status: " << status;
        return;
    }

    // Create an object to save context information
    MegaTCPContext* tcpctx = ((MegaTCPServer *)server_handle->data)->initializeContext(server_handle);

    LOG_debug << "Connection received at port " << tcpctx->server->port << " ! " << tcpctx->server->connections.size();

    // Mutex to protect the data buffer
    uv_mutex_init(&tcpctx->mutex);

    // Async handle to perform writes
    uv_async_init(&tcpctx->server->uv_loop, &tcpctx->asynchandle, onAsyncEvent);

    // Accept the connection
    uv_tcp_init(&tcpctx->server->uv_loop, &tcpctx->tcphandle);
    if (uv_accept(server_handle, (uv_stream_t*)&tcpctx->tcphandle))
    {
        LOG_err << "uv_accept failed";
        onClose((uv_handle_t*)&tcpctx->tcphandle);
        return;
    }

    tcpctx->evt_tls = evt_ctx_get_tls(&tcpctx->server->evtctx);
    assert(tcpctx->evt_tls != NULL);
    tcpctx->evt_tls->data = tcpctx;
    if (evt_tls_accept(tcpctx->evt_tls, on_hd_complete))
    {
        LOG_err << "evt_tls_accept failed";
        evt_tls_close(tcpctx->evt_tls, on_evt_tls_close);
        return;
    }

    tcpctx->server->connections.push_back(tcpctx);

    tcpctx->server->readData(tcpctx);
}
#endif

void MegaTCPServer::readData(MegaTCPContext* tcpctx)
{
#ifdef ENABLE_EVT_TLS
    if (useTLS)
    {
        uv_read_start((uv_stream_t*)(&tcpctx->tcphandle), allocBuffer, on_tcp_read);
    }
    else
    {
#endif
        uv_read_start((uv_stream_t*)&tcpctx->tcphandle, allocBuffer, onDataReceived);
#ifdef ENABLE_EVT_TLS
    }
#endif
}

void MegaTCPServer::onNewClient(uv_stream_t* server_handle, int status)
{
    if (status < 0)
    {
        return;
    }

    // Create an object to save context information
    MegaTCPContext* tcpctx = ((MegaTCPServer *)server_handle->data)->initializeContext(server_handle);

    LOG_debug << "Connection received at port " << tcpctx->server->port << "! " << tcpctx->server->connections.size() << " tcpctx = " << tcpctx;

    // Mutex to protect the data buffer
    uv_mutex_init(&tcpctx->mutex);

    // Async handle to perform writes
    uv_async_init(&tcpctx->server->uv_loop, &tcpctx->asynchandle, onAsyncEvent);

    // Accept the connection
    uv_tcp_init(&tcpctx->server->uv_loop, &tcpctx->tcphandle);
    if (uv_accept(server_handle, (uv_stream_t*)&tcpctx->tcphandle))
    {
        LOG_err << "uv_accept failed";
        onClose((uv_handle_t*)&tcpctx->tcphandle);
        return;
    }

    tcpctx->server->connections.push_back(tcpctx);
    if (tcpctx->server->respondNewConnection(tcpctx))
    {
        // Start reading
        tcpctx->server->readData(tcpctx);
    }
}

void MegaTCPServer::allocBuffer(uv_handle_t *, size_t suggested_size, uv_buf_t* buf)
{
    // Reserve a buffer with the suggested size
    *buf = uv_buf_init(new char[suggested_size], static_cast<unsigned>(suggested_size));
}

void MegaTCPServer::onDataReceived(uv_stream_t* tcp, ssize_t nread, const uv_buf_t * buf)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*) tcp->data;
    tcpctx->server->processReceivedData(tcpctx, nread, buf);
    delete [] buf->base;
}

#ifdef ENABLE_EVT_TLS
void MegaTCPServer::on_tcp_read(uv_stream_t *tcp, ssize_t nrd, const uv_buf_t *data)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*) tcp->data;
    assert( tcpctx != NULL);

    LOG_debug << "Received " << nrd << " bytes at port " << tcpctx->server->port;
    if (!nrd)
    {
        return;
    }

    if (nrd < 0)
    {
        if (evt_tls_is_handshake_over(tcpctx->evt_tls))
        {
            LOG_verbose << "MegaTCPServer::on_tcp_read calling processReceivedData";
            tcpctx->server->processReceivedData(tcpctx, nrd, data);
            evt_tls_close(tcpctx->evt_tls, on_evt_tls_close);
        }
        else
        {
            //if handshake is not over, simply tear down without close_notify
            closeTCPConnection(tcpctx);
        }
        delete[] data->base;
        return;
    }

    evt_tls_feed_data(tcpctx->evt_tls, data->base, static_cast<int>(nrd));
    delete[] data->base;
}
#endif

void MegaTCPServer::onClose(uv_handle_t* handle)
{
    MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data;

    // streaming transfers are automatically stopped when their listener is removed
    tcpctx->megaApi->removeTransferListener(tcpctx);
    tcpctx->megaApi->removeRequestListener(tcpctx);

    tcpctx->server->connections.remove(tcpctx);
    LOG_debug << "Connection closed: " << tcpctx->server->connections.size() << " port = " << tcpctx->server->port << " closing async handle";
    uv_close((uv_handle_t *)&tcpctx->asynchandle, onAsyncEventClose);
}

void MegaTCPServer::onAsyncEventClose(uv_handle_t *handle)
{
    MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data;
    assert(!tcpctx->writePointers.size());

    int port = tcpctx->server->port;

    tcpctx->server->remainingcloseevents--;
    tcpctx->server->processOnAsyncEventClose(tcpctx);

    LOG_verbose << "At onAsyncEventClose port = " << tcpctx->server->port << " remaining=" << tcpctx->server->remainingcloseevents;

    if (!tcpctx->server->remainingcloseevents && tcpctx->server->closing && !tcpctx->server->semaphoresdestroyed)
    {
        uv_sem_post(&tcpctx->server->semaphoreStartup);
        uv_sem_post(&tcpctx->server->semaphoreEnd);
    }

    uv_mutex_destroy(&tcpctx->mutex);
    delete tcpctx;
    LOG_debug << "Connection deleted, port = " << port;
}

#ifdef ENABLE_EVT_TLS
void MegaTCPServer::onWriteFinished_tls(evt_tls_t *evt_tls, int status)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data;
    assert(tcpctx != NULL);

    if (status < 0)
    {
        LOG_warn << " error received at onWriteFinished_tls: " << status;
    }

    if (tcpctx->finished)
    {
        LOG_debug << "At onWriteFinished_tls; TCP link closed, ignoring the result of the write";
        return;
    }
    tcpctx->server->processWriteFinished(tcpctx, status);
}

void MegaTCPServer::onWriteFinished_tls_async(uv_write_t* req, int status)
{
    MegaTCPContext *tcpctx = (MegaTCPContext*)req->data;
    assert(tcpctx->writePointers.size());
    delete [] tcpctx->writePointers.front();
    tcpctx->writePointers.pop_front();
    delete req;

    if (tcpctx->finished)
    {
        if (tcpctx->size == tcpctx->bytesWritten && !tcpctx->writePointers.size())
        {
            LOG_debug << "TCP link closed, shutdown result: " << status << " port = " << tcpctx->server->port;
        }
        else
        {
            LOG_debug << "TCP link closed, ignoring the result of the async TLS write: " << status << " port = " << tcpctx->server->port;
        }
        return;
    }

    if (status < 0)
    {
        LOG_warn << "Finishing request. Async TLS write failed: " << status;
        evt_tls_close(tcpctx->evt_tls, on_evt_tls_close);
        return;
    }

    if (tcpctx->size == tcpctx->bytesWritten && !tcpctx->writePointers.size())
    {
        LOG_debug << "Finishing request. All data delivered";
        evt_tls_close(tcpctx->evt_tls, on_evt_tls_close);
        return;
    }

    LOG_verbose << "Async TLS write finished";
    uv_async_send(&tcpctx->asynchandle);
}
#endif

void MegaTCPServer::onWriteFinished(uv_write_t* req, int status)
{
    MegaTCPContext* tcpctx = (MegaTCPContext*) req->data;
    assert(tcpctx != NULL);
    if (tcpctx->finished)
    {
        LOG_debug << "At onWriteFinished; TCP link closed, ignoring the result of the write";
        delete req;
        return;
    }

    tcpctx->server->processWriteFinished(tcpctx, status);
    delete req;
}

MegaTCPContext::MegaTCPContext()
{
    size = -1;
    finished = false;
    bytesWritten = 0;
#ifdef ENABLE_EVT_TLS
    evt_tls = NULL;
    invalid = false;
#endif
    server = NULL;
    megaApi = NULL;
}

MegaTCPContext::~MegaTCPContext()
{
#ifdef ENABLE_EVT_TLS
    if (evt_tls)
    {
        evt_tls_free(evt_tls);
    }
#endif
}

void MegaTCPServer::onAsyncEvent(uv_async_t* handle)
{
    MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data;

    assert(tcpctx->server != NULL);
#ifdef ENABLE_EVT_TLS
    if (tcpctx->server->useTLS && !evt_tls_is_handshake_over(tcpctx->evt_tls))
    {
        LOG_debug << " skipping processAsyncEvent due to handshake not over on port = " << tcpctx->server->port;
        return;
    }
#endif
    tcpctx->server->processAsyncEvent(tcpctx);
}

void MegaTCPServer::onExitHandleClose(uv_handle_t *handle)
{
    MegaTCPServer *tcpServer = (MegaTCPServer*) handle->data;
    assert(tcpServer != NULL);

    tcpServer->remainingcloseevents--;
    LOG_verbose << "At onExitHandleClose port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents;

    tcpServer->processOnExitHandleClose(tcpServer);

    if (!tcpServer->remainingcloseevents && !tcpServer->semaphoresdestroyed)
    {
        uv_sem_post(&tcpServer->semaphoreStartup);
        uv_sem_post(&tcpServer->semaphoreEnd);
    }
}

void MegaTCPServer::onCloseRequested(uv_async_t *handle)
{
    MegaTCPServer *tcpServer = (MegaTCPServer*) handle->data;
    LOG_debug << "TCP server stopping port=" << tcpServer->port;

    tcpServer->closing = true;

    for (list<MegaTCPContext*>::iterator it = tcpServer->connections.begin(); it != tcpServer->connections.end(); it++)
    {
        MegaTCPContext *tcpctx = (*it);
        closeTCPConnection(tcpctx);
    }

    tcpServer->remainingcloseevents++;
    LOG_verbose << "At onCloseRequested: closing server port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents;
    uv_close((uv_handle_t *)&tcpServer->server, onExitHandleClose);
    tcpServer->remainingcloseevents++;
    LOG_verbose << "At onCloseRequested: closing exit_handle port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents;
    uv_close((uv_handle_t *)&tcpServer->exit_handle, onExitHandleClose);
}

void MegaTCPServer::closeConnection(MegaTCPContext *tcpctx)
{
    LOG_verbose << "At closeConnection port = " << tcpctx->server->port;
#ifdef ENABLE_EVT_TLS
    if (tcpctx->server->useTLS)
    {
        evt_tls_close(tcpctx->evt_tls, on_evt_tls_close);
    }
    else
    {
#endif
        closeTCPConnection(tcpctx);
        return;
#ifdef ENABLE_EVT_TLS
    }
#endif
}

void MegaTCPServer::closeTCPConnection(MegaTCPContext *tcpctx)
{
    tcpctx->finished = true;
    if (!uv_is_closing((uv_handle_t*)&tcpctx->tcphandle))
    {
        tcpctx->server->remainingcloseevents++;
        LOG_verbose << "At closeTCPConnection port = " << tcpctx->server->port << " remainingcloseevent = " << tcpctx->server->remainingcloseevents;
        uv_close((uv_handle_t*)&tcpctx->tcphandle, onClose);
    }
}

void MegaTCPServer::processOnAsyncEventClose(MegaTCPContext *tcpctx) // without this closing breaks!
{
    LOG_debug << "At supposed to be virtual processOnAsyncEventClose";
}

void MegaTCPServer::processOnExitHandleClose(MegaTCPServer *tcpServer) // without this closing breaks!
{
    LOG_debug << "At supposed to be virtual processOnExitHandleClose";
}

void MegaTCPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t *buf)
{
    LOG_debug << "At supposed to be virtual processReceivedData";
}

void MegaTCPServer::processAsyncEvent(MegaTCPContext *tcpctx)
{
    LOG_debug << "At supposed to be virtual processAsyncEvent";
}

///////////////////////////////
//  MegaHTTPServer specifics //
///////////////////////////////

MegaHTTPServer::MegaHTTPServer(MegaApiImpl *megaApi, string basePath, bool useTLS, string certificatepath, string keypath, bool useIPv6)
    : MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath, useIPv6)
{
    // parser callbacks
    parsercfg.on_url = onUrlReceived;
    parsercfg.on_message_begin = onMessageBegin;
    parsercfg.on_headers_complete = onHeadersComplete;
    parsercfg.on_message_complete = onMessageComplete;
    parsercfg.on_header_field = onHeaderField;
    parsercfg.on_header_value = onHeaderValue;
    parsercfg.on_body = onBody;

    this->fileServerEnabled = true;
    this->folderServerEnabled = true;
    this->offlineAttribute = false;
    this->subtitlesSupportEnabled = false;
}

MegaTCPContext * MegaHTTPServer::initializeContext(uv_stream_t *server_handle)
{
    MegaHTTPContext* httpctx = new MegaHTTPContext();

    // Initialize the parser
    http_parser_init(&httpctx->parser, HTTP_REQUEST);

    // Set connection data
    MegaHTTPServer *server = (MegaHTTPServer *)(server_handle->data);

    httpctx->server = server;
    httpctx->megaApi = server->megaApi;
    httpctx->parser.data = httpctx;
    httpctx->tcphandle.data = httpctx;
    httpctx->asynchandle.data = httpctx;

    return httpctx;
}

void MegaHTTPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf)
{
    MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx);

    LOG_debug << "Received " << nread << " bytes";

    ssize_t parsed = -1;
    if (nread >= 0)
    {
        if (nread == 0 && httpctx->parser.method == HTTP_PUT) //otherwise it will fail for files >65k in GVFS-DAV
        {
            LOG_debug << " Skipping parsing 0 length data for HTTP_PUT";
            parsed = 0;
        }
        else
        {
            parsed = http_parser_execute(&httpctx->parser, &parsercfg, buf->base, nread);
        }
    }

    LOG_verbose << " at onDataReceived, received " << nread << " parsed = " << parsed;

    if (parsed < 0 || nread < 0 || parsed < nread || httpctx->parser.upgrade)
    {
        LOG_debug << "Finishing request. Connection reset by peer or unsupported data";
        closeConnection(httpctx);
    }
}

void MegaHTTPServer::processWriteFinished(MegaTCPContext* tcpctx, int status)
{
    MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx);
    if (httpctx->finished)
    {
        LOG_debug << "HTTP link closed, ignoring the result of the write";
        return;
    }

    httpctx->bytesWritten += httpctx->lastBufferLen;
    LOG_verbose << "Bytes written: " << httpctx->lastBufferLen << " Remaining: " << (httpctx->size - httpctx->bytesWritten);
    httpctx->lastBuffer = NULL;

    if (status < 0 || httpctx->size == httpctx->bytesWritten)
    {
        if (status < 0)
        {
            LOG_warn << "Finishing request. Write failed: " << status;
        }
        else
        {
            LOG_debug << "Finishing request. All data sent";
            if (httpctx->resultCode == API_EINTERNAL)
            {
                httpctx->resultCode = API_OK;
            }
        }

        closeConnection(httpctx);
        return;
    }

    uv_mutex_lock(&httpctx->mutex);
    if (httpctx->lastBufferLen)
    {
        httpctx->streamingBuffer.freeData(httpctx->lastBufferLen);
    }

    if (httpctx->pause)
    {
        if (httpctx->streamingBuffer.availableSpace() >= DirectReadSlot::MAX_DELIVERY_CHUNK)
        {
            httpctx->pause = false;
            m_off_t start = httpctx->rangeStart + httpctx->rangeWritten + httpctx->streamingBuffer.availableData();
            m_off_t len =  httpctx->rangeEnd - httpctx->rangeStart - httpctx->rangeWritten - httpctx->streamingBuffer.availableData();

            LOG_debug << "[Streaming] Resuming streaming from " << start << " len: " << len
                      << " " << httpctx->streamingBuffer.bufferStatus();
            httpctx->megaApi->startStreaming(httpctx->node, start, len, httpctx);
        }
    }
    httpctx->lastBufferLen = 0;
    uv_mutex_unlock(&httpctx->mutex);

    uv_async_send(&httpctx->asynchandle);
}

void MegaHTTPServer::processOnAsyncEventClose(MegaTCPContext* tcpctx)
{
    MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx);

    if (httpctx->resultCode == API_EINTERNAL)
    {
        httpctx->resultCode = API_EINCOMPLETE;
    }

    if (httpctx->transfer)
    {
        httpctx->megaApi->cancelTransfer(httpctx->transfer.get());
        httpctx->megaApi->fireOnStreamingFinish(httpctx->transfer.release(), make_unique<MegaErrorPrivate>(httpctx->resultCode)); // transfer will be deleted in fireOnStreamingFinish
    }

    delete httpctx->node;
    httpctx->node = NULL;
}

bool MegaHTTPServer::respondNewConnection(MegaTCPContext* tcpctx)
{
    return true;
}

void MegaHTTPServer::processOnExitHandleClose(MegaTCPServer *tcpServer)
{
}

MegaHTTPServer::~MegaHTTPServer()
{
    // if not stopped, the uv thread might want to access a pointer to this.
    // though this is done in the parent destructor, it could try to access it after vtable has been erased
    stop();
}

bool MegaHTTPServer::isHandleWebDavAllowed(handle h)
{
    return allowedWebDavHandles.count(h);
}

void MegaHTTPServer::clearAllowedHandles()
{
    allowedWebDavHandles.clear();
    MegaTCPServer::clearAllowedHandles();
}

set<handle> MegaHTTPServer::getAllowedWebDavHandles()
{
    return allowedWebDavHandles;
}

void MegaHTTPServer::removeAllowedWebDavHandle(MegaHandle handle)
{
    allowedWebDavHandles.erase(handle);
}

void MegaHTTPServer::enableFileServer(bool enable)
{
    this->fileServerEnabled = enable;
}

void MegaHTTPServer::enableFolderServer(bool enable)
{
    this->folderServerEnabled = enable;
}

void MegaHTTPServer::enableOfflineAttribute(bool enable)
{
    this->offlineAttribute = enable;
}

bool MegaHTTPServer::isFileServerEnabled()
{
    return fileServerEnabled;
}

bool MegaHTTPServer::isFolderServerEnabled()
{
    return folderServerEnabled;
}

bool MegaHTTPServer::isOfflineAttributeEnabled()
{
    return offlineAttribute;
}

bool MegaHTTPServer::isSubtitlesSupportEnabled()
{
    return subtitlesSupportEnabled;
}

void MegaHTTPServer::enableSubtitlesSupport(bool enable)
{
    this->subtitlesSupportEnabled = enable;
}

char *MegaHTTPServer::getWebDavLink(MegaNode *node)
{
    allowedWebDavHandles.insert(node->getHandle());
    return getLink(node);
}

int MegaHTTPServer::onMessageBegin(http_parser *)
{
    return 0;
}

int MegaHTTPServer::onHeadersComplete(http_parser *)
{
    return 0;
}

int MegaHTTPServer::onUrlReceived(http_parser *parser, const char *url, size_t length)
{
    MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data;
    httpctx->path.assign(url, length);
    LOG_debug << "URL received: " << httpctx->path;

    if (length < 9 || url[0] != '/' || (length >= 10 && url[9] != '/' && url[9] != '!'))
    {
        LOG_debug << "URL without node handle";
        return 0;
    }

    size_t index = 9;
    httpctx->nodehandle.assign(url + 1, 8);
    LOG_debug << "Node handle: " << httpctx->nodehandle;

    if (length > 53 && url[index] == '!')
    {
        httpctx->nodekey.assign(url + 10, 43);
        LOG_debug << "Link key: " << httpctx->nodekey;
        index = 53;

        if (length > 54 && url[index] == '!')
        {
           const char* startsize = url + index + 1;
           const char* endsize = strstr(startsize, "/");
           const char* endparam = strstr(startsize, "!");
           if (endsize && *startsize >= '0' && *startsize <= '9')
           {
               char* endptr;
               m_off_t size = strtoll(startsize, &endptr, 10);
               if ((endptr == endsize || endptr == endparam) && errno != ERANGE)
               {
                   httpctx->nodesize = size;
                   LOG_debug << "Link size: " << size;
                   index += (endptr - startsize) + 1;
                   if (url[index] == '!')
                   {
                       const char *typeauth = url + index + 1;
                       index++;

                       const char *ptr = url + index + 1;
                       string auth;
                       auth.assign(ptr, endsize - ptr);
                       if (*typeauth == 'p')
                       {
                           assert(auth.size() == 8);
                           httpctx->nodepubauth = auth;
                           LOG_debug << "Link public auth: " << auth;
                       }
                       else if (*typeauth == 'c')
                       {
                           assert(auth.size() == 8);
                           httpctx->nodechatauth = auth;
                           LOG_debug << "Chat link auth: " << auth;
                       }
                       else if (*typeauth == 'f')
                       {
                           httpctx->nodeprivauth = auth;
                           LOG_debug << "Link private auth: " << auth;
                       }
                       else
                       {
                           LOG_err << "Unknown type of auth token: " << *typeauth;
                       }
                       index += auth.size() + 1;
                   }
               }
           }
        }
    }

    if (length > index && url[index] != '/')
    {
        LOG_warn << "Invalid URL";
        return 0;
    }

    index++;
    if (length > index)
    {
        string nodename(url + index, length - index);

        //get subpath (used in webdav)
        size_t psep = nodename.find("/");
        if (psep != string::npos)
        {
            string subpathrelative = nodename.substr(psep + 1);
            nodename = nodename.substr(0, psep);
            URLCodec::unescape(&subpathrelative, &httpctx->subpathrelative);
            LOG_debug << "subpathrelative: " << httpctx->subpathrelative;
        }

        URLCodec::unescape(&nodename, &httpctx->nodename);
        LocalPath::utf8_normalize(&httpctx->nodename);
        LOG_debug << "Node name: " << httpctx->nodename;
    }

    return 0;
}

int MegaHTTPServer::onHeaderField(http_parser *parser, const char *at, size_t length)
{
    MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data;
    httpctx->lastheader = string(at, length);
    tolower_string(httpctx->lastheader);

    if (length == 5 && !memcmp(at, "Range", 5))
    {
        httpctx->range = true;
        LOG_debug << "Range header detected";
    }

    return 0;
}

int MegaHTTPServer::onHeaderValue(http_parser *parser, const char *at, size_t length)
{
    MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data;
    string value(at, length);
    size_t index;
    char *endptr;

    LOG_verbose << " onHeaderValue: " << httpctx->lastheader << " = " << value;
    if (httpctx->lastheader == "depth")
    {
        httpctx->depth = atoi(value.c_str());
    }
    else if (httpctx->lastheader == "host")
    {
        httpctx->host = value;
    }
    else if (httpctx->lastheader == "destination")
    {
        httpctx->destination = value;
    }
    else if (httpctx->lastheader == "overwrite")
    {
        httpctx->overwrite = (value == "T");
    }
    else if (httpctx->range)
    {
        LOG_debug << "Range header value: " << value;
        httpctx->range = false;
        if (length > 7 && !memcmp(at, "bytes=", 6)
                && ((index = value.find_first_of('-')) != string::npos))
        {
            endptr = (char *)value.c_str();
            unsigned long long number = strtoull(value.c_str() + 6, &endptr, 10);
            if (endptr == value.c_str() || *endptr != '-' || number == ULLONG_MAX)
            {
                return 0;
            }

            httpctx->rangeStart = number;
            if (length > (index + 1))
            {
                number = strtoull(value.c_str() + index + 1, &endptr, 10);
                if (endptr == value.c_str() || *endptr != '\0' || number == ULLONG_MAX)
                {
                    return 0;
                }
                httpctx->rangeEnd = number;
            }
            LOG_debug << "Range value parsed: " << httpctx->rangeStart << " - " << httpctx->rangeEnd;
        }
    }
    return 0;
}

int MegaHTTPServer::onBody(http_parser *parser, const char *b, size_t n)
{
    MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data;

    if (parser->method == HTTP_PUT)
    {
        //create tmp file with contents in messageBody
        if (!httpctx->tmpFileAccess)
        {
            httpctx->tmpFileName=httpctx->server->basePath;
            httpctx->tmpFileName.append("httputfile");
            httpctx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false));

            string ext;
            LocalPath localpath = LocalPath::fromAbsolutePath(httpctx->path);
            if (httpctx->server->fsAccess->getextension(localpath, ext))
            {
                httpctx->tmpFileName.append(ext);
            }

            httpctx->tmpFileAccess = httpctx->server->fsAccess->newfileaccess();
            LocalPath localPath = LocalPath::fromAbsolutePath(httpctx->tmpFileName);
            httpctx->server->fsAccess->unlinklocal(localPath);
            if (!httpctx->tmpFileAccess->fopen(localPath, false, true, FSLogging::logOnError))
            {
                returnHttpCode(httpctx, 500); //is it ok to have a return here (not int onMessageComplete)?
                return 0;
            }
        }

        if (!httpctx->tmpFileAccess->fwrite((const byte*)b, static_cast<unsigned>(n), httpctx->messageBodySize))
        {
            returnHttpCode(httpctx, 500);
            return 0;
        }
        httpctx->messageBodySize += n;
    }
    else
    {
        char *newbody = new char[n + httpctx->messageBodySize];
        memcpy(newbody, httpctx->messageBody, httpctx->messageBodySize);
        memcpy(newbody + httpctx->messageBodySize, b, n);
        httpctx->messageBodySize += n;
        delete [] httpctx->messageBody;
        httpctx->messageBody = newbody;
    }
    return 0;
}

string MegaHTTPServer::getWebDavProfFindNodeContents(MegaNode *node, string baseURL, bool offlineAttribute)
{
    std::ostringstream web;

    web << "<d:response>\r\n"
           "<d:href>" << webdavurlescape(baseURL) << "</d:href>\r\n"
           "<d:propstat>\r\n"
           "<d:status>HTTP/1.1 200 OK</d:status>\r\n"
           "<d:prop>\r\n"
           "<d:displayname>"<< webdavnameescape(node->getName()) << "</d:displayname>\r\n"

           "<d:creationdate>" << rfc1123_datetime(node->getCreationTime()) << "</d:creationdate>"
           "<d:getlastmodified>" << rfc1123_datetime(node->getModificationTime()) << "</d:getlastmodified>"
           ;

    if (offlineAttribute)
    {
        //(perhaps this could be based on number of files / or even better: size)
          web << "<Z:Win32FileAttributes>00001000</Z:Win32FileAttributes> \r\n"; //FILE_ATTRIBUTE_OFFLINE
//        web << "<Z:Win32FileAttributes>00040000</Z:Win32FileAttributes> \r\n"; // FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS (no actual difference)
    }

    if (node->isFolder())
    {
        web << "<d:resourcetype>\r\n"
               "<d:collection />\r\n"
               "</d:resourcetype>\r\n";
    }
    else
    {
        web << "<d:resourcetype />\r\n";
        web << "<d:getcontentlength>" << node->getSize() << "</d:getcontentlength>\r\n";
    }
    web << "</d:prop>\r\n"
           "</d:propstat>\r\n";
    web << "</d:response>\r\n";
    return web.str();
}

string MegaHTTPServer::getWebDavPropFindResponseForNode(string baseURL, string subnodepath, MegaNode *node, MegaHTTPContext* httpctx)
{
    std::ostringstream response;
    std::ostringstream web;

    web << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
           "<d:multistatus xmlns:d=\"DAV:\" xmlns:Z=\"urn:schemas-microsoft-com::\">\r\n";

    string subbaseURL = baseURL + subnodepath;
    if (node->isFolder() && subbaseURL.size() && subbaseURL.at(subbaseURL.size() - 1) != '/')
    {
        subbaseURL.append("/");
    }
    MegaHTTPServer* httpserver = dynamic_cast<MegaHTTPServer *>(httpctx->server);

    web << getWebDavProfFindNodeContents(node, subbaseURL, httpserver->isOfflineAttributeEnabled());
    if (node->isFolder() && (httpctx->depth != 0))
    {
        MegaNodeList *children = httpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE);
        for (int i = 0; i < children->size(); i++)
        {
            MegaNode *child = children->get(i);
            string childURL = subbaseURL + child->getName();
            web << getWebDavProfFindNodeContents(child, childURL, httpserver->isOfflineAttributeEnabled());
        }
        delete children;
    }

    web << "</d:multistatus>"
           "\r\n";

    string sweb = web.str();
    response << "HTTP/1.1 207 Multi-Status\r\n"
                "content-length: " << sweb.size() << "\r\n"
                                                     "content-type: application/xml; charset=utf-8\r\n"
                                                     "server: MEGAsdk\r\n"
                                                     "\r\n";
    response << sweb;
    httpctx->resultCode = API_OK;
    return response.str();
}

string MegaHTTPServer::getResponseForNode(MegaNode *node, MegaHTTPContext* httpctx)
{
    MegaNode *parent = httpctx->megaApi->getParentNode(node);
    MegaNodeList *children = httpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE);
    std::ostringstream response;
    std::ostringstream web;

    // Title
    web << "<title>MEGA</title>";

    // Styles
    web << "<head><meta charset=\"utf-8\" /><style>"
           ".folder {"
           "padding: 0;"
           "width: 24px;"
           "height: 24px;"
           "margin: 0 0 0 -2px;"
           "display: block;"
           "position: absolute;"
           "background-image: url(https://eu.static.mega.co.nz/3/images/mega/nw-fm-sprite_v12.svg);"
           "background-position: -14px -7465px;"
           "background-repeat: no-repeat;}"

           ".file {"
           "padding: 0;"
           "width: 24px;"
           "height: 24px;"
           "margin: 0 0 0 -6px;"
           "display: block;"
           "position: absolute;"
           "background-image: url(https://eu.static.mega.co.nz/3/images/mega/nw-fm-sprite_v12.svg);"
           "background-position: -7px -1494px;"
           "background-repeat: no-repeat;} "

           ".headerimage {"
           "padding: 0 8px 0 46px;"
           "width: 100%;"
           "height: 24px;"
           "margin: 0 0 0 -12px;"
           "display: block;"
           "position: absolute;"
           "background-image: url(https://eu.static.mega.co.nz/3/images/mega/nw-fm-sprite_v12.svg);"
           "background-position: 5px -1000px;"
           "line-height: 23px;"
           "background-repeat: no-repeat;} "

           ".headertext {"
           "line-height: 23px;"
           "color: #777777;"
           "font-size: 18px;"
           "font-weight: bold;"
           "display: block;"
           "position: absolute;"
           "line-height: 23px;}"

           "a {"
           "text-decoration: none; }"

           ".text {"
           "height: 24px;"
           "padding: 0 10px 0 26px;"
           "word-break: break-all;"
           "white-space: pre-wrap;"
           "overflow: hidden;"
           "max-width: 100%;"
           "text-decoration: none;"
           "-moz-box-sizing: border-box;"
           "-webkit-box-sizing: border-box;"
           "box-sizing: border-box;"
           "font-size: 13px;"
           "line-height: 23px;"
           "color: #666666;}"
           "</style></head>";

    // Folder path
    web << "<span class=\"headerimage\"><span class=\"headertext\">";
    char *path = httpctx->megaApi->getNodePath(node);
    if (path)
    {
        web << path;
        delete [] path;
    }
    else
    {
        web << node->getName();
    }
    web << "</span></span><br /><br />";

    // Child nodes
    web << "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" style=\"width: auto;\">";
    if (parent)
    {
        web << "<tr><td>";
        char *base64Handle = parent->getBase64Handle();
        if (httpctx->megaApi->httpServerGetRestrictedMode() == MegaApi::TCP_SERVER_ALLOW_ALL)
        {
            web << "<a href=\"/" << base64Handle << "/" << parent->getName();
        }
        else
        {
            web << "<a href=\"" << "../" << parent->getName();
        }

        web << "\"><span class=\"folder\"></span><span class=\"text\">..</span></a>";
        delete [] base64Handle;
        delete parent;
        web << "</td></tr>";
    }

    for (int i = 0; i < children->size(); i++)
    {
        web << "<tr><td>";
        MegaNode *child = children->get(i);
        char *base64Handle = child->getBase64Handle();
        if (httpctx->megaApi->httpServerGetRestrictedMode() == MegaApi::TCP_SERVER_ALLOW_ALL)
        {
            web << "<a href=\"/" << base64Handle << "/" << child->getName();
        }
        else
        {
            web << "<a href=\"" << node->getName() << "/" << child->getName();
        }
        web << "\"><span class=\"" << (child->isFile() ? "file" : "folder") << "\"></span><span class=\"text\">"
            << child->getName() << "</span></a>";
        delete [] base64Handle;

        if (!child->isFile())
        {
            web << "</td><td>";
        }
        else
        {
            unsigned const long long KB = 1024;
            unsigned const long long MB = 1024 * KB;
            unsigned const long long GB = 1024 * MB;
            unsigned const long long TB = 1024 * GB;

            web << "</td><td><span class=\"text\">";
            unsigned long long bytes = child->getSize();
            if (bytes > TB)
                web << double(((unsigned long long)((100 * bytes) / TB)))/100.0 << " TB";
            else if (bytes > GB)
                web << double(((unsigned long long)((100 * bytes) / GB)))/100.0 << " GB";
            else if (bytes > MB)
                web << double(((unsigned long long)((100 * bytes) / MB)))/100.0 << " MB";
            else if (bytes > KB)
                web << double(((unsigned long long)((100 * bytes) / KB)))/100.0 << " KB";
            web << "</span>";
        }
        web << "</td></tr>";
    }
    web << "</table>";
    delete children;

    string sweb = web.str();
    response << "HTTP/1.1 200 OK\r\n"
        << "Content-Type: text/html; charset=utf-8\r\n"
        << "Connection: close\r\n"
        << "Content-Length: " << sweb.size() << "\r\n"
        << "Access-Control-Allow-Origin: *\r\n"
        << "\r\n";

    if (httpctx->parser.method != HTTP_HEAD)
    {
        response << sweb;
    }
    httpctx->resultCode = API_OK;

    return response.str();
}

string MegaHTTPServer::getHTTPMethodName(int httpmethod)
{
    switch (httpmethod)
    {
    case HTTP_DELETE:
        return "HTTP_DELETE";
    case HTTP_GET:
        return "HTTP_GET";
    case HTTP_HEAD:
        return "HTTP_HEAD";
    case HTTP_POST:
        return "HTTP_POST";
    case HTTP_PUT:
        return "HTTP_PUT";
    case HTTP_CONNECT:
        return "HTTP_CONNECT";
    case HTTP_OPTIONS:
        return "HTTP_OPTIONS";
    case HTTP_TRACE:
        return "HTTP_TRACE";
    case HTTP_COPY:
        return "HTTP_COPY";
    case HTTP_LOCK:
        return "HTTP_LOCK";
    case HTTP_MKCOL:
        return "HTTP_MKCOL";
    case HTTP_MOVE:
        return "HTTP_MOVE";
    case HTTP_PROPFIND:
        return "HTTP_PROPFIND";
    case HTTP_PROPPATCH:
        return "HTTP_PROPPATCH";
    case HTTP_SEARCH:
        return "HTTP_SEARCH";
    case HTTP_UNLOCK:
        return "HTTP_UNLOCK";
    case HTTP_BIND:
        return "HTTP_BIND";
    case HTTP_REBIND:
        return "HTTP_REBIND";
    case HTTP_UNBIND:
        return "HTTP_UNBIND";
    case HTTP_ACL:
        return "HTTP_ACL";
    case HTTP_REPORT:
        return "HTTP_REPORT";
    case HTTP_MKACTIVITY:
        return "HTTP_MKACTIVITY";
    case HTTP_CHECKOUT:
        return "HTTP_CHECKOUT";
    case HTTP_MERGE:
        return "HTTP_MERGE";
    case HTTP_MSEARCH:
        return "HTTP_MSEARCH";
    case HTTP_NOTIFY:
        return "HTTP_NOTIFY";
    case HTTP_SUBSCRIBE:
        return "HTTP_SUBSCRIBE";
    case HTTP_UNSUBSCRIBE:
        return "HTTP_UNSUBSCRIBE";
    case HTTP_PATCH:
        return "HTTP_PATCH";
    case HTTP_PURGE:
        return "HTTP_PURGE";
    case HTTP_MKCALENDAR:
        return "HTTP_MKCALENDAR";
    case HTTP_LINK:
        return "HTTP_LINK";
    case HTTP_UNLINK:
        return "HTTP_UNLINK";
    default:
        return "HTTP_UNKOWN";
    }
}

string MegaHTTPServer::getHTTPErrorString(int errorcode)
{
    switch (errorcode)
    {
    case 200:
        return "OK";
    case 201:
        return "Created";
    case 204:
        return "No Content";
    case 403:
        return "Forbidden";
    case 404:
        return "Not Found";
    case 409:
        return "Conflict";
    case 412:
        return "Precondition Failed";
    case 423:
        return "Locked";
    case 500:
        return "Internal Server Error";
    case 502:
        return "Bad Gateway";
    case 503:
        return "Service Unavailable";
    case 507:
        return "Insufficient Storage";
    case 508:
        return "Loop Detected";
    default:
        return "Unknown Error";
    }
}

void MegaHTTPServer::returnHttpCodeBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e, bool synchronous)
{
    int reqError = e->getErrorCode();
    int httpreturncode = 500;

    switch(reqError)
    {
    case API_EACCESS:
        httpreturncode = 403;
        break;
    case API_EOVERQUOTA:
    case API_EGOINGOVERQUOTA:
        httpreturncode = 507;
        break;
    case API_EAGAIN:
    case API_ERATELIMIT:
    case API_ETEMPUNAVAIL:
        httpreturncode = 503;
        break;
    case API_ECIRCULAR:
        httpreturncode = 508;
        break;
    default:
        httpreturncode = 500;
        break;
    }

    LOG_debug << "HTTP petition failed. request error = " << reqError << " HTTP status to return = " << httpreturncode;
    string errorMessage = e->getErrorString(reqError);
    return returnHttpCode(httpctx, httpreturncode, errorMessage, synchronous);
}

void MegaHTTPServer::returnHttpCode(MegaHTTPContext* httpctx, int errorCode, string errorMessage, bool synchronous)
{
    std::ostringstream response;
    response << "HTTP/1.1 " << errorCode << " "
             << (errorMessage.size() ? errorMessage : getHTTPErrorString(errorCode))
             << "\r\n"
                "Connection: close\r\n"
              << "\r\n";

    httpctx->resultCode = errorCode;
    string resstr = response.str();
    if (synchronous)
    {
        sendHeaders(httpctx, &resstr);
    }
    else
    {
        uv_mutex_lock(&httpctx->mutex_responses);
        httpctx->responses.push_back(resstr);
        uv_mutex_unlock(&httpctx->mutex_responses);
        uv_async_send(&httpctx->asynchandle);
    }
}

void MegaHTTPServer::returnHttpCodeAsyncBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e)
{
    return returnHttpCodeBasedOnRequestError(httpctx, e, false);
}

void MegaHTTPServer::returnHttpCodeAsync(MegaHTTPContext* httpctx, int errorCode, string errorMessage)
{
    return returnHttpCode(httpctx, errorCode, errorMessage, false);
}

int MegaHTTPServer::onMessageComplete(http_parser *parser)
{
    LOG_debug << "Message complete";
    MegaNode *node = NULL;
    std::ostringstream response;
    MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data;
    httpctx->bytesWritten = 0;
    httpctx->size = 0;
    httpctx->streamingBuffer.setMaxBufferSize(httpctx->server->getMaxBufferSize());
    httpctx->streamingBuffer.setMaxOutputSize(httpctx->server->getMaxOutputSize());

    MegaHTTPServer* httpserver = dynamic_cast<MegaHTTPServer *>(httpctx->server);

    switch (parser->method)
    {
    case HTTP_GET:
    case HTTP_POST:
    case HTTP_HEAD:
    case HTTP_OPTIONS:
    case HTTP_MOVE:
    case HTTP_PUT:
    case HTTP_DELETE:
    case HTTP_MKCOL:
    case HTTP_COPY:
    case HTTP_LOCK:
    case HTTP_UNLOCK:
    case HTTP_PROPPATCH:
    case HTTP_PROPFIND:
        LOG_debug << "Request method: " << getHTTPMethodName(parser->method);
        break;
    default:
        LOG_debug << "Method not allowed: " << getHTTPMethodName(parser->method);
        response << "HTTP/1.1 405 Method not allowed\r\n"
                    "Connection: close\r\n"
                    "\r\n";

        httpctx->resultCode = 405;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        return 0;
    }

    if (httpctx->path == "/favicon.ico")
    {
        LOG_debug << "Favicon requested";
        response << "HTTP/1.1 301 Moved Permanently\r\n"
                    "Location: ";
        response << MegaClient::MEGAURL;
        response << "/favicon.ico\r\n"
                    "Connection: close\r\n"
                    "\r\n";

        httpctx->resultCode = API_OK;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        return 0;
    }

    if (httpctx->path == "/")
    {
        node = httpctx->megaApi->getRootNode();
        char *base64Handle = node->getBase64Handle();
        httpctx->nodehandle = base64Handle;
        delete [] base64Handle;
        httpctx->nodename = node->getName();
    }
    else if (httpctx->nodehandle.size())
    {
        node = httpctx->megaApi->getNodeByHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str()));
    }

    if (!httpctx->nodehandle.size())
    {
        response << "HTTP/1.1 404 Not Found\r\n"
                    "Connection: close\r\n"
                  << "\r\n";
        httpctx->resultCode = 404;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        delete node;
        return 0;
    }

    handle h = MegaApi::base64ToHandle(httpctx->nodehandle.c_str());
    if (!httpctx->server->isHandleAllowed(h))
    {
        LOG_debug << "Forbidden due to the restricted mode";
        response << "HTTP/1.1 403 Forbidden\r\n"
                    "Connection: close\r\n"
                  << "\r\n";

        httpctx->resultCode = 403;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        delete node;
        return 0;
    }

    if (parser->method == HTTP_OPTIONS)
    {
        LOG_debug << "Returning HTTP_OPTIONS for a " << (httpserver->isHandleWebDavAllowed(h) ? "" : "non ") << "WEBDAV URI";
        response << "HTTP/1.1 200 OK\r\n";

        if (httpserver->isHandleWebDavAllowed(h))
        {
            response << "Allow: GET, POST, HEAD, OPTIONS, PROPFIND, MOVE, PUT, DELETE, MKCOL, COPY, LOCK, UNLOCK, PROPPATCH\r\n"
                        "dav: 1, 2 \r\n"; // 2 requires LOCK to be fully functional
        }
        else
        {
            response << "Allow: GET,POST,HEAD,OPTIONS\r\n";
        }

        response << "content-length: 0\r\n"
        "Connection: close\r\n"
        "\r\n";

        httpctx->resultCode = API_OK;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        delete node;
        return 0;
    }

    //if webdav method, check is handle is a valid webdav
    if ((parser->method != HTTP_GET) && (parser->method != HTTP_POST)
            && (parser->method != HTTP_PUT) && (parser->method != HTTP_HEAD)
            && !httpserver->isHandleWebDavAllowed(h))
    {
        LOG_debug << "Forbidden due to not webdav allowed";
        returnHttpCode(httpctx, 405);
        delete node;
        return 0;
    }

    if (!node)
    {
        if (!httpctx->nodehandle.size() || !httpctx->nodekey.size())
        {
            LOG_warn << "URL not found: " << httpctx->path;
            response << "HTTP/1.1 404 Not Found\r\n"
                        "Connection: close\r\n"
                      << "\r\n";

            httpctx->resultCode = 404;
            string resstr = response.str();
            sendHeaders(httpctx, &resstr);
            return 0;
        }
        else if (httpctx->nodesize >= 0)
        {
            LOG_debug << "Getting foreign node";
            node = httpctx->megaApi->createForeignFileNode(
                        h, httpctx->nodekey.c_str(),
                        httpctx->nodename.c_str(),
                        httpctx->nodesize,
                        -1, UNDEF, httpctx->nodeprivauth.c_str(), httpctx->nodepubauth.c_str(), httpctx->nodechatauth.c_str());
        }
        else
        {
            handle h = MegaApi::base64ToHandle(httpctx->nodehandle.c_str());
            string link = MegaClient::publicLinkURL(httpctx->megaApi->getMegaClient()->mNewLinkFormat, TypeOfLink::FILE, h, httpctx->nodekey.c_str());
            LOG_debug << "Getting public link: " << link;
            httpctx->megaApi->getPublicNode(link.c_str(), httpctx);
            httpctx->transfer.reset(new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD));
            httpctx->transfer->setPath(httpctx->path.c_str());
            httpctx->transfer->setFileName(httpctx->nodename.c_str());
            httpctx->transfer->setNodeHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str()));
            httpctx->transfer->setStartTime(Waiter::ds);
            return 0;
        }
    }

    if (node && httpctx->nodename != node->getName())
    {
        if (parser->method == HTTP_PROPFIND)
        {
            response << "HTTP/1.1 404 Not Found\r\n"
                        "Connection: close\r\n"
                     << "\r\n";

            httpctx->resultCode = 404;
            string resstr = response.str();
            sendHeaders(httpctx, &resstr);
            delete node;
            return 0;
        }
        else
        {
            //Subtitles support
            bool subtitles = false;

            if (httpserver->isSubtitlesSupportEnabled())
            {
                string originalname = node->getName();
                string::size_type dotpos = originalname.find_last_of('.');
                if (dotpos != string::npos)
                {
                    originalname.resize(dotpos);
                }

                if (dotpos == httpctx->nodename.find_last_of('.') && !memcmp(originalname.data(), httpctx->nodename.data(), originalname.size()))
                {
                    LOG_debug << "Possible subtitles file";
                    MegaNode *parent = httpctx->megaApi->getParentNode(node);
                    if (parent)
                    {
                        MegaNode *child = httpctx->megaApi->getChildNode(parent, httpctx->nodename.c_str());
                        if (child)
                        {
                            LOG_debug << "Matching file found: " << httpctx->nodename << " - " << node->getName();
                            subtitles = true;
                            delete node;
                            node = child;
                        }
                        delete parent;
                    }
                }
            }

            if (!subtitles)
            {
                LOG_warn << "Invalid name: " << httpctx->nodename << " - " << node->getName();
                response << "HTTP/1.1 404 Not Found\r\n"
                            "Connection: close\r\n"
                         << "\r\n";

                httpctx->resultCode = 404;
                string resstr = response.str();
                sendHeaders(httpctx, &resstr);
                delete node;
                return 0;
            }
        }
    }

    MegaNode *baseNode = NULL;
    if (httpctx->subpathrelative.size())
    {
        string subnodepath = httpctx->subpathrelative;
        //remove trailing "/"
        size_t seppos = subnodepath.find_last_of("/");
        while ( (seppos != string::npos) && ((seppos + 1) == subnodepath.size()) )
        {
            subnodepath = subnodepath.substr(0,seppos);
            seppos = subnodepath.find_last_of("/");
        }

        MegaNode *subnode = httpctx->megaApi->getNodeByPath(subnodepath.c_str(), node);
        if (parser->method != HTTP_PUT && parser->method != HTTP_MKCOL && !subnode)
        {
            returnHttpCode(httpctx, 404);
            delete node;
            return 0;
        }
        else
        {
            baseNode = node;
            node = subnode;
        }
    }

    if (parser->method == HTTP_PROPFIND)
    {
        string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://"
                + httpctx->host + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/";
        string resstr = getWebDavPropFindResponseForNode(baseURL, httpctx->subpathrelative, node, httpctx);
        sendHeaders(httpctx, &resstr);
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_UNLOCK)
    {
        // let's create a minimum unlock compliant response
        returnHttpCode(httpctx, 204);
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_PROPPATCH)
    {
        std::ostringstream web;

//        Typicall Body received: //TODO: actualy update creation and mod times? does not seem to be required (it seems PUT updates them ... with what time: file or PUT time?)
//        <?xml version="1.0" encoding="utf-8" ?>
//        <D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:">
//        <D:set>
//        <D:prop>
//        <Z:Win32CreationTime>Tue, 20 Feb 2018 18:00:20 GMT</Z:Win32CreationTime>
//        <Z:Win32LastAccessTime>Tue, 20 Feb 2018 18:00:21 GMT</Z:Win32LastAccessTime>
//        <Z:Win32LastModifiedTime>Tue, 20 Feb 2018 18:00:21 GMT</Z:Win32LastModifiedTime>
//        <Z:Win32FileAttributes>00000020</Z:Win32FileAttributes>
//        </D:prop>
//        </D:set>
//        </D:propertyupdate>

        web << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
               "<d:multistatus xmlns:d=\"DAV:\">\r\n"
                  "<d:response>\r\n"
                  //"<d:href>" << urlelement << "</d:href>\r\n" //this should come from the input but seems to be not required!
                    "<d:propstat>\r\n"
                      "<d:status>HTTP/1.1 200 OK</d:status>\r\n"
                        "<d:prop>\r\n"
                        // Here we might want to include the updated properties.
                        //we might want to to do so with original namespace?(not sure if that makes sense). e.g:
//                                 "<Z:Win32CreationTime/>\r\n"
                        "</d:prop>\r\n"
                    "</d:propstat>\r\n"
                  "</d:response>\r\n"
                "</d:multistatus>\r\n\r\n";

        string sweb = web.str();

        response << "HTTP/1.1 207 Multi-Status\r\n"
                    "Content-Type: application/xml; charset=\"utf-8\" \r\n"
                    "Content-Length: " << sweb.size() << "\r\n\r\n";

        response << sweb;
        httpctx->resultCode = 207;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_LOCK)
    {
        std::ostringstream web;

        // let's create a minimum lock compliant response. we should actually provide locking functionality based on URL
        web << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
          "<D:prop xmlns:D=\"DAV:\">\r\n"
            "<D:lockdiscovery>\r\n"
              "<D:activelock>\r\n"
                "<D:locktype><D:write/></D:locktype>\r\n"
                "<D:lockscope><D:exclusive/></D:lockscope>\r\n"
//                "<D:depth>infinity</D:depth>\r\n" // read from req?
                "<D:owner>\r\n"
//                  "<D:href>" << owner << "</D:href>\r\n" // should be read from req body
                "</D:owner>\r\n"
//                "<D:timeout>Second-604800</D:timeout>\r\n"
                "<D:locktoken>\r\n"
                  //"<D:href>urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4</D:href>\r\n" //An unique identifier is required
               "<D:href>urn:uuid:this-is-a-fake-lock</D:href>\r\n" //An unique identifier is required
                "</D:locktoken>\r\n"
                "<D:lockroot>\r\n"
//                  "<D:href>" << urlelement << "</D:href>\r\n"  // should be read from req body
                "</D:lockroot>\r\n"
              "</D:activelock>\r\n"
            "</D:lockdiscovery>\r\n"
          "</D:prop>\r\n\r\n";

        string sweb = web.str();
        response << "HTTP/1.1 200 OK\r\n"
          "Lock-Token: <urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4> \r\n"
          "Content-Type: application/xml; charset=\"utf-8\" \r\n"
          "Content-Length: " << sweb.size() << "\r\n\r\n";

        response << sweb;
        httpctx->resultCode = 200;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_DELETE)
    {
        if (!node)
        {
            returnHttpCode(httpctx, 404);
            delete node;
            delete baseNode;
            return 0;
        }

        httpctx->megaApi->remove(node, false, httpctx);
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_MKCOL)
    {
//        201 (Created)	The collection was created.
//        401 (Access Denied)	Resource requires authorization or authorization was denied.
//        403 (Forbidden)	The server does not allow collections to be created at the specified location, or the parent collection of the specified request URI exists but cannot accept members.
//        405 (Method Not Allowed)	The MKCOL method can only be performed on a deleted or non-existent resource.
//        409 (Conflict)	A resource cannot be created at the destination URI until one or more intermediate collections are created.
//        415 (Unsupported Media Type)	The request type of the body is not supported by the server.
//        507 (Insufficient Storage)	The destination resource does not have sufficient storage space.
        if (node)
        {
            returnHttpCode(httpctx, 405);
            delete node;
            delete baseNode;
            return 0;
        }

        MegaNode *newParentNode = NULL;
        string newname;

        string dest = httpctx->subpathrelative;
        size_t seppos = dest.find_last_of("/");

        while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) )
        {
            dest = dest.substr(0,seppos);
            seppos = dest.find_last_of("/");
        }
        if (seppos == string::npos)
        {
            newParentNode = baseNode ? baseNode->copy() : node->copy();
            newname = dest;
        }
        else
        {
            if ((seppos + 1) < dest.size())
            {
                newname = dest.substr(seppos + 1);
            }
            string newparentpath = dest.substr(0, seppos);
            newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(),baseNode?baseNode:node);
        }
        if (!newParentNode)
        {
            returnHttpCode(httpctx, 409);
            delete node;
            delete baseNode;
            return 0;
        }

        httpctx->megaApi->createFolder(newname.c_str(), newParentNode, httpctx);
        delete newParentNode;
        delete node;
        delete baseNode;
        return 0;
    }
    else if (parser->method == HTTP_COPY)
    {
//        201 (Created)	The resource was successfully copied.
//        204 (No Content)	The source resource was successfully copied to a pre-existing destination resource.
//        403 (Forbidden)	The source URI and the destination URI are the same.
//        409 (Conflict)	A resource cannot be created at the destination URI until one or more intermediate collections are created.
//        412 (Precondition Failed)	Either the Overwrite header is "F" and the state of the destination resource is not null, or the method was used in a Depth: 0 transaction.
//        423 (Locked)	The destination resource is locked.
//        502 (Bad Gateway)	The COPY destination is located on a different server, which refuses to accept the resource.
//        507 (Insufficient Storage)	The destination resource does not have sufficient storage space.

        if (!node)
        {
            returnHttpCode(httpctx, 404);
            delete node;
            delete baseNode;
            return 0;
        }

        MegaNode *newParentNode = NULL;
        string newname;
        string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://"
                + httpctx->host + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/";

        string dest;
        URLCodec::unescape(&httpctx->destination, &dest);
        size_t posBase = dest.find(baseURL);
        if (posBase != 0) // Notice that if 2 WEBDAV locations are enabled we won't be able to copy between the 2
        {
            returnHttpCode(httpctx, 502); // The destination URI is located elsewhere
            delete node;
            delete baseNode;
            return 0;
        }

        dest = dest.substr(baseURL.size());
        MegaNode *destNode = httpctx->megaApi->getNodeByPath(dest.c_str(), baseNode ? baseNode : node);
        if (destNode)
        {
            if (node->getHandle() == destNode->getHandle())
            {
                returnHttpCode(httpctx, 403);
                delete node;
                delete baseNode;
                delete destNode;
                return 0;
            }
            else
            {
                //overwrite?
                if (httpctx->overwrite)
                {
                    newParentNode = httpctx->megaApi->getNodeByHandle(destNode->getParentHandle());
                }
                else
                {
                    returnHttpCode(httpctx, 412);
                    delete node;
                    delete baseNode;
                    delete destNode;
                    return 0;
                }
            }
        }

        if (!newParentNode)
        {
            size_t seppos = dest.find_last_of("/");
            while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) )
            {
                dest = dest.substr(0,seppos);
                seppos = dest.find_last_of("/");
            }
            if (seppos == string::npos)
            {
                newParentNode = baseNode?baseNode->copy():node->copy();
                newname = dest;
            }
            else
            {
                if ((seppos + 1) < dest.size())
                {
                    newname = dest.substr(seppos + 1);
                }
                string newparentpath = dest.substr(0, seppos);
                newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(),baseNode?baseNode:node);
            }
        }
        if (!newParentNode)
        {
            returnHttpCode(httpctx, 409);
            delete node;
            delete baseNode;
            return 0;
        }
        if (newname.size())
        {
            httpctx->megaApi->copyNode(node, newParentNode, newname.c_str(), httpctx);
        }
        else
        {
            httpctx->megaApi->copyNode(node, newParentNode, httpctx);
        }

        delete node;
        delete baseNode;
        delete newParentNode;
        return 0;
    }
    else if (parser->method == HTTP_PUT)
    {
        if (node && !httpctx->overwrite)
        {
            returnHttpCode(httpctx, 412);
            delete node;
            delete baseNode;
            return 0;
        }
        else
        {
            MegaNode *newParentNode = NULL;
            string newname;

            string dest = httpctx->subpathrelative;
            size_t seppos = dest.find_last_of("/");
            while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) )
            {
                dest = dest.substr(0,seppos);
                seppos = dest.find_last_of("/");
            }
            if (seppos == string::npos)
            {
                newParentNode = baseNode ? baseNode->copy() : node->copy();
                newname = dest;
            }
            else
            {
                if ((seppos + 1) < dest.size())
                {
                    newname = dest.substr(seppos + 1);
                }
                string newparentpath = dest.substr(0, seppos);
                newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(), baseNode ? baseNode : node);
            }

            if (!newParentNode)
            {
                returnHttpCode(httpctx, 409);
                delete node;
                delete baseNode;
                return 0;
            }

            if (!httpctx->tmpFileAccess) //put with no body contents
            {
                httpctx->tmpFileName=httpctx->server->basePath;
                httpctx->tmpFileName.append("httputfile");
                httpctx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false));
                string ext;
                if (httpctx->server->fsAccess->getextension(LocalPath::fromAbsolutePath(httpctx->path), ext))
                {
                    httpctx->tmpFileName.append(ext);
                }
                httpctx->tmpFileAccess = httpctx->server->fsAccess->newfileaccess();
                auto tmpFileNamePath = LocalPath::fromAbsolutePath(httpctx->tmpFileName);
                httpctx->server->fsAccess->unlinklocal(tmpFileNamePath);
                if (!httpctx->tmpFileAccess->fopen(tmpFileNamePath, false, true, FSLogging::logOnError))
                {
                    returnHttpCode(httpctx, 500);
                    delete node;
                    delete baseNode;
                    delete newParentNode;
                    return 0;
                }
            }

            FileSystemType fsType = httpctx->server->fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(httpctx->tmpFileName));
            httpctx->megaApi->startUpload(false, httpctx->tmpFileName.c_str(), newParentNode, newname.c_str(), nullptr,
                                                -1, 0, true, nullptr, false, false, fsType, CancelToken(), httpctx);

            delete node;
            delete baseNode;
            delete newParentNode;
            return 0;
        }
    }
    else if (parser->method == HTTP_MOVE)
    {
        //        201 (Created)	The resource was moved successfully and a new resource was created at the specified destination URI.
        //        204 (No Content)	The resource was moved successfully to a pre-existing destination URI.
        //        403 (Forbidden)	The source URI and the destination URI are the same.
        //        409 (Conflict)	A resource cannot be created at the destination URI until one or more intermediate collections are created.
        //        412 (Precondition Failed)	Either the Overwrite header is "F" and the state of the destination resource is not null, or the method was used in a Depth: 0 transaction.
        //        423 (Locked)	The destination resource is locked.
        //        502 (Bad Gateway)	The destination URI is located on a different server, which refuses to accept the resource.

        if (!node)
        {
            returnHttpCode(httpctx, 404);
            delete node;
            delete baseNode;
            return 0;
        }

        string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://" + httpctx->host
                + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/";

        string dest;
        URLCodec::unescape(&httpctx->destination, &dest);
        size_t posBase = dest.find(baseURL);
        if (posBase != 0) // Notice that if 2 WEBDAV locations are enabled we won't be able to copy between the 2
        {
            returnHttpCode(httpctx, 502); // The destination URI is located elsewhere
            delete node;
            delete baseNode;
            return 0;
        }
        dest = dest.substr(baseURL.size());
        MegaNode *destNode = httpctx->megaApi->getNodeByPath(dest.c_str(), baseNode ? baseNode : node);
        if (destNode)
        {
            if (node->getHandle() == destNode->getHandle())
            {
                returnHttpCode(httpctx, 403);
                delete node;
                delete baseNode;
                delete destNode;
                return 0;
            }
            else
            {
                //overwrite?
                if (httpctx->overwrite)
                {
                    httpctx->newParentNode = destNode->getParentHandle();
                    httpctx->newname = destNode->getName();
                    httpctx->nodeToMove = node->getHandle();
                    httpctx->megaApi->remove(destNode, false, httpctx);
                    delete node;
                    delete baseNode;
                    delete destNode;
                    return 0;
                }
                else
                {
                    returnHttpCode(httpctx, 412);
                    delete node;
                    delete baseNode;
                    delete destNode;
                    return 0;
                }
            }
        }
        else
        {
            MegaNode *newParentNode = NULL;
            size_t seppos = dest.find_last_of("/");
            httpctx->newname = dest;
            if (seppos == string::npos)
            {
                newParentNode = baseNode ? baseNode->copy() : node->copy();
            }
            else
            {
                if ((seppos + 1) < httpctx->newname.size())
                {
                    httpctx->newname = httpctx->newname.substr(seppos + 1);
                }
                string newparentpath = dest.substr(0, seppos);
                newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(), baseNode ? baseNode : node);
            }
            if (!newParentNode)
            {
                returnHttpCode(httpctx, 409);
                delete node;
                delete baseNode;
                return 0;
            }

            if (newParentNode->getHandle() == node->getHandle())
            {
                LOG_warn << "HTTP_MOVE trying to mov a node into itself";
                returnHttpCode(httpctx, 500);
                delete node;
                delete baseNode;
                delete newParentNode;
                return 0;
            }

            if (newParentNode->getHandle() != node->getParentHandle())
            {
                httpctx->megaApi->moveNode(node, newParentNode, httpctx);
            }
            else
            {
                httpctx->megaApi->renameNode(node, httpctx->newname.c_str(), httpctx);
            }
            delete newParentNode;
        }

        delete node;
        delete baseNode;
        return 0;
    }
    else //GET/POST/HEAD
    {
        if (node->isFolder())
        {
            if (!httpserver->isFolderServerEnabled())
            {
                response << "HTTP/1.1 403 Forbidden\r\n"
                            "Connection: close\r\n"
                          << "\r\n";

                httpctx->resultCode = 403;
                string resstr = response.str();
                sendHeaders(httpctx, &resstr);
                delete node;
                delete baseNode;
                return 0;
            }

            string resstr = getResponseForNode(node, httpctx);

            sendHeaders(httpctx, &resstr);
            delete node;
            delete baseNode;
            return 0;
        }

        //File node
        if (!httpserver->isFileServerEnabled())
        {
            response << "HTTP/1.1 403 Forbidden\r\n"
                        "Connection: close\r\n"
                      << "\r\n";

            httpctx->resultCode = 403;
            string resstr = response.str();
            sendHeaders(httpctx, &resstr);
            delete node;
            delete baseNode;
            return 0;
        }

        httpctx->transfer.reset(new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD));
        httpctx->transfer->setPath(httpctx->path.c_str());
        if (httpctx->nodename.size())
        {
            httpctx->transfer->setFileName(httpctx->nodename.c_str());
        }
        if (httpctx->nodehandle.size())
        {
            httpctx->transfer->setNodeHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str()));
        }
        httpctx->transfer->setStartTime(Waiter::ds);

        delete httpctx->node;
        httpctx->node = node;
        streamNode(httpctx);
    }
    delete baseNode;
    return 0;
}

int MegaHTTPServer::streamNode(MegaHTTPContext *httpctx)
{
    std::ostringstream response;
    MegaNode *node = httpctx->node;

    string name;
    const char *extension = NULL;
    const char *nodeName = httpctx->node->getName();
    if (nodeName)
    {
        name = nodeName;
    }

    string::size_type dotindex = name.find_last_of('.');
    if (dotindex != string::npos)
    {
        extension = name.c_str() + dotindex;
    }

    char *mimeType = MegaApi::getMimeType(extension);
    if (!mimeType)
    {
        mimeType = MegaApi::strdup("application/octet-stream");
    }

    m_off_t totalSize = node->getSize();
    m_off_t start = 0;
    m_off_t end = totalSize - 1;
    if (httpctx->rangeStart >= 0)
    {
        start = httpctx->rangeStart;
    }
    httpctx->rangeStart = start;

    if (httpctx->rangeEnd >= 0)
    {
        end = std::min(totalSize - 1, httpctx->rangeEnd);
    }
    httpctx->rangeEnd = end + 1;

    bool rangeRequested = (httpctx->rangeEnd - httpctx->rangeStart) != totalSize;

    m_off_t len = end - start + 1;
    if (totalSize && (start < 0 || start >= totalSize || end < 0 || end >= totalSize || len <= 0 || len > totalSize) )
    {
        response << "HTTP/1.1 416 Requested Range Not Satisfiable\r\n"
            << "Content-Type: " << mimeType << "\r\n"
            << "Connection: close\r\n"
            << "Access-Control-Allow-Origin: *\r\n"
            << "Accept-Ranges: bytes\r\n"
            << "Content-Range: bytes 0-0/" << totalSize << "\r\n"
            << "\r\n";

        delete [] mimeType;
        httpctx->resultCode = 416;
        string resstr = response.str();
        sendHeaders(httpctx, &resstr);
        return 0;
    }

    if (rangeRequested)
    {
        response << "HTTP/1.1 206 Partial Content\r\n";
        response << "Content-Range: bytes " << start << "-" << end << "/" << totalSize << "\r\n";
    }
    else
    {
        response << "HTTP/1.1 200 OK\r\n";
    }

    response << "Content-Type: " << mimeType << "\r\n"
        << "Connection: close\r\n"
        << "Content-Length: " << len << "\r\n"
        << "Access-Control-Allow-Origin: *\r\n"
        << "Accept-Ranges: bytes\r\n"
        << "\r\n";

    delete [] mimeType;
    httpctx->pause = false;
    httpctx->lastBuffer = NULL;
    httpctx->lastBufferLen = 0;
    if (httpctx->transfer)
    {
        httpctx->transfer->setStartPos(start);
        httpctx->transfer->setEndPos(end);
    }

    httpctx->streamingBuffer.setFileSize(totalSize);
    httpctx->streamingBuffer.setDuration(httpctx->node->getDuration());

    string resstr = response.str();
    if (httpctx->parser.method != HTTP_HEAD)
    {
        httpctx->streamingBuffer.init(std::max(static_cast<size_t>(len), resstr.size()));
        httpctx->server->setMaxBufferSize(httpctx->streamingBuffer.getMaxBufferSize());
        httpctx->server->setMaxOutputSize(httpctx->streamingBuffer.getMaxOutputSize());
        httpctx->size = len;
    }

    sendHeaders(httpctx, &resstr);
    if (httpctx->parser.method == HTTP_HEAD)
    {
        return 0;
    }

    LOG_debug << "Requesting range. From " << start << "  size " << len;
    httpctx->rangeWritten = 0;
    if (start || len)
    {
        httpctx->streamingBuffer.reset(!httpctx->lastBufferLen, resstr.size());
        httpctx->megaApi->startStreaming(node, start, len, httpctx);
    }
    else
    {
        MegaHTTPServer *httpserver = ((MegaHTTPServer *)httpctx->server);
        LOG_debug << "Skipping startStreaming call since empty file";
        httpserver->processWriteFinished(httpctx, 0);
    }
    return 0;
}

void MegaHTTPServer::sendHeaders(MegaHTTPContext *httpctx, string *headers)
{
    LOG_debug << "Response headers: " << *headers;
    httpctx->streamingBuffer.append(headers->data(), headers->size());
    uv_buf_t resbuf = httpctx->streamingBuffer.nextBuffer();
    httpctx->size += headers->size();
    httpctx->lastBuffer = resbuf.base;
    httpctx->lastBufferLen = resbuf.len;

    if (httpctx->transfer)
    {
        httpctx->transfer->setTotalBytes(httpctx->size);
        httpctx->megaApi->fireOnStreamingStart(httpctx->transfer.get());
    }

#ifdef ENABLE_EVT_TLS
    if (httpctx->server->useTLS)
    {
        assert (resbuf.len);
        int err = evt_tls_write(httpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls);
        if (err <= 0)
        {
            LOG_warn << "Finishing due to an error sending the response: " << err;
            closeConnection(httpctx);
        }
    }
    else
    {
#endif
        uv_write_t *req = new uv_write_t();
        req->data = httpctx;
        if (int err = uv_write(req, (uv_stream_t*)&httpctx->tcphandle, &resbuf, 1, onWriteFinished))
        {
            delete req;
            LOG_warn << "Finishing due to an error sending the response: " << err;
            closeTCPConnection(httpctx);
        }
#ifdef ENABLE_EVT_TLS
    }
#endif
}

void MegaHTTPServer::processAsyncEvent(MegaTCPContext* tcpctx)
{
    MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx);

    if (httpctx->finished)
    {
        LOG_debug << "HTTP link closed, ignoring async event";
        return;
    }

    if (httpctx->failed)
    {
        LOG_warn << "Streaming transfer failed. Closing connection.";
        closeConnection(httpctx);
        return;
    }

    uv_mutex_lock(&httpctx->mutex_responses);
    while (httpctx->responses.size())
    {
        sendHeaders(httpctx,&httpctx->responses.front());
        httpctx->responses.pop_front();
    }
    uv_mutex_unlock(&httpctx->mutex_responses);

    if (httpctx->nodereceived)
    {
        httpctx->nodereceived = false;
        if (!httpctx->node || httpctx->nodename != httpctx->node->getName())
        {
            if (!httpctx->node)
            {
                LOG_warn << "Public link not found";
            }
            else
            {
                LOG_warn << "Invalid name for public link";
            }

            httpctx->resultCode = 404;
            string resstr = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n";
            sendHeaders(httpctx, &resstr);
            return;
        }

        streamNode(httpctx);
        return;
    }

    sendNextBytes(httpctx);
}

void MegaHTTPServer::sendNextBytes(MegaHTTPContext *httpctx)
{
    if (httpctx->finished)
    {
        LOG_debug << "HTTP link closed, aborting write";
        return;
    }

    if (httpctx->lastBuffer)
    {
        LOG_verbose << "[Streaming] Skipping write due to another ongoing write";
        return;
    }

    uv_mutex_lock(&httpctx->mutex);
    if (httpctx->lastBufferLen)
    {
        httpctx->streamingBuffer.freeData(httpctx->lastBufferLen);
        httpctx->lastBufferLen = 0;
    }

    if (httpctx->tcphandle.write_queue_size > httpctx->streamingBuffer.availableCapacity() / 8)
    {
        LOG_warn << "[Streaming] Skipping write. Too much queued data. " << httpctx->streamingBuffer.bufferStatus();
        uv_mutex_unlock(&httpctx->mutex);
        return;
    }

    uv_buf_t resbuf = httpctx->streamingBuffer.nextBuffer();
    uv_mutex_unlock(&httpctx->mutex);

    if (!resbuf.len)
    {
        LOG_debug << "[Streaming] Skipping write. No data available. " << httpctx->streamingBuffer.bufferStatus();
        return;
    }

    LOG_verbose << "Writing " << resbuf.len << " bytes";
    httpctx->rangeWritten += resbuf.len;
    httpctx->lastBuffer = resbuf.base;
    httpctx->lastBufferLen = resbuf.len;

#ifdef ENABLE_EVT_TLS
    if (httpctx->server->useTLS)
    {
        //notice this, contrary to !useTLS is synchronous
        int err = evt_tls_write(httpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls);
        if (err <= 0)
        {
            LOG_warn << "[Streaming] Finishing due to an error sending the response: " << err;
            evt_tls_close(httpctx->evt_tls, on_evt_tls_close);
        }
    }
    else
    {
#endif
        uv_write_t *req = new uv_write_t();
        req->data = httpctx;

        if (int err = uv_write(req, (uv_stream_t*)&httpctx->tcphandle, &resbuf, 1, onWriteFinished))
        {
            delete req;
            LOG_warn << "[Streaming] Finishing due to an error in uv_write: " << err;
            httpctx->finished = true;
            if (!uv_is_closing((uv_handle_t*)&httpctx->tcphandle))
            {
                uv_close((uv_handle_t*)&httpctx->tcphandle, onClose);
            }
        }
#ifdef ENABLE_EVT_TLS
    }
#endif
}

MegaHTTPContext::MegaHTTPContext()
{
    rangeStart = -1;
    rangeEnd = -1;
    rangeWritten = -1;
    range = false;
    failed = false;
    pause = false;
    nodereceived = false;
    resultCode = API_EINTERNAL;
    node = NULL;
    nodesize = -1;
    messageBody = NULL;
    messageBodySize = 0;
    tmpFileAccess = NULL;
    newParentNode = UNDEF;
    nodeToMove = UNDEF;
    depth = -1;
    overwrite = true; //GVFS-DAV via command line does not include this header (assumed true)
    lastBuffer = NULL;
    lastBufferLen = 0;

    // Mutex to protect the data buffer
    uv_mutex_init(&mutex_responses);
}

MegaHTTPContext::~MegaHTTPContext()
{
    delete node;
    if (tmpFileName.size())
    {
        LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName);
        server->fsAccess->unlinklocal(localPath);
    }
    delete [] messageBody;
    uv_mutex_destroy(&mutex_responses);
}

void MegaHTTPContext::onTransferStart(MegaApi *, MegaTransfer *transfer)
{
    if (this->transfer)
    {
        this->transfer->setTag(transfer->getTag());
    }
}

bool MegaHTTPContext::onTransferData(MegaApi *, MegaTransfer *transfer, char *buffer, size_t size)
{
    LOG_verbose << "Streaming data received: " << transfer->getTransferredBytes()
                << " Size: " << size
                << " Queued: " << this->tcphandle.write_queue_size
                << " " << streamingBuffer.bufferStatus();

    if (finished)
    {
        LOG_info << "Removing streaming transfer after " << transfer->getTransferredBytes() << " bytes";
        return false;
    }

    // append the data to the buffer
    uv_mutex_lock(&mutex);
    long long remaining = size + (transfer->getTotalBytes() - transfer->getTransferredBytes());
    long long availableSpace = streamingBuffer.availableSpace();
    if ((remaining > availableSpace) && ((availableSpace - size) < static_cast<long long>(DirectReadSlot::MAX_DELIVERY_CHUNK)))
    {
        LOG_debug << "[Streaming] Buffer full: Pausing streaming. " << streamingBuffer.bufferStatus();
        pause = true;
    }
    streamingBuffer.append(buffer, size);
    uv_mutex_unlock(&mutex);

    // notify the HTTP server
    uv_async_send(&asynchandle);
    return !pause;
}

void MegaHTTPContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e)
{
    if (finished)
    {
        LOG_debug << "HTTP link closed, ignoring the result of the transfer";
        return;
    }

    MegaHTTPServer* httpserver = dynamic_cast<MegaHTTPServer *>(server);

    int ecode = e->getErrorCode();

    if (parser.method == HTTP_PUT)
    {
        if (ecode == API_OK)
        {
            httpserver->returnHttpCodeAsync(this, 201); //TODO actually if resource already existed this should be 200
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }

    if (ecode != API_OK && ecode != API_EINCOMPLETE)
    {
        LOG_warn << "Transfer failed with error code: " << ecode;
        failed = true;
    }
    uv_async_send(&asynchandle);
}

void MegaHTTPContext::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e)
{
    if (finished)
    {
        LOG_debug << "HTTP link closed, ignoring the result of the request";
        return;
    }
    MegaHTTPServer* httpserver = dynamic_cast<MegaHTTPServer *>(server);

    if (request->getType() == MegaRequest::TYPE_MOVE)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            if (this->newname.size())
            {
                MegaNode *nodetoRename = this->megaApi->getNodeByHandle(request->getNodeHandle());
                if (!nodetoRename || !strcmp(nodetoRename->getName(), newname.c_str()))
                {
                    httpserver->returnHttpCodeAsync(this, 204);
                }
                else
                {
                    this->megaApi->renameNode(nodetoRename, newname.c_str(), this);
                }
                delete nodetoRename;
            }
            else
            {
                httpserver->returnHttpCodeAsync(this, 204);
            }
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_RENAME)
    {
        if (e->getErrorCode() == MegaError::API_OK )
        {
            httpserver->returnHttpCodeAsync(this, 204);
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_REMOVE)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            MegaNode *n = this->megaApi->getNodeByHandle(nodeToMove);
            MegaNode *p = this->megaApi->getNodeByHandle(newParentNode);
            if (n && p) //delete + move
            {
                this->megaApi->moveNode(n, p, this);
            }
            else
            {
                httpserver->returnHttpCodeAsync(this, 204); // Standard success response
            }

            nodeToMove = UNDEF;
            newParentNode = UNDEF;
            delete n;
            delete p;
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_CREATE_FOLDER)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            httpserver->returnHttpCodeAsync(this, 201);
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_COPY)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            httpserver->returnHttpCodeAsync(this, 201);
        }
        else
        {
            httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_GET_PUBLIC_NODE)
    {
        node = request->getPublicMegaNode();
        nodereceived = true;
    }
    uv_async_send(&asynchandle);
}


//////////////////////////////
//  MegaFTPServer specifics //
//////////////////////////////

/**
 * Gets permissions string: e.g: 777 -> rwxrwxrwx
 * @param permissions permissions
 * @param permsString permission string buffer
 */
void MegaFTPServer::getPermissionsString(int permissions, char *permsString)
{
    string ps = "";
    for(int i = 0; i<3; i++) // user, group, others
    {
        int curperm = permissions%10;
        permissions = permissions / 10;

        bool read = (curperm >> 2) & 0x1;
        bool write = (curperm >> 1) & 0x1;
        bool exec = (curperm >> 0) & 0x1;

        char rwx[4];
        snprintf(rwx, sizeof(rwx), "%c%c%c",read?'r':'-' ,write?'w':'-', exec?'x':'-');
        rwx[3]='\0';
        ps = rwx + ps;
    }
    strcat(permsString, ps.c_str());
}

//ftp_parser_settings MegaTCPServer::parsercfg;
MegaFTPServer::MegaFTPServer(MegaApiImpl *megaApi, string basePath, int dataportBegin, int dataPortEnd, bool useTLS, string certificatepath, string keypath)
: MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath)
{
    nodeHandleToRename = UNDEF;
    this->pport = dataportBegin;
    this->dataportBegin = dataportBegin;
    this->dataPortEnd = dataPortEnd;

    crlfout = "\r\n";
}

MegaFTPServer::~MegaFTPServer()
{
    // if not stopped, the uv thread might want to access a pointer to this.
    // though this is done in the parent destructor, it could try to access it after vtable has been erased
    stop();
}

MegaTCPContext* MegaFTPServer::initializeContext(uv_stream_t *server_handle)
{
    MegaFTPContext* ftpctx = new MegaFTPContext();

    // Set connection data
    MegaFTPServer *server = (MegaFTPServer *)(server_handle->data);
    ftpctx->server = server;
    ftpctx->megaApi = server->megaApi;
    ftpctx->tcphandle.data = ftpctx;
    ftpctx->asynchandle.data = ftpctx;

    return ftpctx;
}

void MegaFTPServer::processWriteFinished(MegaTCPContext *tcpctx, int status)
{
    LOG_verbose << "MegaFTPServer::processWriteFinished. status=" << status;
}

string MegaFTPServer::getListingLineFromNode(MegaNode *child, string nameToShow)
{
    char perms[10];
    memset(perms,0,10);
    //str_perm((statbuf.st_mode & ALLPERMS), perms);
    getPermissionsString(child->isFolder() ? 777 : 664, perms);

    char timebuff[80];
    time_t rawtime = (child->isFolder()?child->getCreationTime():child->getModificationTime());
    struct tm time;
    m_localtime(rawtime, &time);
    strftime(timebuff,80,"%b %d %H:%M",&time);

    char toprint[3000];
    snprintf(toprint, sizeof(toprint),
            "%c%s %5d %4d %4d %8"
            PRId64
            " %s %s",
            (child->isFolder())?'d':'-',
            perms,
            1,//number of contents for folders
            1000, //uid
            1000, //gid
             (child->isFolder())?4:child->getSize(),
            timebuff,
            nameToShow.size()?nameToShow.c_str():child->getName());

    return toprint;
}


string MegaFTPServer::getFTPErrorString(int errorcode, string argument)
{
    switch (errorcode)
    {
    case 110:
        return "Restart marker reply.";
    case 120:
        return "Service ready in " + argument + " minutes.";
    case 125:
        return "Data connection already open; transfer starting.";
    case 150:
        return "File status okay; about to open data connection.";
    case 200:
        return "Command okay.";
    case 202:
        return "Command not implemented, superfluous at this site.";
    case 211:
        return "System status, or system help reply.";
    case 212:
        return "Directory status.";
    case 213:
        return "File status.";
    case 214:
        return "Help message.";
    case 215:
        return "NAME system type.";
    case 220:
        return "Service ready for new user.";
    case 221:
        return "Service closing control connection.";
    case 225:
        return "Data connection open; no transfer in progress.";
    case 226:
        return "Closing data connection. Requested file action successful.";
    case 227:
        return "Entering Passive Mode (h1,h2,h3,h4,p1,p2).";
    case 230:
        return "User logged in, proceed.";
    case 250:
        return "Requested file action okay, completed.";
    case 257:
        return argument + " created.";
    case 331:
        return "User name okay, need password.";
    case 332:
        return "Need account for login.";
    case 350:
        return "Requested file action pending further information.";
    case 421:
        return "Service not available, closing control connection.";
    case 425:
        return "Can't open data connection.";
    case 426:
        return "Connection closed; transfer aborted.";
    case 450:
        return "Requested file action not taken. File unavailable (e.g., file busy).";
    case 451:
        return "Requested action aborted: local error in processing.";
    case 452:
        return "Requested action not taken. Insufficient storage space in system.";
    case 500:
        return "Syntax error, command unrecognized.";
    case 501:
        return "Syntax error in parameters or arguments.";
    case 502:
        return "Command not implemented.";
    case 503:
        return "Bad sequence of commands.";
    case 504:
        return "Command not implemented for that parameter.";
    case 530:
        return "Not logged in.";
    case 532:
        return "Need account for storing files.";
    case 550:
        return "Requested action not taken. File unavailable (e.g., file not found, no access).";
    case 551:
        return "Requested action aborted: page type unknown.";
    case 552:
        return "Requested file action aborted. Exceeded storage allocation."; // (for current directory or dataset).
    case 553:
        return "Requested action not taken. File name not allowed.";
    default:
        return "Unknown Error";
    }
}

void MegaFTPServer::returnFtpCodeBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e)
{
    int reqError = e->getErrorCode();
    int ftpreturncode = 500;

    switch(reqError)
    {
    case API_OK:
        ftpreturncode = 300;
        break;
    case API_EACCESS:
        ftpreturncode = 550; //this might not be accurate
        break;
    case API_EOVERQUOTA:
    case API_EGOINGOVERQUOTA:
        ftpreturncode = 452; // 552?
        break;
    case API_EAGAIN:
    case API_ERATELIMIT:
    case API_ETEMPUNAVAIL:
        ftpreturncode = 120;
        break;
    case API_EREAD:
        ftpreturncode = 450;
        break;
    case API_ECIRCULAR:
        ftpreturncode = 508;
        break;
    default:
        ftpreturncode = 503;
        break;
    }

    LOG_debug << "FTP petition failed. request error = " << reqError << " FTP status to return = " << ftpreturncode;
    string errorMessage = e->getErrorString(reqError);
    return returnFtpCode(ftpctx, ftpreturncode, errorMessage);
}

void MegaFTPServer::returnFtpCode(MegaFTPContext* ftpctx, int errorCode, string errorMessage)
{
    MegaFTPServer* ftpserver = dynamic_cast<MegaFTPServer *>(ftpctx->server);

    std::ostringstream response;
    response << errorCode << " " << (errorMessage.size() ? errorMessage : getFTPErrorString(errorCode))
             << ftpserver->crlfout;

    string resstr = response.str();
    uv_mutex_lock(&ftpctx->mutex_responses);
    ftpctx->responses.push_back(resstr);
    uv_mutex_unlock(&ftpctx->mutex_responses);
    uv_async_send(&ftpctx->asynchandle);
}

void MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e)
{
    return returnFtpCodeBasedOnRequestError(ftpctx, e);
}

void MegaFTPServer::returnFtpCodeAsync(MegaFTPContext* ftpctx, int errorCode, string errorMessage)
{
    return returnFtpCode(ftpctx, errorCode, errorMessage);
}

// you get the ownership
MegaNode *MegaFTPServer::getBaseFolderNode(string path)
{
    if (path.size() && path.at(0) == '/')
    {
        string rest = path.substr(1);
        size_t possep = rest.find('/');
        handle h = megaApi->base64ToHandle(rest.substr(0,possep).c_str());
        MegaNode *n = megaApi->getNodeByHandle(h);
        if (possep != string::npos && possep != (rest.size() - 1) )
        {
            if (n)
            {
                if (rest.size() > (possep + 1))
                {
                    rest = rest.substr(possep + 1);
                    if (rest == n->getName())
                    {
                        return n;
                    }
                    if (rest.size() > strlen(n->getName()) && (rest.at(strlen(n->getName())) == '/' ) && (rest.find(n->getName()) == 0) )
                    {
                        return n;
                    }
                }
                delete n;
            }
        }
        else
        {
            return n;
        }
    }
    return NULL;
}

// you get the ownership
MegaNode *MegaFTPServer::getNodeByFullFtpPath(string path)
{
    if (path.size() && path.at(0) == '/')
    {
        string rest = path.substr(1);
        size_t possep = rest.find('/');
        handle h = megaApi->base64ToHandle(rest.substr(0,possep).c_str());
        MegaNode *n = megaApi->getNodeByHandle(h);
        if (possep != string::npos && possep != (rest.size() - 1) )
        {
            if (n)
            {
                if (rest.size() > possep)
                {
                    rest = rest.substr(possep + 1);
                    if (rest == n->getName())
                    {
                        return n;
                    }
                    if (rest.size() > strlen(n->getName()) && (rest.at(strlen(n->getName())) == '/' ) && (rest.find(n->getName()) == 0) )
                    {
                        string relpath = rest.substr(strlen(n->getName())+1);
                        MegaNode *toret = megaApi->getNodeByPath(relpath.c_str(), n);
                        delete n;
                        return toret;
                    }
                }
                delete n;
            }
        }
        else
        {
            return n;
        }
    }
    return NULL;
}

MegaNode * MegaFTPServer::getNodeByFtpPath(MegaFTPContext* ftpctx, string path)
{
    if (ftpctx->atroot && path.size() && path.at(0) != '/')
    {
        path= "/" + path;
    }
    else if (ftpctx->athandle && path.size() && path.at(0) != '/')
    {
        char *cshandle = ftpctx->megaApi->handleToBase64(ftpctx->cwd);
        string handle(cshandle);
        delete []cshandle;

        path= "/" + handle + "/" + path;
    }
    else if (path.size() && path.at(0) != '/')
    {
        path = ftpctx->cwdpath + "/" + path;
        path = shortenpath(path);
    }

    if (path.find("..") == 0)
    {
        string fullpath = ftpctx->cwdpath + "/" + path;
        size_t seppos = fullpath.find("/");
        int count = 0;
        while (seppos != string::npos && fullpath.size() > (seppos +1) )
        {
            string part = fullpath.substr(0,seppos);
            if (part.size() && part != "..")
            {
                count++;
            }
            if (part == "..")
            {
                count--;
                if (count < 2)
                {
                    return NULL; // do not allow to escalate in the path
                }
            }

            fullpath = fullpath.substr(seppos+1);
            if (fullpath == ".." && count == 2)
            {
                return NULL; // do not allow to escalate in the path
            }
            seppos = fullpath.find("/");
        }
    }

    if (path.size() && path.at(0) == '/')
    {
        MegaNode *baseFolderNode = getBaseFolderNode(path);
        if (!baseFolderNode)
        {
            return NULL;
        }
        if (!isHandleAllowed(baseFolderNode->getHandle()) )
        {
            delete baseFolderNode;
            return NULL;
        }
        delete baseFolderNode;

        return getNodeByFullFtpPath(path);
    }
    else //it should only enter here if path == ""
    {
        MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd);
        if (!n)
        {
            return NULL;
        }
        MegaNode *toret = ftpctx->megaApi->getNodeByPath(path.c_str(), n);
        delete n;
        return toret;
    }
}

std::string MegaFTPServer::shortenpath(std::string path)
{
    string orig = path;

    while ((path.size() > 1) && path.at(path.size() - 1) == '/') // remove trailing /
    {
        path = path.substr(0,path.size()-1);
    }
    list<string> parts;
    size_t seppos = path.find("/");
    while (seppos != string::npos && path.size() > (seppos +1) )
    {
        string part = path.substr(0,seppos);
        if (part.size() && part != "..")
        {
            parts.push_back(part);
        }
        if (part == "..")
        {
            if (!parts.size())
            {
                return "INVALIDPATH"; // FAILURE!
            }
            parts.pop_back();
        }

        path = path.substr(seppos+1);
        if (path == "..")
        {
            if (!parts.size())
            {
                return "INVALIDPATH"; // FAILURE!
            }
            parts.pop_back();
            path = "";
        }
        seppos = path.find("/");
    }
    if (path.size() && path != "..")
    {
        parts.push_back(path);
    }

    string toret;
    if (!parts.size() && orig.size() && orig.at(0) == '/')
    {
        toret = "/";
    }
    else
    {
        while (parts.size())
        {
            toret.append("/");
            toret.append(parts.front());
            parts.pop_front();
        }
    }
    return toret;
}

std::string MegaFTPServer::cdup(handle parentHandle, MegaFTPContext* ftpctx)
{
    string response;
    MegaNode *newcwd = ftpctx->megaApi->getNodeByHandle(parentHandle);
    if (newcwd)
    {
        bool allowed = isHandleAllowed(newcwd->getHandle()) || isHandleAllowed(newcwd->getParentHandle());
        MegaNode *pn = ftpctx->megaApi->getNodeByHandle(newcwd->getHandle());
        while (!allowed && pn)
        {
            MegaNode *aux = pn;
            pn = ftpctx->megaApi->getNodeByHandle(pn->getParentHandle());
            delete aux;
            if (pn)
            {
                allowed = isHandleAllowed(pn->getParentHandle());
            }
        }
        delete pn;

        if (!allowed)
        {
            LOG_warn << "Ftp client trying to access not allowed path";
            response = "550 Path not allowed";
        }
        else if (newcwd->isFolder() && newcwd->getHandle() != UNDEF)
        {
            ftpctx->cwd = newcwd->getHandle();
            ftpctx->cwdpath = ftpctx->cwdpath + "/..";
            ftpctx->cwdpath = shortenpath(ftpctx->cwdpath);
            ftpctx->athandle = false;
            ftpctx->atroot = false;
            size_t seps = std::count(ftpctx->cwdpath.begin(), ftpctx->cwdpath.end(), '/');
            if (seps < 2)
            {
                ftpctx->cwdpath = string("/") + megaApi->handleToBase64(newcwd->getHandle()) + "/" + newcwd->getName();
            }
            ftpctx->parentcwd = newcwd->getParentHandle();

            response = "250 Directory successfully changed";
        }
        else
        {
            response = "550 CDUP failed.";
        }

        delete newcwd;
    }
    else
    {
        response = "550 Not Found";
    }
    return response;
}

std::string MegaFTPServer::cd(string newpath, MegaFTPContext* ftpctx)
{
    string response;
    if (newpath == "/")
    {
        MegaNode *rootNode = megaApi->getRootNode();
        if (rootNode)
        {
            ftpctx->cwd = rootNode->getHandle();
            ftpctx->cwdpath = "/";
            ftpctx->atroot = true;
            ftpctx->athandle = false;
            response = "250 Directory successfully changed";
            delete rootNode;
            return response;
        }
        response = "550 CWD not Found.";
        return response;
    }

    MegaNode *newcwd = getNodeByFtpPath(ftpctx, newpath);
    if (!newcwd)
    {
        response = "550 CWD not Found.";
        return response;
    }

    ftpctx->cwd = newcwd->getHandle();
    if (newpath.size() && newpath.at(0) == '/')
    {
        ftpctx->cwdpath = newpath;
    }
    else // relative paths!
    {
        ftpctx->cwdpath = (ftpctx->cwdpath == "/"?"":ftpctx->cwdpath) + "/" + newpath;
    }
    ftpctx->cwdpath = shortenpath(ftpctx->cwdpath);
    ftpctx->athandle = false;
    string handlepath = "/";

    char *cshandle = megaApi->handleToBase64(newcwd->getHandle());
    string shandle(cshandle);
    delete []cshandle;

    handlepath.append(shandle);
    if (ftpctx->cwdpath == handlepath || ftpctx->cwdpath == shandle || ftpctx->cwdpath == (handlepath +"/") )
    {
        ftpctx->cwdpath = handlepath;
        ftpctx->athandle = true;
    }
    ftpctx->atroot = false;
    ftpctx->parentcwd = newcwd->getParentHandle();

    if (ftpctx->athandle || newcwd->isFolder())
    {
        response = "250 Directory successfully changed";
    }
    else
    {
        response = "550 CWD failed."; //chrome requires this
    }
    delete newcwd;
    return response;
}

void MegaFTPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf)
{
    MegaFTPContext* ftpctx = dynamic_cast<MegaFTPContext *>(tcpctx);

    ssize_t parsed = -1; ((void)(parsed));
    string petition;
    std::string command;
    string response;
    bool delayresponse = false;

    if (!nread)
    {
        LOG_debug << " Discarding processReceivedData read = " << nread;
        return;
    }

    uv_mutex_lock(&tcpctx->mutex);

    if (nread >= 0)
    {
        bool failed = false;
        const char *separators = " ";
        const char *crlf = "\r\n";

        petition = string(buf->base, nread);

        LOG_verbose << "FTP Server received: " << petition << " at port = " << port;

        size_t psep = petition.find_first_of(separators);
        size_t psepend = petition.find(crlf);

        if (psepend != petition.size()-strlen(crlf))
        {
            parsed = -1;
            failed = true;

            LOG_warn << " Failed to parse petition:<" << petition << ">" << " psep=" << psep << " psepend=" << psepend
                     << " petition.size=" << petition.size() << " tcpctx=" << tcpctx;
        }
        else
        {
            parsed = petition.size();
            petition = petition.substr(0,psepend);
            command = petition.substr(0,psep);
            for (char& c : command) { c = static_cast<char>(toupper(c)); };
        }

        if (failed)
        {
            ftpctx->command = FTP_CMD_INVALID;
        }
        else if(command == "USER")
        {
            ftpctx->command = FTP_CMD_USER;
        }
        else if(command == "PASS")
        {
            ftpctx->command = FTP_CMD_PASS;
        }
        else if(command == "ACCT")
        {
            ftpctx->command = FTP_CMD_ACCT;
        }
        else if(command == "CWD")
        {
            ftpctx->command = FTP_CMD_CWD;
        }
        else if(command == "CDUP")
        {
            ftpctx->command = FTP_CMD_CDUP;
        }
        else if(command == "SMNT")
        {
            ftpctx->command = FTP_CMD_SMNT;
        }
        else if(command == "QUIT")
        {
            ftpctx->command = FTP_CMD_QUIT;
        }
        else if(command == "REIN")
        {
            ftpctx->command = FTP_CMD_REIN;
        }
        else if(command == "PORT")
        {
            ftpctx->command = FTP_CMD_PORT;
        }
        else if(command == "PASV")
        {
            ftpctx->command = FTP_CMD_PASV;
        }
        else if(command == "TYPE")
        {
            ftpctx->command = FTP_CMD_TYPE;
        }
        else if(command == "STRU")
        {
            ftpctx->command = FTP_CMD_STRU;
        }
        else if(command == "MODE")
        {
            ftpctx->command = FTP_CMD_MODE;
        }
        else if(command == "RETR")
        {
            ftpctx->command = FTP_CMD_RETR;
        }
        else if(command == "STOR")
        {
            ftpctx->command = FTP_CMD_STOR;
        }
        else if(command == "STOU")
        {
            ftpctx->command = FTP_CMD_STOU;
        }
        else if(command == "APPE")
        {
            ftpctx->command = FTP_CMD_APPE;
        }
        else if(command == "ALLO")
        {
            ftpctx->command = FTP_CMD_ALLO;
        }
        else if(command == "REST")
        {
            ftpctx->command = FTP_CMD_REST;
        }
        else if(command == "RNFR")
        {
            ftpctx->command = FTP_CMD_RNFR;
        }
        else if(command == "RNTO")
        {
            ftpctx->command = FTP_CMD_RNTO;
        }
        else if(command == "ABOR")
        {
            ftpctx->command = FTP_CMD_ABOR;
        }
        else if(command == "DELE")
        {
            ftpctx->command = FTP_CMD_DELE;
        }
        else if(command == "RMD")
        {
            ftpctx->command = FTP_CMD_RMD;
        }
        else if(command == "MKD")
        {
            ftpctx->command = FTP_CMD_MKD;
        }
        else if(command == "PWD")
        {
            ftpctx->command = FTP_CMD_PWD;
        }
        else if(command == "LIST")
        {
            ftpctx->command = FTP_CMD_LIST;
        }
        else if(command == "NLST")
        {
            ftpctx->command = FTP_CMD_NLST;
        }
        else if(command == "SITE")
        {
            ftpctx->command = FTP_CMD_SITE;
        }
        else if(command == "SYST")
        {
            ftpctx->command = FTP_CMD_SYST;
        }
        else if(command == "STAT")
        {
            ftpctx->command = FTP_CMD_STAT;
        }
        else if(command == "HELP")
        {
            ftpctx->command = FTP_CMD_HELP;
        }
        else if(command == "FEAT")
        {
            ftpctx->command = FTP_CMD_FEAT;
        }
        else if(command == "SIZE")
        {
            ftpctx->command = FTP_CMD_SIZE;
        }
        else if(command == "PROT")
        {
            ftpctx->command = FTP_CMD_PROT;
        }
        else if(command == "NOOP")
        {
            ftpctx->command = FTP_CMD_NOOP;
        }
        else if(command == "EPSV")
        {
            ftpctx->command = FTP_CMD_EPSV;
        }
        else if(command == "PBSZ")
        {
            ftpctx->command = FTP_CMD_PBSZ;
        }
        else if(command == "OPTS")
        {
            ftpctx->command = FTP_CMD_OPTS;
        }
        else
        {
            LOG_warn << " Could not match command: " << command;
            ftpctx->command = FTP_CMD_INVALID;
        }

        LOG_debug << " parsed command = " << ftpctx->command << " tcpctx=" << tcpctx;
        ftpctx->arg1 = "";
        ftpctx->arg2 = "";

        switch (ftpctx->command)
        {
        // no args
        case FTP_CMD_ABOR:
        case FTP_CMD_STOU:
        case FTP_CMD_QUIT:
        case FTP_CMD_REIN:
        case FTP_CMD_CDUP:
        case FTP_CMD_PASV:
        case FTP_CMD_EPSV:
        case FTP_CMD_PWD:
        case FTP_CMD_SYST:
        case FTP_CMD_FEAT:
        case FTP_CMD_NOOP:
            if (psep != string::npos)
            {
                parsed = -1;
            }
            break;
        // single arg
        case FTP_CMD_USER:
        case FTP_CMD_PASS:
        case FTP_CMD_ACCT:
        case FTP_CMD_CWD:
        case FTP_CMD_SMNT:
        case FTP_CMD_PORT:
        case FTP_CMD_TYPE:
        case FTP_CMD_STRU:
        case FTP_CMD_MODE:
        case FTP_CMD_RETR:
        case FTP_CMD_STOR:
        case FTP_CMD_APPE:
        case FTP_CMD_DELE:
        case FTP_CMD_RMD:
        case FTP_CMD_MKD:
        case FTP_CMD_REST:
        case FTP_CMD_RNFR:
        case FTP_CMD_RNTO:
        case FTP_CMD_SITE:
        case FTP_CMD_SIZE:
        case FTP_CMD_PBSZ:
        case FTP_CMD_PROT:
            if (psep != string::npos && ( (psep + 1)< petition.size()) )
            {
                string rest = petition.substr(psep+1);
                ftpctx->arg1 = rest;
            }
            else
            {
                parsed = -1;
            }

            break;
            //optional arg
        case FTP_CMD_LIST:
        case FTP_CMD_NLST:
        case FTP_CMD_STAT:
        case FTP_CMD_HELP:
            if (psep != string::npos)
            {
                string rest = petition.substr(psep+1);
                ftpctx->arg1 = rest;
            }
            break;

        case FTP_CMD_ALLO: //ALLO <SP> <decimal-integer> [<SP> R <SP> <decimal-integer>] <CRLF>
            if (psep != string::npos && ((psep + 1) < petition.size()))
            {
                string rest = petition.substr(psep+1);
                psep = rest.find_first_of(separators);
                ftpctx->arg1 = rest.substr(0,psep);
                if (psep != string::npos && ((psep + 1) < petition.size()))
                {  //optional R <SP> <decimal-integer>
                    rest = petition.substr(psep+1);
                    psep = rest.find_first_of(separators);
                    if (psep != 1 && (rest.at(0) != 'R' || rest.at(0) != 'r'))
                    {
                        parsed = -1;
                    }
                    else if ((psep + 1) < petition.size())
                    {
                        rest = petition.substr(psep+1);
                        ftpctx->arg2 = rest;
                    }
                    else
                    {
                        parsed = -1;
                    }
                }
            }
            else
            {
                parsed = -1;
            }
            break;
        case FTP_CMD_OPTS:
            if (psep != string::npos)
            {
                string rest = petition.substr(psep+1);
                psep = rest.find_first_of(separators);
                ftpctx->arg1 = rest.substr(0,psep);
                if (psep != string::npos && ((psep + 1) < rest.size()))
                {
                    ftpctx->arg2 = rest.substr(psep+1);
                }
                else
                {
                    parsed = -1;
                }
            }
            break;
        default:
            parsed = -1;
            break;
        };

        switch (ftpctx->command)
        {
        case FTP_CMD_USER:
        {
            response = "331 User name okay, need password";
            break;
        }
        case FTP_CMD_PASS:
        {
            response = "230 User logged in, proceed";

            MegaNode *n = ftpctx->megaApi->getRootNode();
            if (n)
            {
                ftpctx->cwd = n->getHandle();
                ftpctx->cwdpath = "/";
                ftpctx->atroot = true;
                ftpctx->athandle = false;
                delete n;
            }
            break;
        }
        case FTP_CMD_NOOP:
        {
            response = "200 NOOP from MEGA!";
            break;
        }
        case FTP_CMD_TYPE:
        case FTP_CMD_PROT: // we might want to require that arg1 = "P". or disable useTLS in data channel otherwise
        {
            response = "200 OK";
            break;
        }
        case FTP_CMD_PBSZ: //we don't use received buffer size (some client might fail)
        {
            response = "200 PBSZ=0";
            break;
        }
        case FTP_CMD_PASV:
        case FTP_CMD_EPSV:
        {
            if (!ftpctx->ftpDataServer)
            {
                if (pport > (dataPortEnd))
                {
                    pport = dataportBegin;
                }
                ftpctx->pasiveport = pport++;

                LOG_debug << "Creating new MegaFTPDataServer on port " << ftpctx->pasiveport;
#ifdef ENABLE_EVT_TLS
                MegaFTPDataServer *fds = new MegaFTPDataServer(megaApi, basePath, ftpctx, useTLS, certificatepath, keypath);
#else
                MegaFTPDataServer *fds = new MegaFTPDataServer(megaApi, basePath, ftpctx, useTLS, string(), string());
#endif
                bool result = fds->start(ftpctx->pasiveport, localOnly);
                if (result)
                {
                    ftpctx->ftpDataServer = fds;
                }
                else
                {
                    response = "421 Failed to initialize data channel";
                    break;
                }
            }
            else
            {
                LOG_debug << "Reusing FTP Data connection with port: " << ftpctx->pasiveport;
            }

            // gathering IP connected to
            struct sockaddr_in addr;
            int sadrlen = sizeof (struct sockaddr_in);
            uv_tcp_getsockname(&ftpctx->tcphandle,(struct sockaddr*)&addr, &sadrlen);
#ifdef WIN32
            string sIPtoPASV = inet_ntoa(addr.sin_addr);
#else
            char strIP[INET_ADDRSTRLEN];
            inet_ntop( AF_INET, &addr.sin_addr.s_addr, strIP, INET_ADDRSTRLEN);
            string sIPtoPASV = strIP;
#endif
            replace( sIPtoPASV.begin(), sIPtoPASV.end(), '.', ',');

            if (ftpctx->command == FTP_CMD_PASV)
            {
                char url[30];
                snprintf(url, sizeof(url), "%s,%d,%d", sIPtoPASV.c_str(), ftpctx->pasiveport/256, ftpctx->pasiveport%256);
                response = "227 Entering Passive Mode (";
                response.append(url);
                response.append(")");
            }
            else // FTP_CMD_EPSV
            {
                char url[30];
                snprintf(url, sizeof(url), "%d", ftpctx->pasiveport);
                response = "229 Entering Extended Passive Mode (|||";
                response.append(url);
                response.append("|)");
            }
            break;
        }
        case FTP_CMD_OPTS:
        {
            for (char& c : ftpctx->arg1) { c = static_cast<char>(toupper(c)); };
            for (char& c : ftpctx->arg2) { c = static_cast<char>(toupper(c)); };
            if (ftpctx->arg1 == "UTF8" && ftpctx->arg2 == "ON")
            {
                response = "200 All good";
            }
            else
            {
                response = "501 Unrecognized OPTS " + ftpctx->arg1 + " " + ftpctx->arg2;
            }
            break;
        }
        case FTP_CMD_PWD:
        {
            MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd);
            if (n)
            {
                response = "257 ";
                response.append("\"");
                response.append(ftpctx->cwdpath);
                response.append("\"");
                delete n;
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
        case FTP_CMD_CWD:
        {
            response = cd(ftpctx->arg1, ftpctx);
            break;
        }
        case FTP_CMD_CDUP:
        {
            MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd);
            if (n)
            {
                handle parentHandle = n->getParentHandle();
                response = cdup(parentHandle, ftpctx);
                delete n;
            }
            else
            {
                response = "550 CWD not Found.";
            }
            break;
        }
        case FTP_CMD_LIST:
        case FTP_CMD_NLST:
        {
            if (!ftpctx->ftpDataServer)
            {
                response = "425 No Data Connection available";
                break;
            }

            MegaNode *node = NULL;
            if (ftpctx->arg1.size() && ftpctx->arg1 != "-l" && ftpctx->arg1 != "-a")
            {
                node = getNodeByFtpPath(ftpctx, ftpctx->arg1);
            }
            else
            {
                if (ftpctx->atroot)
                {
                    assert(!ftpctx->ftpDataServer->resultmsj.size());
                    set<handle> handles = getAllowedHandles();
                    for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it)
                    {
                        string name = megaApi->handleToBase64(*it);
                        MegaNode *n = megaApi->getNodeByHandle(*it);
                        if (n)
                        {
                            string toret = getListingLineFromNode(n, name);
                            toret.append(crlfout);
                            ftpctx->ftpDataServer->resultmsj.append(toret);
                            delete n;
                        }
                    }

                    ftpctx->ftpDataServer->resultmsj.append(crlfout);

                    response = "150 Here comes the directory listing";
                    break;
                }
                else if (ftpctx->athandle)
                {
                    MegaNode *n = megaApi->getNodeByHandle(ftpctx->cwd);
                    if (n)
                    {
                        string toret = getListingLineFromNode(n);
                        toret.append(crlfout);
                        assert(!ftpctx->ftpDataServer->resultmsj.size());
                        ftpctx->ftpDataServer->resultmsj.append(toret);
                        delete n;
                    }
                    ftpctx->ftpDataServer->resultmsj.append(crlfout);

                    response = "150 Here comes the directory listing";
                    break;
                }

                node = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd);
            }
            if (node)
            {
                if (node->isFolder())
                {
                    MegaNodeList *children = ftpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE);
                    assert(!ftpctx->ftpDataServer->resultmsj.size());

                    if (ftpctx->command == FTP_CMD_LIST)
                    {
                        ftpctx->ftpDataServer->resultmsj.append(getListingLineFromNode(node,"."));
                        ftpctx->ftpDataServer->resultmsj.append(crlfout);
                    }

                    for (int i = 0; i < children->size(); i++)
                    {
                        MegaNode *child = children->get(i);
                        //string childURL = subbaseURL + child->getName();
                        string toret;
                        if (ftpctx->command == FTP_CMD_LIST)
                        {
                            toret = getListingLineFromNode(child);
                        }
                        else //NLST
                        {
                            toret = child->getName();
                        }
                        toret.append(crlfout);
                        ftpctx->ftpDataServer->resultmsj.append(toret);
                    }
                    delete children;

                    ftpctx->ftpDataServer->resultmsj.append(crlfout);

                    response = "150 Here comes the directory listing";
                }
                else
                {
                    string toret;
                    if (ftpctx->command == FTP_CMD_LIST)
                    {
                        toret = getListingLineFromNode(node);
                    }
                    else //NLST
                    {
                        toret = node->getName();
                    }
                    toret.append(crlfout);
                    assert(!ftpctx->ftpDataServer->resultmsj.size());
                    ftpctx->ftpDataServer->resultmsj.append(toret);
                    response = "150 Here comes the file listing";
                }
                delete node;
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
        case FTP_CMD_RETR:
        {
            if (!ftpctx->ftpDataServer)
            {
                response = "425 No Data Connection available";
                break;
            }

            MegaNode *node = getNodeByFtpPath(ftpctx, ftpctx->arg1);
            if (node)
            {
                if (node->isFile())
                {
                    uv_mutex_lock(&ftpctx->mutex_nodeToDownload);

                    MegaNode *oldNodeToDownload = ftpctx->ftpDataServer->nodeToDownload;
                    ftpctx->ftpDataServer->nodeToDownload = node;
                    delete oldNodeToDownload;

                    uv_mutex_unlock(&ftpctx->mutex_nodeToDownload);

                    response = "150 Here comes the file: ";
                    response.append(node->getName());
                }
                else
                {
                    response = "501 Not a file";
                    delete node;
                }
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
//        case FTP_CMD_STOU: //do create random unique name
        case FTP_CMD_STOR:
        {
            //We might want to deal with foreign node / public link and so on ?
            if (!ftpctx->ftpDataServer)
            {
                response = "425 No Data Connection available";
                break;
            }

            MegaNode *newParentNode = NULL;
            size_t seppos = ftpctx->arg1.find_last_of("/");
            ftpctx->ftpDataServer->newNameToUpload = ftpctx->arg1;
            if (seppos == string::npos)
            {
                ftpctx->ftpDataServer->newParentNodeHandle = ftpctx->cwd;
            }
            else
            {
                if (!seppos) //new folder structure does not allow this
                {
                    response = "550 Not Found";
                    break;
                }

                if ((seppos + 1) < ftpctx->ftpDataServer->newNameToUpload.size())
                {
                    ftpctx->ftpDataServer->newNameToUpload = ftpctx->ftpDataServer->newNameToUpload.substr(seppos + 1);
                }
                string newparentpath = ftpctx->arg1.substr(0, seppos);
                newParentNode = getNodeByFtpPath(ftpctx, newparentpath);
                if (newParentNode)
                {
                    ftpctx->ftpDataServer->newParentNodeHandle = newParentNode->getHandle();
                    delete newParentNode;
                }
                else
                {
                    ftpctx->ftpDataServer->newNameToUpload = ""; //empty
                }
            }

            if (ftpctx->ftpDataServer->newNameToUpload.size())
            {
                ftpctx->ftpDataServer->remotePathToUpload = ftpctx->arg1;
                response = "150 Opening data connection for storing ";
                response.append(ftpctx->arg1);
            }
            else
            {
                response = "550 Destiny Not Found";
            }
            break;
        }
        case FTP_CMD_RNFR:
        {
            MegaNode *nodetoRename = getNodeByFtpPath(ftpctx, ftpctx->arg1);
            if (nodetoRename)
            {
                this->nodeHandleToRename = nodetoRename->getHandle();
                response = "350 Pending RNTO";
                delete nodetoRename;
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
        case FTP_CMD_RMD:
        case FTP_CMD_DELE:
        {
            MegaNode *nodeToDelete = getNodeByFtpPath(ftpctx, ftpctx->arg1);

            if (nodeToDelete)
            {
                if ((ftpctx->command == FTP_CMD_DELE) ? nodeToDelete->isFile() : nodeToDelete->isFolder())
                {
                    ftpctx->megaApi->remove(nodeToDelete, false, ftpctx);
                    delayresponse = true;
                }
                else
                {
                    response = "501 Wrong type";
                }
                delete nodeToDelete;
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
        case FTP_CMD_RNTO:
        {
            if (this->nodeHandleToRename == UNDEF)
            {
                response = "503 Bad sequence of commands, required RNFR first";
            }
            else
            {
                MegaNode *nodeToRename = ftpctx->megaApi->getNodeByHandle(this->nodeHandleToRename);
                if (nodeToRename)
                {
                    MegaNode *n = getNodeByFtpPath(ftpctx, ftpctx->arg1);
                    if (n)
                    {
                        ftpctx->nodeToDeleteAfterMove = n;
                    }

                    MegaNode *newParentNode = NULL;
                    size_t seppos = ftpctx->arg1.find_last_of("/");
                    string newName = ftpctx->arg1;

                    if (seppos != string::npos)
                    {
                        if (!seppos) //new folder structure does not allow this
                        {
                            response = "553 Requested action not taken: Invalid destiny";
                            delete n;
                            ftpctx->nodeToDeleteAfterMove = NULL;
                            delete nodeToRename;
                            break;
                        }

                        if ((seppos + 1) < newName.size())
                        {
                            newName = newName.substr(seppos + 1);
                        }
                        string newparentpath = ftpctx->arg1.substr(0, seppos);
                        newParentNode = getNodeByFtpPath(ftpctx, newparentpath);
                        if (!newParentNode)
                        {
                            newName = ""; //empty
                        }
                    }

                    if (newName.size())
                    {
                        if (newParentNode && newParentNode->getHandle() != nodeToRename->getParentHandle())
                        {
                            newNameAfterMove = newName;
                            ftpctx->megaApi->moveNode(nodeToRename, newParentNode, ftpctx);
                            delayresponse = true;
                        }
                        else
                        {
                            ftpctx->megaApi->renameNode(nodeToRename, newName.c_str(), ftpctx);
                            delayresponse = true;
                        }
                    }
                    else
                    {
                        response = "553 Requested action not taken. Invalid destiny";
                        delete n;
                        ftpctx->nodeToDeleteAfterMove = NULL;
                    }
                    delete newParentNode;
                    delete nodeToRename;
                }
                else
                {
                    response = "553 Requested action not taken. Origin not found: no longer available";
                }

                nodeHandleToRename = UNDEF;
            }
            break;
        }
        case FTP_CMD_MKD:
        {
            MegaNode *newParentNode = NULL;
            size_t seppos = ftpctx->arg1.find_last_of("/");
            string newNameFolder = ftpctx->arg1;
            MegaNode *n = getNodeByFtpPath(ftpctx, newNameFolder);
            if (n)
            {
                response = "550 already existing!";
                delete n;
            }
            else
            {
                if (!seppos) //new folder structure does not allow this
                {
                    response = "550 Not Found";
                    break;
                }

                if (seppos != string::npos)
                {
                    if ((seppos + 1) < newNameFolder.size())
                    {
                        newNameFolder = newNameFolder.substr(seppos + 1);
                    }
                    string newparentpath = ftpctx->arg1.substr(0, seppos);
                    newParentNode = getNodeByFtpPath(ftpctx, newparentpath);
                    if (!newParentNode)
                    {
                        newNameFolder = ""; //empty
                    }
                }

                if (newNameFolder.size())
                {
                    MegaNode *nodecwd = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd);
                    ftpctx->megaApi->createFolder(newNameFolder.c_str(),newParentNode ? newParentNode : nodecwd, ftpctx);
                    delete nodecwd;
                    delayresponse = true;
                }
                else
                {
                    response = "550 Not Found";
                }
                delete newParentNode;
            }
            break;
        }
        case FTP_CMD_REST:
        {
            if (ftpctx->ftpDataServer && ftpctx->ftpDataServer->nodeToDownload)
            {
                unsigned long long number = strtoull(ftpctx->arg1.c_str(), NULL, 10);
                if (number != ULLONG_MAX)
                {
                    ftpctx->ftpDataServer->rangeStartREST = number;
                    response = "350 Restarting at: ";
                    response.append(ftpctx->arg1);
                }
                else
                {
                    response = "500 Syntax error, invalid start point";
                }
            }
            else
            {
                response = "350 Requested file action pending further information: Download file not available";
            }
            break;
        }
        case FTP_CMD_FEAT:
        {
            response = "211-Features:";
            response.append(crlfout);
            response.append(" SIZE");
            response.append(crlfout);
            response.append(" PROT");
            response.append(crlfout);
            response.append(" EPSV");
            response.append(crlfout);
            response.append(" PBSZ");
            response.append(crlfout);
//            response.append(" OPTS"); // This is actually compulsory when FEAT exists (no need to return it)
//            response.append(crlfout);
            response.append(" UTF8 ON");
            response.append(crlfout);
            response.append("211 End");
            break;
        }
        case FTP_CMD_SIZE: //This has to be exact, and depends ond STRU, MODE & TYPE!! (since assumed TYPE I, it's ok)
        {
            MegaNode *nodeToGetSize = getNodeByFtpPath(ftpctx, ftpctx->arg1);
            if (nodeToGetSize)
            {
                if (nodeToGetSize->isFile())
                {
                    response = "213 ";

                    ostringstream sizenumber;
                    sizenumber << nodeToGetSize->getSize();
                    response.append(sizenumber.str());
                }
                else
                {
                    response = "213 ";
                    response.append("0");
                }
                delete nodeToGetSize;
            }
            else
            {
                response = "550 Not Found";
            }
            break;
        }
        case FTP_CMD_INVALID:
        {
            response = "500 Syntax error, command unrecognized";
            break;
        }
        default:
            response = "502 Command not implemented";
            break;
        }

        response += crlfout;
    }

    if (nread < 0)
    {
        LOG_debug << "FTP Control Server received invalid read size. Closing connection";
        closeConnection(ftpctx);
    }
    else
    {
        if (!delayresponse)
        {
            LOG_verbose << " Processed: " << petition << ". command="<< ftpctx->command << ".\n   Responding: " << response << " tpctx=" << ftpctx;
            answer(ftpctx,response.c_str(),response.size());
        }
        else
        {
            LOG_verbose << " Processed: " << petition << ". command="<< ftpctx->command << ".\n   Delaying response. tpctx=" << ftpctx;
        }

        // Initiate data transfer for required commands
        if (ftpctx->ftpDataServer &&
                ( ftpctx->command == FTP_CMD_LIST
                  || ftpctx->command == FTP_CMD_NLST
                  || ftpctx->command == FTP_CMD_REST
                  || ftpctx->command == FTP_CMD_STOR
                  || ftpctx->command == FTP_CMD_RETR )
            )
        {
            LOG_debug << " calling sending data... ";
            ftpctx->ftpDataServer->sendData(); //rename to sth more sensefull: like wake data channel
        }
    }
    uv_mutex_unlock(&tcpctx->mutex);
}

void MegaFTPServer::processAsyncEvent(MegaTCPContext *tcpctx)
{
    LOG_verbose << "Processing FTP Server async event";
    if (tcpctx->finished)
    {
        LOG_debug << "FTP link closed, ignoring async event";
        return;
    }

    MegaFTPContext* ftpctx = dynamic_cast<MegaFTPContext *>(tcpctx);

    uv_mutex_lock(&ftpctx->mutex_responses);
    while (ftpctx->responses.size())
    {
        answer(tcpctx,ftpctx->responses.front().c_str(),ftpctx->responses.front().size());
        ftpctx->responses.pop_front();
    }
    uv_mutex_unlock(&ftpctx->mutex_responses);
}

void MegaFTPServer::processOnAsyncEventClose(MegaTCPContext* tcpctx)
{
    LOG_verbose << "At MegaFTPServer::processOnAsyncEventClose";
}

void MegaTCPServer::answer(MegaTCPContext* tcpctx, const char *rsp, size_t rlen)
{
    LOG_verbose << " answering in port " << tcpctx->server->port << " : " << string(rsp,rlen);

    uv_buf_t resbuf = uv_buf_init((char *)rsp, static_cast<unsigned>(rlen));
#ifdef ENABLE_EVT_TLS
    if (tcpctx->server->useTLS)
    {
        // we are sending the response as a whole
        int err = evt_tls_write(tcpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls);
        if (err <= 0)
        {
            LOG_warn << "Finishing due to an error sending the response: " << err;
            closeConnection(tcpctx);
        }
    }
    else
    {
#endif
        uv_write_t *req = new uv_write_t();
        req->data = tcpctx;
        if (int err = uv_write(req, (uv_stream_t*)&tcpctx->tcphandle, &resbuf, 1, onWriteFinished))
        {
            delete req;
            LOG_warn << "Finishing due to an error sending the response: " << err;
            closeTCPConnection(tcpctx);
        }
#ifdef ENABLE_EVT_TLS
    }
#endif
}

bool MegaFTPServer::respondNewConnection(MegaTCPContext* tcpctx)
{
    MegaFTPContext* ftpctx = dynamic_cast<MegaFTPContext *>(tcpctx);

    string response = "220 Wellcome to FTP MEGA Server";
    response.append(crlfout);

    answer(ftpctx, response.c_str(), response.size());
    return true;

}

void MegaFTPServer::processOnExitHandleClose(MegaTCPServer *tcpServer)
{

}

MegaFTPContext::MegaFTPContext()
{
    command = 0;
    resultcode = 0;

    cwd = UNDEF;
    atroot = false;
    athandle = false;
    parentcwd = UNDEF;
    pasiveport = -1;
    ftpDataServer = NULL;
    nodeToDeleteAfterMove = NULL;
    uv_mutex_init(&mutex_responses);
    uv_mutex_init(&mutex_nodeToDownload);
}

MegaFTPContext::~MegaFTPContext()
{
    if (ftpDataServer)
    {
        LOG_verbose << "Deleting ftpDataServer associated with ftp context";
        delete ftpDataServer;
    }
    if (tmpFileName.size())
    {
        LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName);
        server->fsAccess->unlinklocal(localPath);
        tmpFileName = "";
    }
    uv_mutex_destroy(&mutex_responses);
    uv_mutex_destroy(&mutex_nodeToDownload);
}

void MegaFTPContext::onTransferStart(MegaApi *, MegaTransfer *transfer)
{
}

bool MegaFTPContext::onTransferData(MegaApi *, MegaTransfer *transfer, char *buffer, size_t size)
{
    LOG_verbose << "MegaFTPContext::onTransferData";
    return true;
}

void MegaFTPContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e)
{
    if (finished)
    {
        LOG_debug << "FTP link closed, ignoring the result of the transfer";
        return;
    }

    if (e->getErrorCode() == MegaError::API_OK)
    {
        MegaFTPServer::returnFtpCodeAsync(this, 250);
    }
    else
    {
        MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e);
    }
    if (tmpFileName.size())
    {
        LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName);
        server->fsAccess->unlinklocal(localPath);
        tmpFileName = "";
    }
}

void MegaFTPContext::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e)
{
    if (finished)
    {
        LOG_debug << "HTTP link closed, ignoring the result of the request";
        return;
    }

    MegaFTPServer* ftpserver = dynamic_cast<MegaFTPServer *>(this->server);

    if (request->getType() == MegaRequest::TYPE_REMOVE)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            if (cwd == request->getNodeHandle())
            {
                LOG_verbose << " Removing cwd node, going back to parent";
                ftpserver->cdup(parentcwd, this);
            }
            else // This could be unexpected if CWD is removed elsewhere by the time this RequestFinish completes //TODO: decide upon revision: is this OK?
            {
                MegaNode *n = megaApi->getNodeByHandle(cwd);
                size_t seps = std::count(cwdpath.begin(), cwdpath.end(), '/');
                unsigned int isep = 0;
                string sup = cwdpath;
                while (!n && (isep++ < seps))
                {
                    sup.append("/..");
                    string nsup = ftpserver->shortenpath(sup);
                    ftpserver->cd(nsup, this);
                    n = megaApi->getNodeByHandle(cwd);
                }
                delete n;
            }
            MegaFTPServer::returnFtpCodeAsync(this, 250);
        }
        else
        {
            MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_CREATE_FOLDER)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            MegaFTPServer::returnFtpCodeAsync(this, 257, request->getName());
        }
        else
        {
            MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else if (request->getType() == MegaRequest::TYPE_RENAME)
    {
        if (e->getErrorCode() == MegaError::API_OK )
        {
            if (nodeToDeleteAfterMove)
            {
                this->megaApi->remove(nodeToDeleteAfterMove, false, this);
                nodeToDeleteAfterMove = NULL;
                delete nodeToDeleteAfterMove;
            }
            else
            {
                MegaFTPServer::returnFtpCodeAsync(this, 250);
            }
        }
        else
        {
            MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e);
        }
    }
    else  if (request->getType() == MegaRequest::TYPE_MOVE)
    {
        if (e->getErrorCode() == MegaError::API_OK)
        {
            if (ftpserver->newNameAfterMove.size())
            {
                MegaNode *nodetoRename = this->megaApi->getNodeByHandle(request->getNodeHandle());
                if (!nodetoRename)
                {
                    MegaFTPServer::returnFtpCodeAsync(this, 550, "Moved node not found");
                }
                else if (!strcmp(nodetoRename->getName(), ftpserver->newNameAfterMove.c_str()))
                {
                    if (nodeToDeleteAfterMove)
                    {
                        this->megaApi->remove(nodeToDeleteAfterMove, false, this);
                        nodeToDeleteAfterMove = NULL;
                        delete nodeToDeleteAfterMove;
                    }
                    else
                    {
                        MegaFTPServer::returnFtpCodeAsync(this, 250);
                    }
                }
                else
                {
                    this->megaApi->renameNode(nodetoRename, ftpserver->newNameAfterMove.c_str(), this);
                }
                delete nodetoRename;
            }
            else
            {
                if (nodeToDeleteAfterMove)
                {
                    this->megaApi->remove(nodeToDeleteAfterMove, false, this);
                    nodeToDeleteAfterMove = NULL;
                    delete nodeToDeleteAfterMove;
                }
                else
                {
                    MegaFTPServer::returnFtpCodeAsync(this, 250);
                }
            }
        }
        else
        {
            MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e);
        }
    }

    uv_async_send(&asynchandle);
}


// FTP DATA Server
MegaFTPDataServer::MegaFTPDataServer(MegaApiImpl *megaApi, string basePath, MegaFTPContext * controlftpctx, bool useTLS, string certificatepath, string keypath)
: MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath)
{
    this->controlftpctx = controlftpctx;
    this->nodeToDownload = NULL;
    this->rangeStartREST = 0;
    this->notifyNewConnectionRequired = false;
    this->newParentNodeHandle = UNDEF;
}

MegaFTPDataServer::~MegaFTPDataServer()
{
    LOG_verbose << "MegaFTPDataServer::~MegaFTPDataServer";
    delete nodeToDownload;

    // if not stopped, the uv thread might want to access a pointer to this.
    // though this is done in the parent destructor, it could try to access it after vtable has been erased
    stop();
    LOG_verbose << "MegaFTPDataServer::~MegaFTPDataServer. end";
}

MegaTCPContext* MegaFTPDataServer::initializeContext(uv_stream_t *server_handle)
{
    MegaFTPDataContext* ftpctx = new MegaFTPDataContext();

    // Set connection data
    MegaFTPDataServer *server = (MegaFTPDataServer *)(server_handle->data);
    ftpctx->server = server;
    ftpctx->megaApi = server->megaApi;
    ftpctx->tcphandle.data = ftpctx;
    ftpctx->asynchandle.data = ftpctx;

    return ftpctx;
}

void MegaFTPDataServer::processWriteFinished(MegaTCPContext *tcpctx, int status)
{
    if (status < 0)
    {
        LOG_warn << " error received at processWriteFinished: " << status << ": " << uv_err_name(status);
    }

    MegaFTPDataContext* ftpdatactx = dynamic_cast<MegaFTPDataContext *>(tcpctx);

    LOG_debug << " processWriteFinished on MegaFTPDataServer. status = " << status;
    if (resultmsj.size())
    {
        resultmsj = ""; // empty result msj // this would be incorrect if we used partial writes (does not seem to be required)

        if (this->controlftpctx)
        {
            ftpdatactx->setControlCodeUponDataClose(226);
        }
        else
        {
            LOG_verbose << "Avoiding waking controlftp aync handle, ftpctx already closed";
        }
        closeConnection(tcpctx);
    }
    else // transfering node (download)
    {
        ftpdatactx->bytesWritten += ftpdatactx->lastBufferLen;
        LOG_verbose << "Bytes written: " << ftpdatactx->lastBufferLen << " Remaining: " << (ftpdatactx->size - ftpdatactx->bytesWritten);
        ftpdatactx->lastBuffer = NULL;

        if (status < 0 || ftpdatactx->size == ftpdatactx->bytesWritten)
        {
            if (status < 0)
            {
                LOG_warn << "Finishing request. Write failed: " << status << ": " << uv_err_name(status);
            }
            else
            {
                LOG_debug << "Finishing request. All data sent";
            }

            if (this->controlftpctx)
            {
                ftpdatactx->setControlCodeUponDataClose(226);
            }
            else
            {
                LOG_verbose << "Avoiding waking controlftp aync handle, ftpctx already closed";
            }
            closeConnection(ftpdatactx);
            return;
        }

        uv_mutex_lock(&ftpdatactx->mutex);
        if (ftpdatactx->lastBufferLen)
        {
            ftpdatactx->streamingBuffer.freeData(ftpdatactx->lastBufferLen);
            ftpdatactx->lastBufferLen = 0;
        }

        if (ftpdatactx->pause)
        {
            if (ftpdatactx->streamingBuffer.availableSpace() > ftpdatactx->streamingBuffer.availableCapacity() / 2)
            {
                ftpdatactx->pause = false;
                m_off_t start = ftpdatactx->rangeStart + ftpdatactx->rangeWritten + ftpdatactx->streamingBuffer.availableData();
                m_off_t len =  ftpdatactx->rangeEnd - ftpdatactx->rangeStart -  ftpdatactx->rangeWritten - ftpdatactx->streamingBuffer.availableData();

                LOG_debug << "[Streaming] Resuming streaming from " << start << " len: " << len
                          << " " << ftpdatactx->streamingBuffer.bufferStatus();
                ftpdatactx->megaApi->startStreaming(ftpdatactx->node, start, len, ftpdatactx);
            }
        }
        uv_mutex_unlock(&ftpdatactx->mutex);

        uv_async_send(&ftpdatactx->asynchandle);
    }
}

void MegaFTPDataServer::sendData()
{
    MegaTCPContext * tcpctx = NULL;

    this->notifyNewConnectionRequired = true;

    if (connections.size())
    {
        tcpctx = connections.back(); //only interested in the last connection received (the one that needs response)
    }
    //Some client might create connections before receiving a 150 in the control channel (e.g: ftp linux command)
    // This could cause never answered / never closed connections.
    //assert(connections.size() <= 1); //This might not be true due to that

    if (tcpctx)
    {
        LOG_verbose << "MegaFTPDataServer::sendData. triggering asyncsend for tcpctx=" << tcpctx;
#ifdef ENABLE_EVT_TLS
        if (tcpctx->evt_tls == NULL)
        {
            LOG_warn << "MegaFTPDataServer::sendData, evt_tls is NULL";
        }

        if (useTLS && (!tcpctx->evt_tls || tcpctx->finished || !evt_tls_is_handshake_over(tcpctx->evt_tls)))
        {
            if (!tcpctx->evt_tls)
            {
                LOG_verbose << "MegaFTPDataServer::sendData. no evt_tls";
            }
            else if (tcpctx->finished)
            {
                LOG_verbose << "MegaFTPDataServer::sendData. tcpctx->finished";
                this->notifyNewConnectionRequired = false;
            }
            else
            {
                LOG_verbose << "MegaFTPDataServer::sendData. handshake not over";
            }
        }
        else
        {
#endif
            LOG_verbose << "MegaFTPDataServer::sendData. do triggering asyncsend 03";
            this->notifyNewConnectionRequired = false;
            uv_async_send(&tcpctx->asynchandle);
#ifdef ENABLE_EVT_TLS
        }
#endif
    }
    else
    {
        LOG_verbose << "MegaFTPDataServer::sendData. no tcpctx. notifyNewConnectionRequired";
        this->notifyNewConnectionRequired = true;
    }
}

void MegaFTPDataServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf)
{
    MegaFTPDataContext* ftpdatactx = dynamic_cast<MegaFTPDataContext *>(tcpctx);
    MegaFTPDataServer* fds = dynamic_cast<MegaFTPDataServer *>(ftpdatactx->server);

    if (fds->newNameToUpload.size())
    {
        //create tmp file with contents in messageBody
        if (!ftpdatactx->tmpFileAccess)
        {
            ftpdatactx->tmpFileName = fds->basePath;
            ftpdatactx->tmpFileName.append("ftpstorfile");
            ftpdatactx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false));

            string ext;
            if (ftpdatactx->server->fsAccess->getextension(LocalPath::fromAbsolutePath(fds->controlftpctx->arg1), ext))
            {
                ftpdatactx->tmpFileName.append(ext);
            }

            ftpdatactx->tmpFileAccess = fds->fsAccess->newfileaccess();
            LocalPath localPath = LocalPath::fromAbsolutePath(ftpdatactx->tmpFileName);
            fds->fsAccess->unlinklocal(localPath);

            if (!ftpdatactx->tmpFileAccess->fopen(localPath, false, true, FSLogging::logOnError))
            {
                ftpdatactx->setControlCodeUponDataClose(450);
                remotePathToUpload = ""; //empty, so that we don't read in the next connections
                closeConnection(tcpctx);
                return;
            }
        }

        if (nread > 0)
        {
            LOG_verbose << " Writing " << nread << " bytes " << " to temporal file: " << ftpdatactx->tmpFileName;
            if (!ftpdatactx->tmpFileAccess->fwrite((const byte*)buf->base, static_cast<unsigned>(nread), ftpdatactx->tmpFileSize) )
            {
                ftpdatactx->setControlCodeUponDataClose(450);
                remotePathToUpload = ""; //empty, so that we don't read in the next connections
                closeConnection(tcpctx);
            }
            ftpdatactx->tmpFileSize += nread;
        }
    }
    else
    {
        LOG_err << "FTPData server receiving unexpected data: " << nread << " bytes";
    }


    if (nread < 0) //transfer finish
    {
        LOG_verbose << "FTP Data Channel received invalid read size: " << nread << ". Closing connection";
        if (ftpdatactx->tmpFileName.size())
        {
            MegaNode *newParentNode = ftpdatactx->megaApi->getNodeByHandle(fds->newParentNodeHandle);
            if (newParentNode)
            {
                LOG_debug << "Starting upload of file " << fds->newNameToUpload;
                fds->controlftpctx->tmpFileName = ftpdatactx->tmpFileName;

                FileSystemType fsType = fds->fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(ftpdatactx->tmpFileName));
                ftpdatactx->megaApi->startUpload(false, ftpdatactx->tmpFileName.c_str(), newParentNode, fds->newNameToUpload.c_str(),
                                                    nullptr, -1, 0, true, nullptr, false, false, fsType, CancelToken(), fds->controlftpctx);

                ftpdatactx->controlRespondedElsewhere = true;
            }
            else
            {
                LOG_err << "Unable to start upload: " << fds->newNameToUpload;
                ftpdatactx->setControlCodeUponDataClose(550, "Destination folder not available");
            }
            ftpdatactx->tmpFileName="";
        }
        else
        {
            LOG_err << "Data channel received close without tmp file created!";
            ftpdatactx->setControlCodeUponDataClose(426);
        }

        ftpdatactx->tmpFileName = ""; // empty so that we don't try to upload it
        remotePathToUpload = ""; //empty, so that we don't read in the next connections
        closeConnection(tcpctx);
        return;
    }
}

void MegaFTPDataServer::processAsyncEvent(MegaTCPContext *tcpctx)
{
    LOG_verbose << "MegaFTPDataServer::processAsyncEvent. tcptcx= " << tcpctx;
    MegaFTPDataContext* ftpdatactx = dynamic_cast<MegaFTPDataContext *>(tcpctx);
    MegaFTPDataServer* fds = dynamic_cast<MegaFTPDataServer *>(tcpctx->server);

    if (ftpdatactx->finished)
    {
        LOG_debug << "FTP DATA link closed, ignoring async event";
        return;
    }

    if (ftpdatactx->failed)
    {
        LOG_warn << "Streaming transfer failed. Closing connection.";
        closeConnection(ftpdatactx);
        return;
    }


    uv_mutex_lock(&fds->controlftpctx->mutex_nodeToDownload);

    if (resultmsj.size())
    {
        LOG_debug << " responding DATA: " << resultmsj;
        answer(ftpdatactx, resultmsj.c_str(), resultmsj.size());
    }
    else if (remotePathToUpload.size())
    {
        LOG_debug << " receive data to store in tmp file:";
        readData(ftpdatactx);
    }
    else if (nodeToDownload)
    {
        if (!ftpdatactx->node || rangeStartREST) //alterantive to || rangeStart, define aborted?
        {
            if (!rangeStartREST)
            {
                LOG_debug << "Initiating node download via port " << fds->port;
            }
            else
            {
                LOG_debug << "Initiating node download from: " << rangeStartREST << " via port " << fds->port;
            }

            ftpdatactx->rangeStart = rangeStartREST;
            rangeStartREST = 0;// so as not to start again
            ftpdatactx->bytesWritten = 0;
            ftpdatactx->size = 0;
            ftpdatactx->streamingBuffer.setMaxBufferSize(ftpdatactx->server->getMaxBufferSize());
            ftpdatactx->streamingBuffer.setMaxOutputSize(ftpdatactx->server->getMaxOutputSize());

            ftpdatactx->transfer = new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD);

            ftpdatactx->transfer->setPath(fds->controlftpctx->arg1.c_str());
            if (ftpdatactx->nodename.size())
            {
                ftpdatactx->transfer->setFileName(ftpdatactx->nodename.c_str());
            }
            if (ftpdatactx->nodehandle.size())
            {
                ftpdatactx->transfer->setNodeHandle(MegaApi::base64ToHandle(ftpdatactx->nodehandle.c_str()));
            }

            ftpdatactx->transfer->setStartTime(Waiter::ds);

            m_off_t totalSize = nodeToDownload->getSize();
            m_off_t start = 0;
            m_off_t end = totalSize - 1;
            if (ftpdatactx->rangeStart > 0)
            {
                start = ftpdatactx->rangeStart;
            }
            ftpdatactx->rangeEnd = end + 1;
            ftpdatactx->rangeStart = start;

            //bool rangeRequested = (/*ftpdatactx->rangeEnd*/ - ftpdatactx->rangeStart) != totalSize;
            m_off_t len = end - start + 1;

            ftpdatactx->pause = false;
            ftpdatactx->lastBuffer = NULL;
            ftpdatactx->lastBufferLen = 0;
            ftpdatactx->transfer->setStartPos(0);
            ftpdatactx->transfer->setEndPos(end); //This will actually be override later
            ftpdatactx->node = nodeToDownload->copy();

            ftpdatactx->streamingBuffer.setFileSize(nodeToDownload->getSize());
            ftpdatactx->streamingBuffer.setDuration(nodeToDownload->getDuration());

            ftpdatactx->streamingBuffer.init(len);
            ftpdatactx->size = len;

            ftpdatactx->megaApi->fireOnFtpStreamingStart(ftpdatactx->transfer);

            LOG_debug << "Requesting range. From " << start << "  size " << len;
            ftpdatactx->rangeWritten = 0;
            if (start || len)
            {
                ftpdatactx->megaApi->startStreaming(nodeToDownload, start, len, ftpdatactx);
            }
            else
            {
                LOG_debug << "Skipping startStreaming call since empty file";
                ftpdatactx->megaApi->fireOnFtpStreamingFinish(ftpdatactx->transfer, make_unique<MegaErrorPrivate>(API_OK));
                ftpdatactx->transfer = NULL; // this has been deleted in fireOnStreamingFinish
                fds->processWriteFinished(ftpdatactx, 0);
            }
        }
        else
        {
            LOG_debug << "Calling sendNextBytes port = " << fds->port;
            sendNextBytes(ftpdatactx);
        }
    }
    else
    {

        LOG_err << " Async event with no result mesj!!!";
    }

    uv_mutex_unlock(&fds->controlftpctx->mutex_nodeToDownload);
}

void MegaFTPDataServer::processOnAsyncEventClose(MegaTCPContext* tcpctx)
{
    MegaFTPDataContext* ftpdatactx = dynamic_cast<MegaFTPDataContext *>(tcpctx);
    MegaFTPDataServer *fds = ((MegaFTPDataServer *)ftpdatactx->server);

    LOG_verbose << "MegaFTPDataServer::processOnAsyncEventClose. tcpctx=" << tcpctx << " port = " << fds->port << " remaining = " << fds->remainingcloseevents;

    fds->remotePathToUpload = "";

    if (ftpdatactx->transfer)
    {
        ftpdatactx->megaApi->cancelTransfer(ftpdatactx->transfer);
        ftpdatactx->megaApi->fireOnFtpStreamingFinish(ftpdatactx->transfer, make_unique<MegaErrorPrivate>(ftpdatactx->ecode));
        ftpdatactx->transfer = NULL; // this has been deleted in fireOnStreamingFinish
    }

    if (!fds->remainingcloseevents && fds->closing)
    {
        LOG_verbose << "MegaFTPDataServer::processOnAsyncEventClose stopping without waiting. port = " << fds->port;
        fds->stop(true);
    }

    if (!ftpdatactx->controlRespondedElsewhere && fds->started && !this->controlftpctx->finished)
    {
        LOG_debug << "MegaFTPDataServer::processOnAsyncEventClose port = " << fds->port << ". Responding " << ftpdatactx->controlResponseCode << ". " << ftpdatactx->controlResponseMessage;
        MegaFTPServer* ftpControlServer = dynamic_cast<MegaFTPServer *>(fds->controlftpctx->server);
        ftpControlServer->returnFtpCode(this->controlftpctx, ftpdatactx->controlResponseCode, ftpdatactx->controlResponseMessage);
    }
}

bool MegaFTPDataServer::respondNewConnection(MegaTCPContext* tcpctx)
{
    MegaFTPDataContext* ftpdatactx = dynamic_cast<MegaFTPDataContext *>(tcpctx);
    if (notifyNewConnectionRequired) //in some cases, control connection tried this before the ftpdatactx was ready. this fixes that
    {
        LOG_verbose << "MegaFTPDataServer::respondNewConnection async sending to notify new connection";
        uv_async_send(&ftpdatactx->asynchandle);
        notifyNewConnectionRequired = false;
    }

    return false;
}

void MegaFTPDataServer::processOnExitHandleClose(MegaTCPServer *tcpServer)
{
}

void MegaFTPDataServer::sendNextBytes(MegaFTPDataContext *ftpdatactx)
{
    if (ftpdatactx->finished)
    {
        LOG_debug << "FTP link closed, aborting write";
        return;
    }

    if (ftpdatactx->lastBuffer)
    {
        LOG_verbose << "[Streaming] Skipping write due to another ongoing write";
        return;
    }

    uv_mutex_lock(&ftpdatactx->mutex);
    if (ftpdatactx->lastBufferLen)
    {
        ftpdatactx->streamingBuffer.freeData(ftpdatactx->lastBufferLen);
        ftpdatactx->lastBufferLen = 0;
    }

    if (ftpdatactx->tcphandle.write_queue_size > ftpdatactx->streamingBuffer.availableCapacity() / 8)
    {
        LOG_warn << "[Streaming] Skipping write. Too much queued data. " << ftpdatactx->streamingBuffer.bufferStatus();
        uv_mutex_unlock(&ftpdatactx->mutex);
        return;
    }

    uv_buf_t resbuf = ftpdatactx->streamingBuffer.nextBuffer();
    uv_mutex_unlock(&ftpdatactx->mutex);

    if (!resbuf.len)
    {
        LOG_verbose << "[Streaming] Skipping write. No data available. " << ftpdatactx->streamingBuffer.bufferStatus();
        return;
    }

    LOG_verbose << "Writing " << resbuf.len << " bytes" << " buffered = " << ftpdatactx->streamingBuffer.availableData();
    ftpdatactx->rangeWritten += resbuf.len;
    ftpdatactx->lastBuffer = resbuf.base;
    ftpdatactx->lastBufferLen = resbuf.len;

#ifdef ENABLE_EVT_TLS
    if (ftpdatactx->server->useTLS)
    {
        //notice this, contrary to !useTLS is synchronous
        int err = evt_tls_write(ftpdatactx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls);
        if (err <= 0)
        {
            LOG_warn << "[Streaming] Finishing due to an error sending the response: " << err;
            closeConnection(ftpdatactx);
        }
    }
    else
    {
#endif
        uv_write_t *req = new uv_write_t();
        req->data = ftpdatactx;

        if (int err = uv_write(req, (uv_stream_t*)&ftpdatactx->tcphandle, &resbuf, 1, onWriteFinished))
        {
            delete req;
            LOG_warn << "[Streaming] Finishing due to an error in uv_write: " << err;
            closeTCPConnection(ftpdatactx);
        }
#ifdef ENABLE_EVT_TLS
    }
#endif
}


MegaFTPDataContext::MegaFTPDataContext()
{
    transfer = NULL;
    lastBuffer = NULL;
    lastBufferLen = 0;
    failed = false;
    ecode = API_OK;
    pause = false;
    node = NULL;
    rangeWritten = 0;
    rangeStart = 0;
    tmpFileAccess = NULL;
    tmpFileSize = 0;
    this->controlRespondedElsewhere = false;
    this->controlResponseCode = 426;
}

MegaFTPDataContext::~MegaFTPDataContext()
{
    delete transfer;
    delete node;
}

void MegaFTPDataContext::setControlCodeUponDataClose(int code, string msg)
{
    controlResponseCode = code;
    controlResponseMessage = msg;
}

void MegaFTPDataContext::onTransferStart(MegaApi *, MegaTransfer *transfer)
{
    this->transfer->setTag(transfer->getTag());
}

bool MegaFTPDataContext::onTransferData(MegaApi *, MegaTransfer *transfer, char *buffer, size_t size)
{
    LOG_verbose << "Streaming data received: " << transfer->getTransferredBytes()
                << " Size: " << size
                << " Remaining from transfer: "   << (size + (transfer->getTotalBytes() - transfer->getTransferredBytes()) )
                << " Remaining to write TCP: "   << (this->size - bytesWritten)
                << " Queued: " << this->tcphandle.write_queue_size
                << " Buffered: " << streamingBuffer.availableData()
                << " Free: " << streamingBuffer.availableSpace();

    if (finished)
    {
        LOG_info << "Removing streaming transfer after " << transfer->getTransferredBytes() << " bytes";
        return false;
    }

    // append the data to the buffer
    uv_mutex_lock(&mutex);
    long long remaining = size + (transfer->getTotalBytes() - transfer->getTransferredBytes());
    long long availableSpace = streamingBuffer.availableSpace();
    if (remaining > availableSpace && availableSpace < (2 * m_off_t(size)))
    {
        LOG_debug << "[Streaming] Buffer full: Pausing streaming. " << streamingBuffer.bufferStatus();
        pause = true;
    }
    streamingBuffer.append(buffer, size);
    uv_mutex_unlock(&mutex);

    // notify the HTTP server
    uv_async_send(&asynchandle);
    return !pause;
}

void MegaFTPDataContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e)
{
    LOG_verbose << "MegaFTPDataContext::onTransferFinish";
    if (finished)
    {
        LOG_debug << "FTP Data link closed";
        return;
    }
    ecode = e->getErrorCode();
    if (ecode != API_OK && ecode != API_EINCOMPLETE)
    {
        LOG_warn << "Transfer failed with error code: " << ecode;
        failed = true;
    }
    uv_async_send(&asynchandle);
}

void MegaFTPDataContext::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *)
{
    if (finished)
    {
        LOG_debug << "FTP data link closed, ignoring the result of the request";
        return;
    }

    uv_async_send(&asynchandle);
}

#endif

#ifdef ENABLE_CHAT
MegaTextChatPeerListPrivate::MegaTextChatPeerListPrivate()
{

}

MegaTextChatPeerListPrivate::~MegaTextChatPeerListPrivate()
{

}

MegaTextChatPeerList *MegaTextChatPeerListPrivate::copy() const
{
    MegaTextChatPeerListPrivate *ret = new MegaTextChatPeerListPrivate;

    for (int i = 0; i < size(); i++)
    {
        ret->addPeer(list.at(i).first, list.at(i).second);
    }

    return ret;
}

void MegaTextChatPeerListPrivate::addPeer(MegaHandle h, int priv)
{
    list.push_back(userpriv_pair(h, (privilege_t) priv));
}

MegaHandle MegaTextChatPeerListPrivate::getPeerHandle(int i) const
{
    if (i > size())
    {
        return INVALID_HANDLE;
    }
    else
    {
        return list.at(i).first;
    }
}

int MegaTextChatPeerListPrivate::getPeerPrivilege(int i) const
{
    if (i > size())
    {
        return PRIV_UNKNOWN;
    }
    else
    {
        return list.at(i).second;
    }
}

int MegaTextChatPeerListPrivate::size() const
{
    return int(list.size());
}

const userpriv_vector *MegaTextChatPeerListPrivate::getList() const
{
    return &list;
}

void MegaTextChatPeerListPrivate::setPeerPrivilege(handle uh, privilege_t priv)
{
    for (unsigned int i = 0; i < list.size(); i++)
    {
        if (list.at(i).first == uh)
        {
            list.at(i).second = priv;
            return;
        }
    }

    // handle not found. Create new item
    addPeer(uh, priv);
}

MegaTextChatPeerListPrivate::MegaTextChatPeerListPrivate(userpriv_vector *userpriv)
{
    handle uh;
    privilege_t priv;

    for (unsigned i = 0; i < userpriv->size(); i++)
    {
        uh = userpriv->at(i).first;
        priv = userpriv->at(i).second;

        this->addPeer(uh, priv);
    }
}

MegaTextChatPrivate::MegaTextChatPrivate(const MegaTextChat *chat)
{
    this->id = chat->getHandle();
    this->priv = chat->getOwnPrivilege();
    this->shard = chat->getShard();
    this->peers = chat->getPeerList() ? chat->getPeerList()->copy() : NULL;
    this->group = chat->isGroup();
    this->ou = chat->getOriginatingUser();
    this->title = chat->getTitle() ? chat->getTitle() : "";
    this->ts = chat->getCreationTime();
    this->archived = chat->isArchived();
    this->publicchat = chat->isPublicChat();
    this->tag = chat->isOwnChange();
    this->changed = chat->getChanges();
    this->unifiedKey = chat->getUnifiedKey() ? chat->getUnifiedKey() : "";
    this->meeting = chat->isMeeting();
    this->chatOptions = chat->getChatOptions();

    if (chat->getScheduledMeetingList() && chat->getScheduledMeetingList()->size())
    {
        mScheduledMeetings.reset(chat->getScheduledMeetingList()->copy());
    }

    if (chat->getSchedMeetingsChanged() && chat->getSchedMeetingsChanged()->size())
    {
        mSchedMeetingsChanged.reset(chat->getSchedMeetingsChanged()->copy());
    }

    if (chat->getUpdatedOccurrencesList() && chat->getUpdatedOccurrencesList()->size())
    {
        mUpdatedOcurrences.reset(chat->getUpdatedOccurrencesList()->copy());
    }
}

MegaTextChatPrivate::MegaTextChatPrivate(const TextChat *chat)
{
    this->id = chat->id;
    this->priv = chat->priv;
    this->shard = chat->shard;
    this->peers = chat->userpriv ? new MegaTextChatPeerListPrivate(chat->userpriv) : NULL;
    this->group = chat->group;
    this->ou = chat->ou;
    this->title = chat->title;
    this->tag = chat->tag;
    this->ts = chat->ts;
    this->archived = chat->isFlagSet(TextChat::FLAG_OFFSET_ARCHIVE);
    this->publicchat = chat->publicchat;
    this->unifiedKey = chat->unifiedKey;
    this->meeting = chat->meeting;
    this->chatOptions = chat->chatOptions;
    this->changed = 0;

    if (!chat->mScheduledMeetings.empty())
    {
        mScheduledMeetings.reset(MegaScheduledMeetingList::createInstance());
        for (auto it = chat->mScheduledMeetings.begin(); it != chat->mScheduledMeetings.end(); it++)
        {
            mScheduledMeetings->insert(new MegaScheduledMeetingPrivate(it->second.get()));
        }
    }

    if (!chat->mSchedMeetingsChanged.empty())
    {
        mSchedMeetingsChanged.reset(MegaHandleList::createInstance());
        for (auto it = chat->mSchedMeetingsChanged.begin(); it != chat->mSchedMeetingsChanged.end(); it++)
        {
            mSchedMeetingsChanged->addMegaHandle(*it);
        }
        changed |= MegaTextChat::CHANGE_TYPE_SCHED_MEETING;
    }

    if (chat->changed.schedOcurrReplace || chat->changed.schedOcurrAppend)
    {
        changed |= chat->changed.schedOcurrReplace
                ? MegaTextChat::CHANGE_TYPE_SCHED_REPLACE_OCURR
                : MegaTextChat::CHANGE_TYPE_SCHED_APPEND_OCURR;

        if (!chat->mUpdatedOcurrences.empty())
        {
            mUpdatedOcurrences.reset(MegaScheduledMeetingList::createInstance());
            for (auto it = chat->mUpdatedOcurrences.begin(); it != chat->mUpdatedOcurrences.end(); it++)
            {
                mUpdatedOcurrences->insert(new MegaScheduledMeetingPrivate((*it).get()));
            }
        }
    }

    if (chat->changed.attachments)
    {
        changed |= MegaTextChat::CHANGE_TYPE_ATTACHMENT;
    }
    if (chat->changed.flags)
    {
        changed |= MegaTextChat::CHANGE_TYPE_FLAGS;
    }
    if (chat->changed.mode)
    {
        changed |= MegaTextChat::CHANGE_TYPE_MODE;
    }
    if (chat->changed.options)
    {
        changed |= MegaTextChat::CHANGE_TYPE_CHAT_OPTIONS;
    }
}

MegaTextChat *MegaTextChatPrivate::copy() const
{
    return new MegaTextChatPrivate(this);
}

MegaTextChatPrivate::~MegaTextChatPrivate()
{
    delete peers;
}

MegaHandle MegaTextChatPrivate::getHandle() const
{
    return id;
}

int MegaTextChatPrivate::getOwnPrivilege() const
{
    return priv;
}

int MegaTextChatPrivate::getShard() const
{
    return shard;
}

const MegaTextChatPeerList *MegaTextChatPrivate::getPeerList() const
{
    return peers;
}

void MegaTextChatPrivate::setPeerList(const MegaTextChatPeerList *peers)
{
    if (this->peers)
    {
        delete this->peers;
    }
    this->peers = peers ? peers->copy() : NULL;
}

bool MegaTextChatPrivate::isGroup() const
{
    return group;
}

MegaHandle MegaTextChatPrivate::getOriginatingUser() const
{
    return ou;
}

const char *MegaTextChatPrivate::getTitle() const
{
    return !title.empty() ? title.c_str() : NULL;
}

const char *MegaTextChatPrivate::getUnifiedKey() const
{
    return !unifiedKey.empty() ? unifiedKey.c_str() : NULL;
}

unsigned char MegaTextChatPrivate::getChatOptions() const
{
    return chatOptions;
}

int MegaTextChatPrivate::isOwnChange() const
{
    return tag;
}

int64_t MegaTextChatPrivate::getCreationTime() const
{
    return ts;
}

bool MegaTextChatPrivate::isArchived() const
{
    return archived;
}

bool MegaTextChatPrivate::isPublicChat() const
{
    return publicchat;
}

bool MegaTextChatPrivate::isMeeting() const
{
    return meeting;
}

bool MegaTextChatPrivate::hasChanged(int changeType) const
{
    return (changed & changeType);
}

int MegaTextChatPrivate::getChanges() const
{
    return changed;
}

const MegaScheduledMeetingList* MegaTextChatPrivate::getScheduledMeetingList() const
{
    return mScheduledMeetings.get();
}

const MegaScheduledMeetingList* MegaTextChatPrivate::getUpdatedOccurrencesList() const
{
    return mUpdatedOcurrences.get();
}

const MegaHandleList* MegaTextChatPrivate::getSchedMeetingsChanged() const
{
    return mSchedMeetingsChanged.get();
}

MegaTextChatListPrivate::~MegaTextChatListPrivate()
{
    for (unsigned int i = 0; i < (unsigned int) size(); i++)
    {
        delete list.at(i);
    }
}

MegaTextChatList *MegaTextChatListPrivate::copy() const
{
    return new MegaTextChatListPrivate(this);
}

const MegaTextChat *MegaTextChatListPrivate::get(unsigned int i) const
{
    if (i >= (unsigned int) size())
    {
        return NULL;
    }
    else
    {
        return list.at(i);
    }
}

int MegaTextChatListPrivate::size() const
{
    return int(list.size());
}

void MegaTextChatListPrivate::addChat(MegaTextChatPrivate *chat)
{
    list.push_back(chat);
}

MegaTextChatListPrivate::MegaTextChatListPrivate(const MegaTextChatListPrivate *list)
{
    MegaTextChatPrivate *chat;

    for (unsigned int i = 0; i < (unsigned int) list->size(); i++)
    {
        chat = new MegaTextChatPrivate(list->get(i));
        this->list.push_back(chat);
    }
}

MegaTextChatListPrivate::MegaTextChatListPrivate()
{

}

MegaTextChatListPrivate::MegaTextChatListPrivate(textchat_map *list)
{
    MegaTextChatPrivate *megaChat;

    textchat_map::iterator it;
    for (it = list->begin(); it != list->end(); it++)
    {
        megaChat = new MegaTextChatPrivate(it->second);
        this->list.push_back(megaChat);
    }
}

MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate()
    : mScheduledFlags(mega::make_unique<ScheduledFlags>())
{}

MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const unsigned long numericValue)
    : mScheduledFlags(mega::make_unique<ScheduledFlags>(numericValue))
{}

MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const MegaScheduledFlagsPrivate *flags)
    : mScheduledFlags(mega::make_unique<ScheduledFlags>(flags ? flags->getNumericValue() : 0))
{}

MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const ScheduledFlags* flags)
    : mScheduledFlags(mega::make_unique<ScheduledFlags>(flags ? flags->getNumericValue() : 0))
{}

void MegaScheduledFlagsPrivate::reset()                             { mScheduledFlags->reset(); }
void MegaScheduledFlagsPrivate::setSendEmails(bool enabled)         { mScheduledFlags->setSendEmails(enabled); }
void MegaScheduledFlagsPrivate::importFlagsValue(unsigned long val) { mScheduledFlags->importFlagsValue(val); }

bool MegaScheduledFlagsPrivate::sendEmails() const                  { return mScheduledFlags->sendEmails(); }
unsigned long MegaScheduledFlagsPrivate::getNumericValue() const    { return mScheduledFlags->getNumericValue();}

bool MegaScheduledFlagsPrivate::isEmpty() const                     { return mScheduledFlags->isEmpty(); }
unique_ptr<ScheduledFlags> MegaScheduledFlagsPrivate::getSdkScheduledFlags() const
{
    return mega::make_unique<ScheduledFlags>(getNumericValue());
}

MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const int freq, const int interval, const MegaTimeStamp until,
                                                     const MegaIntegerList* byWeekDay, const MegaIntegerList* byMonthDay,
                                                     const MegaIntegerMap* byMonthWeekDay)
    : mScheduledRules(mega::make_unique<ScheduledRules>(
                          isValidFreq(freq) ? freq : FREQ_INVALID,
                          isValidInterval(interval) ? interval : int{INTERVAL_INVALID},
                          //                                     ^^^^^^^^^^^^^^^^^^^^^
                          // int workaroud due to ODR-use requirement in C++11 (dropped from C++17)
                          isValidUntil(until) ? until : MEGA_INVALID_TIMESTAMP,
                          byWeekDay ? unique_ptr<MegaSmallIntVector>(
                                  dynamic_cast<const MegaIntegerListPrivate*>(byWeekDay)->toByteList()
                                  ).get()
                              : nullptr,
                          byMonthDay ? unique_ptr<MegaSmallIntVector>(
                                  dynamic_cast<const MegaIntegerListPrivate*>(byMonthDay)->toByteList()
                                  ).get()
                              :nullptr,
                          byMonthWeekDay ? unique_ptr<MegaSmallIntMap>(
                                  dynamic_cast<const MegaIntegerMapPrivate*>(byMonthWeekDay)->toByteMap()
                                  ).get()
                              : nullptr))
{}

MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const MegaScheduledRulesPrivate *rules)
    : mScheduledRules(rules->getSdkScheduledRules())
{}

MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const ScheduledRules *rules)
    : mScheduledRules(rules->copy())
{}

int MegaScheduledRulesPrivate::freq() const            { return mScheduledRules->freq(); }
int MegaScheduledRulesPrivate::interval() const        { return mScheduledRules->interval(); }
MegaTimeStamp MegaScheduledRulesPrivate::until() const { return mScheduledRules->until(); }

/*
 * Data cannot be changed through the public interface and is only set via ctor. We abuse the
 * choke point of the following getters to set it instead of the various ctors.
 *
 * Probable redesign of MegaApi will end up with returning ownership of these pointers
 * and the temporal variables won't be needed
 */
const MegaIntegerList* MegaScheduledRulesPrivate::byWeekDay() const
{
    const auto p = mScheduledRules->byWeekDay();
    if (!mTransformedByWeekDay && p)
    {
        mTransformedByWeekDay = mega::make_unique<MegaIntegerListPrivate>(*p);
    }

    return mTransformedByWeekDay.get();
}

const MegaIntegerList* MegaScheduledRulesPrivate::byMonthDay() const
{
    const auto p = mScheduledRules->byMonthDay();
    if (!mTransformedByMonthDay && p)
    {
        mTransformedByMonthDay.reset(new MegaIntegerListPrivate(*p));
    }

    return mTransformedByMonthDay.get();
}

const MegaIntegerMap* MegaScheduledRulesPrivate::byMonthWeekDay() const
{
    const auto p = mScheduledRules->byMonthWeekDay();
    if (!mTransformedByMonthWeekDay && p)
    {
        mTransformedByMonthWeekDay.reset(new MegaIntegerMapPrivate(*p));
    }

    return mTransformedByMonthWeekDay.get();
}

unique_ptr<ScheduledRules> MegaScheduledRulesPrivate::getSdkScheduledRules() const
{
    return unique_ptr<ScheduledRules>(mScheduledRules->copy());
}

bool MegaScheduledRulesPrivate::isValidFreq(const int freq)         { return ScheduledRules::isValidFreq(freq); }
bool MegaScheduledRulesPrivate::isValidInterval(const int interval) { return ScheduledRules::isValidInterval(interval); }
bool MegaScheduledRulesPrivate::isValidUntil(const m_time_t until)  { return ScheduledRules::isValidUntil(until); }

MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const MegaHandle chatid, const char* timezone,
                                                         const MegaTimeStamp startDateTime, const MegaTimeStamp endDateTime,
                                                         const char* title, const char* description, MegaHandle schedId,
                                                         MegaHandle parentSchedId, MegaHandle organizerUserId, int cancelled,
                                                         const char* attributes, MegaTimeStamp overrides,
                                                         const MegaScheduledFlags* flags, const MegaScheduledRules* rules)
    : mScheduledMeeting(mega::make_unique<ScheduledMeeting>(
                            chatid,
                            timezone ? timezone : std::string(),
                            startDateTime, endDateTime,
                            title ? title : std::string(),
                            description ? description : std::string(),
                            organizerUserId, schedId, parentSchedId, cancelled,
                            attributes ? attributes : std::string(),
                            overrides,
                            flags ? dynamic_cast<const MegaScheduledFlagsPrivate*>(flags)->getSdkScheduledFlags().get() : nullptr,
                            rules ? dynamic_cast<const MegaScheduledRulesPrivate*>(rules)->getSdkScheduledRules().get() : nullptr))
{}

MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const MegaScheduledMeetingPrivate* sm)
    : mScheduledMeeting(sm->scheduledMeeting()->copy())
{}

MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const ScheduledMeeting *scheduledMeeting)
    : mScheduledMeeting(scheduledMeeting->copy())
{}

MegaHandle MegaScheduledMeetingPrivate::chatid() const           { return mScheduledMeeting->chatid(); }
MegaHandle MegaScheduledMeetingPrivate::schedId() const          { return mScheduledMeeting->schedId(); }
MegaHandle MegaScheduledMeetingPrivate::parentSchedId() const    { return mScheduledMeeting->parentSchedId(); }
MegaHandle MegaScheduledMeetingPrivate::organizerUserid() const  { return mScheduledMeeting->organizerUserid(); }
const char* MegaScheduledMeetingPrivate::timezone() const        { return mScheduledMeeting->timezone().size() ? mScheduledMeeting->timezone().c_str() : nullptr; }
MegaTimeStamp MegaScheduledMeetingPrivate::startDateTime() const { return mScheduledMeeting->startDateTime(); }
MegaTimeStamp MegaScheduledMeetingPrivate::endDateTime() const   { return mScheduledMeeting->endDateTime(); }
const char* MegaScheduledMeetingPrivate::title() const           { return mScheduledMeeting->title().size() ? mScheduledMeeting->title().c_str() : nullptr; }
const char* MegaScheduledMeetingPrivate::description() const     { return mScheduledMeeting->description().size() ? mScheduledMeeting->description().c_str() : nullptr; }
const char* MegaScheduledMeetingPrivate::attributes() const      { return mScheduledMeeting->attributes().size() ? mScheduledMeeting->attributes().c_str() : nullptr; }
MegaTimeStamp MegaScheduledMeetingPrivate::overrides() const     { return mScheduledMeeting->overrides(); }
int MegaScheduledMeetingPrivate::cancelled() const               { return mScheduledMeeting->cancelled(); }
MegaScheduledFlags* MegaScheduledMeetingPrivate::flags() const   { return mScheduledMeeting->flags() ? new MegaScheduledFlagsPrivate(mScheduledMeeting->flags()) : nullptr;}
MegaScheduledRules* MegaScheduledMeetingPrivate::rules() const   { return mScheduledMeeting->rules() ? new MegaScheduledRulesPrivate(mScheduledMeeting->rules()) : nullptr;}

MegaScheduledMeetingListPrivate::MegaScheduledMeetingListPrivate()
{
}

MegaScheduledMeetingListPrivate::MegaScheduledMeetingListPrivate(const MegaScheduledMeetingListPrivate& l)
{
    mList.reserve(l.size());
    for (unsigned long i = 0; i < l.size(); i++)
    {
        mList.emplace_back(l.at(i)->copy());
    }
}

MegaScheduledMeetingListPrivate::~MegaScheduledMeetingListPrivate()
{
    // all objects managed by unique_ptr's containted in mList will be deallocated when mList is destroyed
}

unsigned long MegaScheduledMeetingListPrivate::size() const
{
    return static_cast<unsigned long>(mList.size());
}

MegaScheduledMeetingListPrivate* MegaScheduledMeetingListPrivate::copy() const
{
   return new MegaScheduledMeetingListPrivate(*this);
}

MegaScheduledMeeting* MegaScheduledMeetingListPrivate::at(unsigned long i) const
{
    return mList.at(i).get();
}

MegaScheduledMeeting* MegaScheduledMeetingListPrivate::getBySchedId(handle h) const
{
    auto it = std::find_if(mList.begin(),
                   mList.end(),
                   [h](const std::unique_ptr<MegaScheduledMeeting>& sm) -> bool
                   {
                       return h == sm ->schedId();
                   });

    return (it != mList.end())
        ? it->get()
        : nullptr;
}

void MegaScheduledMeetingListPrivate::insert(MegaScheduledMeeting* sm)
{
    mList.emplace_back(sm);
}

void MegaScheduledMeetingListPrivate::clear()
{
     mList.clear();
}
#endif

MegaTransferDataPrivate::MegaTransferDataPrivate(TransferList *transferList, long long notificationNumber)
{
    numDownloads = int(transferList->transfers[GET].size());
    downloadTags.reserve(numDownloads);
    downloadPriorities.reserve(numDownloads);
    for (auto it = transferList->begin(GET); it != transferList->end(GET); it++)
    {
        Transfer *transfer = (*it);
        for (file_list::iterator fit = transfer->files.begin(); fit != transfer->files.end(); fit++)
        {
            File *file = (*fit);
            downloadTags.push_back(file->tag);
            downloadPriorities.push_back(transfer->priority);
        }
    }
    numDownloads = int(downloadTags.size());

    numUploads = int(transferList->transfers[PUT].size());
    uploadTags.reserve(numUploads);
    uploadPriorities.reserve(numUploads);
    for (auto it = transferList->begin(PUT); it != transferList->end(PUT); it++)
    {
        Transfer *transfer = (*it);
        for (file_list::iterator fit = transfer->files.begin(); fit != transfer->files.end(); fit++)
        {
            File *file = (*fit);
            uploadTags.push_back(file->tag);
            uploadPriorities.push_back(transfer->priority);
        }
    }
    numUploads = int(uploadTags.size());

    this->notificationNumber = notificationNumber;
}

MegaTransferDataPrivate::MegaTransferDataPrivate(const MegaTransferDataPrivate *transferData)
{
    this->numDownloads = transferData->numDownloads;
    this->numUploads = transferData->numUploads;
    this->downloadTags = transferData->downloadTags;
    this->uploadTags = transferData->uploadTags;
    this->downloadPriorities = transferData->downloadPriorities;
    this->uploadPriorities = transferData->uploadPriorities;
    this->notificationNumber = transferData->notificationNumber;
}

MegaTransferDataPrivate::~MegaTransferDataPrivate()
{

}

MegaTransferData *MegaTransferDataPrivate::copy() const
{
    return new MegaTransferDataPrivate(this);
}

int MegaTransferDataPrivate::getNumDownloads() const
{
    return numDownloads;
}

int MegaTransferDataPrivate::getNumUploads() const
{
    return numUploads;
}

int MegaTransferDataPrivate::getDownloadTag(int i) const
{
    return downloadTags[i];
}

int MegaTransferDataPrivate::getUploadTag(int i) const
{
    return uploadTags[i];
}

unsigned long long MegaTransferDataPrivate::getDownloadPriority(int i) const
{
    return downloadPriorities[i];
}

unsigned long long MegaTransferDataPrivate::getUploadPriority(int i) const
{
    return uploadPriorities[i];
}

long long MegaTransferDataPrivate::getNotificationNumber() const
{
    return notificationNumber;
}

MegaSizeProcessor::MegaSizeProcessor()
{
    totalBytes = 0;
}

bool MegaSizeProcessor::processMegaNode(MegaNode *node)
{
    if (node->getType() == MegaNode::TYPE_FILE)
    {
        totalBytes += node->getSize();
    }
    return true;
}

long long MegaSizeProcessor::getTotalBytes()
{
    return totalBytes;
}

MegaEventPrivate::MegaEventPrivate(int atype)
    :type(atype)
{
}

MegaEventPrivate::MegaEventPrivate(MegaEventPrivate *event)
{
    this->type = event->getType();
    this->setText(event->getText());
    this->setNumber(event->getNumber());
    this->setHandle(event->getHandle());
}

MegaEventPrivate::~MegaEventPrivate()
{
    delete [] text;
}

MegaEvent *MegaEventPrivate::copy()
{
    return new MegaEventPrivate(this);
}

int MegaEventPrivate::getType() const
{
    return type;
}

const char *MegaEventPrivate::getText() const
{
    return text;
}

int64_t MegaEventPrivate::getNumber() const
{
    return number;
}

void MegaEventPrivate::setText(const char *text)
{
    if(this->text)
    {
        delete [] this->text;
    }
    this->text = MegaApi::strdup(text);
}

void MegaEventPrivate::setNumber(int64_t number)
{
    this->number = number;
}

MegaHandle MegaEventPrivate::getHandle() const
{
    return mHandle;
}

const char *MegaEventPrivate::getEventString() const
{
    return MegaEventPrivate::getEventString(type);
}

const char *MegaEventPrivate::getEventString(int type)
{
    switch (type)
    {
        case MegaEvent::EVENT_COMMIT_DB: return "EVENT_COMMIT_DB";
        case MegaEvent::EVENT_ACCOUNT_CONFIRMATION: return "EVENT_ACCOUNT_CONFIRMATION";
        case MegaEvent::EVENT_CHANGE_TO_HTTPS: return "EVENT_CHANGE_TO_HTTPS";
        case MegaEvent::EVENT_DISCONNECT: return "EVENT_DISCONNECT";
        case MegaEvent::EVENT_ACCOUNT_BLOCKED: return "EVENT_ACCOUNT_BLOCKED";
        case MegaEvent::EVENT_STORAGE: return "EVENT_STORAGE";
        case MegaEvent::EVENT_NODES_CURRENT: return "EVENT_NODES_CURRENT";
        case MegaEvent::EVENT_MEDIA_INFO_READY: return "EVENT_MEDIA_INFO_READY";
        case MegaEvent::EVENT_STORAGE_SUM_CHANGED: return "EVENT_STORAGE_SUM_CHANGED";
        case MegaEvent::EVENT_BUSINESS_STATUS: return "BUSINESS_STATUS";
        case MegaEvent::EVENT_KEY_MODIFIED: return "KEY_MODIFIED";
        case MegaEvent::EVENT_MISC_FLAGS_READY: return "MISC_FLAGS_READY";
#ifdef ENABLE_SYNC
        case MegaEvent::EVENT_SYNCS_DISABLED: return "SYNCS_DISABLED";
        case MegaEvent::EVENT_SYNCS_RESTORED: return "SYNCS_RESTORED";
#endif
        case MegaEvent::EVENT_REQSTAT_PROGRESS: return "REQSTAT_PROGRESS";
        case MegaEvent::EVENT_RELOADING: return "RELOADING";
        case MegaEvent::EVENT_FATAL_ERROR: return "FATAL_ERROR";
        case MegaEvent::EVENT_UPGRADE_SECURITY: return "UPGRADE_SECURITY";
        case MegaEvent::EVENT_DOWNGRADE_ATTACK: return "DOWNGRADE_ATTACK";
    }

    return "UNKNOWN";
}

std::string MegaEventPrivate::getValidDataToString() const
{
    std::string msg;
    if (getText())
    {
        msg.append(" text: ").append(getText());
    }

    if (getNumber() >= 0)
    {
        msg.append(" number: ").append(std::to_string(getNumber()));
    }

    if (getHandle() != INVALID_HANDLE)
    {
        msg.append(" handle: ").append(Base64Str<sizeof(MegaHandle)>(getHandle()));
    }
    return msg;
}

void MegaEventPrivate::setHandle(const MegaHandle &handle)
{
    mHandle = handle;
}

MegaHandleListPrivate::MegaHandleListPrivate()
{

}

MegaHandleListPrivate::MegaHandleListPrivate(const MegaHandleListPrivate *hList)
{
    mList = hList->mList;
}

MegaHandleListPrivate::MegaHandleListPrivate(const vector<handle> &handles)
{
    mList = handles;
}

MegaHandleListPrivate::~MegaHandleListPrivate()
{

}

MegaHandleList *MegaHandleListPrivate::copy() const
{
    return new MegaHandleListPrivate(this);
}

MegaHandle MegaHandleListPrivate::get(unsigned int i) const
{
    MegaHandle h = INVALID_HANDLE;

    if (i < mList.size())
    {
        h = mList.at(i);
    }

    return h;
}

unsigned int MegaHandleListPrivate::size() const
{
    return unsigned(mList.size());
}

void MegaHandleListPrivate::addMegaHandle(MegaHandle h)
{
    mList.push_back(h);
}

MegaIntegerListPrivate::MegaIntegerListPrivate(const vector<int8_t>& bytesList)
{
    mIntegers.reserve(bytesList.size());
    std::transform(bytesList.begin(), bytesList.end(), std::back_inserter(mIntegers), [](int8_t x) { return static_cast<int64_t>(x);});
}

MegaIntegerListPrivate::MegaIntegerListPrivate(const vector<int64_t>& integerList)
    : mIntegers(integerList)
{

}

MegaIntegerListPrivate::MegaIntegerListPrivate()
{

}

MegaIntegerListPrivate::~MegaIntegerListPrivate()
{

}

MegaSmallIntVector* MegaIntegerListPrivate::toByteList() const
{
    MegaSmallIntVector* bytesList= new MegaSmallIntVector();
    bytesList->reserve(mIntegers.size());
    std::transform(mIntegers.begin(), mIntegers.end(), std::back_inserter(*bytesList), [](int64_t x) { return static_cast<int8_t>(x);});
    return bytesList;
}

MegaIntegerList* MegaIntegerListPrivate::copy() const
{
    return new MegaIntegerListPrivate(mIntegers);
}

int64_t MegaIntegerListPrivate::get(int i) const
{
    if (i >= static_cast<int>(mIntegers.size()))
        {
                return -1;
        }

    return mIntegers.at(i);
}

void MegaIntegerListPrivate::add(long long i)
{
    mIntegers.emplace_back(i);
}

int MegaIntegerListPrivate::size() const
{
    return static_cast<int>(mIntegers.size());
}

const vector<int64_t>* MegaIntegerListPrivate::getList() const
{
    return &mIntegers;
}

MegaChildrenListsPrivate::MegaChildrenListsPrivate(MegaChildrenLists *list)
    : folders(list->getFolderList()->copy())
    , files(list->getFileList()->copy())
{
}

MegaChildrenListsPrivate::MegaChildrenListsPrivate(unique_ptr<MegaNodeListPrivate> folderList, unique_ptr<MegaNodeListPrivate> fileList)
    : folders(move(folderList))
    , files(move(fileList))
{
}

MegaChildrenLists *MegaChildrenListsPrivate::copy()
{
    return new MegaChildrenListsPrivate(this);
}

MegaNodeList *MegaChildrenListsPrivate::getFileList()
{
    return files.get();
}

MegaNodeList *MegaChildrenListsPrivate::getFolderList()
{
    return folders.get();
}

MegaChildrenListsPrivate::MegaChildrenListsPrivate()
    : folders(new MegaNodeListPrivate())
    , files(new MegaNodeListPrivate())
{
}

MegaAchievementsDetails *MegaAchievementsDetailsPrivate::fromAchievementsDetails(AchievementsDetails *details)
{
    return new MegaAchievementsDetailsPrivate(details);
}

MegaAchievementsDetailsPrivate::~MegaAchievementsDetailsPrivate()
{ }

MegaAchievementsDetails *MegaAchievementsDetailsPrivate::copy()
{
    return new MegaAchievementsDetailsPrivate(&details);
}

long long MegaAchievementsDetailsPrivate::getBaseStorage()
{
    return details.permanent_size;
}

long long MegaAchievementsDetailsPrivate::getClassStorage(int class_id)
{
    achievements_map::iterator it = details.achievements.find(class_id);
    if (it != details.achievements.end())
    {
        return it->second.storage;
    }

    return 0;
}

long long MegaAchievementsDetailsPrivate::getClassTransfer(int class_id)
{
    achievements_map::iterator it = details.achievements.find(class_id);
    if (it != details.achievements.end())
    {
        return it->second.transfer;
    }

    return 0;
}

int MegaAchievementsDetailsPrivate::getClassExpire(int class_id)
{
    achievements_map::iterator it = details.achievements.find(class_id);
    if (it != details.achievements.end())
    {
        return it->second.expire;
    }

    return 0;
}

unsigned int MegaAchievementsDetailsPrivate::getAwardsCount()
{
    return unsigned(details.awards.size());
}

int MegaAchievementsDetailsPrivate::getAwardClass(unsigned int index)
{
    if (index < details.awards.size())
    {
        return details.awards.at(index).achievement_class;
    }

    return 0;
}

int MegaAchievementsDetailsPrivate::getAwardId(unsigned int index)
{
    if (index < details.awards.size())
    {
        return details.awards.at(index).award_id;
    }

    return 0;
}

int64_t MegaAchievementsDetailsPrivate::getAwardTimestamp(unsigned int index)
{
    if (index < details.awards.size())
    {
        return details.awards.at(index).ts;
    }

    return 0;
}

int64_t MegaAchievementsDetailsPrivate::getAwardExpirationTs(unsigned int index)
{
    if (index < details.awards.size())
    {
        return details.awards.at(index).expire;
    }

    return 0;
}

MegaStringList *MegaAchievementsDetailsPrivate::getAwardEmails(unsigned int index)
{
    if (index < details.awards.size())
    {
        if (details.awards.at(index).achievement_class == MEGA_ACHIEVEMENT_INVITE)
        {
            string_vector data;
            vector<string>::iterator it = details.awards.at(index).emails_invited.begin();
            while (it != details.awards.at(index).emails_invited.end())
            {
                data.push_back(*it);
                it++;
            }
            return new MegaStringListPrivate(move(data));
        }
    }

    return new MegaStringListPrivate();
}

int MegaAchievementsDetailsPrivate::getRewardsCount()
{
    return int(details.rewards.size());
}

int MegaAchievementsDetailsPrivate::getRewardAwardId(unsigned int index)
{
    if (index < details.rewards.size())
    {
        return details.rewards.at(index).award_id;
    }

    return -1;
}

long long MegaAchievementsDetailsPrivate::getRewardStorage(unsigned int index)
{
    if (index < details.rewards.size())
    {
        return details.rewards.at(index).storage;
    }

    return 0;
}

long long MegaAchievementsDetailsPrivate::getRewardTransfer(unsigned int index)
{
    if (index < details.rewards.size())
    {
        return details.rewards.at(index).transfer;
    }

    return 0;
}

long long MegaAchievementsDetailsPrivate::getRewardStorageByAwardId(int award_id)
{
    for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
    {
        if (itr->award_id == award_id)
        {
            return itr->storage;
        }
    }

    return 0;
}

long long MegaAchievementsDetailsPrivate::getRewardTransferByAwardId(int award_id)
{
    for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
    {
        if (itr->award_id == award_id)
        {
            return itr->transfer;
        }
    }

    return 0;
}

int MegaAchievementsDetailsPrivate::getRewardExpire(unsigned int index)
{
    if (index < details.rewards.size())
    {
        return details.rewards.at(index).expire;
    }

    return 0;
}

long long MegaAchievementsDetailsPrivate::currentStorage()
{
    long long total = 0;
    m_time_t ts = m_time();

    for (vector<Award>::iterator it = details.awards.begin(); it != details.awards.end(); it++)
    {
        if (it->expire > ts)
        {
            for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
            {
                if (itr->award_id == it->award_id)
                {
                    total += itr->storage;
                }
            }
        }
    }

    return total;
}

long long MegaAchievementsDetailsPrivate::currentTransfer()
{
    long long total = 0;
    m_time_t ts = m_time();

    for (vector<Award>::iterator it = details.awards.begin(); it != details.awards.end(); it++)
    {
        if (it->expire > ts)
        {
            for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
            {
                if (itr->award_id == it->award_id)
                {
                    total += itr->transfer;
                }
            }
        }
    }

    return total;
}

long long MegaAchievementsDetailsPrivate::currentStorageReferrals()
{
    long long total = 0;
    m_time_t ts = m_time();

    for (vector<Award>::iterator it = details.awards.begin(); it != details.awards.end(); it++)
    {
        if ( (it->expire > ts) && (it->achievement_class == MEGA_ACHIEVEMENT_INVITE) )
        {
            for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
            {
                if (itr->award_id == it->award_id)
                {
                    total += itr->storage;
                }
            }
        }
    }

    return total;
}

long long MegaAchievementsDetailsPrivate::currentTransferReferrals()
{
    long long total = 0;
    m_time_t ts = m_time();

    for (vector<Award>::iterator it = details.awards.begin(); it != details.awards.end(); it++)
    {
        if ( (it->expire > ts) && (it->achievement_class == MEGA_ACHIEVEMENT_INVITE) )
        {
            for (vector<Reward>::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++)
            {
                if (itr->award_id == it->award_id)
                {
                    total += itr->transfer;
                }
            }
        }
    }

    return total;
}

MegaAchievementsDetailsPrivate::MegaAchievementsDetailsPrivate(AchievementsDetails *details)
{
    this->details = (*details);
}

MegaFolderInfoPrivate::MegaFolderInfoPrivate(int numFiles, int numFolders, int numVersions, long long currentSize, long long versionsSize)
{
    this->numFiles = numFiles;
    this->numFolders = numFolders;
    this->numVersions = numVersions;
    this->currentSize = currentSize;
    this->versionsSize = versionsSize;
}

MegaFolderInfoPrivate::MegaFolderInfoPrivate(const MegaFolderInfoPrivate *folderData)
{
    this->numFiles = folderData->getNumFiles();
    this->numFolders = folderData->getNumFolders();
    this->numVersions = folderData->getNumVersions();
    this->currentSize = folderData->getCurrentSize();
    this->versionsSize = folderData->getVersionsSize();
}

MegaFolderInfoPrivate::~MegaFolderInfoPrivate()
{

}

MegaFolderInfo *MegaFolderInfoPrivate::copy() const
{
    return new MegaFolderInfoPrivate(this);
}

int MegaFolderInfoPrivate::getNumVersions() const
{
    return numVersions;
}

int MegaFolderInfoPrivate::getNumFiles() const
{
    return numFiles;
}

int MegaFolderInfoPrivate::getNumFolders() const
{
    return numFolders;
}

long long MegaFolderInfoPrivate::getCurrentSize() const
{
    return currentSize;
}

long long MegaFolderInfoPrivate::getVersionsSize() const
{
    return versionsSize;
}

MegaTimeZoneDetailsPrivate::MegaTimeZoneDetailsPrivate(vector<std::string> *timeZones, vector<int> *timeZoneOffsets, int defaultTimeZone)
{
    this->timeZones = *timeZones;
    this->timeZoneOffsets = *timeZoneOffsets;
    this->defaultTimeZone = defaultTimeZone;
}

MegaTimeZoneDetailsPrivate::MegaTimeZoneDetailsPrivate(const MegaTimeZoneDetailsPrivate *timeZoneDetails)
{
    this->timeZones = timeZoneDetails->timeZones;
    this->timeZoneOffsets = timeZoneDetails->timeZoneOffsets;
    this->defaultTimeZone = timeZoneDetails->defaultTimeZone;
}

MegaTimeZoneDetailsPrivate::~MegaTimeZoneDetailsPrivate()
{

}

MegaTimeZoneDetails *MegaTimeZoneDetailsPrivate::copy() const
{
    return new MegaTimeZoneDetailsPrivate(this);
}

int MegaTimeZoneDetailsPrivate::getNumTimeZones() const
{
    return int(timeZones.size());
}

const char *MegaTimeZoneDetailsPrivate::getTimeZone(int index) const
{
    if (index >= 0 && index < int(timeZones.size()))
    {
        return timeZones[index].c_str();
    }
    return "";
}

int MegaTimeZoneDetailsPrivate::getTimeOffset(int index) const
{
    if (index >= 0 && index < int(timeZoneOffsets.size()))
    {
        return timeZoneOffsets[index];
    }
    return 0;
}

int MegaTimeZoneDetailsPrivate::getDefault() const
{
    return defaultTimeZone;
}

MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate(const string &settingsJSON)
{
    JSON json;
    json.begin(settingsJSON.c_str() + 1);
    std::string name = json.getname();
    while (!name.empty())
    {
        std::string globalObject;
        json.storeobject(&globalObject);
        if (name == "GLOBAL")
        {
            JSON jsonGlobal;
            jsonGlobal.begin(globalObject.c_str() + 1);
            std::string globalSubsetting = jsonGlobal.getname();
            while (!globalSubsetting.empty())
            {
                if (globalSubsetting == "dnd")
                {
                    if (jsonGlobal.isnumeric())
                    {
                        mGlobalDND = jsonGlobal.getint();
                    }

                    if (mGlobalDND < 0)
                    {
                        LOG_err << "Invalid format in GLOBAL.dnd notification settings";
                        mJsonInvalid = true;
                    }
                }
                else if (globalSubsetting == "nsch")
                {
                    std::string schedule;
                    jsonGlobal.storeobject(&schedule);
                    JSON scheduleJson;
                    scheduleJson.begin(schedule.c_str() + 1);
                    std::string scheduleSubTitle = scheduleJson.getname();
                    while (!scheduleSubTitle.empty())
                    {
                        if (scheduleSubTitle == "tz")
                        {
                            scheduleJson.storeobject(&mGlobalScheduleTimezone);
                        }
                        else if (scheduleSubTitle == "start" && scheduleJson.isnumeric())
                        {
                            mGlobalScheduleStart = scheduleJson.getint32();
                        }
                        else if (scheduleSubTitle == "end" && scheduleJson.isnumeric())
                        {
                            mGlobalScheduleEnd = scheduleJson.getint32();
                        }
                        else
                        {
                            LOG_warn << "Unknown option in GLOBAL.nsch notification settings";
                        }

                        scheduleSubTitle = scheduleJson.getname();
                    }

                    if (mGlobalScheduleTimezone.empty() || mGlobalScheduleStart < 0 || mGlobalScheduleEnd < 0)
                    {
                        LOG_err << "Invalid format in GLOBAL.nsch notification settings";
                        mJsonInvalid = true;
                    }
                }
                else
                {
                    LOG_warn << "Unknown option in Global notification settings";
                    mJsonInvalid = true;
                }

                globalSubsetting = jsonGlobal.getname();
            }
        }
        else if (name == "PCR")
        {
            JSON jsonContact;
            jsonContact.begin(globalObject.c_str() + 1);
            std::string subname = jsonContact.getname();
            if (subname != "dnd")
            {
                LOG_warn << "Unknown option in PCR notification settings";
                mJsonInvalid = true;
            }
            else if (jsonContact.isnumeric())
            {
                mContactsDND = jsonContact.getint();
            }
            else
            {
                LOG_err << "Invalid format in PCR.dnd notification settings";
                mJsonInvalid = true;
            }
        }
        else if (name == "INSHARE")
        {
            JSON jsonShares;
            jsonShares.begin(globalObject.c_str() + 1);
            std::string subname = jsonShares.getname();
            if (subname != "dnd")
            {
                LOG_warn << "Unknown option in SHARES notification settings";
                mJsonInvalid = true;
            }
            else if (jsonShares.isnumeric())
            {
                mSharesDND = jsonShares.getint();
            }
            else
            {
                LOG_err << "Invalid format in SHARES.dnd notification settings";
                mJsonInvalid = true;
            }
        }
        else if (name == "CHAT")
        {
            JSON jsonChats;
            jsonChats.begin(globalObject.c_str() + 1);
            std::string subname = jsonChats.getname();
            if (subname != "dnd")
            {
                LOG_warn << "Unknown option in CHAT notification settings";
                mJsonInvalid = true;
            }
            else if (jsonChats.isnumeric())
            {
                mGlobalChatsDND = jsonChats.getint();
            }
            else
            {
                LOG_err << "Invalid format in CHAT.dnd notification settings";
                mJsonInvalid = true;
            }
        }
        else if (name.length() == 11)    // settings for specific 'chatid'
        {
            MegaHandle chatid;
            if (Base64::atob(name.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE) == MegaClient::CHATHANDLE)
            {
                JSON jsonChat;
                jsonChat.begin(globalObject.c_str() + 1);
                std::string subname = jsonChat.getname();
                if (subname == "dnd" && jsonChat.isnumeric())
                {
                    mChatDND[chatid] = jsonChat.getint();
                }
                else if (subname == "an" && jsonChat.isnumeric())
                {
                    bool alwaysNotify = jsonChat.getint();
                    if (alwaysNotify)
                    {
                        mChatAlwaysNotify[chatid] = true;
                    }
                }
                else
                {
                    LOG_err << "Unknown option for chatid '" << name << "' in notification settings";
                    mJsonInvalid = true;
                }
            }
            else
            {
                LOG_warn << "Invalid format for chatid '" << name << "' in notification settings";
                mJsonInvalid = true;
            }
        }
        else
        {
            LOG_err << "Unknown option in notification settings";
            mJsonInvalid = true;
        }

        if (mJsonInvalid)
        {
            return;
        }

        name = json.getname();
    }
}

MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate()
{
}

MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate(const MegaPushNotificationSettingsPrivate *settings)
{
    mGlobalDND = settings->mGlobalDND;
    mGlobalScheduleStart = settings->mGlobalScheduleStart;
    mGlobalScheduleEnd = settings->mGlobalScheduleEnd;
    mGlobalScheduleTimezone = settings->mGlobalScheduleTimezone;
    mChatDND = settings->mChatDND;
    mChatAlwaysNotify = settings->mChatAlwaysNotify;
    mContactsDND = settings->mContactsDND;
    mSharesDND = settings->mSharesDND;
    mGlobalChatsDND = settings->mGlobalChatsDND;
}

string MegaPushNotificationSettingsPrivate::generateJson() const
{
    std::string json;
    if ((mGlobalScheduleStart > -1 && mGlobalScheduleEnd == -1)
            || (mGlobalScheduleStart == -1 && mGlobalScheduleEnd > -1))
    {
        LOG_warn << "Invalid notification settings for GLOBAL.nsch";
        return json;
    }

    json = "{";
    if (mGlobalDND > -1 || isGlobalScheduleEnabled())
    {
        json.append("\"GLOBAL\":{");
        if (isGlobalDndEnabled())
        {
            json.append("\"dnd\":").append(std::to_string(mGlobalDND));
            json.append(",");
        }

        if (isGlobalScheduleEnabled())
        {
            json.append("\"nsch\":{\"start\":").append(std::to_string(mGlobalScheduleStart));
            json.append(",\"end\":").append(std::to_string(mGlobalScheduleEnd));
            json.append(",\"tz\":\"").append(mGlobalScheduleTimezone).append("\"}");
        }

        if (json.at(json.length() - 1) == ',')  // clear a tailing comma (not needed)
        {
            json.pop_back();
        }

        json.append("},");
    }

    if (mContactsDND > -1)
    {
        json.append("\"PCR\":{\"dnd\":").append(std::to_string(mContactsDND)).append("}");
        json.append(",");
    }

    if (mSharesDND > -1)
    {
        json.append("\"INSHARE\":{\"dnd\":").append(std::to_string(mSharesDND)).append("}");
        json.append(",");
    }

    if (isGlobalChatsDndEnabled())
    {
        json.append("\"CHAT\":{\"dnd\":").append(std::to_string(mGlobalChatsDND)).append("}");
        json.append(",");
    }

    char chatid[MegaClient::CHATHANDLE * 4 / 3 + 4];

    std::map<uint64_t, m_time_t>::const_iterator itDND;
    for (itDND = mChatDND.begin(); itDND != mChatDND.end(); itDND++)
    {
        if (!isChatAlwaysNotifyEnabled(itDND->first) && isChatDndEnabled(itDND->first))
        {
            Base64::btoa((byte*)&(itDND->first), MegaClient::CHATHANDLE, chatid);
            json.append("\"").append(chatid).append("\":{");
            json.append("\"dnd\":").append(std::to_string(itDND->second)).append("}");
            json.append(",");
        }
    }

    std::map<uint64_t, bool>::const_iterator itAn;
    for (itAn = mChatAlwaysNotify.begin(); itAn != mChatAlwaysNotify.end(); itAn++)
    {
        assert(isChatAlwaysNotifyEnabled(itAn->first));
        assert(!isChatDndEnabled(itAn->first));

        if (isChatAlwaysNotifyEnabled(itAn->first))
        {
            Base64::btoa((byte*)&(itAn->first), MegaClient::CHATHANDLE, chatid);
            json.append("\"").append(chatid).append("\":{");
            json.append("\"an\":").append("1").append("}");
            json.append(",");
        }
    }

    if (json.at(json.length() - 1) == ',')  // clear a tailing comma (not needed)
    {
        json.pop_back();
    }
    json.append("}");
    return json;
}

bool MegaPushNotificationSettingsPrivate::isValid() const
{
    return !mJsonInvalid;
}

MegaPushNotificationSettingsPrivate::~MegaPushNotificationSettingsPrivate()
{

}

bool MegaPushNotificationSettingsPrivate::isGlobalEnabled() const
{
    return !isGlobalDndEnabled();
}

bool MegaPushNotificationSettingsPrivate::isGlobalDndEnabled() const
{
    return (mGlobalDND == 0 || mGlobalDND > m_time(NULL));
}

bool MegaPushNotificationSettingsPrivate::isChatsEnabled() const
{
    return !isGlobalChatsDndEnabled();
}

bool MegaPushNotificationSettingsPrivate::isGlobalChatsDndEnabled() const
{
    return (mGlobalChatsDND == 0 || mGlobalChatsDND > m_time(NULL));
}

int64_t MegaPushNotificationSettingsPrivate::getGlobalDnd() const
{
    return mGlobalDND;
}

int64_t MegaPushNotificationSettingsPrivate::getGlobalChatsDnd() const
{
    return mGlobalChatsDND;
}

bool MegaPushNotificationSettingsPrivate::isGlobalScheduleEnabled() const
{
    return (mGlobalScheduleStart != -1 && mGlobalScheduleEnd != -1 && !mGlobalScheduleTimezone.empty());
}

int MegaPushNotificationSettingsPrivate::getGlobalScheduleStart() const
{
    return mGlobalScheduleStart;
}

int MegaPushNotificationSettingsPrivate::getGlobalScheduleEnd() const
{
    return mGlobalScheduleEnd;
}

const char *mega::MegaPushNotificationSettingsPrivate::getGlobalScheduleTimezone() const
{
    return MegaApi::strdup(mGlobalScheduleTimezone.c_str());
}

bool MegaPushNotificationSettingsPrivate::isChatEnabled(MegaHandle chatid) const
{
    return !isChatDndEnabled(chatid);
}

bool MegaPushNotificationSettingsPrivate::isChatDndEnabled(MegaHandle chatid) const
{
    std::map<uint64_t, m_time_t>::const_iterator it = mChatDND.find(chatid);
    return (it != mChatDND.end() && (it->second == 0 || it->second > m_time(NULL)));
}

int64_t MegaPushNotificationSettingsPrivate::getChatDnd(MegaHandle chatid) const
{
    std::map<uint64_t, m_time_t>::const_iterator it = mChatDND.find(chatid);
    if (it != mChatDND.end())
    {
        assert(it->second != -1);
        return it->second;
    }

    return -1;
}

bool MegaPushNotificationSettingsPrivate::isChatAlwaysNotifyEnabled(MegaHandle chatid) const
{
    std::map<uint64_t, bool>::const_iterator it = mChatAlwaysNotify.find(chatid);
    return (it != mChatAlwaysNotify.end() && it->second);
}

bool MegaPushNotificationSettingsPrivate::isContactsEnabled() const
{
    return (mContactsDND == -1 || (mContactsDND > 0 && mContactsDND < m_time(NULL)));
}

bool MegaPushNotificationSettingsPrivate::isSharesEnabled() const
{
    return (mSharesDND == -1 || (mSharesDND > 0 && mSharesDND < m_time(NULL)));
}

MegaPushNotificationSettings *MegaPushNotificationSettingsPrivate::copy() const
{
    return new MegaPushNotificationSettingsPrivate(this);
}

void MegaPushNotificationSettingsPrivate::enableGlobal(bool enable)
{
    if (!isGlobalDndEnabled() == enable)
    {
        return;
    }

    mGlobalDND = enable ? -1 : 0;
}

void MegaPushNotificationSettingsPrivate::setGlobalDnd(int64_t timestamp)
{
    assert(timestamp > 0);
    if (isGlobalDndEnabled())
    {
        LOG_warn << "setGlobalDnd(): global notifications are currently disabled."
                    " Setting a new time period for DND mode";
    }
    mGlobalDND = timestamp;
}

void MegaPushNotificationSettingsPrivate::disableGlobalDnd()
{
    if (isGlobalDndEnabled())
    {
        LOG_warn << "disableGlobalDnd(): global notifications were disabled. Now are enabled";
    }
    mGlobalDND = -1;
}

void MegaPushNotificationSettingsPrivate::setGlobalSchedule(int start, int end, const char *timezone)
{
    if (start <= -1 || end <= -1 || !timezone || !timezone[0] || start == end)
    {
        LOG_warn << "setGlobalSchedule(): wrong arguments";
        assert(false);
        return;
    }

    mGlobalScheduleStart = start;
    mGlobalScheduleEnd = end;
    mGlobalScheduleTimezone.assign(timezone);
}

void MegaPushNotificationSettingsPrivate::disableGlobalSchedule()
{
    mGlobalScheduleStart = -1;
    mGlobalScheduleEnd = -1;
    mGlobalScheduleTimezone.clear();
}

void MegaPushNotificationSettingsPrivate::enableChat(MegaHandle chatid, bool enable)
{
    assert(!ISUNDEF(chatid));
    if (!isChatDndEnabled(chatid) == enable)
    {
        return;
    }

    if (enable)
    {
        mChatDND.erase(chatid);
    }
    else    // disable
    {
        mChatDND[chatid] = 0;
        if (isChatAlwaysNotifyEnabled(chatid))
        {
            LOG_warn << "enableChat(): always notify was enabled. Now is disabled";
            enableChatAlwaysNotify(chatid, false);
        }
    }
}

void MegaPushNotificationSettingsPrivate::setChatDnd(MegaHandle chatid, int64_t timestamp)
{
    assert(timestamp > 0);
    assert(!ISUNDEF(chatid));
    if (isChatAlwaysNotifyEnabled(chatid))
    {
        LOG_warn << "setChatDnd(): always notify was enabled. Now is disabled";
        enableChatAlwaysNotify(chatid, false);
    }

    mChatDND[chatid] = timestamp;
}

void MegaPushNotificationSettingsPrivate::setGlobalChatsDnd(int64_t timestamp)
{
    assert(timestamp > 0);
    if (isGlobalChatsDndEnabled())
    {
        LOG_warn << "setChatsDnd(): global chats notifications are currently disabled."
                    " Setting a new time period for chats DND mode";
    }
    mGlobalChatsDND = timestamp;
}

void MegaPushNotificationSettingsPrivate::enableChatAlwaysNotify(MegaHandle chatid, bool enable)
{
    assert(!ISUNDEF(chatid));
    if (enable)
    {
        if (isChatDndEnabled(chatid))
        {
            LOG_warn << "enableChatAlwaysNotify(): notifications are now disabled, DND mode is enabled";
            enableChat(chatid, true);
        }

        mChatAlwaysNotify[chatid] = 1;
    }
    else
    {
        mChatAlwaysNotify.erase(chatid);
    }
}

void MegaPushNotificationSettingsPrivate::enableContacts(bool enable)
{
    mContactsDND = enable ? -1 : 0;
}

void MegaPushNotificationSettingsPrivate::enableShares(bool enable)
{
    mSharesDND = enable ? -1 : 0;
}

void MegaPushNotificationSettingsPrivate::enableChats(bool enable)
{
    mGlobalChatsDND = enable ? -1 : 0;
}

MegaCancelTokenPrivate::MegaCancelTokenPrivate()
{
}

MegaCancelTokenPrivate::MegaCancelTokenPrivate(CancelToken t)
    : cancelFlag(t)
{
}

void MegaCancelTokenPrivate::cancel()
{
    cancelFlag.cancel();
}

bool MegaCancelTokenPrivate::isCancelled() const
{
    return cancelFlag.isCancelled();
}

} // namespace mega
