Chris@81: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@81: Chris@81: /* Chris@81: Sonic Visualiser Chris@81: An audio file viewer and annotation editor. Chris@81: Centre for Digital Music, Queen Mary, University of London. Chris@81: This file copyright 2006 Chris Cannam. Chris@81: Chris@81: This program is free software; you can redistribute it and/or Chris@81: modify it under the terms of the GNU General Public License as Chris@81: published by the Free Software Foundation; either version 2 of the Chris@81: License, or (at your option) any later version. See the file Chris@81: COPYING included with this distribution for more information. Chris@81: */ Chris@81: Chris@81: #include "TempDirectory.h" Chris@680: #include "ResourceFinder.h" Chris@150: #include "system/System.h" Chris@130: #include "Exceptions.h" Chris@81: Chris@81: #include Chris@81: #include Chris@81: #include Chris@170: #include Chris@81: Chris@81: #include Chris@81: #include Chris@1219: Chris@658: #include Chris@81: Chris@81: TempDirectory * Chris@81: TempDirectory::m_instance = new TempDirectory; Chris@81: Chris@81: TempDirectory * Chris@145: TempDirectory::getInstance() Chris@81: { Chris@81: return m_instance; Chris@81: } Chris@81: Chris@81: TempDirectory::TempDirectory() : Chris@81: m_tmpdir("") Chris@81: { Chris@81: } Chris@81: Chris@81: TempDirectory::~TempDirectory() Chris@81: { Chris@690: SVDEBUG << "TempDirectory::~TempDirectory" << endl; Chris@81: Chris@81: cleanup(); Chris@81: } Chris@81: Chris@81: void Chris@81: TempDirectory::cleanup() Chris@81: { Chris@81: cleanupDirectory(""); Chris@81: } Chris@81: Chris@81: QString Chris@460: TempDirectory::getContainingPath() Chris@81: { Chris@81: QMutexLocker locker(&m_mutex); Chris@680: Chris@170: QSettings settings; Chris@170: settings.beginGroup("TempDirectory"); Chris@170: QString svDirParent = settings.value("create-in", "$HOME").toString(); Chris@170: settings.endGroup(); Chris@170: Chris@680: QString svDir = ResourceFinder().getUserResourcePrefix(); Chris@680: if (svDirParent != "$HOME") { Chris@680: //!!! iffy Chris@680: svDir.replace(QDir::home().absolutePath(), svDirParent); Chris@520: } Chris@170: Chris@98: if (!QFileInfo(svDir).exists()) { Chris@733: if (!QDir(svDirParent).mkpath(svDir)) { Chris@170: throw DirectoryCreationFailed(QString("%1 directory in %2") Chris@680: .arg(svDir).arg(svDirParent)); Chris@98: } Chris@98: } else if (!QFileInfo(svDir).isDir()) { Chris@680: throw DirectoryCreationFailed(QString("%1 is not a directory") Chris@680: .arg(svDir)); Chris@98: } Chris@98: Chris@98: cleanupAbandonedDirectories(svDir); Chris@98: Chris@460: return svDir; Chris@460: } Chris@460: Chris@460: QString Chris@460: TempDirectory::getPath() Chris@460: { Chris@460: if (m_tmpdir != "") return m_tmpdir; Chris@460: Chris@460: return createTempDirectoryIn(getContainingPath()); Chris@98: } Chris@98: Chris@98: QString Chris@98: TempDirectory::createTempDirectoryIn(QString dir) Chris@98: { Chris@98: // Entered with mutex held. Chris@98: Chris@98: QDir tempDirBase(dir); Chris@81: Chris@81: // Generate a temporary directory. Qt4.1 doesn't seem to be able Chris@81: // to do this for us, and mkdtemp is not standard. This method is Chris@81: // based on the way glibc does mkdtemp. Chris@81: Chris@81: static QString chars = Chris@81: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Chris@81: Chris@81: QString suffix; Chris@81: int padlen = 6, attempts = 100; Chris@1038: unsigned int r = (unsigned int)(time(0) ^ getpid()); Chris@81: Chris@81: for (int i = 0; i < padlen; ++i) { Chris@81: suffix += "X"; Chris@81: } Chris@81: Chris@81: for (int j = 0; j < attempts; ++j) { Chris@81: Chris@81: unsigned int v = r; Chris@81: Chris@81: for (int i = 0; i < padlen; ++i) { Chris@81: suffix[i] = chars[v % 62]; Chris@81: v /= 62; Chris@81: } Chris@81: Chris@81: QString candidate = QString("sv_%1").arg(suffix); Chris@81: Chris@86: if (tempDirBase.mkpath(candidate)) { Chris@86: m_tmpdir = tempDirBase.filePath(candidate); Chris@81: break; Chris@81: } Chris@81: Chris@81: r = r + 7777; Chris@81: } Chris@81: Chris@81: if (m_tmpdir == "") { Chris@81: throw DirectoryCreationFailed(QString("temporary subdirectory in %1") Chris@86: .arg(tempDirBase.canonicalPath())); Chris@81: } Chris@81: Chris@98: QString pidpath = QDir(m_tmpdir).filePath(QString("%1.pid").arg(getpid())); Chris@98: QFile pidfile(pidpath); Chris@98: Chris@98: if (!pidfile.open(QIODevice::WriteOnly)) { Chris@98: throw DirectoryCreationFailed(QString("pid file creation in %1") Chris@98: .arg(m_tmpdir)); Chris@98: } else { Chris@98: pidfile.close(); Chris@98: } Chris@98: Chris@81: return m_tmpdir; Chris@81: } Chris@81: Chris@81: QString Chris@81: TempDirectory::getSubDirectoryPath(QString subdir) Chris@81: { Chris@81: QString tmpdirpath = getPath(); Chris@81: Chris@81: QMutexLocker locker(&m_mutex); Chris@81: Chris@81: QDir tmpdir(tmpdirpath); Chris@81: QFileInfo fi(tmpdir.filePath(subdir)); Chris@81: Chris@81: if (!fi.exists()) { Chris@81: if (!tmpdir.mkdir(subdir)) { Chris@81: throw DirectoryCreationFailed(fi.filePath()); Chris@81: } else { Chris@81: return fi.filePath(); Chris@81: } Chris@81: } else if (fi.isDir()) { Chris@81: return fi.filePath(); Chris@81: } else { Chris@81: throw DirectoryCreationFailed(fi.filePath()); Chris@81: } Chris@81: } Chris@81: Chris@81: void Chris@81: TempDirectory::cleanupDirectory(QString tmpdir) Chris@81: { Chris@81: bool isRoot = false; Chris@81: Chris@81: if (tmpdir == "") { Chris@81: Chris@81: m_mutex.lock(); Chris@81: Chris@81: isRoot = true; Chris@81: tmpdir = m_tmpdir; Chris@81: Chris@81: if (tmpdir == "") { Chris@81: m_mutex.unlock(); Chris@81: return; Chris@81: } Chris@81: } Chris@81: Chris@81: QDir dir(tmpdir); Chris@83: dir.setFilter(QDir::Dirs | QDir::Files); Chris@81: Chris@81: for (unsigned int i = 0; i < dir.count(); ++i) { Chris@81: Chris@83: if (dir[i] == "." || dir[i] == "..") continue; Chris@81: QFileInfo fi(dir.filePath(dir[i])); Chris@81: Chris@81: if (fi.isDir()) { Chris@81: cleanupDirectory(fi.absoluteFilePath()); Chris@81: } else { Chris@81: if (!QFile(fi.absoluteFilePath()).remove()) { Chris@843: cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to unlink file \"" Chris@686: << fi.absoluteFilePath() << "\"" Chris@843: << endl; Chris@81: } Chris@81: } Chris@81: } Chris@81: Chris@81: QString dirname = dir.dirName(); Chris@81: if (dirname != "") { Chris@81: if (!dir.cdUp()) { Chris@843: cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to cd to parent directory of " Chris@843: << tmpdir << endl; Chris@81: return; Chris@81: } Chris@81: if (!dir.rmdir(dirname)) { Chris@843: cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to remove directory " Chris@843: << dirname << endl; Chris@81: } Chris@81: } Chris@81: Chris@81: if (isRoot) { Chris@81: m_tmpdir = ""; Chris@81: m_mutex.unlock(); Chris@81: } Chris@81: } Chris@98: Chris@98: void Chris@98: TempDirectory::cleanupAbandonedDirectories(QString svDir) Chris@98: { Chris@98: QDir dir(svDir, "sv_*", QDir::Name, QDir::Dirs); Chris@98: Chris@98: for (unsigned int i = 0; i < dir.count(); ++i) { Chris@98: Chris@621: QString dirpath = dir.filePath(dir[i]); Chris@621: Chris@621: QDir subdir(dirpath, "*.pid", QDir::Name, QDir::Files); Chris@621: Chris@621: if (subdir.count() == 0) { Chris@843: cerr << "INFO: Found temporary directory with no .pid file in it!\n(directory=\"" Chris@843: << dirpath << "\"). Removing it..." << endl; Chris@621: cleanupDirectory(dirpath); Chris@843: cerr << "...done." << endl; Chris@621: continue; Chris@621: } Chris@98: Chris@98: for (unsigned int j = 0; j < subdir.count(); ++j) { Chris@98: Chris@98: bool ok = false; Chris@98: int pid = QFileInfo(subdir[j]).baseName().toInt(&ok); Chris@98: if (!ok) continue; Chris@98: Chris@98: if (GetProcessStatus(pid) == ProcessNotRunning) { Chris@843: cerr << "INFO: Found abandoned temporary directory from " Chris@327: << "a previous, defunct process\n(pid=" << pid Chris@98: << ", directory=\"" Chris@844: << dirpath Chris@843: << "\"). Removing it..." << endl; Chris@621: cleanupDirectory(dirpath); Chris@843: cerr << "...done." << endl; Chris@98: break; Chris@98: } Chris@98: } Chris@98: } Chris@98: } Chris@98: Chris@98: Chris@98: