/* * lftp - file transfer program * * Copyright (c) 1996-2012 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "FileAccess.h" #include "LsCache.h" #include "plural.h" #include "misc.h" int LsCacheEntry::EstimateSize() const { int size=sizeof(*this); size+=LsCacheEntryLoc::EstimateSize(); size+=LsCacheEntryData::EstimateSize(); return size; } LsCacheEntryLoc::LsCacheEntryLoc(const FileAccess *p_loc,const char *a,int m) : arg(a), mode(m) { loc=p_loc->Clone(); loc->Suspend(); } const char *LsCacheEntryLoc::GetClosure() const { return loc->GetHostName(); } LsCacheEntryData::LsCacheEntryData(int e,const char *d,int l,const FileSet *fs) { SetData(e,d,l,fs); } LsCacheEntry::LsCacheEntry(const FileAccess *p_loc,const char *a,int m,int e,const char *d,int l,const FileSet *fs) : LsCacheEntryLoc(p_loc,a,m), LsCacheEntryData(e,d,l,fs) { SetResource(e==FA::OK?"cache:expire":"cache:expire-negative",GetClosure()); } void LsCacheEntryData::SetData(int e,const char *d,int l,const FileSet *fs) { afset=fs?new FileSet(fs):0; data.nset(d,l); err_code=e; } void LsCacheEntryData::GetData(int *e,const char **d,int *l,const FileSet **fs) { if(d && l) { *d=data; *l=data.length(); } if(fs) *fs=afset; *e=err_code; } bool LsCacheEntryLoc::Matches(const FileAccess *p_loc,const char *a,int m) { return (m==-1 || mode==m) && arg.eq(a) && p_loc->SameLocationAs(loc); } ResDecl res_cache_empty_listings("cache:cache-empty-listings","no",ResMgr::BoolValidate,0); ResDecl res_cache_enable("cache:enable","yes",ResMgr::BoolValidate,0); ResDecl res_cache_expire("cache:expire","60m",ResMgr::TimeIntervalValidate,0); ResDecl res_cache_expire_neg("cache:expire-negative","1m",ResMgr::TimeIntervalValidate,0); ResDecl res_cache_size ("cache:size","16M",ResMgr::UNumberValidate,ResMgr::NoClosure); LsCache::LsCache() : Cache(&res_cache_size,&res_cache_enable) {} void LsCache::Add(const FileAccess *p_loc,const char *a,int m,int e,const char *d,int l,const FileSet *fs) { if(!strcmp(p_loc->GetProto(),"file")) return; // don't cache local objects if(l == 0 && !res_cache_empty_listings.QueryBool(p_loc->GetHostName())) return; if(e!=FA::OK && e!=FA::NO_FILE && e!=FA::NOT_SUPP) return; Trim(); LsCacheEntry *c=Find(p_loc,a,m); if(!c) { if(!IsEnabled(p_loc->GetHostName())) return; AddCacheEntry(new LsCacheEntry(p_loc,a,m,e,d,l,fs)); } else { c->SetData(e,d,l,fs); } } void LsCache::Add(const FileAccess *p_loc,const char *a,int m,int e,const Buffer *ubuf,const FileSet *fs) { if(!ubuf->IsSaving()) return; const char *cache_buffer; int cache_buffer_size; if(e) { cache_buffer=ubuf->ErrorText(); cache_buffer_size=strlen(cache_buffer)+1; } else ubuf->GetSaved(&cache_buffer,&cache_buffer_size); LsCache::Add(p_loc,a,m,e,cache_buffer,cache_buffer_size,fs); } LsCacheEntry *LsCache::Find(const FileAccess *p_loc,const char *a,int m) { if(!IsEnabled(p_loc->GetHostName())) return 0; LsCacheEntry *c; for(c=IterateFirst(); c; c=IterateNext()) { if(c->Matches(p_loc,a,m)) break; } if(c && c->Stopped()) { Trim(); return 0; } return c; } bool LsCache::Find(const FileAccess *p_loc,const char *a,int m,int *e,const char **d,int *l,const FileSet **fs) { LsCacheEntry *c=Find(p_loc,a,m); if(!c) return false; c->GetData(e,d,l,fs); return true; } const FileSet *LsCache::FindFileSet(const FileAccess *p_loc,const char *a,int m) { LsCacheEntry *c=Find(p_loc,a,m); if(!c) return 0; return c->GetFileSet(c->loc); } const FileSet *LsCacheEntryData::GetFileSet(const FileAccess *parser) { if(afset) return afset; if(err_code!=FA::OK) return 0; afset=parser->ParseLongList(data, data.length()); return afset; } void LsCache::UpdateFileSet(const FileAccess *p_loc,const char *a,int m,const FileSet *fs) { if(!fs) return; LsCacheEntry *c=Find(p_loc,a,m); if(!c) return; c->UpdateFileSet(fs); } void LsCache::List() { Trim(); long vol=0; for(LsCacheEntry *c=IterateFirst(); c; c=IterateNext()) vol+=c->EstimateSize(); printf(plural("%ld $#l#byte|bytes$ cached",vol),vol); long sizelimit=res_cache_size.Query(0); if(sizelimit<0) puts(_(", no size limit")); else printf(_(", maximum size %ld\n"),sizelimit); } void LsCache::Changed(change_mode m,const FileAccess *f,const char *dir) { xstring fdir(dir_file(f->GetCwd(),dir)); if(m==FILE_CHANGED) dirname_modify(fdir); LsCacheEntry *c=IterateFirst(); while(c) { const FileAccess *sloc=c->loc; if(f->SameLocationAs(sloc) || (f->SameSiteAs(sloc) && (m==TREE_CHANGED? !strncmp(fdir,dir_file(sloc->GetCwd(),c->arg),fdir.length()) : !strcmp (fdir,dir_file(sloc->GetCwd(),c->arg))))) c=IterateDelete(); else c=IterateNext(); } } /* Mark a path as a directory or file. (We have other ways of knowing this; * this is the most explicit and least expensive.) */ void LsCache::SetDirectory(const FileAccess *p_loc, const char *path, bool dir) { if(!path) return; FileAccess::Path new_cwd = p_loc->GetCwd(); new_cwd.Change(path,!dir); SMTaskRef new_p_loc(p_loc->Clone()); new_p_loc->SetCwd(new_cwd); const char *entry = dir? "1":"0"; LsCache::Add(new_p_loc,"",FileAccess::CHANGE_DIR, dir?FA::OK:FA::NO_FILE, entry, strlen(entry)); } /* This is a hint function. If file type is really needed, use GetFileInfo * with showdir set to true. (GetFileInfo uses this function.) * Returns -1 if type is not known, 1 if a directory, 0 if a file. */ int LsCache::IsDirectory(const FileAccess *p_loc,const char *dir_c) { FileAccess::Path new_cwd(p_loc->GetCwd()); new_cwd.Change(dir_c); FileAccessRef new_p_loc(p_loc->Clone()); new_p_loc->SetCwd(new_cwd); int ret = -1; /* Cheap tests first: * * First, we know the path is a directory or not if we have an expicit * CHANGE_DIR entry for it. */ const char *buf_c; int bufsiz; int e; if(Find(new_p_loc, "", FileAccess::CHANGE_DIR, &e, &buf_c,&bufsiz)) { assert(bufsiz==1); return (e==FA::OK); } /* We know the path is a directory if we have a cache entry for it. This is * true regardless of the list type. (Unless it's a CHANGE_DIR entry; do this * test after the CHANGE_DIR check.) */ if(Find(new_p_loc, "", FA::LONG_LIST, &e, 0,0)) return(e==FA::OK); if(Find(new_p_loc, "", FA::MP_LIST, &e, 0,0)) return(e==FA::OK); if(Find(new_p_loc, "", FA::LIST, &e, 0,0)) return(e==FA::OK); /* We know this is a file or a directory if the dirname is cached and * contains the basename. */ { const char *bn=basename_ptr(new_cwd.path); bn=alloca_strdup(bn); // save basename new_cwd.Change(".."); new_p_loc->SetCwd(new_cwd); const FileSet *fs=FindFileSet(new_p_loc, "", FA::MP_LIST); if(!fs) fs=FindFileSet(new_p_loc, "", FA::LONG_LIST); if(fs) { FileInfo *fi=fs->FindByName(bn); if(fi && (fi->defined&fi->TYPE)) return(fi->filetype == fi->DIRECTORY); } } return ret; }