bool GfxProc::savefa(string *localfilepath, GfxProc::meta_t type, string *localdstpath) { if (!isgfx(localfilepath) // (this assumes that the width of the largest dimension is max) || !readbitmap(NULL, localfilepath, dimensions[sizeof dimensions/sizeof dimensions[0]-1][0])) { return false; } string jpeg; bool success = resizebitmap(dimensions[type][0], dimensions[type][1], &jpeg); freebitmap(); if (!success) { return false; } FileAccess *f = client->fsaccess->newfileaccess(); client->fsaccess->unlinklocal(localdstpath); if (!f->fopen(localdstpath, false, true)) { delete f; return false; } if (!f->fwrite((const byte*)jpeg.data(), jpeg.size(), 0)) { delete f; return false; } delete f; return true; }
bool GfxProc::savefa(string *localfilepath, int width, int height, string *localdstpath) { if (!isgfx(localfilepath)) { return false; } mutex.lock(); if (!readbitmap(NULL, localfilepath, width > height ? width : height)) { mutex.unlock(); return false; } int w = width; int h = height; if (this->w < w && this->h < h) { LOG_debug << "Skipping upsizing of local preview"; w = this->w; h = this->h; } string jpeg; bool success = resizebitmap(w, h, &jpeg); freebitmap(); mutex.unlock(); if (!success) { return false; } FileAccess *f = client->fsaccess->newfileaccess(); client->fsaccess->unlinklocal(localdstpath); if (!f->fopen(localdstpath, false, true)) { delete f; return false; } if (!f->fwrite((const byte*)jpeg.data(), unsigned(jpeg.size()), 0)) { delete f; return false; } delete f; return true; }
// check local path - if !localname, localpath is relative to l, with l == NULL // being the root of the sync // if localname is set, localpath is absolute and localname its last component // path references a new FOLDERNODE: returns created node // path references a existing FILENODE: returns node // otherwise, returns NULL LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname) { LocalNode* ll = l; FileAccess* fa; bool newnode = false, changed = false; bool isroot; LocalNode* parent; string path; // UTF-8 representation of tmppath string tmppath; // full path represented by l + localpath string newname; // portion of tmppath not covered by the existing // LocalNode structure (always the last path component // that does not have a corresponding LocalNode yet) if (localname) { // shortcut case (from within syncdown()) isroot = false; parent = l; l = NULL; client->fsaccess->local2path(localpath, &path); } else { // construct full filesystem path in tmppath if (l) { l->getlocalpath(&tmppath); } if (localpath->size()) { if (tmppath.size()) { tmppath.append(client->fsaccess->localseparator); } tmppath.append(*localpath); } // look up deepest existing LocalNode by path, store remainder (if any) // in newname l = localnodebypath(l, localpath, &parent, &newname); // path invalid? if (!l && !newname.size()) { return NULL; } string name = newname; client->fsaccess->local2name(&name); if (!client->app->sync_syncable(name.c_str(), &tmppath, &newname)) { return NULL; } isroot = (l == &localroot && !newname.size()); client->fsaccess->local2path(&tmppath, &path); } // postpone moving nodes into nonexistent parents if (parent && !parent->node) { return (LocalNode*)~0; } // attempt to open/type this file fa = client->fsaccess->newfileaccess(); if (fa->fopen(localname ? localpath : &tmppath, true, false)) { // match cached LocalNode state during initial/rescan to prevent costly re-fingerprinting // (just compare the fsids, sizes and mtimes to detect changes) if (fullscan) { // find corresponding LocalNode by file-/foldername int lastpart = client->fsaccess->lastpartlocal(localname ? localpath : &tmppath); string fname(localname ? *localpath : tmppath, lastpart, (localname ? *localpath : tmppath).size()-lastpart); LocalNode* cl = (parent ? parent : &localroot)->childbyname(&fname); if (cl && fa->fsid == cl->fsid) { // node found and same file l = cl; l->deleted = false; l->setnotseen(0); // if it's a file, size and mtime must match to qualify if (l->type != FILENODE || (l->size == fa->size && l->mtime == fa->mtime)) { l->scanseqno = scanseqno; if (l->type == FOLDERNODE) { scan(localname ? localpath : &tmppath, fa); } else localbytes += l->size; delete fa; return l; } } } if (!isroot) { if (l) { // mark as present l->setnotseen(0); if (fa->type == FILENODE) { // has the file been overwritten or changed since the last scan? // or did the size or mtime change? if (fa->fsidvalid) { // if fsid has changed, the file was overwritten // (FIXME: handle type changes) if (l->fsid != fa->fsid) { handlelocalnode_map::iterator it; // was the file overwritten by moving an existing // file over it? if ((it = client->fsidnode.find(fa->fsid)) != client->fsidnode.end()) { client->app->syncupdate_local_move(this, it->second->name.c_str(), path.c_str()); // immediately delete existing LocalNode and // replace with moved one delete l; // (in case of a move, this synchronously // updates l->parent and l->node->parent) it->second->setnameparent(parent, localname ? localpath : &tmppath); // mark as seen / undo possible deletion it->second->setnotseen(0); statecacheadd(it->second); delete fa; return it->second; } else { l->mtime = -1; // trigger change detection } } } // no fsid change detected or overwrite with unknown file: if (fa->mtime != l->mtime || fa->size != l->size) { if (fa->fsidvalid && (l->fsid != fa->fsid)) { l->setfsid(fa->fsid); } m_off_t dsize = l->size; if (l->genfingerprint(fa)) { localbytes -= dsize - l->size; } client->app->syncupdate_local_file_change(this, path.c_str()); client->stopxfer(l); l->bumpnagleds(); l->deleted = false; client->syncactivity = true; statecacheadd(l); delete fa; return l; } } else { // (we tolerate overwritten folders, because we do a // content scan anyway) if (fa->fsidvalid) { l->setfsid(fa->fsid); } } } // new node if (!l) { // rename or move of existing node? handlelocalnode_map::iterator it; if (fa->fsidvalid && ((it = client->fsidnode.find(fa->fsid)) != client->fsidnode.end())) { client->app->syncupdate_local_move(this, it->second->name.c_str(), path.c_str()); // (in case of a move, this synchronously updates l->parent // and l->node->parent) it->second->setnameparent(parent, localname ? localpath : &tmppath); // make sure that active PUTs receive their updated filenames client->updateputs(); statecacheadd(it->second); // unmark possible deletion it->second->setnotseen(0); // immediately scan folder to detect deviations from cached state if (fullscan) { scan(localname ? localpath : &tmppath, fa); } } else { // this is a new node: add l = new LocalNode; l->init(this, fa->type, parent, localname ? localpath : &tmppath); if (fa->fsidvalid) { l->setfsid(fa->fsid); } newnode = true; } } } if (l) { // detect file changes or recurse into new subfolders if (l->type == FOLDERNODE) { if (newnode) { scan(localname ? localpath : &tmppath, fa); client->app->syncupdate_local_folder_addition(this, path.c_str()); if (!isroot) { statecacheadd(l); } } else { l = NULL; } } else { if (isroot) { // root node cannot be a file changestate(SYNC_FAILED); } else { if (l->size > 0) { localbytes -= l->size; } if (l->genfingerprint(fa)) { changed = true; l->bumpnagleds(); l->deleted = false; } if (l->size > 0) { localbytes += l->size; } if (newnode) { client->app->syncupdate_local_file_addition(this, path.c_str()); } else if (changed) { client->app->syncupdate_local_file_change(this, path.c_str()); } if (newnode || changed) { statecacheadd(l); } } } } if (changed || newnode) { client->syncactivity = true; } } else { if (fa->retry) { // fopen() signals that the failure is potentially transient - do // nothing and request a recheck dirnotify->notify(DirNotify::RETRY, ll, localpath->data(), localpath->size()); } else if (l) { // immediately stop outgoing transfer, if any if (l->transfer) { client->stopxfer(l); } client->syncactivity = true; // in fullscan mode, missing files are handled in bulk in deletemissing() // rather than through setnotseen() if (!fullscan) l->setnotseen(1); } l = NULL; } delete fa; return l; }
// new Syncs are automatically inserted into the session's syncs list // and a full read of the subtree is initiated Sync::Sync(MegaClient* cclient, string* crootpath, const char* cdebris, string* clocaldebris, Node* remotenode, int ctag) { string dbname; client = cclient; tag = ctag; tmpfa = NULL; localbytes = 0; localnodes[FILENODE] = 0; localnodes[FOLDERNODE] = 0; state = SYNC_INITIALSCAN; fullscan = true; if (cdebris) { debris = cdebris; client->fsaccess->path2local(&debris, &localdebris); dirnotify = client->fsaccess->newdirnotify(crootpath, &localdebris); localdebris.insert(0, client->fsaccess->localseparator); localdebris.insert(0, *crootpath); } else { localdebris = *clocaldebris; // FIXME: pass last segment of localdebris dirnotify = client->fsaccess->newdirnotify(crootpath, &localdebris); } localroot.init(this, FOLDERNODE, NULL, crootpath); localroot.setnode(remotenode); sync_it = client->syncs.insert(client->syncs.end(), this); if (client->dbaccess) { // open state cache table handle tableid[3]; string dbname; FileAccess *fas = client->fsaccess->newfileaccess(); if (fas->fopen(crootpath, true, false)) { tableid[0] = fas->fsid; tableid[1] = remotenode->nodehandle; tableid[2] = client->me; dbname.resize(sizeof tableid * 4 / 3 + 3); dbname.resize(Base64::btoa((byte*)tableid, sizeof tableid, (char*)dbname.c_str())); statecachetable = client->dbaccess->open(client->fsaccess, &dbname); readstatecache(); } delete fas; } }
// transfer completion: copy received file locally, set timestamp(s), verify // fingerprint, notify app, notify files void Transfer::complete() { if (type == GET) { LOG_debug << "Download complete: " << (files.size() ? LOG_NODEHANDLE(files.front()->h) : "NO_FILES") << " " << files.size(); bool transient_error = false; string tmplocalname; string localname; bool success; // disconnect temp file from slot... delete slot->fa; slot->fa = NULL; // FIXME: multiple overwrite race conditions below (make copies // from open file instead of closing/reopening!) // set timestamp (subsequent moves & copies are assumed not to alter mtime) success = client->fsaccess->setmtimelocal(&localfilename, mtime); #ifdef ENABLE_SYNC if (!success) { transient_error = client->fsaccess->transient_error; LOG_debug << "setmtimelocal failed " << transient_error; } #endif // verify integrity of file FileAccess* fa = client->fsaccess->newfileaccess(); FileFingerprint fingerprint; Node* n; bool fixfingerprint = false; if (!transient_error && fa->fopen(&localfilename, true, false)) { fingerprint.genfingerprint(fa); if (isvalid && !(fingerprint == *(FileFingerprint*)this)) { if (!badfp.isvalid || !(badfp == fingerprint)) { badfp = fingerprint; delete fa; client->fsaccess->unlinklocal(&localfilename); return failed(API_EWRITE); } else { fixfingerprint = true; } } } #ifdef ENABLE_SYNC else { if (!transient_error) { transient_error = fa->retry; LOG_debug << "Unable to validate fingerprint " << transient_error; } } #endif delete fa; int missingattr = 0; handle attachh; SymmCipher* symmcipher; if (!transient_error) { // set FileFingerprint on source node(s) if missing for (file_list::iterator it = files.begin(); it != files.end(); it++) { if ((*it)->hprivate && (n = client->nodebyhandle((*it)->h))) { if (client->gfx && client->gfx->isgfx(&(*it)->localname)) { // check for missing imagery if (!n->hasfileattribute(GfxProc::THUMBNAIL120X120)) missingattr |= 1 << GfxProc::THUMBNAIL120X120; if (!n->hasfileattribute(GfxProc::PREVIEW1000x1000)) missingattr |= 1 << GfxProc::PREVIEW1000x1000; attachh = n->nodehandle; symmcipher = n->nodecipher(); } if (fingerprint.isvalid && (!n->isvalid || fixfingerprint)) { *(FileFingerprint*)n = fingerprint; n->serializefingerprint(&n->attrs.map['c']); client->setattr(n); } } } if (fingerprint.isvalid && fixfingerprint) { (*(FileFingerprint*)this) = fingerprint; } if (missingattr) { // FIXME: do this while file is still open client->gfx->gendimensionsputfa(NULL, &localfilename, attachh, symmcipher, missingattr); } // ...and place it in all target locations. first, update the files' // local target filenames, in case they have changed during the upload for (file_list::iterator it = files.begin(); it != files.end(); it++) { (*it)->updatelocalname(); } // place file in all target locations - use up to one renames, copy // operations for the rest // remove and complete successfully completed files for (file_list::iterator it = files.begin(); it != files.end(); ) { transient_error = false; success = false; localname = (*it)->localname; fa = client->fsaccess->newfileaccess(); if (fa->fopen(&localname)) { // the destination path already exists #ifdef ENABLE_SYNC if((*it)->syncxfer) { sync_list::iterator it2; for (it2 = client->syncs.begin(); it2 != client->syncs.end(); it2++) { Sync *sync = (*it2); LocalNode *localNode = sync->localnodebypath(NULL, &localname); if (localNode) { LOG_debug << "Overwritting a local synced file. Moving the previous one to debris"; // try to move to local debris if(!sync->movetolocaldebris(&localname)) { transient_error = client->fsaccess->transient_error; } break; } } if(it2 == client->syncs.end()) { LOG_err << "LocalNode for destination file not found"; if(client->syncs.size()) { // try to move to debris in the first sync if(!client->syncs.front()->movetolocaldebris(&localname)) { transient_error = client->fsaccess->transient_error; } } } } else #endif { LOG_debug << "The destination file exist (not synced). Saving with a different name"; // the destination path isn't synced, save with a (x) suffix string utf8fullname; client->fsaccess->local2path(&localname, &utf8fullname); size_t dotindex = utf8fullname.find_last_of('.'); string name; string extension; if (dotindex == string::npos) { name = utf8fullname; } else { string separator; client->fsaccess->local2path(&client->fsaccess->localseparator, &separator); size_t sepindex = utf8fullname.find_last_of(separator); if(sepindex == string::npos || sepindex < dotindex) { name = utf8fullname.substr(0, dotindex); extension = utf8fullname.substr(dotindex); } else { name = utf8fullname; } } string suffix; string newname; string localnewname; int num = 0; do { num++; ostringstream oss; oss << " (" << num << ")"; suffix = oss.str(); newname = name + suffix + extension; client->fsaccess->path2local(&newname, &localnewname); } while (fa->fopen(&localnewname)); (*it)->localname = localnewname; localname = localnewname; } } else { transient_error = fa->retry; } delete fa; if (transient_error) { LOG_warn << "Transient error checking if the destination file exist"; it++; continue; } if (!tmplocalname.size()) { if (client->fsaccess->renamelocal(&localfilename, &localname)) { tmplocalname = localname; success = true; } else if (client->fsaccess->transient_error) { transient_error = true; } } if (!success) { if((tmplocalname.size() ? tmplocalname : localfilename) == localname) { LOG_debug << "Identical node downloaded to the same folder"; success = true; } else if (client->fsaccess->copylocal(tmplocalname.size() ? &tmplocalname : &localfilename, &localname, mtime)) { success = true; } else if (client->fsaccess->transient_error) { transient_error = true; } } if (success || !transient_error) { if (success) { // prevent deletion of associated Transfer object in completed() (*it)->transfer = NULL; (*it)->completed(this, NULL); } if (success || !(*it)->failed(API_EAGAIN)) { File* f = (*it); files.erase(it++); if(!success) { LOG_warn << "Unable to complete transfer due to a persistent error"; f->transfer = NULL; f->terminated(); } } else { LOG_debug << "Persistent error completing file"; it++; } } else { LOG_debug << "Transient error completing file"; it++; } } if (!tmplocalname.size() && !files.size()) { client->fsaccess->unlinklocal(&localfilename); } } if (!files.size()) { localfilename = localname; client->app->transfer_complete(this); delete this; } else { // some files are still pending completion, close fa and set retry timer delete slot->fa; slot->fa = NULL; LOG_debug << "Files pending completion: " << files.size() << ". Waiting for a retry."; LOG_debug << "First pending file: " << files.front()->name; slot->retrying = true; slot->retrybt.backoff(11); } } else { LOG_debug << "Upload complete: " << (files.size() ? files.front()->name : "NO_FILES") << " " << files.size(); // files must not change during a PUT transfer if (genfingerprint(slot->fa, true)) { return failed(API_EREAD); } // if this transfer is put on hold, do not complete client->checkfacompletion(uploadhandle, this); return; } }