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@98: #include "System.h" Chris@81: Chris@81: #include Chris@81: #include Chris@81: #include Chris@81: Chris@81: #include Chris@81: #include Chris@81: Chris@81: TempDirectory * Chris@81: TempDirectory::m_instance = new TempDirectory; Chris@81: Chris@81: TempDirectory * Chris@81: TempDirectory::instance() Chris@81: { Chris@81: return m_instance; Chris@81: } Chris@81: Chris@81: TempDirectory::DirectoryCreationFailed::DirectoryCreationFailed(QString directory) throw() : Chris@81: m_directory(directory) Chris@81: { Chris@81: std::cerr << "ERROR: Directory creation failed for directory: " Chris@81: << directory.toLocal8Bit().data() << std::endl; Chris@81: } Chris@81: Chris@81: const char * Chris@81: TempDirectory::DirectoryCreationFailed::what() const throw() Chris@81: { Chris@81: return QString("Directory creation failed for \"%1\"") Chris@81: .arg(m_directory).toLocal8Bit().data(); 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@81: std::cerr << "TempDirectory::~TempDirectory" << std::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@81: TempDirectory::getPath() Chris@81: { Chris@81: QMutexLocker locker(&m_mutex); Chris@81: Chris@81: if (m_tmpdir != "") return m_tmpdir; Chris@81: Chris@126: QString svDirBase = ".sv1"; Chris@98: QString svDir = QDir::home().filePath(svDirBase); Chris@98: if (!QFileInfo(svDir).exists()) { Chris@98: if (!QDir::home().mkdir(svDirBase)) { Chris@98: throw DirectoryCreationFailed(QString("%1 directory in $HOME") Chris@98: .arg(svDirBase)); Chris@98: } Chris@98: } else if (!QFileInfo(svDir).isDir()) { Chris@98: throw DirectoryCreationFailed(QString("$HOME/%1 is not a directory") Chris@98: .arg(svDirBase)); Chris@98: } Chris@98: Chris@98: cleanupAbandonedDirectories(svDir); Chris@98: Chris@98: return createTempDirectoryIn(svDir); 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@81: unsigned int r = 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@81: std::cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to unlink file \"" Chris@81: << fi.absoluteFilePath().toStdString() << "\"" Chris@81: << std::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@81: std::cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to cd to parent directory of " Chris@81: << tmpdir.toStdString() << std::endl; Chris@81: return; Chris@81: } Chris@81: if (!dir.rmdir(dirname)) { Chris@81: std::cerr << "WARNING: TempDirectory::cleanup: " Chris@81: << "Failed to remove directory " Chris@81: << dirname.toStdString() << std::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@98: QDir subdir(dir.filePath(dir[i]), "*.pid", QDir::Name, QDir::Files); 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@98: std::cerr << "INFO: Found abandoned temporary directory from " Chris@98: << "an old Sonic Visualiser process\n(pid=" << pid Chris@98: << ", directory=\"" Chris@98: << dir.filePath(dir[i]).toStdString() Chris@98: << "\"). Removing it..." << std::endl; Chris@98: cleanupDirectory(dir.filePath(dir[i])); Chris@98: std::cerr << "...done." << std::endl; Chris@98: break; Chris@98: } Chris@98: } Chris@98: } Chris@98: } Chris@98: Chris@98: Chris@98: