From df361599569bab491226e8f478f73d4908a44a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Wed, 3 Sep 2014 12:42:52 +0200 Subject: [PATCH] modified comics and folders information served to remote devices (YACReader for iOS 2.0) --- YACReaderLibrary/db_helper.cpp | 28 ++++++------- YACReaderLibrary/db_helper.h | 18 ++++----- .../server/controllers/comiccontroller.cpp | 19 ++++----- .../comicdownloadinfocontroller.cpp | 24 ++++++++++++ .../controllers/comicdownloadinfocontroller.h | 19 +++++++++ .../server/controllers/foldercontroller.cpp | 10 ++--- .../controllers/folderinfocontroller.cpp | 39 ++++++++++++------- .../server/controllers/folderinfocontroller.h | 3 ++ YACReaderLibrary/server/requestmapper.cpp | 12 ++++-- YACReaderLibrary/server/server.pri | 6 ++- YACReaderLibrary/yacreader_local_server.cpp | 6 +-- common/comic_db.cpp | 12 ++++-- common/comic_db.h | 3 ++ 13 files changed, 133 insertions(+), 66 deletions(-) create mode 100644 YACReaderLibrary/server/controllers/comicdownloadinfocontroller.cpp create mode 100644 YACReaderLibrary/server/controllers/comicdownloadinfocontroller.h diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index 323dfcf7..17678360 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -31,9 +31,9 @@ YACReaderLibraries DBHelper::getLibraries() libraries.load(); return libraries; } -QList DBHelper::getFolderContentFromLibrary(const QString & libraryName, qulonglong folderId) +QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); QList list = DBHelper::getFoldersFromParent(folderId,db,false); @@ -42,9 +42,9 @@ QList DBHelper::getFolderContentFromLibrary(const QString & libra QSqlDatabase::removeDatabase(libraryPath); return list; } -QList DBHelper::getFolderComicsFromLibrary(const QString & libraryName, qulonglong folderId) +QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); QList list = DBHelper::getComicsFromParent(folderId,db,false); @@ -53,9 +53,9 @@ QList DBHelper::getFolderComicsFromLibrary(const QString & librar QSqlDatabase::removeDatabase(libraryPath); return list; } -qulonglong DBHelper::getParentFromComicFolderId(const QString & libraryName, qulonglong id) +qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong id) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); Folder f = DBHelper::loadFolder(id,db); @@ -64,9 +64,9 @@ qulonglong DBHelper::getParentFromComicFolderId(const QString & libraryName, qul QSqlDatabase::removeDatabase(libraryPath); return f.parentId; } -ComicDB DBHelper::getComicInfo(const QString & libraryName, qulonglong id) +ComicDB DBHelper::getComicInfo(qulonglong libraryId, qulonglong id) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); ComicDB comic = DBHelper::loadComic(id,db); @@ -76,9 +76,9 @@ ComicDB DBHelper::getComicInfo(const QString & libraryName, qulonglong id) return comic; } -QList DBHelper::getSiblings(const QString & libraryName, qulonglong parentId) +QList DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); QList comics = DBHelper::getSortedComicsFromParent(parentId,db); @@ -87,9 +87,9 @@ QList DBHelper::getSiblings(const QString & libraryName, qulonglong par return comics; } -QString DBHelper::getFolderName(const QString & libraryName, qulonglong id) +QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); QString name=""; @@ -153,9 +153,9 @@ void DBHelper::update(ComicDB * comic, QSqlDatabase & db) //do nothing } -void DBHelper::update(const QString & libraryName, ComicInfo & comicInfo) +void DBHelper::update(qulonglong libraryId, ComicInfo & comicInfo) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryName); + QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath+"/.yacreaderlibrary"); DBHelper::update(&comicInfo,db); diff --git a/YACReaderLibrary/db_helper.h b/YACReaderLibrary/db_helper.h index 146d60d5..c06251d9 100644 --- a/YACReaderLibrary/db_helper.h +++ b/YACReaderLibrary/db_helper.h @@ -19,12 +19,12 @@ class DBHelper public: //server static YACReaderLibraries getLibraries(); - static QList getFolderContentFromLibrary(const QString & libraryName, qulonglong folderId); - static QList getFolderComicsFromLibrary(const QString & libraryName, qulonglong folderId); - static qulonglong getParentFromComicFolderId(const QString & libraryName, qulonglong id); - static ComicDB getComicInfo(const QString & libraryName, qulonglong id); - static QList getSiblings(const QString & libraryName, qulonglong parentId); - static QString getFolderName(const QString & libraryName, qulonglong id); + static QList getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId); + static QList getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId); + static qulonglong getParentFromComicFolderId(qulonglong libraryId, qulonglong id); + static ComicDB getComicInfo(qulonglong libraryId, qulonglong id); + static QList getSiblings(qulonglong libraryId, qulonglong parentId); + static QString getFolderName(qulonglong libraryId, qulonglong id); static QList getLibrariesNames(); static QString getLibraryName(int id); @@ -37,13 +37,13 @@ public: static qulonglong insert(Folder * folder, QSqlDatabase & db); static qulonglong insert(ComicDB * comic, QSqlDatabase & db); //updates - static void update(const QString & libraryName, ComicInfo & comicInfo); + static void update(qulonglong libraryId, ComicInfo & comicInfo); static void update(ComicDB * comics, QSqlDatabase & db); static void update(ComicInfo * comicInfo, QSqlDatabase & db); static void updateRead(ComicInfo * comicInfo, QSqlDatabase & db); static void update(const Folder & folder, QSqlDatabase & db); - static void updateProgress(qulonglong libraryId,const ComicInfo & comicInfo); //TODO change libraryName by libraryId in all methods. - //queries + static void updateProgress(qulonglong libraryId,const ComicInfo & comicInfo); + static QList getFoldersFromParent(qulonglong parentId, QSqlDatabase & db, bool sort = true); static QList getSortedComicsFromParent(qulonglong parentId, QSqlDatabase & db); static QList getComicsFromParent(qulonglong parentId, QSqlDatabase & db, bool sort = true); diff --git a/YACReaderLibrary/server/controllers/comiccontroller.cpp b/YACReaderLibrary/server/controllers/comiccontroller.cpp index 88c39abf..a7ec0480 100644 --- a/YACReaderLibrary/server/controllers/comiccontroller.cpp +++ b/YACReaderLibrary/server/controllers/comiccontroller.cpp @@ -21,7 +21,8 @@ void ComicController::service(HttpRequest& request, HttpResponse& response) QString path = QUrl::fromPercentEncoding(request.getPath()).toLatin1(); QStringList pathElements = path.split('/'); - QString libraryName = DBHelper::getLibraryName(pathElements.at(2).toInt()); + qulonglong libraryId = pathElements.at(2).toLongLong(); + QString libraryName = DBHelper::getLibraryName(libraryId); qulonglong comicId = pathElements.at(4).toULongLong(); bool remoteComic = path.endsWith("remote"); @@ -38,20 +39,14 @@ void ComicController::service(HttpRequest& request, HttpResponse& response) // } //} - //Aplicar a todos los controladores - //TODO usar LibraryWindow para acceder a información de las bases de datos está mal, hay - //que crear una clase que se encargue de estas cosas - //¿Se está accediendo a la UI desde un hilo? - YACReaderLibraries libraries = DBHelper::getLibraries(); - - ComicDB comic = DBHelper::getComicInfo(libraryName, comicId); + ComicDB comic = DBHelper::getComicInfo(libraryId, comicId); if(!remoteComic) session.setDownloadedComic(comic.info.hash); - Comic * comicFile = FactoryComic::newComic(libraries.getPath(libraryName)+comic.path); + Comic * comicFile = FactoryComic::newComic(libraries.getPath(libraryId)+comic.path); if(comicFile != NULL) { @@ -64,7 +59,7 @@ void ComicController::service(HttpRequest& request, HttpResponse& response) connect(thread, SIGNAL(started()), comicFile, SLOT(process())); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - comicFile->load(libraries.getPath(libraryName)+comic.path); + comicFile->load(libraries.getPath(libraryId)+comic.path); if(thread != NULL) thread->start(); @@ -84,10 +79,10 @@ void ComicController::service(HttpRequest& request, HttpResponse& response) response.setHeader("Content-Type", "plain/text; charset=ISO-8859-1"); //TODO this field is not used by the client! response.writeText(QString("library:%1\r\n").arg(libraryName)); - response.writeText(QString("libraryId:%1\r\n").arg(pathElements.at(2))); + response.writeText(QString("libraryId:%1\r\n").arg(libraryId)); if(remoteComic) //send previous and next comics id { - QList siblings = DBHelper::getFolderComicsFromLibrary(libraryName, comic.parentId); + QList siblings = DBHelper::getFolderComicsFromLibrary(libraryId, comic.parentId); bool found = false; int i; for(i = 0; i < siblings.length(); i++) diff --git a/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.cpp b/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.cpp new file mode 100644 index 00000000..d596371b --- /dev/null +++ b/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.cpp @@ -0,0 +1,24 @@ +#include "comicdownloadinfocontroller.h" + +#include "db_helper.h" +#include "yacreader_libraries.h" + +#include "comic_db.h" + +ComicDownloadInfoController::ComicDownloadInfoController() {} + + +void ComicDownloadInfoController::service(HttpRequest& request, HttpResponse& response) +{ + QString path = QUrl::fromPercentEncoding(request.getPath()).toLatin1(); + QStringList pathElements = path.split('/'); + + qulonglong libraryId = pathElements.at(2).toLongLong(); + qulonglong comicId = pathElements.at(4).toULongLong(); + + ComicDB comic = DBHelper::getComicInfo(libraryId, comicId); + + //TODO: check if the comic wasn't found; + response.writeText(QString("comicName:%1\r\n").arg(comic.getFileName())); + response.writeText(QString("fileSize:%1\r\n").arg(comic.getFileSize()),true); +} diff --git a/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.h b/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.h new file mode 100644 index 00000000..e98518e8 --- /dev/null +++ b/YACReaderLibrary/server/controllers/comicdownloadinfocontroller.h @@ -0,0 +1,19 @@ +#ifndef COMICDOWNLOADINFOCONTROLLER_H +#define COMICDOWNLOADINFOCONTROLLER_H + +#include "httprequest.h" +#include "httpresponse.h" +#include "httprequesthandler.h" + +class ComicDownloadInfoController : public HttpRequestHandler { + Q_OBJECT + Q_DISABLE_COPY(ComicDownloadInfoController); +public: + /** Constructor **/ + ComicDownloadInfoController(); + + /** Generates the response */ + void service(HttpRequest& request, HttpResponse& response); +}; + +#endif // COMICDOWNLOADINFOCONTROLLER_H diff --git a/YACReaderLibrary/server/controllers/foldercontroller.cpp b/YACReaderLibrary/server/controllers/foldercontroller.cpp index 847e0f58..54921493 100644 --- a/YACReaderLibrary/server/controllers/foldercontroller.cpp +++ b/YACReaderLibrary/server/controllers/foldercontroller.cpp @@ -43,7 +43,7 @@ void FolderController::service(HttpRequest& request, HttpResponse& response) folderId = qMax(1,folderId); - QString folderName = DBHelper::getFolderName(libraryName,folderId); + QString folderName = DBHelper::getFolderName(libraryId,folderId); if(folderName.isEmpty()) { ErrorController(300).service(request,response); @@ -54,8 +54,8 @@ void FolderController::service(HttpRequest& request, HttpResponse& response) t.setVariable("folder.name",folderName); else t.setVariable("folder.name",libraryName); - QList folderContent = DBHelper::getFolderContentFromLibrary(libraryName,folderId); - QList folderComics = DBHelper::getFolderComicsFromLibrary(libraryName,folderId); + QList folderContent = DBHelper::getFolderSubfoldersFromLibrary(libraryId,folderId); + QList folderComics = DBHelper::getFolderComicsFromLibrary(libraryId,folderId); //response.writeText(libraryName); @@ -152,7 +152,7 @@ void FolderController::service(HttpRequest& request, HttpResponse& response) t.loop("path",foldersPath.count()-1); for(int i = 1; i < foldersPath.count(); i++){ t.setVariable(QString("path%1.url").arg(i-1),QString("/library/%1/folder/%2").arg(libraryId).arg(foldersPath[i].first)); - t.setVariable(QString("path%1.name").arg(i-1),DBHelper::getFolderName(libraryName,foldersPath[i].first)); + t.setVariable(QString("path%1.name").arg(i-1),DBHelper::getFolderName(libraryId,foldersPath[i].first)); } t.loop("element",numFoldersAtCurrentPage); @@ -165,7 +165,7 @@ void FolderController::service(HttpRequest& request, HttpResponse& response) { t.setVariable(QString("element%1.class").arg(i),"folder"); - QList children = DBHelper::getFolderComicsFromLibrary(libraryName, item->id); + QList children = DBHelper::getFolderComicsFromLibrary(libraryId, item->id); if(children.length()>0) { const ComicDB * comic = static_cast(children.at(0)); diff --git a/YACReaderLibrary/server/controllers/folderinfocontroller.cpp b/YACReaderLibrary/server/controllers/folderinfocontroller.cpp index 9dbb2103..40be45c1 100644 --- a/YACReaderLibrary/server/controllers/folderinfocontroller.cpp +++ b/YACReaderLibrary/server/controllers/folderinfocontroller.cpp @@ -19,21 +19,30 @@ void FolderInfoController::service(HttpRequest& request, HttpResponse& response) int libraryId = pathElements.at(2).toInt(); QString libraryName = DBHelper::getLibraryName(libraryId); qulonglong parentId = pathElements.at(4).toULongLong(); - QList folderContent = DBHelper::getFolderContentFromLibrary(libraryName,parentId); - QList folderComics = DBHelper::getFolderComicsFromLibrary(libraryName,parentId); - Folder * currentFolder; - for(QList::const_iterator itr = folderContent.constBegin();itr!=folderContent.constEnd();itr++) - { - currentFolder = (Folder *)(*itr); - response.writeText(QString("/library/%1/folder/%2/info\n").arg(libraryId).arg(currentFolder->id)); - } + serviceComics(libraryId, parentId, response); - ComicDB * currentComic; - for(QList::const_iterator itr = folderComics.constBegin();itr!=folderComics.constEnd();itr++) - { - currentComic = (ComicDB *)(*itr); - response.writeText(QString("/library/%1/comic/%2\n").arg(libraryId).arg(currentComic->id)); - } + response.writeText("",true); +} -} \ No newline at end of file +void FolderInfoController::serviceComics(const int &library, const qulonglong &folderId, HttpResponse &response) +{ + QList folderContent = DBHelper::getFolderSubfoldersFromLibrary(library,folderId); + QList folderComics = DBHelper::getFolderComicsFromLibrary(library,folderId); + + ComicDB * currentComic; + for(QList::const_iterator itr = folderComics.constBegin();itr!=folderComics.constEnd();itr++) + { + currentComic = (ComicDB *)(*itr); + response.writeText(QString("/library/%1/comic/%2:%3:%4\r\n").arg(library).arg(currentComic->id).arg(currentComic->getFileName()).arg(currentComic->getFileSize())); + delete currentComic; + } + + Folder * currentFolder; + for(QList::const_iterator itr = folderContent.constBegin();itr!=folderContent.constEnd();itr++) + { + currentFolder = (Folder *)(*itr); + serviceComics(library, currentFolder->id, response); + delete currentFolder; + } +} diff --git a/YACReaderLibrary/server/controllers/folderinfocontroller.h b/YACReaderLibrary/server/controllers/folderinfocontroller.h index 69bdc6b6..87df58ce 100644 --- a/YACReaderLibrary/server/controllers/folderinfocontroller.h +++ b/YACReaderLibrary/server/controllers/folderinfocontroller.h @@ -15,6 +15,9 @@ public: /** Generates the response */ void service(HttpRequest& request, HttpResponse& response); + +private: + void serviceComics(const int &library, const qulonglong & folderId, HttpResponse& response); }; #endif // FOLDERINFOCONTROLLER_H diff --git a/YACReaderLibrary/server/requestmapper.cpp b/YACReaderLibrary/server/requestmapper.cpp index e71b28d0..df080918 100644 --- a/YACReaderLibrary/server/requestmapper.cpp +++ b/YACReaderLibrary/server/requestmapper.cpp @@ -20,6 +20,7 @@ #include "controllers/pagecontroller.h" #include "controllers/updatecomiccontroller.h" #include "controllers/errorcontroller.h" +#include "controllers/comicdownloadinfocontroller.h" #include "db_helper.h" #include "yacreader_libraries.h" @@ -95,7 +96,8 @@ void RequestMapper::service(HttpRequest& request, HttpResponse& response) { QRegExp folder("/library/.+/folder/[0-9]+/?");//get comic content QRegExp folderInfo("/library/.+/folder/[0-9]+/info/?"); //get folder info - QRegExp comic("/library/.+/comic/[0-9]+/?"); //get comic info + QRegExp comicDownloadInfo("/library/.+/comic/[0-9]+/?"); //get comic info (basic/download info) + QRegExp comicFullInfo("/library/.+/comic/[0-9]+/info/?"); //get comic info (full info) QRegExp comicOpen("/library/.+/comic/[0-9]+/remote/?"); //the server will open for reading the comic QRegExp comicUpdate("/library/.+/comic/[0-9]+/update/?"); //get comic info QRegExp comicClose("/library/.+/comic/[0-9]+/close/?"); //the server will close the comic and free memory @@ -138,8 +140,12 @@ void RequestMapper::service(HttpRequest& request, HttpResponse& response) { else if(cover.exactMatch(path)) { CoverController().service(request, response); - } - else if(comic.exactMatch(path) || comicOpen.exactMatch(path)) + } + else if(comicDownloadInfo.exactMatch(path)) + { + ComicDownloadInfoController().service(request, response); + } + else if(comicFullInfo.exactMatch(path) || comicOpen.exactMatch(path))//start download or start remote reading { ComicController().service(request, response); } diff --git a/YACReaderLibrary/server/server.pri b/YACReaderLibrary/server/server.pri index 9e3b2920..5fc65043 100644 --- a/YACReaderLibrary/server/server.pri +++ b/YACReaderLibrary/server/server.pri @@ -13,7 +13,8 @@ HEADERS += \ $$PWD/controllers/pagecontroller.h \ $$PWD/controllers/sessionmanager.h \ $$PWD/controllers/covercontroller.h \ - server/controllers/updatecomiccontroller.h + server/controllers/updatecomiccontroller.h \ + server/controllers/comicdownloadinfocontroller.h SOURCES += \ $$PWD/static.cpp \ @@ -27,7 +28,8 @@ SOURCES += \ $$PWD/controllers/pagecontroller.cpp \ $$PWD/controllers/sessionmanager.cpp \ $$PWD/controllers/covercontroller.cpp \ - server/controllers/updatecomiccontroller.cpp + server/controllers/updatecomiccontroller.cpp \ + server/controllers/comicdownloadinfocontroller.cpp include(lib/bfLogging/bfLogging.pri) include(lib/bfHttpServer/bfHttpServer.pri) diff --git a/YACReaderLibrary/yacreader_local_server.cpp b/YACReaderLibrary/yacreader_local_server.cpp index 8d6d7c82..0e55078d 100644 --- a/YACReaderLibrary/yacreader_local_server.cpp +++ b/YACReaderLibrary/yacreader_local_server.cpp @@ -206,13 +206,13 @@ void YACReaderClientConnectionWorker::run() void YACReaderClientConnectionWorker::getComicInfo(quint64 libraryId, ComicDB & comic, QList & siblings) { QMutexLocker locker(&dbMutex); - comic = DBHelper::getComicInfo(DBHelper::getLibrariesNames().at(libraryId), comic.id); - siblings = DBHelper::getSiblings(DBHelper::getLibrariesNames().at(libraryId), comic.parentId); + comic = DBHelper::getComicInfo(libraryId, comic.id); + siblings = DBHelper::getSiblings(libraryId, comic.parentId); } void YACReaderClientConnectionWorker::updateComic(quint64 libraryId, ComicDB & comic) { QMutexLocker locker(&dbMutex); - DBHelper::update(DBHelper::getLibrariesNames().at(libraryId), comic.info); + DBHelper::update(libraryId, comic.info); emit comicUpdated(libraryId, comic); } diff --git a/common/comic_db.cpp b/common/comic_db.cpp index 119dd9a6..6e92b85a 100644 --- a/common/comic_db.cpp +++ b/common/comic_db.cpp @@ -31,7 +31,7 @@ QString ComicDB::toTXT() txt.append(QString("currentPage:%1\r\n").arg(info.currentPage)); txt.append(QString("contrast:%1\r\n").arg(info.contrast)); - //Información general + //Informaci�n general if(!info.coverPage.isNull()) txt.append(QString("coverPage:%1\r\n").arg(info.coverPage.toString())); @@ -80,7 +80,7 @@ QString ComicDB::toTXT() if(!info.coverArtist.isNull()) txt.append(QString("coverArtist:%1\r\n").arg(info.coverArtist.toString())); - //Publicación + //Publicaci�n if(!info.date.isNull()) txt.append(QString("date:%1\r\n").arg(info.date.toString())); @@ -127,7 +127,13 @@ QString ComicDB::getParentFolderName() const if(paths.length()<2) return ""; else - return paths[paths.length()-2]; + return paths[paths.length()-2]; +} + +qulonglong ComicDB::getFileSize() const +{ + //the size is encoded in the hash after the SHA-1 + return info.hash.right(info.hash.length()-40).toLongLong(); } //----------------------------------------------------------------------------- diff --git a/common/comic_db.h b/common/comic_db.h index f3c5cde6..417efa4d 100644 --- a/common/comic_db.h +++ b/common/comic_db.h @@ -139,6 +139,9 @@ public: //returns parent folder name QString getParentFolderName() const; + //return the size of the file in bytes + qulonglong getFileSize() const; + QString toTXT(); ComicInfo info;