Improved relative paths (#5117)

* Create PathUtils

* Replace old SampleBuffer calls

* Fix automatic track names

* Fix things

* Remove accidental duplicate file

* Add includes

* More incldues

* PhysSong's code review + style

* Fix vestige loading?

Seems more reasonable to convert from relative on load and to relative on save than vice versa.

* Typo fix

* More Bases

* Enable more bases

* Add missing semicolons in prefixes

* Nicer sample track tooltip

* Use correct directories

"userXDir" gives the default dir for ladspa, sf2, and gig. "xDir" gives the user dir.

* Make relative to both default and custom locations

Part 1

* Make relative to both default and custom locations

Part 2

* Typofix

* Typofix

* Fix upgrade function after base renaming

* Fix Tests

* Update tests/src/core/RelativePathsTest.cpp

Co-Authored-By: Hyunjin Song <tteu.ingog@gmail.com>

* Choose UserXBase over DefaultXBase if identical

toShortestRelative sticks with the first base found if two bases give the same path length. By placing UserXBase Bases before DefaultXBase Bases in the relativeBases vector, toShortestRelative will prioritize them.

* Ensure baseLocation always has trailing slash

Otherwise, a user configuring a path without one will break things.

* Move loc declaration out of switch

* Semicolon

* Apply suggestions from code review...

* Include PathUtil and sort includes

* More granular includes

* Apply suggestions from code review

Co-Authored-By: Hyunjin Song <tteu.ingog@gmail.com>

* Update include/PathUtil.h

* Leave empty paths alone

* Fix stupid merge

* Really fix merge. Hopefully

* Switch Base from enum to class enum

* Don't pass Base by reference

* Use QStringLiteral for static QString allocation in basePrefix method

* Make VST loading more similar to previous implementation

* Fix tests after enum change

* Attempt to fix VST loading, nicer name for sample clips

* Fix last review comment

Don't append a "/" that will be removed by cleanPath later

* Apply suggestions from code review

Co-authored-by: Dominic Clark <mrdomclark@gmail.com>

Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
Co-authored-by: Dominic Clark <mrdomclark@gmail.com>
This commit is contained in:
Spekular
2020-07-28 17:07:35 +02:00
committed by GitHub
parent 67f0324ff5
commit 17565caf53
13 changed files with 288 additions and 156 deletions

View File

@@ -46,6 +46,7 @@ set(LMMS_SRCS
core/Note.cpp
core/NotePlayHandle.cpp
core/Oscillator.cpp
core/PathUtil.cpp
core/PeakController.cpp
core/PerfLog.cpp
core/Piano.cpp

148
src/core/PathUtil.cpp Normal file
View File

@@ -0,0 +1,148 @@
#include "PathUtil.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include "ConfigManager.h"
namespace PathUtil
{
Base relativeBases[] = { Base::ProjectDir, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset,
Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG };
QString baseLocation(const Base base)
{
QString loc = "";
switch (base)
{
case Base::ProjectDir : loc = ConfigManager::inst()->userProjectsDir(); break;
case Base::FactorySample :
{
QDir fsd = QDir(ConfigManager::inst()->factorySamplesDir());
loc = fsd.absolutePath(); break;
}
case Base::UserSample : loc = ConfigManager::inst()->userSamplesDir(); break;
case Base::UserVST : loc = ConfigManager::inst()->userVstDir(); break;
case Base::Preset : loc = ConfigManager::inst()->userPresetsDir(); break;
case Base::UserLADSPA : loc = ConfigManager::inst()->ladspaDir(); break;
case Base::DefaultLADSPA : loc = ConfigManager::inst()->userLadspaDir(); break;
case Base::UserSoundfont : loc = ConfigManager::inst()->sf2Dir(); break;
case Base::DefaultSoundfont : loc = ConfigManager::inst()->userSf2Dir(); break;
case Base::UserGIG : loc = ConfigManager::inst()->gigDir(); break;
case Base::DefaultGIG : loc = ConfigManager::inst()->userGigDir(); break;
default : return QString("");
}
return QDir::cleanPath(loc) + "/";
}
QDir baseQDir (const Base base) { return QDir(baseLocation(base)); }
QString basePrefix(const Base base)
{
switch (base)
{
case Base::ProjectDir : return QStringLiteral("userprojects:");
case Base::FactorySample : return QStringLiteral("factorysample:");
case Base::UserSample : return QStringLiteral("usersample:");
case Base::UserVST : return QStringLiteral("uservst:");
case Base::Preset : return QStringLiteral("preset:");
case Base::UserLADSPA : return QStringLiteral("userladspa:");
case Base::DefaultLADSPA : return QStringLiteral("defaultladspa:");
case Base::UserSoundfont : return QStringLiteral("usersoundfont:");
case Base::DefaultSoundfont : return QStringLiteral("defaultsoundfont:");
case Base::UserGIG : return QStringLiteral("usergig:");
case Base::DefaultGIG : return QStringLiteral("defaultgig:");
default : return QStringLiteral("");
}
}
Base baseLookup(const QString & path)
{
for (auto base: relativeBases)
{
QString prefix = basePrefix(base);
if ( path.startsWith(prefix) ) { return base; }
}
return Base::Absolute;
}
QString stripPrefix(const QString & path)
{
return path.mid( basePrefix(baseLookup(path)).length() );
}
QString cleanName(const QString & path)
{
return stripPrefix(QFileInfo(path).baseName());
}
QString oldRelativeUpgrade(const QString & input)
{
if (input.isEmpty()) { return input; }
//Start by assuming that the file is a user sample
Base assumedBase = Base::UserSample;
//Check if it's a factory sample
QString factoryPath = baseLocation(Base::FactorySample) + input;
QFileInfo factoryInfo(factoryPath);
if (factoryInfo.exists()) { assumedBase = Base::FactorySample; }
//Check if it's a VST
QString vstPath = baseLocation(Base::UserVST) + input;
QFileInfo vstInfo(vstPath);
if (vstInfo.exists()) { assumedBase = Base::UserVST; }
//Assume we've found the correct base location, return the full path
return basePrefix(assumedBase) + input;
}
QString toAbsolute(const QString & input)
{
//First, do no harm to absolute paths
QFileInfo inputFileInfo = QFileInfo(input);
if (inputFileInfo.isAbsolute()) { return input; }
//Next, handle old relative paths with no prefix
QString upgraded = input.contains(":") ? input : oldRelativeUpgrade(input);
Base base = baseLookup(upgraded);
return baseLocation(base) + upgraded.remove(0, basePrefix(base).length());
}
QString relativeOrAbsolute(const QString & input, const Base base)
{
if (input.isEmpty()) { return input; }
QString absolutePath = toAbsolute(input);
QString relativePath = baseQDir(base).relativeFilePath(absolutePath);
return relativePath.startsWith("..") ? absolutePath : relativePath;
}
QString toShortestRelative(const QString & input)
{
QFileInfo inputFileInfo = QFileInfo(input);
QString absolutePath = inputFileInfo.isAbsolute() ? input : toAbsolute(input);
Base shortestBase = Base::Absolute;
QString shortestPath = relativeOrAbsolute(absolutePath, shortestBase);
for (auto base: relativeBases)
{
QString otherPath = relativeOrAbsolute(absolutePath, base);
if (otherPath.length() < shortestPath.length())
{
shortestBase = base;
shortestPath = otherPath;
}
}
return basePrefix(shortestBase) + relativeOrAbsolute(absolutePath, shortestBase);
}
}

View File

@@ -24,7 +24,6 @@
#include "SampleBuffer.h"
#include <QBuffer>
#include <QFile>
#include <QFileInfo>
@@ -55,11 +54,11 @@
#include "Engine.h"
#include "GuiApplication.h"
#include "Mixer.h"
#include "PathUtil.h"
#include "FileDialog.h"
SampleBuffer::SampleBuffer() :
m_audioFile( "" ),
m_origData( NULL ),
@@ -179,7 +178,7 @@ void SampleBuffer::update( bool _keep_settings )
}
else if( !m_audioFile.isEmpty() )
{
QString file = tryToMakeAbsolute( m_audioFile );
QString file = PathUtil::toAbsolute( m_audioFile );
int_sample_t * buf = NULL;
sample_t * fbuf = NULL;
ch_cnt_t channels = DEFAULT_CHANNELS;
@@ -781,7 +780,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
}
}
if( tmp != NULL )
if( tmp != NULL )
{
MM_FREE( tmp );
}
@@ -1031,7 +1030,7 @@ QString SampleBuffer::openAudioFile() const
{
return QString();
}
return tryToMakeRelative( ofd.selectedFiles()[0] );
return PathUtil::toShortestRelative( ofd.selectedFiles()[0] );
}
return QString();
@@ -1222,7 +1221,7 @@ SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr,
void SampleBuffer::setAudioFile( const QString & _audio_file )
{
m_audioFile = tryToMakeRelative( _audio_file );
m_audioFile = PathUtil::toShortestRelative( _audio_file );
update();
}
@@ -1419,60 +1418,6 @@ void SampleBuffer::setReversed( bool _on )
QString SampleBuffer::tryToMakeRelative( const QString & file )
{
if( QFileInfo( file ).isRelative() == false )
{
// Normalize the path
QString f( QDir::cleanPath( file ) );
// First, look in factory samples
// Isolate "samples/" from "data:/samples/"
QString samplesSuffix = ConfigManager::inst()->factorySamplesDir().mid( ConfigManager::inst()->dataDir().length() );
// Iterate over all valid "data:/" searchPaths
for ( const QString & path : QDir::searchPaths( "data" ) )
{
QString samplesPath = QDir::cleanPath( path + samplesSuffix ) + "/";
if ( f.startsWith( samplesPath ) )
{
return QString( f ).mid( samplesPath.length() );
}
}
// Next, look in user samples
QString usd = ConfigManager::inst()->userSamplesDir();
usd.replace( QDir::separator(), '/' );
if( f.startsWith( usd ) )
{
return QString( f ).mid( usd.length() );
}
}
return file;
}
QString SampleBuffer::tryToMakeAbsolute(const QString& file)
{
QFileInfo f(file);
if(f.isRelative())
{
f = QFileInfo(ConfigManager::inst()->userSamplesDir() + file);
if(! f.exists())
{
f = QFileInfo(ConfigManager::inst()->factorySamplesDir() + file);
}
}
if (f.exists()) {
return f.absoluteFilePath();
}
return file;
}
@@ -1488,7 +1433,7 @@ SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_m
{
int error;
m_interpolationMode = interpolation_mode;
if( ( m_resamplingData = src_new( interpolation_mode, DEFAULT_CHANNELS, &error ) ) == NULL )
{
qDebug( "Error: src_new() failed in sample_buffer.cpp!\n" );

View File

@@ -34,23 +34,24 @@
#include <QPainter>
#include <QPushButton>
#include "BBTrack.h"
#include "EffectRackView.h"
#include "embed.h"
#include "FxMixerView.h"
#include "gui_templates.h"
#include "GuiApplication.h"
#include "Song.h"
#include "embed.h"
#include "ToolTip.h"
#include "BBTrack.h"
#include "SamplePlayHandle.h"
#include "SampleRecordHandle.h"
#include "SongEditor.h"
#include "StringPairDrag.h"
#include "TimeLineWidget.h"
#include "Knob.h"
#include "MainWindow.h"
#include "Mixer.h"
#include "EffectRackView.h"
#include "FxMixerView.h"
#include "PathUtil.h"
#include "SamplePlayHandle.h"
#include "SampleRecordHandle.h"
#include "Song.h"
#include "SongEditor.h"
#include "StringPairDrag.h"
#include "TabWidget.h"
#include "TimeLineWidget.h"
#include "ToolTip.h"
#include "TrackLabelButton.h"
SampleTCO::SampleTCO( Track * _track ) :
@@ -333,7 +334,7 @@ void SampleTCOView::updateSample()
// set tooltip to filename so that user can see what sample this
// sample-tco contains
ToolTip::add( this, ( m_tco->m_sampleBuffer->audioFile() != "" ) ?
m_tco->m_sampleBuffer->audioFile() :
PathUtil::toAbsolute(m_tco->m_sampleBuffer->audioFile()) :
tr( "Double-click to open sample" ) );
}
@@ -535,9 +536,8 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
qMax( static_cast<int>( m_tco->sampleLength() * ppb / ticksPerBar ), 1 ), rect().bottom() - 2 * spacing );
m_tco->m_sampleBuffer->visualize( p, r, pe->rect() );
QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile());
QString filename = fileInfo.fileName();
paintTextLabel(filename, p);
QString name = PathUtil::cleanName(m_tco->m_sampleBuffer->audioFile());
paintTextLabel(name, p);
// disable antialiasing for borders, since its not needed
p.setRenderHint( QPainter::Antialiasing, false );