mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-10-01 02:35:48 -04:00
Optimization, cleanup, compiler warning fix
Chores I have made while working on single file share
This commit is contained in:
parent
817a961013
commit
e850e00a82
@ -73,7 +73,8 @@ InternalFileHierarchyStorage::InternalFileHierarchyStorage() : mRoot(0)
|
||||
mTotalFiles = 0 ;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::getDirHashFromIndex(const DirectoryStorage::EntryIndex& index,RsFileHash& hash) const
|
||||
bool InternalFileHierarchyStorage::getDirHashFromIndex(
|
||||
const DirectoryStorage::EntryIndex& index, RsFileHash& hash ) const
|
||||
{
|
||||
if(!checkIndex(index,FileStorageNode::TYPE_DIR))
|
||||
return false ;
|
||||
@ -82,6 +83,7 @@ bool InternalFileHierarchyStorage::getDirHashFromIndex(const DirectoryStorage::E
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::getIndexFromDirHash(const RsFileHash& hash,DirectoryStorage::EntryIndex& index)
|
||||
{
|
||||
std::map<RsFileHash,DirectoryStorage::EntryIndex>::iterator it = mDirHashes.find(hash) ;
|
||||
@ -91,35 +93,39 @@ bool InternalFileHierarchyStorage::getIndexFromDirHash(const RsFileHash& hash,Di
|
||||
|
||||
index = it->second;
|
||||
|
||||
// make sure the hash actually points to some existing file. If not, remove it. This is a lazy update of dir hashes: when we need them, we check them.
|
||||
if(!checkIndex(index, FileStorageNode::TYPE_DIR) || static_cast<DirEntry*>(mNodes[index])->dir_hash != hash)
|
||||
{
|
||||
std::cerr << "(II) removing non existing hash from dir hash list: " << hash << std::endl;
|
||||
|
||||
mDirHashes.erase(it) ;
|
||||
return false ;
|
||||
}
|
||||
/* make sure the hash actually points to some existing directory. If not,
|
||||
* remove it. This is an opportunistic update of dir hashes: when we need
|
||||
* them, we check them. */
|
||||
if( !checkIndex(index, FileStorageNode::TYPE_DIR) ||
|
||||
static_cast<DirEntry*>(mNodes[index])->dir_hash != hash )
|
||||
{
|
||||
RS_INFO("removing non existing dir hash: ", hash, " from dir hash list");
|
||||
mDirHashes.erase(it);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InternalFileHierarchyStorage::getIndexFromFileHash(const RsFileHash& hash,DirectoryStorage::EntryIndex& index)
|
||||
|
||||
bool InternalFileHierarchyStorage::getIndexFromFileHash(
|
||||
const RsFileHash& hash, DirectoryStorage::EntryIndex& index )
|
||||
{
|
||||
std::map<RsFileHash,DirectoryStorage::EntryIndex>::iterator it = mFileHashes.find(hash) ;
|
||||
auto it = std::as_const(mFileHashes).find(hash);
|
||||
if(it == mFileHashes.end()) return false;
|
||||
|
||||
if(it == mFileHashes.end())
|
||||
return false;
|
||||
index = it->second;
|
||||
|
||||
index = it->second;
|
||||
/* make sure the hash actually points to some existing file. If not, remove
|
||||
* it. This is an opportunistic update of file hashes: when we need them,
|
||||
* we check them. */
|
||||
if( !checkIndex(it->second, FileStorageNode::TYPE_FILE) ||
|
||||
static_cast<FileEntry*>(mNodes[index])->file_hash != hash )
|
||||
{
|
||||
RS_INFO("removing non existing file hash: ", hash, " from file hash list");
|
||||
mFileHashes.erase(it);
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the hash actually points to some existing file. If not, remove it. This is a lazy update of file hashes: when we need them, we check them.
|
||||
if(!checkIndex(it->second, FileStorageNode::TYPE_FILE) || static_cast<FileEntry*>(mNodes[index])->file_hash != hash)
|
||||
{
|
||||
std::cerr << "(II) removing non existing hash from file hash list: " << hash << std::endl;
|
||||
|
||||
mFileHashes.erase(it) ;
|
||||
return false ;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::getChildIndex(DirectoryStorage::EntryIndex e,int row,DirectoryStorage::EntryIndex& c) const
|
||||
@ -158,10 +164,13 @@ bool InternalFileHierarchyStorage::isIndexValid(DirectoryStorage::EntryIndex e)
|
||||
return e < mNodes.size() && mNodes[e] != NULL ;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::updateSubDirectoryList(const DirectoryStorage::EntryIndex& indx, const std::set<std::string>& subdirs, const RsFileHash& random_hash_seed)
|
||||
bool InternalFileHierarchyStorage::updateSubDirectoryList(
|
||||
const DirectoryStorage::EntryIndex& indx,
|
||||
const std::set<std::string>& subdirs,
|
||||
const RsFileHash& random_hash_seed )
|
||||
{
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
return false;
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
return false;
|
||||
|
||||
DirEntry& d(*static_cast<DirEntry*>(mNodes[indx])) ;
|
||||
|
||||
@ -287,10 +296,17 @@ bool InternalFileHierarchyStorage::checkIndex(DirectoryStorage::EntryIndex indx,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::updateSubFilesList(const DirectoryStorage::EntryIndex& indx,const std::map<std::string,DirectoryStorage::FileTS>& subfiles,std::map<std::string,DirectoryStorage::FileTS>& new_files)
|
||||
bool InternalFileHierarchyStorage::updateSubFilesList(
|
||||
const DirectoryStorage::EntryIndex& indx,
|
||||
const std::map<std::string,DirectoryStorage::FileTS>& subfiles,
|
||||
std::map<std::string,DirectoryStorage::FileTS>& new_files )
|
||||
{
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
return false;
|
||||
if(!checkIndex(indx, FileStorageNode::TYPE_DIR))
|
||||
{
|
||||
RS_ERR("indx: ", indx, std::errc::not_a_directory);
|
||||
print_stacktrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
DirEntry& d(*static_cast<DirEntry*>(mNodes[indx])) ;
|
||||
new_files = subfiles ;
|
||||
@ -315,9 +331,11 @@ bool InternalFileHierarchyStorage::updateSubFilesList(const DirectoryStorage::En
|
||||
continue;
|
||||
}
|
||||
|
||||
if(it->second.modtime != f.file_modtime || it->second.size != f.file_size) // file is newer and/or has different size
|
||||
// file is newer and/or has different size
|
||||
if(it->second.modtime != f.file_modtime || it->second.size != f.file_size)
|
||||
{
|
||||
f.file_hash.clear(); // hash needs recomputing
|
||||
// hash needs recomputing
|
||||
f.file_hash.clear();
|
||||
f.file_modtime = it->second.modtime;
|
||||
f.file_size = it->second.size;
|
||||
|
||||
@ -345,13 +363,16 @@ bool InternalFileHierarchyStorage::updateSubFilesList(const DirectoryStorage::En
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InternalFileHierarchyStorage::updateHash(const DirectoryStorage::EntryIndex& file_index,const RsFileHash& hash)
|
||||
bool InternalFileHierarchyStorage::updateHash(
|
||||
const DirectoryStorage::EntryIndex& file_index, const RsFileHash& hash )
|
||||
{
|
||||
if(!checkIndex(file_index,FileStorageNode::TYPE_FILE))
|
||||
{
|
||||
std::cerr << "[directory storage] (EE) cannot update file at index " << file_index << ". Not a valid index, or not a file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if(!checkIndex(file_index, FileStorageNode::TYPE_FILE))
|
||||
{
|
||||
RS_ERR( "Cannot update file at index ", file_index,
|
||||
". Not a valid index, or not a file." );
|
||||
print_stacktrace();
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_DIRECTORY_STORAGE
|
||||
std::cerr << "[directory storage] updating hash at index " << file_index << ", hash=" << hash << std::endl;
|
||||
#endif
|
||||
@ -439,14 +460,20 @@ DirectoryStorage::EntryIndex InternalFileHierarchyStorage::allocateNewIndex()
|
||||
return mNodes.size()-1 ;
|
||||
}
|
||||
|
||||
bool InternalFileHierarchyStorage::updateDirEntry(const DirectoryStorage::EntryIndex& indx,const std::string& dir_name,rstime_t most_recent_time,rstime_t dir_modtime,const std::vector<RsFileHash>& subdirs_hash,const std::vector<FileEntry>& subfiles_array)
|
||||
bool InternalFileHierarchyStorage::updateDirEntry(
|
||||
const DirectoryStorage::EntryIndex& indx, const std::string& dir_name,
|
||||
rstime_t most_recent_time, rstime_t dir_modtime,
|
||||
const std::vector<RsFileHash>& subdirs_hash,
|
||||
const std::vector<FileEntry>& subfiles_array )
|
||||
{
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
{
|
||||
std::cerr << "[directory storage] (EE) cannot update dir at index " << indx << ". Not a valid index, or not an existing dir." << std::endl;
|
||||
return false;
|
||||
}
|
||||
DirEntry& d(*static_cast<DirEntry*>(mNodes[indx])) ;
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
{
|
||||
RS_ERR( "cannot update dir at index ", indx, ". Not a valid index, or "
|
||||
"not an existing dir." );
|
||||
return false;
|
||||
}
|
||||
|
||||
DirEntry& d(*static_cast<DirEntry*>(mNodes[indx]));
|
||||
|
||||
#ifdef DEBUG_DIRECTORY_STORAGE
|
||||
std::cerr << "Updating dir entry: name=\"" << dir_name << "\", most_recent_time=" << most_recent_time << ", modtime=" << dir_modtime << std::endl;
|
||||
@ -706,14 +733,14 @@ const InternalFileHierarchyStorage::FileStorageNode *InternalFileHierarchyStorag
|
||||
return NULL ;
|
||||
}
|
||||
|
||||
const InternalFileHierarchyStorage::DirEntry *InternalFileHierarchyStorage::getDirEntry(DirectoryStorage::EntryIndex indx) const
|
||||
const InternalFileHierarchyStorage::DirEntry*
|
||||
InternalFileHierarchyStorage::getDirEntry(DirectoryStorage::EntryIndex indx) const
|
||||
{
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR))
|
||||
return NULL ;
|
||||
|
||||
return static_cast<DirEntry*>(mNodes[indx]) ;
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_DIR)) return nullptr;
|
||||
return static_cast<DirEntry*>(mNodes[indx]);
|
||||
}
|
||||
const InternalFileHierarchyStorage::FileEntry *InternalFileHierarchyStorage::getFileEntry(DirectoryStorage::EntryIndex indx) const
|
||||
const InternalFileHierarchyStorage::FileEntry*
|
||||
InternalFileHierarchyStorage::getFileEntry(DirectoryStorage::EntryIndex indx) const
|
||||
{
|
||||
if(!checkIndex(indx,FileStorageNode::TYPE_FILE))
|
||||
return NULL ;
|
||||
@ -757,7 +784,10 @@ bool InternalFileHierarchyStorage::searchHash(const RsFileHash& hash,DirectorySt
|
||||
class DirectoryStorageExprFileEntry: public RsRegularExpression::ExpFileEntry
|
||||
{
|
||||
public:
|
||||
DirectoryStorageExprFileEntry(const InternalFileHierarchyStorage::FileEntry& fe,const InternalFileHierarchyStorage::DirEntry& parent) : mFe(fe),mDe(parent) {}
|
||||
DirectoryStorageExprFileEntry(
|
||||
const InternalFileHierarchyStorage::FileEntry& fe,
|
||||
const InternalFileHierarchyStorage::DirEntry& parent ) :
|
||||
mFe(fe), mDe(parent) {}
|
||||
|
||||
inline virtual const std::string& file_name() const { return mFe.file_name ; }
|
||||
inline virtual uint64_t file_size() const { return mFe.file_size ; }
|
||||
@ -771,14 +801,18 @@ private:
|
||||
const InternalFileHierarchyStorage::DirEntry& mDe ;
|
||||
};
|
||||
|
||||
int InternalFileHierarchyStorage::searchBoolExp(RsRegularExpression::Expression * exp, std::list<DirectoryStorage::EntryIndex> &results) const
|
||||
int InternalFileHierarchyStorage::searchBoolExp(
|
||||
RsRegularExpression::Expression* exp,
|
||||
std::list<DirectoryStorage::EntryIndex>& results ) const
|
||||
{
|
||||
for(std::map<RsFileHash,DirectoryStorage::EntryIndex>::const_iterator it(mFileHashes.begin());it!=mFileHashes.end();++it)
|
||||
if(mNodes[it->second] != NULL && exp->eval(
|
||||
DirectoryStorageExprFileEntry(*static_cast<const FileEntry*>(mNodes[it->second]),
|
||||
*static_cast<const DirEntry*>(mNodes[mNodes[it->second]->parent_index])
|
||||
)))
|
||||
results.push_back(it->second);
|
||||
for(auto& it: std::as_const(mFileHashes))
|
||||
if(mNodes[it.second])
|
||||
if(exp->eval(
|
||||
DirectoryStorageExprFileEntry(
|
||||
*static_cast<const FileEntry*>(mNodes[it.second]),
|
||||
*static_cast<const DirEntry*>(mNodes[mNodes[it.second]->parent_index])
|
||||
) ))
|
||||
results.push_back(it.second);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -977,8 +1011,9 @@ void InternalFileHierarchyStorage::recursPrint(int depth,DirectoryStorage::Entry
|
||||
|
||||
bool InternalFileHierarchyStorage::nodeAccessError(const std::string& s)
|
||||
{
|
||||
std::cerr << "(EE) InternalDirectoryStructure: ERROR: " << s << std::endl;
|
||||
return false ;
|
||||
RS_ERR(s);
|
||||
print_stacktrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removes the given subdirectory from the parent node and all its pendign subdirs and files.
|
||||
|
@ -70,8 +70,11 @@ DirectoryStorage::FileIterator& DirectoryStorage::FileIterator::operator++()
|
||||
|
||||
return *this;
|
||||
}
|
||||
DirectoryStorage::EntryIndex DirectoryStorage::FileIterator::operator*() const { return mStorage->getSubFileIndex(mParentIndex,mFileTabIndex) ; }
|
||||
DirectoryStorage::EntryIndex DirectoryStorage::DirIterator ::operator*() const { return mStorage->getSubDirIndex(mParentIndex,mDirTabIndex) ; }
|
||||
DirectoryStorage::EntryIndex DirectoryStorage::FileIterator::operator*() const
|
||||
{ return mStorage->getSubFileIndex(mParentIndex, mFileTabIndex); }
|
||||
|
||||
DirectoryStorage::EntryIndex DirectoryStorage::DirIterator::operator*() const
|
||||
{ return mStorage->getSubDirIndex(mParentIndex, mDirTabIndex); }
|
||||
|
||||
DirectoryStorage::FileIterator::operator bool() const { return **this != DirectoryStorage::NO_INDEX; }
|
||||
DirectoryStorage::DirIterator ::operator bool() const { return **this != DirectoryStorage::NO_INDEX; }
|
||||
@ -142,7 +145,9 @@ bool DirectoryStorage::updateSubDirectoryList(const EntryIndex& indx, const std:
|
||||
mChanged = true ;
|
||||
return res ;
|
||||
}
|
||||
bool DirectoryStorage::updateSubFilesList(const EntryIndex& indx,const std::map<std::string,FileTS>& subfiles,std::map<std::string,FileTS>& new_files)
|
||||
bool DirectoryStorage::updateSubFilesList(
|
||||
const EntryIndex& indx, const std::map<std::string,FileTS>& subfiles,
|
||||
std::map<std::string,FileTS>& new_files )
|
||||
{
|
||||
RS_STACK_MUTEX(mDirStorageMtx) ;
|
||||
bool res = mFileHierarchy->updateSubFilesList(indx,subfiles,new_files) ;
|
||||
@ -351,7 +356,8 @@ int LocalDirectoryStorage::searchHash(const RsFileHash& hash, RsFileHash& real_h
|
||||
return false ;
|
||||
}
|
||||
|
||||
void LocalDirectoryStorage::setSharedDirectoryList(const std::list<SharedDirInfo>& lst)
|
||||
void LocalDirectoryStorage::setSharedDirectoryList(
|
||||
const std::list<SharedDirInfo>& lst )
|
||||
{
|
||||
std::set<std::string> dirs_with_new_virtualname ;
|
||||
bool dirs_with_changed_flags = false ;
|
||||
@ -382,7 +388,9 @@ void LocalDirectoryStorage::setSharedDirectoryList(const std::list<SharedDirInfo
|
||||
virtual_names.insert(candidate_virtual_name) ;
|
||||
}
|
||||
|
||||
// now for each member of the processed list, check if it is an existing shared directory that has been changed. If so, we need to update the dir TS of that directory
|
||||
/* now for each member of the processed list, check if it is an existing
|
||||
* shared directory that has been changed. If so, we need to update the
|
||||
* dir TS of that directory */
|
||||
|
||||
std::map<std::string,SharedDirInfo> new_dirs ;
|
||||
|
||||
@ -532,7 +540,7 @@ bool LocalDirectoryStorage::updateHash(
|
||||
#endif
|
||||
|
||||
ret = (!update_internal_hierarchy) ||
|
||||
mFileHierarchy->updateHash(index,hash);
|
||||
mFileHierarchy->updateHash(index, hash);
|
||||
} // RS_STACK_MUTEX(mDirStorageMtx);
|
||||
|
||||
#ifdef RS_DEEP_FILES_INDEX
|
||||
|
@ -3,7 +3,7 @@
|
||||
* *
|
||||
* libretroshare: retroshare core library *
|
||||
* *
|
||||
* Copyright (C) 2016 by Mr.Alice <mralice@users.sourceforge.net> *
|
||||
* Copyright (C) 2016 Mr.Alice <mralice@users.sourceforge.net> *
|
||||
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
|
||||
* *
|
||||
@ -404,7 +404,8 @@ void LocalDirectoryUpdater::hash_callback(uint32_t client_param, const std::stri
|
||||
|
||||
bool LocalDirectoryUpdater::hash_confirm(uint32_t client_param)
|
||||
{
|
||||
return mSharedDirectories->getEntryType(DirectoryStorage::EntryIndex(client_param)) == DIR_TYPE_FILE ;
|
||||
return mSharedDirectories->getEntryType(
|
||||
DirectoryStorage::EntryIndex(client_param) ) == DIR_TYPE_FILE;
|
||||
}
|
||||
|
||||
void LocalDirectoryUpdater::setFileWatchPeriod(int seconds)
|
||||
|
@ -3,7 +3,9 @@
|
||||
* *
|
||||
* libretroshare: retroshare core library *
|
||||
* *
|
||||
* Copyright 2018 by Mr.Alice <mralice@users.sourceforge.net> *
|
||||
* Copyright (C) 2018 Mr.Alice <mralice@users.sourceforge.net> *
|
||||
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License as *
|
||||
@ -1044,31 +1046,36 @@ void p3FileDatabase::getExtraFilesDirDetails(void *ref,DirectoryStorage::EntryIn
|
||||
}
|
||||
|
||||
// This function converts a pointer into directory details, to be used by the AbstractItemModel for browsing the files.
|
||||
int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags flags) const
|
||||
int p3FileDatabase::RequestDirDetails(
|
||||
void* ref, DirDetails& d, FileSearchFlags flags ) const
|
||||
{
|
||||
RS_STACK_MUTEX(mFLSMtx) ;
|
||||
RS_STACK_MUTEX(mFLSMtx);
|
||||
|
||||
d.children.clear();
|
||||
d.children.clear();
|
||||
|
||||
// Case where the pointer is NULL, which means we're at the top of the list of shared directories for all friends (including us)
|
||||
// or at the top of our own list of shared directories, depending on the flags.
|
||||
/* Case where the pointer is NULL, which means we're at the top of the list
|
||||
* of shared directories for all friends (including us) or at the top of our
|
||||
* own list of shared directories, depending on the flags.
|
||||
*
|
||||
* Friend index is used as follows:
|
||||
* 0 : own id
|
||||
* 1...n : other friends
|
||||
*
|
||||
* entry_index: starts at 0.
|
||||
*
|
||||
* The point is: we cannot use (0,0) because it encodes to NULL. No existing
|
||||
* combination should encode to NULL.
|
||||
* So we need to properly convert the friend index into 0 or into a friend
|
||||
* tab index in mRemoteDirectories.
|
||||
*
|
||||
* We should also check the consistency between flags and the content of ref.
|
||||
*/
|
||||
|
||||
// Friend index is used as follows:
|
||||
// 0 : own id
|
||||
// 1...n : other friends
|
||||
//
|
||||
// entry_index: starts at 0.
|
||||
//
|
||||
// The point is: we cannot use (0,0) because it encodes to NULL. No existing combination should encode to NULL.
|
||||
// So we need to properly convert the friend index into 0 or into a friend tab index in mRemoteDirectories.
|
||||
//
|
||||
// We should also check the consistency between flags and the content of ref.
|
||||
|
||||
if (ref == NULL)
|
||||
{
|
||||
d.ref = NULL ;
|
||||
d.type = DIR_TYPE_ROOT;
|
||||
d.parent = NULL;
|
||||
if (ref == nullptr)
|
||||
{
|
||||
d.ref = nullptr;
|
||||
d.type = DIR_TYPE_ROOT;
|
||||
d.parent = nullptr;
|
||||
d.prow = -1;
|
||||
d.name = "root";
|
||||
d.hash.clear() ;
|
||||
@ -1078,12 +1085,13 @@ int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags
|
||||
d.max_mtime = 0 ;
|
||||
|
||||
if(flags & RS_FILE_HINTS_LOCAL)
|
||||
{
|
||||
void *p;
|
||||
{
|
||||
void *p = nullptr;
|
||||
|
||||
{
|
||||
convertEntryIndexToPointer<sizeof(void*)>(0,0,p); // root of own directories
|
||||
DirStub stub;
|
||||
{
|
||||
// root of own directories
|
||||
convertEntryIndexToPointer<sizeof(void*)>(0, 0, p);
|
||||
DirStub stub;
|
||||
stub.type = DIR_TYPE_PERSON;
|
||||
stub.name = mServCtrl->getOwnId().toStdString();
|
||||
stub.ref = p;
|
||||
@ -1092,9 +1100,11 @@ int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags
|
||||
|
||||
if(mExtraFiles->size() > 0)
|
||||
{
|
||||
convertEntryIndexToPointer<sizeof(void*)>(0,1,p); // local shared files from extra list
|
||||
DirStub stub;
|
||||
stub.type = DIR_TYPE_PERSON; // not totally exact, but used as a trick.
|
||||
// local shared files from extra list
|
||||
convertEntryIndexToPointer<sizeof(void*)>(0, 1, p);
|
||||
DirStub stub;
|
||||
// not totally exact, but used as a trick.
|
||||
stub.type = DIR_TYPE_PERSON;
|
||||
stub.name = "[Extra List]";
|
||||
stub.ref = p;
|
||||
|
||||
@ -1127,18 +1137,19 @@ int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags
|
||||
}
|
||||
|
||||
uint32_t fi;
|
||||
DirectoryStorage::EntryIndex e ;
|
||||
DirectoryStorage::EntryIndex e;
|
||||
|
||||
convertPointerToEntryIndex<sizeof(void*)>(ref,e,fi);
|
||||
|
||||
// check consistency
|
||||
if( (fi == 0 && !(flags & RS_FILE_HINTS_LOCAL)) || (fi > 1 && (flags & RS_FILE_HINTS_LOCAL)))
|
||||
{
|
||||
P3FILELISTS_ERROR() << "(EE) remote request on local index or local request on remote index. This should not happen." << std::endl;
|
||||
return false ;
|
||||
}
|
||||
// check consistency
|
||||
if( (fi == 0 && !(flags & RS_FILE_HINTS_LOCAL)) ||
|
||||
(fi > 1 && (flags & RS_FILE_HINTS_LOCAL)))
|
||||
{
|
||||
RS_ERR("Remote request on local index or local request on remote index");
|
||||
return false;
|
||||
}
|
||||
|
||||
if((flags & RS_FILE_HINTS_LOCAL) && fi == 1) // extra list
|
||||
if((flags & RS_FILE_HINTS_LOCAL) && fi == 1) // extra list
|
||||
{
|
||||
getExtraFilesDirDetails(ref,e,d);
|
||||
|
||||
@ -1150,25 +1161,28 @@ int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags
|
||||
}
|
||||
|
||||
|
||||
DirectoryStorage *storage = (flags & RS_FILE_HINTS_LOCAL)? ((DirectoryStorage*)mLocalSharedDirs) : ((DirectoryStorage*)mRemoteDirectories[fi-1]);
|
||||
DirectoryStorage* storage =
|
||||
(flags & RS_FILE_HINTS_LOCAL) ?
|
||||
((DirectoryStorage*) mLocalSharedDirs) :
|
||||
((DirectoryStorage*) mRemoteDirectories[fi-1]);
|
||||
|
||||
// Case where the index is the top of a single person. Can be us, or a friend.
|
||||
/* Case where the index is the top of a single person.
|
||||
* Can be us, or a friend. */
|
||||
if(!storage || !storage->extractData(e,d))
|
||||
{
|
||||
RS_WARN( "request on index; ", e, ", for directory ID:",
|
||||
( (!storage)? ("[NULL]") : (storage->peerId().toStdString()) ),
|
||||
" failed" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if(storage==NULL || !storage->extractData(e,d))
|
||||
{
|
||||
#ifdef DEBUG_FILE_HIERARCHY
|
||||
P3FILELISTS_DEBUG() << "(WW) request on index " << e << ", for directory ID=" << ((storage==NULL)?("[NULL]"):(storage->peerId().toStdString())) << " failed. This should not happen." << std::endl;
|
||||
#endif
|
||||
return false ;
|
||||
}
|
||||
/* update indexes. This is a bit hacky, but does the job. The cast to
|
||||
* intptr_t is the proper way to convert a pointer into an int. */
|
||||
convertEntryIndexToPointer<sizeof(void*)>((intptr_t)d.ref,fi,d.ref);
|
||||
|
||||
// update indexes. This is a bit hacky, but does the job. The cast to intptr_t is the proper way to convert
|
||||
// a pointer into an int.
|
||||
|
||||
convertEntryIndexToPointer<sizeof(void*)>((intptr_t)d.ref,fi,d.ref) ;
|
||||
|
||||
for(uint32_t i=0;i<d.children.size();++i)
|
||||
convertEntryIndexToPointer<sizeof(void*)>((intptr_t)d.children[i].ref,fi,d.children[i].ref);
|
||||
for(uint32_t i=0; i<d.children.size(); ++i)
|
||||
convertEntryIndexToPointer<sizeof(void*)>(
|
||||
(intptr_t) d.children[i].ref, fi, d.children[i].ref );
|
||||
|
||||
if(e == 0) // root
|
||||
{
|
||||
@ -1178,9 +1192,9 @@ int p3FileDatabase::RequestDirDetails(void *ref, DirDetails& d, FileSearchFlags
|
||||
else
|
||||
{
|
||||
if(d.parent == 0) // child of root node
|
||||
d.prow = (flags & RS_FILE_HINTS_LOCAL)?0:(fi-1);
|
||||
d.prow = (flags & RS_FILE_HINTS_LOCAL) ? 0 : (fi-1);
|
||||
else
|
||||
d.prow = storage->parentRow(e) ;
|
||||
d.prow = storage->parentRow(e);
|
||||
|
||||
convertEntryIndexToPointer<sizeof(void*)>((intptr_t)d.parent,fi,d.parent) ;
|
||||
}
|
||||
@ -1307,7 +1321,9 @@ uint32_t p3FileDatabase::watchPeriod()
|
||||
return mLocalDirWatcher->fileWatchPeriod();
|
||||
}
|
||||
|
||||
int p3FileDatabase::SearchKeywords(const std::list<std::string>& keywords, std::list<DirDetails>& results,FileSearchFlags flags,const RsPeerId& client_peer_id)
|
||||
int p3FileDatabase::SearchKeywords(
|
||||
const std::list<std::string>& keywords, std::list<DirDetails>& results,
|
||||
FileSearchFlags flags, const RsPeerId& client_peer_id )
|
||||
{
|
||||
if(flags & RS_FILE_HINTS_LOCAL)
|
||||
{
|
||||
@ -1319,15 +1335,15 @@ int p3FileDatabase::SearchKeywords(const std::list<std::string>& keywords, std::
|
||||
|
||||
mLocalSharedDirs->searchTerms(keywords,firesults) ;
|
||||
|
||||
for(std::list<EntryIndex>::iterator it(firesults.begin());it!=firesults.end();++it)
|
||||
{
|
||||
void *p=NULL;
|
||||
convertEntryIndexToPointer<sizeof(void*)>(*it,0,p);
|
||||
pointers.push_back(p) ;
|
||||
}
|
||||
for(auto& it: std::as_const(firesults))
|
||||
{
|
||||
void *p = nullptr;
|
||||
convertEntryIndexToPointer<sizeof(void*)>(it, 0, p);
|
||||
pointers.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
filterResults(pointers,results,flags,client_peer_id) ;
|
||||
filterResults(pointers, results, flags, client_peer_id);
|
||||
}
|
||||
|
||||
if(flags & RS_FILE_HINTS_REMOTE)
|
||||
@ -1610,78 +1626,90 @@ void p3FileDatabase::tickSend()
|
||||
|
||||
void p3FileDatabase::handleDirSyncRequest(RsFileListsSyncRequestItem *item)
|
||||
{
|
||||
RsFileListsSyncResponseItem *ritem = new RsFileListsSyncResponseItem;
|
||||
RsFileListsSyncResponseItem* ritem = new RsFileListsSyncResponseItem;
|
||||
|
||||
// look at item TS. If local is newer, send the full directory content.
|
||||
{
|
||||
RS_STACK_MUTEX(mFLSMtx) ;
|
||||
// look at item TS. If local is newer, send the full directory content.
|
||||
{
|
||||
RS_STACK_MUTEX(mFLSMtx);
|
||||
|
||||
#ifdef DEBUG_P3FILELISTS
|
||||
P3FILELISTS_DEBUG() << "Received directory sync request from peer " << item->PeerId() << ". hash=" << item->entry_hash << ", flags=" << (void*)(intptr_t)item->flags << ", request id: " << std::hex << item->request_id << std::dec << ", last known TS: " << item->last_known_recurs_modf_TS << std::endl;
|
||||
#endif
|
||||
RS_DBG( "Received directory sync request from peer ", item->PeerId(),
|
||||
". hash=", item->entry_hash, ", flags=", item->flags,
|
||||
", request id: ", item->request_id, ", last known TS: ",
|
||||
item->last_known_recurs_modf_TS );
|
||||
|
||||
EntryIndex entry_index = DirectoryStorage::NO_INDEX;
|
||||
EntryIndex entry_index = DirectoryStorage::NO_INDEX;
|
||||
if(!mLocalSharedDirs->getIndexFromDirHash(item->entry_hash,entry_index))
|
||||
{
|
||||
RS_DBG("Cannot find entry index for hash ", item->entry_hash,
|
||||
" cannot respond to sync request." );
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mLocalSharedDirs->getIndexFromDirHash(item->entry_hash,entry_index))
|
||||
{
|
||||
#ifdef DEBUG_P3FILELISTS
|
||||
P3FILELISTS_DEBUG() << " (EE) Cannot find entry index for hash " << item->entry_hash << ": cannot respond to sync request." << std::endl;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
uint32_t entry_type = mLocalSharedDirs->getEntryType(entry_index);
|
||||
ritem->PeerId(item->PeerId());
|
||||
ritem->request_id = item->request_id;
|
||||
ritem->entry_hash = item->entry_hash;
|
||||
|
||||
uint32_t entry_type = mLocalSharedDirs->getEntryType(entry_index) ;
|
||||
ritem->PeerId(item->PeerId()) ;
|
||||
ritem->request_id = item->request_id;
|
||||
ritem->entry_hash = item->entry_hash ;
|
||||
std::list<RsNodeGroupId> node_groups;
|
||||
FileStorageFlags node_flags;
|
||||
|
||||
std::list<RsNodeGroupId> node_groups;
|
||||
FileStorageFlags node_flags;
|
||||
if(entry_type != DIR_TYPE_DIR)
|
||||
{
|
||||
RS_DBG( "Directory does not exist anymore, or is not a directory, "
|
||||
"or permission denied. Answering with proper flags." );
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE |
|
||||
RsFileListsItem::FLAGS_ENTRY_WAS_REMOVED;
|
||||
}
|
||||
else if( entry_index != 0 &&
|
||||
(!mLocalSharedDirs->getFileSharingPermissions(
|
||||
entry_index, node_flags,node_groups ) ||
|
||||
!(rsPeers->computePeerPermissionFlags(
|
||||
item->PeerId(), node_flags, node_groups ) &
|
||||
RS_FILE_HINTS_BROWSABLE)) )
|
||||
{
|
||||
RS_ERR("cannot get file permissions for entry index: ", entry_index,
|
||||
", or permission denied." );
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE |
|
||||
RsFileListsItem::FLAGS_ENTRY_WAS_REMOVED;
|
||||
}
|
||||
else
|
||||
{
|
||||
rstime_t local_recurs_max_time;
|
||||
mLocalSharedDirs->getDirectoryRecursModTime(
|
||||
entry_index, local_recurs_max_time );
|
||||
|
||||
if(entry_type != DIR_TYPE_DIR)
|
||||
{
|
||||
#ifdef DEBUG_P3FILELISTS
|
||||
P3FILELISTS_DEBUG() << " Directory does not exist anymore, or is not a directory, or permission denied. Answering with proper flags." << std::endl;
|
||||
#endif
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE | RsFileListsItem::FLAGS_ENTRY_WAS_REMOVED ;
|
||||
}
|
||||
else if(entry_index != 0 && (!mLocalSharedDirs->getFileSharingPermissions(entry_index,node_flags,node_groups) || !(rsPeers->computePeerPermissionFlags(item->PeerId(),node_flags,node_groups) & RS_FILE_HINTS_BROWSABLE)))
|
||||
{
|
||||
P3FILELISTS_ERROR() << "(EE) cannot get file permissions for entry index " << (void*)(intptr_t)entry_index << ", or permission denied." << std::endl;
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE | RsFileListsItem::FLAGS_ENTRY_WAS_REMOVED ;
|
||||
}
|
||||
else
|
||||
{
|
||||
rstime_t local_recurs_max_time ;
|
||||
mLocalSharedDirs->getDirectoryRecursModTime(entry_index,local_recurs_max_time) ;
|
||||
/* normally, should be "<", but since we provided the TS it should
|
||||
* be equal, so != is more robust. */
|
||||
if(item->last_known_recurs_modf_TS != local_recurs_max_time)
|
||||
{
|
||||
RS_DBG( "Directory is more recent than what the friend knows. "
|
||||
"Sending full dir content as response.");
|
||||
|
||||
if(item->last_known_recurs_modf_TS != local_recurs_max_time) // normally, should be "<", but since we provided the TS it should be equal, so != is more robust.
|
||||
{
|
||||
#ifdef DEBUG_P3FILELISTS
|
||||
P3FILELISTS_DEBUG() << " Directory is more recent than what the friend knows. Sending full dir content as response." << std::endl;
|
||||
#endif
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE |
|
||||
RsFileListsItem::FLAGS_SYNC_DIR_CONTENT;
|
||||
ritem->last_known_recurs_modf_TS = local_recurs_max_time;
|
||||
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE | RsFileListsItem::FLAGS_SYNC_DIR_CONTENT;
|
||||
ritem->last_known_recurs_modf_TS = local_recurs_max_time;
|
||||
/* We supply the peer id, in order to possibly remove some
|
||||
* subdirs, if entries are not allowed to be seen by this peer.
|
||||
*/
|
||||
mLocalSharedDirs->serialiseDirEntry(
|
||||
entry_index, ritem->directory_content_data,
|
||||
item->PeerId() );
|
||||
}
|
||||
else
|
||||
{
|
||||
RS_DBG3( "Directory is up to date w.r.t. what the friend knows."
|
||||
" Sending ACK." );
|
||||
|
||||
// We supply the peer id, in order to possibly remove some subdirs, if entries are not allowed to be seen by this peer.
|
||||
mLocalSharedDirs->serialiseDirEntry(entry_index,ritem->directory_content_data,item->PeerId()) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG_P3FILELISTS
|
||||
P3FILELISTS_DEBUG() << " Directory is up to date w.r.t. what the friend knows. Sending ACK." << std::endl;
|
||||
#endif
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE |
|
||||
RsFileListsItem::FLAGS_ENTRY_UP_TO_DATE;
|
||||
ritem->last_known_recurs_modf_TS = local_recurs_max_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ritem->flags = RsFileListsItem::FLAGS_SYNC_RESPONSE | RsFileListsItem::FLAGS_ENTRY_UP_TO_DATE ;
|
||||
ritem->last_known_recurs_modf_TS = local_recurs_max_time ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends the response.
|
||||
|
||||
splitAndSendItem(ritem) ;
|
||||
// sends the response.
|
||||
splitAndSendItem(ritem);
|
||||
}
|
||||
|
||||
void p3FileDatabase::splitAndSendItem(RsFileListsSyncResponseItem *ritem)
|
||||
|
@ -3,7 +3,9 @@
|
||||
* *
|
||||
* libretroshare: retroshare core library *
|
||||
* *
|
||||
* Copyright 2008 by Robert Fernie <retroshare@lunamutt.com> *
|
||||
* Copyright (C) 2008 Robert Fernie <retroshare@lunamutt.com> *
|
||||
* Copyright (C) 2018-2021 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* Copyright (C) 2020-2021 Asociación Civil Altermundi <info@altermundi.net> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License as *
|
||||
@ -873,9 +875,11 @@ int ftServer::SearchKeywords(std::list<std::string> keywords, std::list<DirDetai
|
||||
{
|
||||
return mFileDatabase->SearchKeywords(keywords, results,flags,RsPeerId());
|
||||
}
|
||||
int ftServer::SearchKeywords(std::list<std::string> keywords, std::list<DirDetails> &results,FileSearchFlags flags,const RsPeerId& peer_id)
|
||||
int ftServer::SearchKeywords(
|
||||
std::list<std::string> keywords, std::list<DirDetails> &results,
|
||||
FileSearchFlags flags, const RsPeerId& peer_id )
|
||||
{
|
||||
return mFileDatabase->SearchKeywords(keywords, results,flags,peer_id);
|
||||
return mFileDatabase->SearchKeywords(keywords, results, flags, peer_id);
|
||||
}
|
||||
|
||||
int ftServer::SearchBoolExp(RsRegularExpression::Expression * exp, std::list<DirDetails> &results,FileSearchFlags flags)
|
||||
|
@ -3,7 +3,9 @@
|
||||
* *
|
||||
* libretroshare: retroshare core library *
|
||||
* *
|
||||
* Copyright 2009-2018 by Cyril Soler <csoler@users.sourceforge.net> *
|
||||
* Copyright (C) 2009-2018 Cyril Soler <csoler@users.sourceforge.net> *
|
||||
* Copyright (C) 2018-2021 Gioacchino Mazzurco <gio@eigenlab.org> *
|
||||
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License as *
|
||||
@ -26,6 +28,10 @@
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#include "rsserver/p3face.h"
|
||||
#include "crypto/rscrypto.h"
|
||||
@ -39,13 +45,7 @@
|
||||
#include "ft/ftcontroller.h"
|
||||
|
||||
#include "p3turtle.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util/cxx17retrocompat.h"
|
||||
#include "util/rsdebug.h"
|
||||
#include "util/rsprint.h"
|
||||
#include "util/rsrandom.h"
|
||||
@ -1975,7 +1975,8 @@ void p3turtle::handleTunnelResult(RsTurtleTunnelOkItem *item)
|
||||
//
|
||||
|
||||
|
||||
void RsTurtleStringSearchRequestItem::search(std::list<TurtleFileInfo>& result) const
|
||||
void RsTurtleStringSearchRequestItem::search(
|
||||
std::list<TurtleFileInfo>& result ) const
|
||||
{
|
||||
/* call to core */
|
||||
std::list<DirDetails> initialResults;
|
||||
@ -1988,17 +1989,19 @@ void RsTurtleStringSearchRequestItem::search(std::list<TurtleFileInfo>& result)
|
||||
std::cerr << "Performing rsFiles->search()" << std::endl ;
|
||||
#endif
|
||||
// now, search!
|
||||
rsFiles->SearchKeywords(words, initialResults,RS_FILE_HINTS_LOCAL | RS_FILE_HINTS_SEARCHABLE,PeerId());
|
||||
rsFiles->SearchKeywords(
|
||||
words, initialResults,
|
||||
RS_FILE_HINTS_LOCAL | RS_FILE_HINTS_SEARCHABLE, PeerId() );
|
||||
|
||||
#ifdef P3TURTLE_DEBUG
|
||||
std::cerr << initialResults.size() << " matches found." << std::endl ;
|
||||
#endif
|
||||
result.clear() ;
|
||||
|
||||
for(std::list<DirDetails>::const_iterator it(initialResults.begin());it!=initialResults.end();++it)
|
||||
for(auto& it: std::as_const(initialResults))
|
||||
{
|
||||
// retain only file type
|
||||
if (it->type == DIR_TYPE_DIR)
|
||||
if (it.type == DIR_TYPE_DIR)
|
||||
{
|
||||
#ifdef P3TURTLE_DEBUG
|
||||
std::cerr << " Skipping directory " << it->name << std::endl ;
|
||||
@ -2006,12 +2009,12 @@ void RsTurtleStringSearchRequestItem::search(std::list<TurtleFileInfo>& result)
|
||||
continue;
|
||||
}
|
||||
|
||||
TurtleFileInfo i ;
|
||||
i.hash = it->hash ;
|
||||
i.size = it->size ;
|
||||
i.name = it->name ;
|
||||
TurtleFileInfo i;
|
||||
i.hash = it.hash;
|
||||
i.size = it.size;
|
||||
i.name = it.name;
|
||||
|
||||
result.push_back(i) ;
|
||||
result.push_back(i);
|
||||
}
|
||||
}
|
||||
void RsTurtleRegExpSearchRequestItem::search(std::list<TurtleFileInfo>& result) const
|
||||
|
@ -38,8 +38,11 @@
|
||||
namespace librs { namespace util {
|
||||
|
||||
|
||||
FolderIterator::FolderIterator(const std::string& folderName, bool allow_symlinks, bool allow_files_from_the_future)
|
||||
: mFolderName(folderName),mAllowSymLinks(allow_symlinks),mAllowFilesFromTheFuture(allow_files_from_the_future)
|
||||
FolderIterator::FolderIterator(
|
||||
const std::string& folderName, bool allow_symlinks,
|
||||
bool allow_files_from_the_future ):
|
||||
mFolderName(folderName), mAllowSymLinks(allow_symlinks),
|
||||
mAllowFilesFromTheFuture(allow_files_from_the_future)
|
||||
{
|
||||
is_open = false ;
|
||||
validity = false ;
|
||||
|
@ -44,8 +44,10 @@ namespace librs { namespace util {
|
||||
class FolderIterator
|
||||
{
|
||||
public:
|
||||
FolderIterator(const std::string& folderName,bool allow_symlinks,bool allow_files_from_the_future = true);
|
||||
~FolderIterator();
|
||||
FolderIterator(
|
||||
const std::string& folderName, bool allow_symlinks,
|
||||
bool allow_files_from_the_future = true );
|
||||
~FolderIterator();
|
||||
|
||||
enum { TYPE_UNKNOWN = 0x00,
|
||||
TYPE_FILE = 0x01,
|
||||
|
@ -81,10 +81,11 @@ const char *scanf_string_for_uint(int bytes) ;
|
||||
|
||||
int breakupDirList(const std::string& path, std::list<std::string> &subdirs);
|
||||
|
||||
// Splits the path into parent directory and file. File can be empty if full_path is a dir ending with '/'
|
||||
// if full_path does not contain a directory, then dir will be "." and file will be full_path.
|
||||
|
||||
bool splitDirFromFile(const std::string& full_path,std::string& dir, std::string& file);
|
||||
/** Splits the path into parent directory and file. File can be empty if
|
||||
* full_path is a dir ending with '/' if full_path does not contain a directory,
|
||||
* then dir will be "." and file will be full_path */
|
||||
bool splitDirFromFile( const std::string& full_path,
|
||||
std::string& dir, std::string& file );
|
||||
|
||||
bool copyFile(const std::string& source,const std::string& dest);
|
||||
|
||||
|
@ -3,7 +3,8 @@
|
||||
* *
|
||||
* libretroshare: retroshare core library *
|
||||
* *
|
||||
* Copyright 2013-2013 by Cyril Soler <csoler@users.sourceforge.net> *
|
||||
* Copyright 2013 Cyril Soler <csoler@users.sourceforge.net> *
|
||||
* Copyright 2018 Gioacchino Mazzurco <gio@altermundi.net> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License as *
|
||||
|
Loading…
Reference in New Issue
Block a user