lbajardsilogic@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ lbajardsilogic@0: lbajardsilogic@0: /* lbajardsilogic@0: Sonic Visualiser lbajardsilogic@0: An audio file viewer and annotation editor. lbajardsilogic@0: Centre for Digital Music, Queen Mary, University of London. lbajardsilogic@0: This file copyright 2007 QMUL. lbajardsilogic@0: lbajardsilogic@0: This program is free software; you can redistribute it and/or lbajardsilogic@0: modify it under the terms of the GNU General Public License as lbajardsilogic@0: published by the Free Software Foundation; either version 2 of the lbajardsilogic@0: License, or (at your option) any later version. See the file lbajardsilogic@0: COPYING included with this distribution for more information. lbajardsilogic@0: */ lbajardsilogic@0: lbajardsilogic@0: #include "FileFinder.h" lbajardsilogic@0: #include "RemoteFile.h" lbajardsilogic@0: #include "AudioFileReaderFactory.h" lbajardsilogic@0: #include "DataFileReaderFactory.h" lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: #include lbajardsilogic@0: lbajardsilogic@0: #include lbajardsilogic@0: lbajardsilogic@0: FileFinder * lbajardsilogic@0: FileFinder::m_instance = 0; lbajardsilogic@0: lbajardsilogic@0: FileFinder::FileFinder() : lbajardsilogic@0: m_lastLocatedLocation("") lbajardsilogic@0: { lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: FileFinder::~FileFinder() lbajardsilogic@0: { lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: FileFinder * lbajardsilogic@0: FileFinder::getInstance() lbajardsilogic@0: { lbajardsilogic@0: if (m_instance == 0) { lbajardsilogic@0: m_instance = new FileFinder(); lbajardsilogic@0: } lbajardsilogic@0: return m_instance; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString lbajardsilogic@0: FileFinder::getOpenFileName(FileType type, QString fallbackLocation) lbajardsilogic@0: { lbajardsilogic@0: QString settingsKey; lbajardsilogic@0: QString lastPath = fallbackLocation; lbajardsilogic@0: lbajardsilogic@0: QString title = tr("Select file"); lbajardsilogic@0: QString filter = tr("All files (*.*)"); lbajardsilogic@0: lbajardsilogic@0: switch (type) { lbajardsilogic@0: lbajardsilogic@0: case SessionFile: lbajardsilogic@0: settingsKey = "sessionpath"; lbajardsilogic@0: title = tr("Select a session file"); lbajardsilogic@42: filter = tr("Sound Access session files (*.sv)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AudioFile: lbajardsilogic@0: settingsKey = "audiopath"; lbajardsilogic@0: title = "Select an audio file"; lbajardsilogic@0: filter = tr("Audio files (%1)\nAll files (*.*)") lbajardsilogic@0: .arg(AudioFileReaderFactory::getKnownExtensions()); lbajardsilogic@0: break; ivand_qmul@118: // Ivan Damnjanovic 09-10/2007 added video file import ivand_qmul@118: case VideoFile: ivand_qmul@118: settingsKey = "videopath"; ivand_qmul@118: title = "Select an video file"; ivand_qmul@118: filter = tr("All supported video files (*.avi *.mpg *.divx *.mov)\nAll files (*.*)") ivand_qmul@118: .arg(AudioFileReaderFactory::getKnownExtensions()); ivand_qmul@118: break; ivand_qmul@118: // ivand_qmul@118: case LayerFile: lbajardsilogic@0: settingsKey = "layerpath"; lbajardsilogic@42: filter = tr("All supported files (%1)\nSound Access Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case SessionOrAudioFile: lbajardsilogic@0: settingsKey = "lastpath"; lbajardsilogic@42: filter = tr("All supported files (*.sv %1)\nSound Access session files (*.sv)\nAudio files (%1)\nAll files (*.*)") lbajardsilogic@0: .arg(AudioFileReaderFactory::getKnownExtensions()); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case ImageFile: lbajardsilogic@0: settingsKey = "imagepath"; lbajardsilogic@0: filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AnyFile: lbajardsilogic@0: settingsKey = "lastpath"; lbajardsilogic@42: filter = tr("All supported files (*.sv %1 %2)\nSound Access session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)") lbajardsilogic@0: .arg(AudioFileReaderFactory::getKnownExtensions()) lbajardsilogic@0: .arg(DataFileReaderFactory::getKnownExtensions()); lbajardsilogic@0: break; lbajardsilogic@0: }; lbajardsilogic@0: lbajardsilogic@0: if (lastPath == "") { lbajardsilogic@0: char *home = getenv("HOME"); lbajardsilogic@0: if (home) lastPath = home; lbajardsilogic@0: else lastPath = "."; lbajardsilogic@0: } else if (QFileInfo(lastPath).isDir()) { lbajardsilogic@0: lastPath = QFileInfo(lastPath).canonicalPath(); lbajardsilogic@0: } else { lbajardsilogic@0: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QSettings settings; lbajardsilogic@0: settings.beginGroup("FileFinder"); lbajardsilogic@0: lastPath = settings.value(settingsKey, lastPath).toString(); lbajardsilogic@0: lbajardsilogic@0: QString path = ""; lbajardsilogic@0: lbajardsilogic@0: // Use our own QFileDialog just for symmetry with getSaveFileName below lbajardsilogic@0: lbajardsilogic@0: QFileDialog dialog; lbajardsilogic@0: dialog.setFilters(filter.split('\n')); lbajardsilogic@0: dialog.setWindowTitle(title); lbajardsilogic@0: dialog.setDirectory(lastPath); lbajardsilogic@0: lbajardsilogic@0: dialog.setAcceptMode(QFileDialog::AcceptOpen); lbajardsilogic@0: dialog.setFileMode(QFileDialog::ExistingFile); lbajardsilogic@128: dialog.setViewMode(QFileDialog::Detail); lbajardsilogic@0: lbajardsilogic@0: if (dialog.exec()) { lbajardsilogic@0: QStringList files = dialog.selectedFiles(); lbajardsilogic@0: if (!files.empty()) path = *files.begin(); lbajardsilogic@0: lbajardsilogic@0: QFileInfo fi(path); lbajardsilogic@0: lbajardsilogic@0: if (!fi.exists()) { lbajardsilogic@0: lbajardsilogic@0: QMessageBox::critical(0, tr("File does not exist"), lbajardsilogic@0: tr("File \"%1\" does not exist").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: lbajardsilogic@0: } else if (!fi.isReadable()) { lbajardsilogic@0: lbajardsilogic@0: QMessageBox::critical(0, tr("File is not readable"), lbajardsilogic@0: tr("File \"%1\" can not be read").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: lbajardsilogic@0: } else if (fi.isDir()) { lbajardsilogic@0: lbajardsilogic@0: QMessageBox::critical(0, tr("Directory selected"), lbajardsilogic@0: tr("File \"%1\" is a directory").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: lbajardsilogic@0: } else if (!fi.isFile()) { lbajardsilogic@0: lbajardsilogic@0: QMessageBox::critical(0, tr("Non-file selected"), lbajardsilogic@0: tr("Path \"%1\" is not a file").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: lbajardsilogic@0: } else if (fi.size() == 0) { lbajardsilogic@0: lbajardsilogic@0: QMessageBox::critical(0, tr("File is empty"), lbajardsilogic@0: tr("File \"%1\" is empty").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (path != "") { lbajardsilogic@0: settings.setValue(settingsKey, lbajardsilogic@0: QFileInfo(path).absoluteDir().canonicalPath()); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: return path; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString lbajardsilogic@0: FileFinder::getSaveFileName(FileType type, QString fallbackLocation) lbajardsilogic@0: { lbajardsilogic@0: QString settingsKey; lbajardsilogic@0: QString lastPath = fallbackLocation; lbajardsilogic@0: lbajardsilogic@0: QString title = tr("Select file"); lbajardsilogic@0: QString filter = tr("All files (*.*)"); lbajardsilogic@0: lbajardsilogic@0: switch (type) { lbajardsilogic@0: lbajardsilogic@0: case SessionFile: lbajardsilogic@0: settingsKey = "savesessionpath"; lbajardsilogic@0: title = tr("Select a session file"); lbajardsilogic@42: filter = tr("Sound Access session files (*.sv)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AudioFile: lbajardsilogic@0: settingsKey = "saveaudiopath"; lbajardsilogic@0: title = "Select an audio file"; lbajardsilogic@0: title = tr("Select a file to export to"); lbajardsilogic@0: filter = tr("WAV audio files (*.wav)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case LayerFile: lbajardsilogic@0: settingsKey = "savelayerpath"; lbajardsilogic@0: title = tr("Select a file to export to"); lbajardsilogic@42: filter = tr("Sound Access Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case SessionOrAudioFile: lbajardsilogic@0: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl; lbajardsilogic@0: abort(); lbajardsilogic@0: lbajardsilogic@0: case ImageFile: lbajardsilogic@0: settingsKey = "saveimagepath"; lbajardsilogic@0: title = tr("Select a file to export to"); lbajardsilogic@0: filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AnyFile: lbajardsilogic@0: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl; lbajardsilogic@0: abort(); lbajardsilogic@0: }; lbajardsilogic@0: lbajardsilogic@0: if (lastPath == "") { lbajardsilogic@0: char *home = getenv("HOME"); lbajardsilogic@0: if (home) lastPath = home; lbajardsilogic@0: else lastPath = "."; lbajardsilogic@0: } else if (QFileInfo(lastPath).isDir()) { lbajardsilogic@0: lastPath = QFileInfo(lastPath).canonicalPath(); lbajardsilogic@0: } else { lbajardsilogic@0: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QSettings settings; lbajardsilogic@0: settings.beginGroup("FileFinder"); lbajardsilogic@0: lastPath = settings.value(settingsKey, lastPath).toString(); lbajardsilogic@0: lbajardsilogic@0: QString path = ""; lbajardsilogic@0: lbajardsilogic@0: // Use our own QFileDialog instead of static functions, as we may lbajardsilogic@0: // need to adjust the file extension based on the selected filter lbajardsilogic@0: lbajardsilogic@0: QFileDialog dialog; lbajardsilogic@0: dialog.setFilters(filter.split('\n')); lbajardsilogic@0: dialog.setWindowTitle(title); lbajardsilogic@0: dialog.setDirectory(lastPath); lbajardsilogic@0: lbajardsilogic@0: dialog.setAcceptMode(QFileDialog::AcceptSave); lbajardsilogic@0: dialog.setFileMode(QFileDialog::AnyFile); lbajardsilogic@0: dialog.setConfirmOverwrite(false); // we'll do that lbajardsilogic@0: lbajardsilogic@0: if (type == SessionFile) { lbajardsilogic@0: dialog.setDefaultSuffix("sv"); lbajardsilogic@0: } else if (type == AudioFile) { lbajardsilogic@0: dialog.setDefaultSuffix("wav"); lbajardsilogic@0: } else if (type == ImageFile) { lbajardsilogic@0: dialog.setDefaultSuffix("png"); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: bool good = false; lbajardsilogic@0: lbajardsilogic@0: while (!good) { lbajardsilogic@0: lbajardsilogic@0: path = ""; lbajardsilogic@0: lbajardsilogic@0: if (!dialog.exec()) break; lbajardsilogic@0: lbajardsilogic@0: QStringList files = dialog.selectedFiles(); lbajardsilogic@0: if (files.empty()) break; lbajardsilogic@0: path = *files.begin(); lbajardsilogic@0: lbajardsilogic@0: QFileInfo fi(path); lbajardsilogic@0: lbajardsilogic@0: if (type == LayerFile && fi.suffix() == "") { lbajardsilogic@0: QString expectedExtension; lbajardsilogic@0: QString selectedFilter = dialog.selectedFilter(); lbajardsilogic@0: if (selectedFilter.contains(".svl")) { lbajardsilogic@0: expectedExtension = "svl"; lbajardsilogic@0: } else if (selectedFilter.contains(".txt")) { lbajardsilogic@0: expectedExtension = "txt"; lbajardsilogic@0: } else if (selectedFilter.contains(".csv")) { lbajardsilogic@0: expectedExtension = "csv"; lbajardsilogic@0: } lbajardsilogic@0: if (expectedExtension != "") { lbajardsilogic@0: path = QString("%1.%2").arg(path).arg(expectedExtension); lbajardsilogic@0: fi = QFileInfo(path); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (fi.isDir()) { lbajardsilogic@0: QMessageBox::critical(0, tr("Directory selected"), lbajardsilogic@0: tr("File \"%1\" is a directory").arg(path)); lbajardsilogic@0: continue; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (fi.exists()) { lbajardsilogic@0: if (QMessageBox::question(0, tr("File exists"), lbajardsilogic@0: tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), lbajardsilogic@0: QMessageBox::Ok, lbajardsilogic@0: QMessageBox::Cancel) != QMessageBox::Ok) { lbajardsilogic@0: continue; lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: good = true; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (path != "") { lbajardsilogic@0: settings.setValue(settingsKey, lbajardsilogic@0: QFileInfo(path).absoluteDir().canonicalPath()); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: return path; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: void lbajardsilogic@0: FileFinder::registerLastOpenedFilePath(FileType type, QString path) lbajardsilogic@0: { lbajardsilogic@0: QString settingsKey; lbajardsilogic@0: lbajardsilogic@0: switch (type) { lbajardsilogic@0: case SessionFile: lbajardsilogic@0: settingsKey = "sessionpath"; lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AudioFile: lbajardsilogic@0: settingsKey = "audiopath"; lbajardsilogic@0: break; ivand_qmul@118: // Ivan Damnjanovic 09-10/2007 added video file import ivand_qmul@118: case VideoFile: ivand_qmul@118: settingsKey = "videopath"; ivand_qmul@118: break; ivand_qmul@118: // lbajardsilogic@0: case LayerFile: lbajardsilogic@0: settingsKey = "layerpath"; lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case SessionOrAudioFile: lbajardsilogic@0: settingsKey = "lastpath"; lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case ImageFile: lbajardsilogic@0: settingsKey = "imagepath"; lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case AnyFile: lbajardsilogic@0: settingsKey = "lastpath"; lbajardsilogic@0: break; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (path != "") { lbajardsilogic@0: QSettings settings; lbajardsilogic@0: settings.beginGroup("FileFinder"); lbajardsilogic@0: path = QFileInfo(path).absoluteDir().canonicalPath(); lbajardsilogic@0: settings.setValue(settingsKey, path); lbajardsilogic@0: settings.setValue("lastpath", path); lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString lbajardsilogic@0: FileFinder::find(FileType type, QString location, QString lastKnownLocation) lbajardsilogic@0: { lbajardsilogic@0: if (QFileInfo(location).exists()) return location; lbajardsilogic@0: lbajardsilogic@0: if (RemoteFile::canHandleScheme(QUrl(location))) { lbajardsilogic@0: RemoteFile rf(location); lbajardsilogic@0: bool available = rf.isAvailable(); lbajardsilogic@0: rf.deleteLocalFile(); lbajardsilogic@0: if (available) return location; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString foundAt = ""; lbajardsilogic@0: lbajardsilogic@0: if ((foundAt = findRelative(location, lastKnownLocation)) != "") { lbajardsilogic@0: return foundAt; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") { lbajardsilogic@0: return foundAt; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: return locateInteractive(type, location); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString lbajardsilogic@0: FileFinder::findRelative(QString location, QString relativeTo) lbajardsilogic@0: { lbajardsilogic@0: if (relativeTo == "") return ""; lbajardsilogic@0: lbajardsilogic@0: std::cerr << "Looking for \"" << location.toStdString() << "\" next to \"" lbajardsilogic@0: << relativeTo.toStdString() << "\"..." << std::endl; lbajardsilogic@0: lbajardsilogic@0: QString fileName; lbajardsilogic@0: QString resolved; lbajardsilogic@0: lbajardsilogic@0: if (RemoteFile::canHandleScheme(QUrl(location))) { lbajardsilogic@0: fileName = QUrl(location).path().section('/', -1, -1, lbajardsilogic@0: QString::SectionSkipEmpty); lbajardsilogic@0: } else { lbajardsilogic@0: fileName = QFileInfo(location).fileName(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (RemoteFile::canHandleScheme(QUrl(relativeTo))) { lbajardsilogic@0: resolved = QUrl(relativeTo).resolved(fileName).toString(); lbajardsilogic@0: RemoteFile rf(resolved); lbajardsilogic@0: if (!rf.isAvailable()) resolved = ""; lbajardsilogic@0: std::cerr << "resolved: " << resolved.toStdString() << std::endl; lbajardsilogic@0: rf.deleteLocalFile(); lbajardsilogic@0: } else { lbajardsilogic@0: resolved = QFileInfo(relativeTo).dir().filePath(fileName); lbajardsilogic@0: if (!QFileInfo(resolved).exists() || lbajardsilogic@0: !QFileInfo(resolved).isFile() || lbajardsilogic@0: !QFileInfo(resolved).isReadable()) { lbajardsilogic@0: resolved = ""; lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: return resolved; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString lbajardsilogic@0: FileFinder::locateInteractive(FileType type, QString thing) lbajardsilogic@0: { lbajardsilogic@0: QString question; lbajardsilogic@0: if (type == AudioFile) { lbajardsilogic@0: question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?"); lbajardsilogic@0: } else { lbajardsilogic@0: question = tr("File \"%1\" could not be opened.\nDo you want to locate it?"); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: QString path = ""; lbajardsilogic@0: bool done = false; lbajardsilogic@0: lbajardsilogic@0: while (!done) { lbajardsilogic@0: lbajardsilogic@0: int rv = QMessageBox::question lbajardsilogic@0: (0, lbajardsilogic@0: tr("Failed to open file"), lbajardsilogic@0: question.arg(thing), lbajardsilogic@0: tr("Locate file..."), lbajardsilogic@0: tr("Use URL..."), lbajardsilogic@0: tr("Cancel"), lbajardsilogic@0: 0, 2); lbajardsilogic@0: lbajardsilogic@0: switch (rv) { lbajardsilogic@0: lbajardsilogic@0: case 0: // Locate file lbajardsilogic@0: lbajardsilogic@0: if (QFileInfo(thing).dir().exists()) { lbajardsilogic@0: path = QFileInfo(thing).dir().canonicalPath(); lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: path = getOpenFileName(type, path); lbajardsilogic@0: done = (path != ""); lbajardsilogic@0: break; lbajardsilogic@0: lbajardsilogic@0: case 1: // Use URL lbajardsilogic@0: { lbajardsilogic@0: bool ok = false; lbajardsilogic@0: path = QInputDialog::getText lbajardsilogic@0: (0, tr("Use URL"), lbajardsilogic@0: tr("Please enter the URL to use for this file:"), lbajardsilogic@0: QLineEdit::Normal, "", &ok); lbajardsilogic@0: lbajardsilogic@0: if (ok && path != "") { lbajardsilogic@0: RemoteFile rf(path); lbajardsilogic@0: if (rf.isAvailable()) { lbajardsilogic@0: done = true; lbajardsilogic@0: } else { lbajardsilogic@0: QMessageBox::critical lbajardsilogic@0: (0, tr("Failed to open location"), lbajardsilogic@0: tr("URL \"%1\" could not be opened").arg(path)); lbajardsilogic@0: path = ""; lbajardsilogic@0: } lbajardsilogic@0: rf.deleteLocalFile(); lbajardsilogic@0: } lbajardsilogic@0: break; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: case 2: // Cancel lbajardsilogic@0: path = ""; lbajardsilogic@0: done = true; lbajardsilogic@0: break; lbajardsilogic@0: } lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: if (path != "") m_lastLocatedLocation = path; lbajardsilogic@0: return path; lbajardsilogic@0: } lbajardsilogic@0: lbajardsilogic@0: