/* $Id$ */ /* * Cantus Tag Editor * Copyright © 2002-2004 by Samuel Abels * Copyright © 2007 by Tim Huetz * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see **/ #ifdef HAVE_CONFIG_H # include #endif #include //#define _DEBUG_ /****************************************************************************** * Constructor/Destructor ******************************************************************************/ /* Constructor. */ FileInfoManager::FileInfoManager(void) { plugins = NULL; stopthreads = FALSE; queued_read = 0; queued_write = 0; processed_read = 0; processed_write = 0; reader = Glib::Thread::create( sigc::mem_fun(*this, &FileInfoManager::reader_func), true); writer = Glib::Thread::create( sigc::mem_fun(*this, &FileInfoManager::writer_func), true); signal_dispatch.connect( sigc::mem_fun(*this, &FileInfoManager::signal_dispatcher)); } /* Destructor. */ FileInfoManager::~FileInfoManager(void) { #ifdef _DEBUG_ printf("FileInfoManager::~FileInfoManager(): Terminating all threads...\n"); #endif mutex.lock(); stopthreads = TRUE; readcond.signal(); writecond.signal(); mutex.unlock(); reader->join(); writer->join(); #ifdef _DEBUG_ printf("FileInfoManager::~FileInfoManager(): All threads terminated.\n"); #endif this->unregister_plugins(); } /****************************************************************************** * Public ******************************************************************************/ /* Stores a copy of the pluginlist in the object. * This is required because the object invokes the FileInfo read function, * which needs a plugin as an argument. */ void FileInfoManager::register_plugins(std::list *pluginlist) { #ifdef _DEBUG_ printf("FileInfoManager::register_plugins(): Called.\n"); #endif this->unregister_plugins(); mutex.lock(); plugins = pluginlist; mutex.unlock(); } /* Replaces the list of files that still need to be read. (read queue) * (filenames == NULL is allowed). */ void FileInfoManager::readqueue_set(GList *filenames) { readqueue_clear(); mutex.lock(); queued_read = 0; while (filenames) { queued_read++; readqueue.push_back((const gchar*)filenames->data); //printf("FileInfoManager::readqueue_set(): Added %s\n", readqueue.back()); filenames = filenames->next; } processed_read = 0; readcond.signal(); mutex.unlock(); } /* Clear the read queue. (=Abort read). */ void FileInfoManager::readqueue_clear(void) { mutex.lock(); readqueue.clear(); queued_read = 0; processed_read = 0; mutex.unlock(); } /* Appends stuff to the list of files that still need to be * written. (write queue) */ void FileInfoManager::writequeue_append(GList *filenames) { #ifdef _DEBUG_ printf("FileInfoManager::writequeue_append(): Called.\n"); #endif struct stat filestat; mutex.lock(); queued_write = 0; while (filenames) { // Append the inode number of each file to the queue. const gchar *filename = (const gchar*)filenames->data; #ifdef _DEBUG_ printf("FileInfoManager::writequeue_append(): %s.\n", filename); #endif if (stat(filename, &filestat) == -1) g_warning("FileInfoManager::writequeue_append(): Can't stat %s\n", filename); else { if (!cache[filestat.st_ino]) cache[filestat.st_ino] = new FileInfo(filename); queued_write++; writequeue.push_back(filestat.st_ino); } #ifdef _DEBUG_ printf("FileInfoManager::writequeue_append(): %s done.\n", filename); #endif filenames = filenames->next; } processed_write = 0; writecond.signal(); mutex.unlock(); } /* Clear the write queue. (=Abort write) */ void FileInfoManager::writequeue_clear(void) { mutex.lock(); writequeue.clear(); queued_write = 0; processed_write = 0; mutex.unlock(); } /* This function locks the fileinfo object with the given name and returns * the hash containing all file data from the fileinfo object. * The FileInfo object will remain locked, so make sure to unlock!! */ CantusHash *FileInfoManager::get_info_locked(const gchar *filename) { mutex.lock(); FileInfo *info = this->read(filename); info->lock(); CantusHash *hash = info->get_edited_hash(); mutex.unlock(); return hash; } /* Unlocks the FileInfo object with the given name. */ void FileInfoManager::unlock(const gchar *filename) { mutex.lock(); glong inode = inodemap[filename]; FileInfo *info = cache[inode]; g_assert(info != NULL); info->unlock(); mutex.unlock(); } /* Returns the progress in percent (float). */ float FileInfoManager::get_progress(void) { int queued = queued_read + queued_write; int processed = processed_read + processed_write; return queued ? (1.0 / queued) * processed : 0; } /* Returns a human readable status (e.g. "Reading /my/file.ogg", or "Ready."). */ std::string FileInfoManager::get_status(void) { char *cstatus = NULL; std::string status; if (reading != "") { cstatus = g_strdup_printf(_("Reading %s"), reading.c_str()); status = cstatus; g_free(cstatus); return status; } if (writing != "") { cstatus = g_strdup_printf(_("Writing %s"), writing.c_str()); status = cstatus; g_free(cstatus); return status; } return _("Ready."); } /****************************************************************************** * Private ******************************************************************************/ /* Unregisters/unrefs all plugins. */ void FileInfoManager::unregister_plugins(void) { #ifdef _DEBUG_ printf("FileInfoManager::unregister_plugins(): Called.\n"); #endif mutex.lock(); if (!plugins) { mutex.unlock(); return; } // Unref plugins. std::list::iterator iter; for (iter = plugins->begin(); iter != plugins->end(); iter++) (*iter)->unref(); delete plugins; plugins = NULL; mutex.unlock(); } /* A worker thread doing all read operations. */ void FileInfoManager::reader_func(void) { while (1) { mutex.lock(); while (readqueue.empty() && !stopthreads) readcond.wait(mutex); // Wait until there is something in the queue. #ifdef _DEBUG_ printf("FileInfoManager::reader_func(): Condition triggered.\n"); #endif if (stopthreads) { // Program shutdown (or something)? -> Return. mutex.unlock(); #ifdef _DEBUG_ printf("FileInfoManager::reader_func(): Thread terminated.\n"); #endif return; } while (!readqueue.empty()) { // Ok, got something. Walk through the queue. const gchar *filename = readqueue.front(); readqueue.pop_front(); this->read(filename); // Read that file. processed_read++; if (stopthreads) { // Program shutdown (or something)? -> Return. mutex.unlock(); #ifdef _DEBUG_ printf("FileInfoManager::reader_func(): Thread terminated.\n"); #endif return; } mutex.unlock(); Glib::usleep(10); // Give some grace time for other threads. mutex.lock(); } processed_read = 0; queued_read = 0; reading = ""; FileInfoManagerEvent *event = new FileInfoManagerEvent; event->first = READQUEUE_FINISHED_EVENT; eventqueue.push_back(event); signal_dispatch(); mutex.unlock(); } } /* A worker thread doing all write operations. */ void FileInfoManager::writer_func(void) { while (1) { mutex.lock(); while (writequeue.empty() && !stopthreads) writecond.wait(mutex); // Wait until there is something in the queue. #ifdef _DEBUG_ printf("FileInfoManager::writer_func(): Condition triggered.\n"); #endif if (stopthreads) { // Program shutdown (or something)? -> Return. mutex.unlock(); #ifdef _DEBUG_ printf("FileInfoManager::writer_func(): Thread terminated.\n"); #endif return; } while (!writequeue.empty()) { // Ok, got something. Walk through the queue. glong inode = writequeue.front(); writequeue.pop_front(); this->write(inode); // Write that file. processed_write++; if (stopthreads) { // Program shutdown (or something)? -> Return. mutex.unlock(); #ifdef _DEBUG_ printf("FileInfoManager::writer_func(): Thread terminated.\n"); #endif return; } mutex.unlock(); Glib::usleep(10); // Give some grace time for other threads. mutex.lock(); } processed_write = 0; queued_write = 0; writing = ""; FileInfoManagerEvent *event = new FileInfoManagerEvent; event->first = WRITEQUEUE_FINISHED_EVENT; eventqueue.push_back(event); signal_dispatch(); mutex.unlock(); } } /* Receives one fileinfo object from the cache. If the item is not in the * cache yet, it will try to read it using all apropriate plugins. */ FileInfo *FileInfoManager::read(const gchar *filename) { #ifdef _DEBUG_ printf("FileInfoManager::read(): Called. (%s)\n", filename); #endif g_assert(filename != NULL); reading = filename; glong inode = inodemap[filename]; // Try to get the file from the cache. FileInfo *info = cache[inode]; if (info) { #ifdef _DEBUG_ printf("FileInfoManager::read(): Found %s cached.\n", filename); #endif reading = ""; FileInfoManagerEvent *event = new FileInfoManagerEvent(FILE_READ_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); return info; } #ifdef _DEBUG_ printf("FileInfoManager::read(): %s NOT cached.\n", filename); #endif info = new FileInfo(filename); // Not cached, so create a new one. inode = info->get_inode(); inodemap[filename] = inode; if (plugins) { // Read using every plugin. std::list::iterator iter; for (iter = plugins->begin(); iter != plugins->end(); iter++) { CantusPlugin* plugin = *iter; ReadFunc readfunc = NULL; if (!plugin->isHandling(filename)) continue; if (!(readfunc = plugin->getReadFunction())) continue; #ifdef _DEBUG_ printf("FileInfoManager::read(): Passing %s to %s.\n", filename, plugin->getName()); #endif if (info->read(readfunc) != 0) { // Send error signals here. FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_READ_FAILED_EVENT, (void*)info); eventqueue.push_back(event); } } } reading = ""; cache[inode] = info; // Push this to the cache. FileInfoManagerEvent *event = new FileInfoManagerEvent(FILE_READ_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); return info; } /* Receives one fileinfo object from the cache. If the item is not in the * cache yet, read it. Then try to write it using all apropriate plugins. */ void FileInfoManager::write(gint inode) { #ifdef _DEBUG_ printf("FileInfoManager::write(): Called. (inode %i)\n", inode); #endif FileInfo *info = cache[inode]; // Get the file. g_return_if_fail(info != NULL); info->lock(); writing = info->get_filename(); info->unlock(); if (!try_rename(info)) // Rename the file, if requested. return; if (plugins) { // Write using every plugin. std::list::iterator iter; for (iter = plugins->begin(); iter != plugins->end(); iter++) { CantusPlugin* plugin = *iter; WriteFunc writefunc = NULL; if (!plugin->isHandling(info->get_filename())) continue; if (!(writefunc = plugin->getWriteFunction())) return; if (info->write(writefunc) != 0) { // Send error signals here. FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_WRITE_FAILED_EVENT, (void*)info); eventqueue.push_back(event); } } } FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_WRITTEN_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); } /* Checks whether the filename has been edited and renames the file. * Returns TRUE on success, FALSE otherwise. */ gboolean FileInfoManager::try_rename(FileInfo *info) { #ifdef _DEBUG_ printf("FileInfoManager::try_rename(): Called.\n"); #endif g_assert(info != NULL); info->lock(); const gchar *oldname = info->get_filename(); const gchar *newname = info->get_edited_filename(); #ifdef _DEBUG_ printf("FileInfoManager::try_rename(): Old name: %s\n", oldname); printf("FileInfoManager::try_rename(): New name: %s\n", newname); #endif g_assert(oldname != NULL && newname != NULL); if (strcmp(oldname, newname) == 0) { // Check whether the name changed. info->unlock(); return TRUE; } struct stat filestat; // Make sure not to overwrite something. if (stat(newname, &filestat) != -1) { info->unlock(); FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_RENAME_FAILED_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); return TRUE; } if (rename(oldname, newname) < 0) { // Rename & fetch errors. info->unlock(); FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_RENAME_FAILED_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); return FALSE; } info->unlock(); info->commit(); FileInfoManagerEvent *event = new FileInfoManagerEvent( FILE_RENAME_FINISHED_EVENT, (void*)info); eventqueue.push_back(event); signal_dispatch(); // Ending up here, the file has successfully been renamed. Update the inode // map. glong inode = inodemap[oldname]; inodemap.erase(oldname); inodemap[newname] = inode; return TRUE; } /* This dispatcher is called via an asynchronous pipe. * Once called, it emits all queued events via a synchronous SigC signal. */ void FileInfoManager::signal_dispatcher(void) { #ifdef _DEBUG_ printf("FileInfoManager::signal_dispatcher(): Called.\n"); #endif mutex.lock(); //FIXME!!! The event data could be outdated! // The currently can not happen though, because once read, a fileinfo object // is never destroyed. while (!eventqueue.empty()) { FileInfoManagerEvent *event = (FileInfoManagerEvent*)eventqueue.front(); mutex.unlock(); switch (event->first) { case FILE_READ_EVENT: signal_file_read_finished.emit((FileInfo*)event->second); break; case FILE_READ_FAILED_EVENT: signal_file_read_failed.emit((FileInfo*)event->second); break; case FILE_WRITTEN_EVENT: signal_file_write_finished.emit((FileInfo*)event->second); break; case FILE_WRITE_FAILED_EVENT: signal_file_write_failed.emit((FileInfo*)event->second); break; case FILE_RENAME_FINISHED_EVENT: signal_file_rename_finished.emit((FileInfo*)event->second); break; case FILE_RENAME_FAILED_EVENT: signal_file_rename_failed.emit((FileInfo*)event->second); break; case WRITEQUEUE_FINISHED_EVENT: signal_queue_write_finished.emit(); break; case READQUEUE_FINISHED_EVENT: signal_queue_read_finished.emit(); break; } delete event; mutex.lock(); eventqueue.pop_front(); } mutex.unlock(); return; }