You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
646 lines
19 KiB
646 lines
19 KiB
/*
|
|
* Copyright (C) 2015-2022 Département de l'Instruction Publique (DIP-SEM)
|
|
*
|
|
* Copyright (C) 2013 Open Education Foundation
|
|
*
|
|
* Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
|
|
* l'Education Numérique en Afrique (GIP ENA)
|
|
*
|
|
* This file is part of OpenBoard.
|
|
*
|
|
* OpenBoard 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, version 3 of the License,
|
|
* with a specific linking exception for the OpenSSL project's
|
|
* "OpenSSL" library (or with modified versions of it that use the
|
|
* same license as the "OpenSSL" library).
|
|
*
|
|
* OpenBoard 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 OpenBoard. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "UBForeignObjectsHandler.h"
|
|
|
|
#include <QtGui>
|
|
#include <QtXml>
|
|
#include "UBSettings.h"
|
|
|
|
const QString tVideo = "video";
|
|
const QString tAudio = "audio";
|
|
const QString tImage = "image";
|
|
const QString tForeignObject = "foreignObject";
|
|
const QString tTeacherGuide = "teacherGuide";
|
|
const QString tMedia = "media";
|
|
const QString tGroups = "groups";
|
|
|
|
const QString aHref = "xlink:href";
|
|
const QString aType = "ub:type";
|
|
const QString aReqExt = "requiredExtensions";
|
|
const QString aSrc = "ub:src";
|
|
const QString aMediaType = "mediaType";
|
|
const QString aRelativePath = "relativePath";
|
|
const QString aActionMedia = "ub:actionFirstParameter";
|
|
|
|
const QString vText = "text";
|
|
const QString vReqExt = "http://ns.adobe.com/pdf/1.3/";
|
|
|
|
const QString wgtSuff = ".wgt";
|
|
const QString thumbSuff = ".png";
|
|
|
|
const QString scanDirs = "audios,images,videos,teacherGuideObjects,widgets";
|
|
const QStringList trashFilter = QStringList() << "*.swf";
|
|
|
|
static QString strIdFrom(const QString &filePath)
|
|
{
|
|
if ((filePath).isEmpty()) {
|
|
return QString();
|
|
}
|
|
|
|
QRegExp rx("\\{.(?!.*\\{).*\\}");
|
|
if (rx.indexIn(filePath) == -1) {
|
|
return QString();
|
|
}
|
|
|
|
return rx.cap();
|
|
}
|
|
|
|
static bool rm_r(const QString &rmPath)
|
|
{
|
|
QFileInfo fi(rmPath);
|
|
if (!fi.exists()) {
|
|
qDebug() << rmPath << "does not exist";
|
|
return false;
|
|
} else if (fi.isFile()) {
|
|
if (!QFile::remove(rmPath)) {
|
|
qDebug() << "can't remove file" << rmPath;
|
|
return false;
|
|
}
|
|
return true;
|
|
} else if (fi.isDir()) {
|
|
QFileInfoList fList = QDir(rmPath).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
|
|
foreach (QFileInfo sub, fList) {
|
|
rm_r(sub.absoluteFilePath());
|
|
}
|
|
if (!QDir().rmdir(rmPath)) {
|
|
qDebug() << "can't remove dir" << rmPath;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool cp_rf(const QString &what, const QString &where)
|
|
{
|
|
QFileInfo whatFi(what);
|
|
QFileInfo whereFi = QFileInfo(where);
|
|
|
|
if (!whatFi.exists()) {
|
|
qDebug() << what << "does not exist" << Q_FUNC_INFO;
|
|
return false;
|
|
} else if (whatFi.isFile()) {
|
|
QString whereDir = where.section("/", 0, -2, QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep);
|
|
QString newFilePath = where;
|
|
if (!whereFi.exists()) {
|
|
QDir().mkpath(whereDir);
|
|
} else if (whereFi.isDir()) {
|
|
newFilePath = whereDir + "/" + whatFi.fileName();
|
|
}
|
|
if (QFile::exists(newFilePath)) {
|
|
QFile::remove(newFilePath);
|
|
}
|
|
if (!QFile::copy(what, newFilePath)) {
|
|
qDebug() << "can't copy" << what << "to" << where << Q_FUNC_INFO;
|
|
return false;
|
|
}
|
|
return true;
|
|
} else if (whatFi.isDir()) {
|
|
|
|
if (whereFi.isFile() && whereFi.fileName().toLower() == whatFi.fileName().toLower()) {
|
|
qDebug() << "can't copy dir" << what << "to file" << where << Q_FUNC_INFO;
|
|
return false;
|
|
} else if (whereFi.isDir()) {
|
|
rm_r(where);
|
|
}
|
|
|
|
QDir().mkpath(where);
|
|
|
|
QFileInfoList fList = QDir(what).entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
|
|
foreach (QFileInfo sub, fList) {
|
|
if (!cp_rf(sub.absoluteFilePath(), where + "/" + sub.fileName()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static QString thumbFileNameFrom(const QString &filePath)
|
|
{
|
|
if (filePath.isEmpty()) {
|
|
return QString();
|
|
}
|
|
|
|
QString thumbPath = filePath;
|
|
thumbPath.replace(QRegExp("[\\{\\}]"), "").replace(wgtSuff, thumbSuff);
|
|
|
|
return thumbPath;
|
|
}
|
|
|
|
|
|
QString svgPageName(int pageIndex)
|
|
{
|
|
return QString("page%1.svg").arg(pageIndex, 3, 10, QLatin1Char('0'));
|
|
}
|
|
|
|
static QDomDocument createDomFromSvg(const QString &svgUrl)
|
|
{
|
|
Q_ASSERT(QFile::exists(svgUrl));
|
|
QString mFoldersXmlStorageName = svgUrl;
|
|
|
|
if (QFileInfo(mFoldersXmlStorageName).exists()) {
|
|
QDomDocument xmlDom;
|
|
QFile inFile(mFoldersXmlStorageName);
|
|
if (inFile.open(QIODevice::ReadOnly)) {
|
|
QString domString(inFile.readAll());
|
|
|
|
int errorLine = 0; int errorColumn = 0;
|
|
QString errorStr;
|
|
|
|
if (xmlDom.setContent(domString, &errorStr, &errorLine, &errorColumn)) {
|
|
return xmlDom;
|
|
} else {
|
|
qDebug() << "Error reading content of " << mFoldersXmlStorageName << endl
|
|
<< "Error:" << inFile.errorString()
|
|
<< "Line:" << errorLine
|
|
<< "Column:" << errorColumn;
|
|
}
|
|
inFile.close();
|
|
} else {
|
|
qDebug() << "Error reading" << mFoldersXmlStorageName << endl
|
|
<< "Error:" << inFile.errorString();
|
|
}
|
|
}
|
|
|
|
return QDomDocument();
|
|
}
|
|
|
|
class Cleaner
|
|
{
|
|
public:
|
|
void cure(const QUrl &dir)
|
|
{
|
|
mCurrentDir = dir.toLocalFile();
|
|
cleanTrash();
|
|
|
|
// Gathering information from svg files
|
|
QFileInfoList svgInfos = QDir(mCurrentDir).entryInfoList(QStringList() << "*.svg", QDir::NoDotAndDotDot | QDir::Files);
|
|
foreach (QFileInfo svgInfo, svgInfos) {
|
|
cureIdsFromSvgDom(createDomFromSvg(svgInfo.absoluteFilePath()));
|
|
}
|
|
|
|
fitIdsFromFileSystem();
|
|
QVector<QString> deleteCandidates;
|
|
findRedundandElements(deleteCandidates);
|
|
|
|
foreach (QString key, deleteCandidates) {
|
|
QString delPath = mPresentIdsMap.value(key);
|
|
if (delPath.isNull()) {
|
|
continue;
|
|
} else if (delPath.endsWith(wgtSuff)) { //remove corresponding thumb
|
|
QString thumbPath = thumbFileNameFrom(delPath);
|
|
|
|
//N/C - NNE - 20140417
|
|
if (QFile::exists(thumbPath)) {
|
|
rm_r(thumbPath);
|
|
}
|
|
}
|
|
rm_r(delPath);
|
|
// Clear parent dir if empty
|
|
QDir dir(delPath);
|
|
dir.cdUp();
|
|
if (dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()) {
|
|
dir.rmdir(dir.absolutePath());
|
|
}
|
|
}
|
|
|
|
qDebug() << "Ok on cure";
|
|
}
|
|
|
|
private:
|
|
void cleanTrash()
|
|
{
|
|
QFileInfoList ifs = QDir(mCurrentDir).entryInfoList(trashFilter, QDir::NoDotAndDotDot | QDir::Files);
|
|
foreach (QFileInfo ifo, ifs) {
|
|
rm_r(ifo.absoluteFilePath());
|
|
}
|
|
}
|
|
|
|
void cureIdsFromSvgDom(const QDomDocument &dom)
|
|
{
|
|
Q_ASSERT(!dom.isNull());
|
|
|
|
QDomElement nextElement = dom.documentElement().firstChildElement();
|
|
while (!nextElement.isNull()) {
|
|
QString nextTag = nextElement.tagName();
|
|
qDebug() << "Tag name of the next parsed element is" << nextTag;
|
|
if (nextTag == tGroups)
|
|
{
|
|
nextElement = nextElement.firstChildElement("group");
|
|
}
|
|
|
|
invokeFromText(nextTag, nextElement);
|
|
nextElement = nextElement.nextSiblingElement();
|
|
}
|
|
}
|
|
|
|
void fitIdsFromFileSystem()
|
|
{
|
|
QString absPrefix = mCurrentDir + "/";
|
|
QStringList dirsList = scanDirs.split(",", QString::SkipEmptyParts);
|
|
foreach (QString dirName, dirsList) {
|
|
QString absPath = absPrefix + dirName;
|
|
if (!QFile::exists(absPath)) {
|
|
continue;
|
|
}
|
|
fitIdsFromDir(absPath);
|
|
}
|
|
|
|
}
|
|
|
|
void fitIdsFromDir(const QString &scanDir)
|
|
{
|
|
QFileInfoList fileList = QDir(scanDir).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
|
foreach (QFileInfo nInfo, fileList) {
|
|
QString uid = strIdFrom(nInfo.fileName());
|
|
if (uid.isNull()) {
|
|
continue;
|
|
}
|
|
mPresentIdsMap.insert(uid, nInfo.absoluteFilePath());
|
|
}
|
|
}
|
|
|
|
void findRedundandElements(QVector<QString> &v)
|
|
{
|
|
// Taking information from the physical file system
|
|
QStringList domIds = mDomIdsMap.keys();
|
|
QStringList presentIds = mPresentIdsMap.keys();
|
|
v.resize(qMax(domIds.count(), presentIds.count()));
|
|
QVector<QString>::iterator it_diff;
|
|
|
|
it_diff=std::set_symmetric_difference(domIds.begin(), domIds.end()
|
|
, presentIds.begin(), presentIds.end()
|
|
, v.begin());
|
|
v.resize(it_diff - v.begin());
|
|
}
|
|
|
|
void invokeFromText(const QString &what, const QDomElement &element)
|
|
{
|
|
if (what == tVideo
|
|
|| what == tAudio
|
|
|| what == tImage) {
|
|
mediaToContainer(element);
|
|
} else if (what == tForeignObject) {
|
|
foreingObjectToContainer(element);
|
|
|
|
//N/C - NNE - 20140317
|
|
cleanObjectFolder(element);
|
|
|
|
//N/C - NNE - 20140520
|
|
//foreign object may referer resource which are not present in the svg
|
|
addResourceIdToSvg(element);
|
|
} else if (what == tTeacherGuide) {
|
|
teacherGuideToContainer(element);
|
|
}
|
|
|
|
pullActionFromElement(element);
|
|
}
|
|
|
|
// N/C - NNE - 20140317 : When export, reduce the size of the ubz file
|
|
void cleanObjectFolder(const QDomElement &element)
|
|
{
|
|
//QDomElement preference = element.firstChildElement("ub:preference");
|
|
|
|
//N/C - NNE - 20141021
|
|
QDomNodeList childrenNode = element.elementsByTagName("ub:preference");
|
|
|
|
QVector<QString> objectsIdUsed;
|
|
|
|
for(int i = 0; i < childrenNode.size(); i++){
|
|
QDomElement preference = childrenNode.at(i).toElement();
|
|
|
|
if(!preference.isNull()){
|
|
QString value = preference.attribute("value");
|
|
|
|
int findPos = value.indexOf("objects/");
|
|
int endPos;
|
|
|
|
//find all objects used
|
|
while(findPos != -1){
|
|
endPos = value.indexOf("\"", findPos);
|
|
objectsIdUsed << value.mid(findPos, endPos - findPos);
|
|
findPos = value.indexOf("objects/", endPos);
|
|
}
|
|
}
|
|
}
|
|
//N/C - NNE - 20141021 : END
|
|
|
|
|
|
QString path = element.attribute(aSrc);
|
|
QString objectsFolderPath = mCurrentDir + "/" + path + "/objects/";
|
|
|
|
QDir dir(objectsFolderPath);
|
|
dir.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
|
|
|
//then check all files in the objects directory
|
|
//delete the file not used (not in te objectIdUsed variable)
|
|
QFileInfoList list = dir.entryInfoList();
|
|
for (int i = 0; i < list.size(); i++) {
|
|
QFileInfo fileInfo = list.at(i);
|
|
|
|
if(!objectsIdUsed.contains("objects/"+fileInfo.fileName())){
|
|
QFile(fileInfo.filePath()).remove();
|
|
}
|
|
|
|
}
|
|
}
|
|
// N/C - NNE - 20140317 : END
|
|
|
|
//N/C - NNE - 20140520
|
|
void addResourceIdToSvg(const QDomElement& element)
|
|
{
|
|
QDomElement textContent = element.firstChildElement("itemTextContent");
|
|
|
|
QString value = textContent.text();
|
|
|
|
int findPos = value.indexOf("images/");
|
|
int endPos;
|
|
|
|
//find all objects used
|
|
while(findPos != -1){
|
|
endPos = value.indexOf("\"", findPos);
|
|
|
|
QString path = value.mid(findPos, endPos - findPos);
|
|
|
|
QString uuid = path.split("/").at(1).split(".").at(0);
|
|
|
|
mDomIdsMap.insert(uuid, path);
|
|
|
|
findPos = value.indexOf("images/", endPos);
|
|
}
|
|
}
|
|
//N/C - NNE - 20140520 : END
|
|
|
|
void pullActionFromElement(const QDomElement &element)
|
|
{
|
|
if (!element.hasAttribute(aActionMedia)) {
|
|
return;
|
|
}
|
|
|
|
QString path = element.attribute(aActionMedia);
|
|
if (path.isNull()) {
|
|
return;
|
|
}
|
|
|
|
QString uid = strIdFrom(path);
|
|
if (uid.isNull()) {
|
|
return;
|
|
}
|
|
|
|
mDomIdsMap.insert(uid, path);
|
|
}
|
|
|
|
void teacherGuideToContainer(const QDomElement &element)
|
|
{
|
|
QDomElement nMediaElement = element.firstChildElement(tMedia);
|
|
while (!nMediaElement.isNull()) {
|
|
|
|
QString path = nMediaElement.attribute(aRelativePath);
|
|
if (path.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
QString uid = strIdFrom(path);
|
|
if (uid.isNull()) {
|
|
return;
|
|
}
|
|
mDomIdsMap.insert(uid, path);
|
|
|
|
nMediaElement = nMediaElement.nextSiblingElement(tMedia);
|
|
}
|
|
}
|
|
|
|
void mediaToContainer(const QDomElement &element)
|
|
{
|
|
QString path = element.attribute(aHref);
|
|
if (path.isNull()) {
|
|
return;
|
|
}
|
|
QString uid = strIdFrom(path);
|
|
if (uid.isNull()) {
|
|
return;
|
|
}
|
|
mDomIdsMap.insert(uid, path);
|
|
}
|
|
|
|
void foreingObjectToContainer(const QDomElement &element)
|
|
{
|
|
QString type = element.attribute(aType);
|
|
if (type == vText) { // We don't have to care of the text object
|
|
return;
|
|
}
|
|
|
|
QString path = element.attribute(aSrc);
|
|
if (path.isNull()) {
|
|
return;
|
|
}
|
|
|
|
QString uid = strIdFrom(path);
|
|
if (uid.isNull()) {
|
|
return;
|
|
}
|
|
|
|
mDomIdsMap.insert(uid, path);
|
|
}
|
|
|
|
private:
|
|
QString mCurrentDir;
|
|
QDomDocument mSvgData;
|
|
QMap<QString, QString> mDomIdsMap;
|
|
QMap<QString, QString> mPresentIdsMap;
|
|
};
|
|
|
|
class PageCopier
|
|
{
|
|
public:
|
|
void copyPage (const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex)
|
|
{
|
|
mFromDir = fromDir.toLocalFile();
|
|
mToDir = toDir.toLocalFile();
|
|
mFromIndex = fromIndex;
|
|
mToIndex = toIndex;
|
|
|
|
QString svgFrom = mFromDir + "/" + svgPageName(fromIndex);
|
|
QString svgTo = toDir.toLocalFile() + "/" + svgPageName(toIndex);
|
|
QDomDocument dd = createDomFromSvg(svgFrom);
|
|
QFile fl(svgTo);
|
|
if (!fl.open(QIODevice::WriteOnly)) {
|
|
qDebug() << Q_FUNC_INFO << "can't open" << fl.fileName() << "for writing";
|
|
return;
|
|
}
|
|
cureIdsFromSvgDom(dd);
|
|
|
|
QTextStream str(&fl);
|
|
dd.save(str, 0);
|
|
fl.close();
|
|
qDebug() << Q_FUNC_INFO;
|
|
}
|
|
|
|
private:
|
|
void cureIdsFromSvgDom(const QDomDocument &dom)
|
|
{
|
|
Q_ASSERT(!dom.isNull());
|
|
|
|
QDomElement nextElement = dom.documentElement().firstChildElement();
|
|
while (!nextElement.isNull( )) {
|
|
qDebug() << "Tag name of the next parsed element is" << nextElement.tagName();
|
|
QString nextTag = nextElement.tagName();
|
|
cureFromText(nextTag, nextElement);
|
|
nextElement = nextElement.nextSiblingElement();
|
|
}
|
|
}
|
|
|
|
void cureFromText(const QString &tagName, QDomElement element)
|
|
{
|
|
if (tagName == tVideo
|
|
|| tagName == tAudio
|
|
|| tagName == tImage) {
|
|
QString newRelative = cureNCopy(element.attribute(aHref));
|
|
element.setAttribute(aHref, newRelative);
|
|
if (element.hasAttribute(aActionMedia)) {
|
|
QString newActionPath = cureNCopy(element.attribute(aActionMedia));
|
|
element.setAttribute(aActionMedia, newActionPath);
|
|
}
|
|
} else if (tagName == tForeignObject) {
|
|
//Pdf object is a special case. Detect if it ends with #reference
|
|
QString reqExt = element.attribute(aReqExt);
|
|
if (reqExt == vReqExt) { //pdf reference
|
|
QString ref = element.attribute(aHref);
|
|
int i = ref.indexOf("#page");
|
|
QString dest = ref.replace(i, ref.length()-i, "");
|
|
if (!QFileInfo::exists(dest))
|
|
cureNCopy(dest, false);
|
|
if (ref.isEmpty()) {
|
|
return;
|
|
}
|
|
ref.replace(QRegExp("^(.*pdf\\#page\\=).*$"), QString("\\1%1").arg(mToIndex));
|
|
return;
|
|
}
|
|
|
|
QString type = element.attribute(aType);
|
|
if (type == vText) { // We don't have to care of the text object
|
|
if (element.hasAttribute(aActionMedia)) {
|
|
QString newRelative = cureNCopy(element.attribute(aActionMedia));
|
|
element.setAttribute(aActionMedia, newRelative);
|
|
}
|
|
return;
|
|
}
|
|
QString newRelative = cureNCopy(element.attribute(aSrc));
|
|
element.setAttribute(aSrc, newRelative);
|
|
} else if (tagName == tTeacherGuide) {
|
|
QDomElement nMediaElement = element.firstChildElement(tMedia);
|
|
while (!nMediaElement.isNull()) {
|
|
QString newRelative = cureNCopy(nMediaElement.attribute(aRelativePath));
|
|
nMediaElement.setAttribute(aRelativePath, newRelative);
|
|
nMediaElement = nMediaElement.nextSiblingElement(tMedia);
|
|
}
|
|
}
|
|
}
|
|
|
|
QString cureNCopy(const QString &relativePath, bool createNewUuid=true)
|
|
{
|
|
QString relative = relativePath;
|
|
if (createNewUuid)
|
|
{
|
|
QUuid newUuid = QUuid::createUuid();
|
|
QString newPath = relative.replace(QRegExp("\\{.*\\}"), newUuid.toString());
|
|
|
|
cp_rf(mFromDir + "/" + relativePath, mToDir + "/" + newPath);
|
|
|
|
return newPath;
|
|
}
|
|
else
|
|
{
|
|
cp_rf(mFromDir + "/" + relativePath, mToDir + "/" + relativePath);
|
|
return relativePath;
|
|
}
|
|
}
|
|
|
|
private:
|
|
QString mFromDir;
|
|
QString mToDir;
|
|
int mFromIndex;
|
|
int mToIndex;
|
|
};
|
|
|
|
class UBForeighnObjectsHandlerPrivate {
|
|
UBForeighnObjectsHandlerPrivate(UBForeighnObjectsHandler *pq)
|
|
: q(pq)
|
|
{
|
|
}
|
|
|
|
public:
|
|
void cure(const QUrl &dir)
|
|
{
|
|
Cleaner *cleaner = new Cleaner;
|
|
cleaner->cure(dir);
|
|
delete cleaner;
|
|
cleaner = 0;
|
|
}
|
|
|
|
void copyPage (const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex)
|
|
{
|
|
PageCopier *copier = new PageCopier;
|
|
copier->copyPage(fromDir, fromIndex, toDir, toIndex);
|
|
delete copier;
|
|
copier = 0;
|
|
}
|
|
|
|
private:
|
|
UBForeighnObjectsHandler *q;
|
|
friend class UBForeighnObjectsHandler;
|
|
};
|
|
|
|
UBForeighnObjectsHandler::UBForeighnObjectsHandler()
|
|
: d(new UBForeighnObjectsHandlerPrivate(this))
|
|
{
|
|
|
|
}
|
|
|
|
UBForeighnObjectsHandler::~UBForeighnObjectsHandler()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void UBForeighnObjectsHandler::cure(const QList<QUrl> &dirs)
|
|
{
|
|
foreach (QUrl dir, dirs) {
|
|
cure(dir);
|
|
}
|
|
}
|
|
|
|
void UBForeighnObjectsHandler::cure(const QUrl &dir)
|
|
{
|
|
d->cure(dir);
|
|
}
|
|
|
|
void UBForeighnObjectsHandler::copyPage(const QUrl &fromDir, int fromIndex, const QUrl &toDir, int toIndex)
|
|
{
|
|
d->copyPage(fromDir, fromIndex, toDir, toIndex);
|
|
}
|
|
|
|
|