File indexing completed on 2024-05-12 16:23:26

0001 /***************************************************************************
0002  *   Copyright (C) 2013-2017 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 "workspacefile.h"
0022 
0023 #include "src/technical/util.h"
0024 #include "src/foundation/uiexception.h"
0025 
0026 #include <stdio.h>
0027 #include <stdlib.h>
0028 #include <unistd.h>
0029 #include <string.h>
0030 #include <sstream>
0031 #include <string>
0032 
0033 namespace {
0034 
0035 uint32_t fileNum;
0036 uint32_t soundNumber;
0037 
0038 uint32_t nextFileNumber() {
0039     return ++fileNum;
0040 }
0041 
0042 enum WorkspacePath { workspacePath };
0043 enum WorkspacePathFrames { workspacePathFrames };
0044 
0045 std::ostream& operator<<(std::ostream& s, WorkspacePath) {
0046     s << getenv("HOME");
0047     s << "/.stopmotion/";
0048     return s;
0049 }
0050 
0051 std::ostream& operator<<(std::ostream& s, WorkspacePathFrames) {
0052     s << getenv("HOME");
0053     s << "/.stopmotion/frames/";
0054     return s;
0055 }
0056 
0057 /**
0058  * Gets a fresh filename in the workspace that doesn't clash with any other
0059  * file.
0060  * @param [out] path Will get a @code{.cpp} new char[] @endcode containing
0061  * the full path to the new file.
0062  * @param [out] namePart Will get a pointer into @a path that points to the
0063  * basename part of the path.
0064  * @param [in] extension Characters that must come at the end of the filename,
0065  * for example ".jpg".
0066  */
0067 void getFreshFilename(char*& path, const char*& namePart,
0068         const char* extension) {
0069     std::string pathOut;
0070     std::stringstream p;
0071     int indexOfName = 0;
0072     int size = 0;
0073     do {
0074         p.str("");
0075         p << workspacePathFrames;
0076         indexOfName = p.str().length();
0077         p.fill('0');
0078         p.width(8);
0079         p << nextFileNumber();
0080         if (extension)
0081             p << extension;
0082         pathOut = p.str();
0083         size = pathOut.length() + 1;
0084         // keep going until we find a filename that doesn't already exist.
0085     } while (0 == access(pathOut.c_str(), F_OK));
0086     path = new char[size];
0087     strncpy(path, pathOut.c_str(), size);
0088     namePart = path + indexOfName;
0089 }
0090 
0091 char upper(char c) {
0092     return 'a' <= c && c <= 'z'? c - ('a' - 'A') : c;
0093 }
0094 
0095 /** ASCII case-insensitive string compare. */
0096 bool asciiEqualI(const char* a, const char* b) {
0097     for (; *a != '\0' && *b != '\0'; ++a, ++b) {
0098         char ca = upper(*a);
0099         char cb = upper(*b);
0100         if (ca != cb)
0101             return false;
0102     }
0103     return true;
0104 }
0105 
0106 }
0107 
0108 WorkspaceFileType::~WorkspaceFileType() {
0109 }
0110 
0111 class ImageFileType : public WorkspaceFileType {
0112 public:
0113     static ImageFileType* instance;
0114     const char* preferredExtension(const char*) const {
0115         return ".jpg";
0116     }
0117     bool isType(const char* path) const {
0118         const char* extension = strrchr(path,'.');
0119         return extension &&
0120                 (asciiEqualI(extension, ".jpg") || asciiEqualI(extension, ".jpeg"));
0121     }
0122 };
0123 
0124 class SoundFileType : public WorkspaceFileType {
0125 public:
0126     static SoundFileType* instance;
0127     const char* preferredExtension(const char*) const {
0128         return ".ogg";
0129     }
0130     bool isType(const char* path) const {
0131         const char* extension = strrchr(path,'.');
0132         return extension &&
0133             (asciiEqualI(extension, ".ogg")
0134             || asciiEqualI(extension, ".test-sound"));
0135     }
0136 };
0137 
0138 ImageFileType* ImageFileType::instance;
0139 SoundFileType* SoundFileType::instance;
0140 
0141 const WorkspaceFileType& WorkspaceFileType::image() {
0142     if (!ImageFileType::instance)
0143         ImageFileType::instance = new ImageFileType();
0144     return *ImageFileType::instance;
0145 }
0146 
0147 const WorkspaceFileType& WorkspaceFileType::sound() {
0148     if (!SoundFileType::instance)
0149         SoundFileType::instance = new SoundFileType();
0150     return *SoundFileType::instance;
0151 }
0152 
0153 /**
0154  * Sets @c fullPath and namePart.
0155  * @param basename The name of the file.
0156  * @param inFrames True for a sound or image file.
0157  */
0158 void WorkspaceFile::setFilename(const char* basename, bool inFrames) {
0159     namePart = 0;
0160     delete[] fullPath;
0161     fullPath = 0;
0162     std::stringstream p;
0163     if (p.fail())
0164         throw UiException(UiException::failedToCopyFilesToWorkspace);
0165     if (inFrames) {
0166         p << workspacePathFrames;
0167     } else {
0168         p << workspacePath;
0169     }
0170     int indexOfName = p.tellp();
0171     p << basename;
0172     int size = p.tellp();
0173     if (p.fail())
0174         throw UiException(UiException::failedToCopyFilesToWorkspace);
0175     fullPath = new char[size + 1];
0176     p.read(fullPath, size);
0177     if (p.fail())
0178         throw UiException(UiException::failedToCopyFilesToWorkspace);
0179     fullPath[size] = '\0';
0180     namePart = fullPath + indexOfName;
0181 }
0182 
0183 void WorkspaceFile::ensureStopmotionDirectoriesExist(AndClear clear) {
0184     {
0185         std::stringstream pathStr;
0186         pathStr << workspacePath;
0187         std::string path = pathStr.str();
0188         Util::ensurePathExists(path.c_str());
0189     }
0190     {
0191         std::stringstream pathStr;
0192         pathStr << workspacePathFrames;
0193         std::string path = pathStr.str();
0194         Util::ensurePathExists(path.c_str());
0195         if (clear == andClear) {
0196             Util::removeDirectoryContents(path.c_str());
0197         }
0198     }
0199 }
0200 
0201 void WorkspaceFile::clear() {
0202     ensureStopmotionDirectoriesExist(andClear);
0203     WorkspaceFile nm(newModelFile);
0204     unlink(nm.path());
0205     WorkspaceFile cm(currentModelFile);
0206     unlink(cm.path());
0207     WorkspaceFile cl(commandLogFile);
0208     unlink(cl.path());
0209     fileNum = 0;
0210     soundNumber = 0;
0211 }
0212 
0213 uint32_t WorkspaceFile::getSoundNumber() {
0214     return soundNumber;
0215 }
0216 
0217 void WorkspaceFile::nextSoundNumber() {
0218     ++soundNumber;
0219 }
0220 
0221 WorkspaceFile::WorkspaceFile()
0222         : fullPath(0), namePart(0) {
0223 }
0224 
0225 WorkspaceFile& WorkspaceFile::operator=(const WorkspaceFile& other) {
0226     if (other.fullPath) {
0227         int nameStart = other.namePart - other.fullPath;
0228         size_t length = 1 + nameStart + strlen(other.namePart);
0229         char* buffer = new char[length];
0230         delete[] fullPath;
0231         fullPath = buffer;
0232         strncpy(fullPath, other.fullPath, length);
0233         namePart = fullPath + nameStart;
0234     }
0235     return *this;
0236 }
0237 
0238 WorkspaceFile::WorkspaceFile(const WorkspaceFile& t)
0239         : fullPath(0), namePart(0) {
0240     *this = t;
0241 }
0242 
0243 WorkspaceFile::WorkspaceFile(const char* name)
0244         : fullPath(0), namePart(0) {
0245     setFilename(name, true);
0246 }
0247 
0248 WorkspaceFile::WorkspaceFile(NewModelFile)
0249         : fullPath(0), namePart(0) {
0250     setFilename("new.dat");
0251 }
0252 
0253 WorkspaceFile::WorkspaceFile(CurrentModelFile)
0254         : fullPath(0), namePart(0) {
0255     setFilename("current.dat");
0256 }
0257 
0258 WorkspaceFile::WorkspaceFile(CommandLogFile)
0259         : fullPath(0), namePart(0) {
0260     setFilename("command.log");
0261 }
0262 
0263 WorkspaceFile::WorkspaceFile(CapturedImage)
0264     : fullPath(0), namePart(0) {
0265     setFilename("capturedfile.jpg");
0266 }
0267 
0268 WorkspaceFile::WorkspaceFile(PreferencesFile)
0269     : fullPath(0), namePart(0) {
0270     setFilename("preferences.xml");
0271 }
0272 
0273 WorkspaceFile::WorkspaceFile(PreferencesFileOld)
0274     : fullPath(0), namePart(0) {
0275     setFilename("preferences.xml.OLD");
0276 }
0277 
0278 WorkspaceFile::~WorkspaceFile() {
0279     delete[] fullPath;
0280 }
0281 
0282 const char* WorkspaceFile::basename() const {
0283     return namePart;
0284 }
0285 
0286 const char* WorkspaceFile::path() const {
0287     return fullPath;
0288 }
0289 
0290 void WorkspaceFile::swap(WorkspaceFile& w) {
0291     char* t = w.fullPath;
0292     w.fullPath = fullPath;
0293     fullPath = t;
0294     const char* t0 = w.namePart;
0295     w.namePart = namePart;
0296     namePart = t0;
0297 }
0298 
0299 void TemporaryWorkspaceFile::copyToWorkspace(const char* filename,
0300         const WorkspaceFileType& type) {
0301     if (!type.isType(filename)) {
0302         throw UiException(UiException::unsupportedImageType);
0303     }
0304     getFreshFilename(fullPath, namePart, type.preferredExtension(filename));
0305     toBeDeleted = false;
0306     if (!Util::copyFile(fullPath, filename)) {
0307         throw UiException(UiException::failedToCopyFilesToWorkspace);
0308     }
0309     toBeDeleted = true;
0310 }
0311 
0312 TemporaryWorkspaceFile::TemporaryWorkspaceFile(const char* filename,
0313         const WorkspaceFileType& type)
0314         : fullPath(0), namePart(0), toBeDeleted(false) {
0315     // not a totally foolproof test...
0316     if (strstr(filename, "/.stopmotion/frames/") != NULL) {
0317         // Already a workspace file; no need to copy it again
0318         int size = strlen(filename) + 1;
0319         fullPath = new char[size];
0320         strncpy(fullPath, filename, size);
0321         namePart = strrchr(fullPath,'/') + 1;
0322     } else {
0323         copyToWorkspace(filename, type);
0324     }
0325 }
0326 
0327 TemporaryWorkspaceFile::TemporaryWorkspaceFile(const char* filename,
0328         const WorkspaceFileType& type, ForceCopy)
0329         : fullPath(0), namePart(0), toBeDeleted(false) {
0330     copyToWorkspace(filename, type);
0331 }
0332 
0333 TemporaryWorkspaceFile::~TemporaryWorkspaceFile() {
0334     if (toBeDeleted) {
0335         unlink(fullPath);
0336     }
0337     delete[] fullPath;
0338     fullPath = 0;
0339     namePart = 0;
0340 }
0341 
0342 ExportDirectory::ExportDirectory() : p(0) {
0343     std::stringstream s;
0344     s << getenv("HOME");
0345     s << "/.stopmotion/export/";
0346     std::string pathOut = s.str();
0347     pathOut.c_str();  // force allocation of trailing '\0'
0348     p = new char[pathOut.length() + 1];
0349     strcpy(p, pathOut.c_str());
0350 }
0351 
0352 ExportDirectory::~ExportDirectory() {
0353     delete[] p;
0354 }
0355 
0356 void ExportDirectory::makeEmpty() {
0357     //TODO
0358 }