File indexing completed on 2024-05-12 08:57:28
0001 /*************************************************************************** 0002 * Copyright (C) 2005-2014 by Linuxstopmotion contributors; * 0003 * see the AUTHORS file for details. * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 2 of the License, or * 0008 * (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0013 * GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License * 0016 * along with this program; if not, write to the * 0017 * Free Software Foundation, Inc., * 0018 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 0019 ***************************************************************************/ 0020 0021 #include "projectserializer.h" 0022 0023 #include "src/foundation/logger.h" 0024 #include "src/domain/filenamevisitor.h" 0025 #include "src/domain/animation/scene.h" 0026 #include "src/domain/animation/frame.h" 0027 #include "src/domain/animation/sound.h" 0028 #include "src/domain/animation/animationimpl.h" 0029 #include "src/domain/animation/workspacefile.h" 0030 #include "src/presentation/frontends/frontend.h" 0031 0032 #include <libxml/tree.h> 0033 0034 #include <assert.h> 0035 #include <fcntl.h> 0036 #include <stdio.h> 0037 #include <libtar.h> 0038 #include <cstring> 0039 #include <errno.h> 0040 #include <iosfwd> 0041 #include <map> 0042 #include <string> 0043 #include <set> 0044 0045 // for older libtars 0046 #define UNCONST(s) const_cast<char*>(s) 0047 0048 FileException::FileException(const char* functionName, int errorno) { 0049 snprintf(buffer, sizeof(buffer), "%s(): %s", functionName, 0050 strerror(errorno)); 0051 } 0052 0053 const char* FileException::what() const throw() { 0054 return buffer; 0055 } 0056 0057 class ProjectFileCreationException : public FileException { 0058 public: 0059 ProjectFileCreationException(const char* functionName, int errorno) 0060 : FileException(functionName, errorno) { 0061 } 0062 }; 0063 0064 class ProjectFileReadException : public FileException { 0065 public: 0066 ProjectFileReadException(const char* functionName, int errorno) 0067 : FileException(functionName, errorno) { 0068 } 0069 }; 0070 0071 ProjectSerializer::ProjectSerializer() : projectFile(0) { 0072 LIBXML_TEST_VERSION; 0073 } 0074 0075 ProjectSerializer::~ProjectSerializer() { 0076 resetProjectFile(); 0077 } 0078 0079 class TarFileRead { 0080 TAR* tar; 0081 public: 0082 TarFileRead(const char* filename) { 0083 if (tar_open(&tar, UNCONST(filename), 0, O_RDONLY, 0, 0 | 0) == -1) { 0084 throw ProjectFileReadException("tar_open", errno); 0085 } 0086 } 0087 ~TarFileRead() { 0088 } 0089 bool next() { 0090 int ret = th_read(tar); 0091 if (ret == 0) 0092 return true; 0093 if (ret < 0) 0094 throw ProjectFileReadException("th_read", errno); 0095 return false; 0096 } 0097 const char *regularFileFilename() const { 0098 return TH_ISREG(tar)? th_get_pathname(tar) : 0; 0099 } 0100 void extract(std::string& filename) { 0101 filename.c_str(); 0102 if (tar_extract_regfile(tar, &filename[0]) < 0) { 0103 throw ProjectFileReadException("th_extract_regfile", errno); 0104 } 0105 } 0106 void finish() { 0107 if (tar_close(tar) < 0) 0108 throw ProjectFileReadException("tar_close", errno); 0109 tar = 0; 0110 } 0111 }; 0112 0113 class StoFileRead { 0114 TarFileRead tar; 0115 static bool isProjectFile(const char* filename) { 0116 if (filename) { 0117 const char* slash1 = strchr(filename, '/'); 0118 if (slash1) { 0119 ++slash1; 0120 if (!strchr(slash1, '/')) { 0121 const char* dot = strrchr(slash1, '.'); 0122 if (dot && strcmp(dot, ".dat") == 0) { 0123 return true; 0124 } 0125 } 0126 } 0127 } 0128 return false; 0129 } 0130 // returns the basename portion of the argument if it is an image or sound, 0131 // null otherwise 0132 static const char* imageOrSoundFile(const char* filename) { 0133 static const char images[] = "images"; 0134 static const char sounds[] = "sounds"; 0135 if (filename) { 0136 const char* slash1 = strchr(filename, '/'); 0137 if (slash1) { 0138 ++slash1; 0139 const char* slash2 = strchr(slash1, '/'); 0140 if (slash2) { 0141 std::ptrdiff_t dirLen = slash2 - slash1; 0142 ++slash2; 0143 if ((strncmp(images, slash1, dirLen) == 0 0144 || strncmp(sounds, slash1, dirLen)) 0145 && strchr(slash2, '/') == 0) { 0146 return slash2; 0147 } 0148 } 0149 } 0150 } 0151 return 0; 0152 } 0153 public: 0154 StoFileRead(const char* filename) : tar(filename) { 0155 } 0156 ~StoFileRead() { 0157 } 0158 void unpack() { 0159 while (tar.next()) { 0160 const char* fn = tar.regularFileFilename(); 0161 const char* base = imageOrSoundFile(fn); 0162 if (base) { 0163 // image or sound 0164 WorkspaceFile wf(base); 0165 std::string path = wf.path(); 0166 tar.extract(path); 0167 } else if (isProjectFile(fn)) { 0168 WorkspaceFile dat(WorkspaceFile::currentModelFile); 0169 std::string path = dat.path(); 0170 tar.extract(path); 0171 } else if (fn) { 0172 Logger::get().logWarning( 0173 "Did not know what to do with tarred file %s", fn); 0174 } 0175 } 0176 } 0177 void finish() { 0178 tar.finish(); 0179 } 0180 }; 0181 0182 void readSto(const char* filename) { 0183 StoFileRead sto(filename); 0184 sto.unpack(); 0185 sto.finish(); 0186 } 0187 0188 // appends .sto to filename if appropriate to make a project filename 0189 void ProjectSerializer::setProjectFile(const char *filename) { 0190 static const char appendix[] = ".sto"; 0191 const size_t len = strlen(filename); 0192 char* newProjectFile = new char[len + sizeof(appendix)]; 0193 strcpy(newProjectFile, filename); 0194 const char *dotPtr = strrchr(filename, '.'); 0195 if (dotPtr == NULL || strcmp(dotPtr, ".sto") != 0) { 0196 strcat(newProjectFile, ".sto"); 0197 } 0198 delete[] projectFile; 0199 projectFile = newProjectFile; 0200 } 0201 0202 bool ProjectSerializer::openDat(std::vector<Scene*>& out, const char* filename) { 0203 xmlDocPtr doc = xmlReadFile(filename, NULL, 0); 0204 if (!doc) { 0205 Logger::get().logWarning("Couldn't load XML file"); 0206 return false; 0207 } 0208 xmlNodePtr rootNode = xmlDocGetRootElement(doc); 0209 out.clear(); 0210 getAttributes(rootNode, out); 0211 xmlFreeDoc(doc); 0212 xmlCleanupParser(); 0213 return true; 0214 } 0215 0216 std::vector<Scene*> ProjectSerializer::openSto(const char *filename) { 0217 assert(filename != NULL); 0218 setProjectFile(filename); 0219 readSto(projectFile); 0220 WorkspaceFile dat(WorkspaceFile::currentModelFile); 0221 std::vector<Scene*> sVect; 0222 openDat(sVect, dat.path()); 0223 return sVect; 0224 } 0225 0226 class UniqueVisitor: public FileNameVisitor { 0227 std::set<std::string> visited; 0228 FileNameVisitor* del; 0229 public: 0230 UniqueVisitor(FileNameVisitor* delegate) : 0231 del(delegate) { 0232 } 0233 ~UniqueVisitor() { 0234 } 0235 void visitImage(const char* path) { 0236 std::string p(path); 0237 if (visited.find(p) == visited.end()) { 0238 visited.insert(p); 0239 del->visitImage(path); 0240 } 0241 } 0242 void visitSound(const char* path) { 0243 std::string p(path); 0244 if (visited.find(p) == visited.end()) { 0245 visited.insert(p); 0246 del->visitSound(path); 0247 } 0248 } 0249 }; 0250 0251 std::string getProjectName(const char* filename) { 0252 const char* nameBegin = strrchr(filename, '/'); 0253 nameBegin = !nameBegin? filename : nameBegin + 1; 0254 const char* nameEnd = strrchr(filename, '.'); 0255 if (!nameEnd || nameEnd < nameBegin) 0256 nameEnd = filename + strlen(filename); 0257 return std::string(nameBegin, nameEnd); 0258 } 0259 0260 class TarPath { 0261 std::string path; 0262 std::string::size_type length; 0263 public: 0264 TarPath(const std::string& rootDir, const char* ext) 0265 : path(rootDir), length(0) { 0266 path.append(ext); 0267 length = path.length(); 0268 } 0269 /** 0270 * Returns the argument prefixed with the rootDir and ext supplied 0271 * to the constructor. The result is valid until the next call of getPath 0272 * or this object is destroyed. 0273 */ 0274 const char* getPath(const char* fname) { 0275 path.resize(length); 0276 path.append(fname); 0277 return path.c_str(); 0278 } 0279 }; 0280 0281 class TarFileWrite { 0282 TAR* tar; 0283 public: 0284 TarFileWrite(const char* tarFilename) { 0285 if (-1== tar_open(&tar, UNCONST(tarFilename), NULL, 0286 O_WRONLY | O_CREAT | O_TRUNC, 0644, 0)) { 0287 throw ProjectFileCreationException("tar_open", errno); 0288 } 0289 } 0290 ~TarFileWrite() { 0291 } 0292 void add(const char* realPath, const char* storedPath) { 0293 if (-1 == tar_append_file(tar, UNCONST(realPath), UNCONST(storedPath))) { 0294 throw ProjectFileCreationException("tar_append_file", errno); 0295 } 0296 } 0297 void eof() { 0298 if (tar_close(tar) < 0) { 0299 throw ProjectFileCreationException("tar_close", errno); 0300 } 0301 tar = 0; 0302 } 0303 }; 0304 0305 class StoFileWrite { 0306 TarFileWrite tar; 0307 std::string name; 0308 TarPath imagePath; 0309 TarPath soundPath; 0310 std::set<std::string> alreadyPacked; 0311 public: 0312 StoFileWrite(const char* stoFilename) 0313 : tar(stoFilename), 0314 name(getProjectName(stoFilename)), 0315 imagePath(name, "/images/"), 0316 soundPath(name, "/sounds/") { 0317 } 0318 void addProjectFile(const char* projectFilename) { 0319 std::string projectFileName = name; 0320 projectFileName.append("/"); 0321 projectFileName.append(name); 0322 projectFileName.append(".dat"); 0323 tar.add(projectFilename, projectFileName.c_str()); 0324 } 0325 void addImage(const char* realName, const char* baseName) { 0326 std::string bn(baseName); 0327 if (alreadyPacked.insert(bn).second) 0328 tar.add(realName, imagePath.getPath(baseName)); 0329 } 0330 void addSound(const char* realName, const char* baseName) { 0331 std::string bn(baseName); 0332 if (alreadyPacked.insert(bn).second) 0333 tar.add(realName, soundPath.getPath(baseName)); 0334 } 0335 void finish() { 0336 tar.eof(); 0337 } 0338 }; 0339 0340 void writeSto(const char* filename, const char* xmlProjectFile, 0341 const AnimationImpl& anim) { 0342 StoFileWrite sto(filename); 0343 sto.addProjectFile(xmlProjectFile); 0344 int sceneCount = anim.sceneCount(); 0345 for (int i = 0; i != sceneCount; ++i) { 0346 const Scene* scene = anim.getScene(i); 0347 int frameCount = scene->getSize(); 0348 for (int j = 0; j != frameCount; ++j) { 0349 const Frame* frame = scene->getFrame(j); 0350 sto.addImage(frame->getImagePath(), frame->getBasename()); 0351 int soundCount = frame->soundCount(); 0352 for (int k = 0; k != soundCount; ++k) { 0353 const Sound* sound = frame->getSound(k); 0354 sto.addSound(sound->getSoundPath(), sound->getBasename()); 0355 } 0356 } 0357 } 0358 sto.finish(); 0359 } 0360 0361 /** 0362 * Save the current file, putting the current state into the new model file. 0363 * The caller is responsible for clearing the command log and then renaming 0364 * the new model file to be the current model file. 0365 * @param filename The filename to save. 0366 * @param anim The animation to save. 0367 * @param frontend The UI for reporting progress and errors. 0368 */ 0369 void ProjectSerializer::save(const char *filename, 0370 const AnimationImpl& anim, Frontend *frontend) { 0371 assert(filename != NULL); 0372 assert(frontend != NULL); 0373 0374 setProjectFile(filename); 0375 0376 xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); 0377 xmlCreateIntSubset(doc, BAD_CAST "smil", 0378 BAD_CAST "-//W3C//DTD SMIL 2.0//EN", 0379 BAD_CAST "http://www.w3.org/2001/SMIL20/SMIL20.dtd"); 0380 0381 xmlNodePtr rootNode = xmlNewNode(NULL, BAD_CAST "smil"); 0382 xmlNewProp(rootNode, BAD_CAST "xmlns", 0383 BAD_CAST "http://www.w3.org/2001/SMIL20/Language"); 0384 xmlNewProp(rootNode, BAD_CAST "xml:lang", BAD_CAST "en"); 0385 xmlNewProp(rootNode, BAD_CAST "title", BAD_CAST "Stopmotion"); 0386 xmlDocSetRootElement(doc, rootNode); 0387 0388 setAttributes(rootNode, anim, frontend); 0389 0390 WorkspaceFile newDat(WorkspaceFile::newModelFile); 0391 saveDOMToFile(doc, newDat.path()); 0392 0393 xmlFreeDoc(doc); 0394 xmlCleanupParser(); 0395 0396 // Write out new.dat file. The recovery system will ignore it until... 0397 writeSto(projectFile, newDat.path(), anim); 0398 int numElem = anim.sceneCount(); 0399 frontend->updateProgress(numElem); 0400 frontend->hideProgress(); 0401 } 0402 0403 void ProjectSerializer::setAttributes(xmlNodePtr rootNode, 0404 const AnimationImpl& anim, Frontend *frontend) { 0405 xmlNodePtr node = NULL; 0406 const Frame *frame = NULL; 0407 const Sound *sound = NULL; 0408 0409 xmlNodePtr scenes = xmlNewChild(rootNode, NULL, BAD_CAST "scenes", NULL); 0410 0411 int numScenes = anim.sceneCount(); 0412 frontend->showProgress(Frontend::savingScenesToDisk, numScenes); 0413 for (int i = 0; i < numScenes; ++i) { 0414 frontend->updateProgress(i); 0415 // Scenes 0416 node = xmlNewChild(scenes, NULL, BAD_CAST "seq", NULL); 0417 0418 // Images 0419 xmlNodePtr images = xmlNewChild(node, NULL, BAD_CAST "images", NULL); 0420 const Scene* scene = anim.getScene(i); 0421 int numFrames = scene->getSize(); 0422 for (int j = 0; j < numFrames; ++j) { 0423 frame = scene->getFrame(j); 0424 const char *filename = frame->getBasename(); 0425 node = xmlNewChild(images, NULL, BAD_CAST "img", NULL); 0426 xmlNewProp(node, BAD_CAST "src", BAD_CAST filename); 0427 0428 // Sounds 0429 int numSounds = frame->soundCount(); 0430 if (numSounds > 0) { 0431 xmlNodePtr sounds = xmlNewChild(node, NULL, BAD_CAST "sounds", 0432 NULL); 0433 for (int k = 0; k < numSounds; ++k) { 0434 sound = frame->getSound(k); 0435 filename = sound->getBasename(); 0436 node = xmlNewChild(sounds, NULL, BAD_CAST "audio", NULL); 0437 xmlNewProp(node, BAD_CAST "src", BAD_CAST filename); 0438 xmlNewProp(node, BAD_CAST "alt", 0439 BAD_CAST frame->getSoundName(i)); 0440 } 0441 } 0442 } 0443 } 0444 } 0445 0446 void ProjectSerializer::getAttributes(xmlNodePtr node, 0447 std::vector<Scene*>& sVect) { 0448 xmlNodePtr currNode = NULL; 0449 for (currNode = node; currNode; currNode = currNode->next) { 0450 if (currNode->type == XML_ELEMENT_NODE) { 0451 char *nodeName = (char*) currNode->name; 0452 // We either have a image node or a sound node 0453 if (strcmp(nodeName, "img") == 0 0454 || strcmp(nodeName, "audio") == 0) { 0455 char *filename = (char*) xmlGetProp(currNode, BAD_CAST "src"); 0456 if (filename != NULL) { 0457 // The node is a image node 0458 if (strcmp(nodeName, "img") == 0) { 0459 WorkspaceFile wf(filename); 0460 Frame *f = new Frame(wf); 0461 Scene *s = sVect.back(); 0462 s->addSavedFrame(f); 0463 } 0464 // The node is a sound node 0465 else { 0466 Scene *s = sVect.back(); 0467 int frameNum = s->getSize() - 1; 0468 WorkspaceFile wf(filename); 0469 int soundNum = s->soundCount(frameNum); 0470 s->newSound(frameNum, wf); 0471 char *soundName = (char*) xmlGetProp(currNode, 0472 BAD_CAST "alt"); 0473 if (soundName != NULL) { 0474 s->setSoundName(frameNum, soundNum, soundName); 0475 xmlFree((xmlChar*) soundName); 0476 } 0477 } 0478 xmlFree((xmlChar*) filename); 0479 } 0480 } 0481 // The node is a scene node 0482 else if (strcmp((char*) currNode->name, "seq") == 0) { 0483 Scene *s = new Scene(); 0484 sVect.push_back(s); 0485 } 0486 } 0487 getAttributes(currNode->children, sVect); 0488 } 0489 } 0490 0491 void ProjectSerializer::saveDOMToFile(xmlDocPtr doc, const char* filename) { 0492 int ret = xmlSaveFormatFile(filename, doc, 1); 0493 if (ret == -1) { 0494 throw ProjectFileCreationException("xmlSaveFormatFile", errno); 0495 } 0496 } 0497 0498 const char* ProjectSerializer::getProjectFile() { 0499 return projectFile; 0500 } 0501 0502 void ProjectSerializer::resetProjectFile(const char* filename) { 0503 char* copy = 0; 0504 if (filename) { 0505 size_t len = strlen(filename); 0506 copy = new char[len + 1]; 0507 strncpy(copy, filename, len + 1); 0508 } 0509 delete[] projectFile; 0510 projectFile = copy; 0511 } 0512 0513 void ProjectSerializer::resetProjectFile() { 0514 resetProjectFile(0); 0515 }