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@130: #include "Exceptions.h"
Chris@81: 
Chris@81: #include <QDir>
Chris@81: #include <QFile>
Chris@81: #include <QMutexLocker>
Chris@81: 
Chris@81: #include <iostream>
Chris@81: #include <cassert>
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::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: