mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-16 16:10:06 +02:00
Preliminaries for grouped block deletion (#628)
* Change usage of AutoCommitTransaction::Rollback... ... It is the more useful pattern (as in many finally blocks) for the failure path in the destructor (which rolls back) to be the default, but an explicit call must inform it of success. This corrects the early return paths in Effect::DoEffect(). Throw inconsistency exception if Commit() is called again after having been called once, successfully Also remove a friend declaration * UndoManager's interface uses consistent 0-based indexing of states... ... Returned by GetCurrentState() and used by SetStateTo(), GetLongDescription(), GetShortDescription() * SampleBlock::GetBlockID is const * Generalized function to visit sample blocks used in a TrackList... ... Eliminating some duplication; put it in WaveTrack, not Track, to avoid a dependency cycle. * Eliminate more repetition with BlockSpaceUsageAccumulator * Function to delete all blocks of/not-of a given set in one command
This commit is contained in:
parent
d677bead7a
commit
a8fd6c6ce8
@ -118,7 +118,7 @@ HistoryDialog::HistoryDialog(AudacityProject *parent, UndoManager *manager):
|
||||
wxDefaultSize,
|
||||
wxSP_ARROW_KEYS,
|
||||
0,
|
||||
mManager->GetCurrentState() - 1,
|
||||
mManager->GetCurrentState(),
|
||||
0);
|
||||
S.AddWindow(mLevels);
|
||||
/* i18n-hint: (verb)*/
|
||||
@ -204,11 +204,11 @@ void HistoryDialog::DoUpdate()
|
||||
mList->DeleteAllItems();
|
||||
|
||||
wxLongLong_t total = 0;
|
||||
mSelected = mManager->GetCurrentState() - 1;
|
||||
mSelected = mManager->GetCurrentState();
|
||||
for (i = 0; i < (int)mManager->GetNumStates(); i++) {
|
||||
TranslatableString desc, size;
|
||||
|
||||
total += mManager->GetLongDescription(i + 1, &desc, &size);
|
||||
total += mManager->GetLongDescription(i, &desc, &size);
|
||||
mList->InsertItem(i, desc.Translation(), i == mSelected ? 1 : 0);
|
||||
mList->SetItem(i, 1, size.Translation());
|
||||
}
|
||||
@ -261,7 +261,7 @@ void HistoryDialog::OnDiscard(wxCommandEvent & WXUNUSED(event))
|
||||
|
||||
mSelected -= i;
|
||||
mManager->RemoveStates(i);
|
||||
ProjectHistory::Get( *mProject ).SetStateTo(mSelected + 1);
|
||||
ProjectHistory::Get( *mProject ).SetStateTo(mSelected);
|
||||
|
||||
while(--i >= 0)
|
||||
mList->DeleteItem(i);
|
||||
@ -299,7 +299,7 @@ void HistoryDialog::OnItemSelected(wxListEvent &event)
|
||||
// entry. Doing so can cause unnecessary delays upon initial load or while
|
||||
// clicking the same entry over and over.
|
||||
if (selected != mSelected) {
|
||||
ProjectHistory::Get( *mProject ).SetStateTo(selected + 1);
|
||||
ProjectHistory::Get( *mProject ).SetStateTo(selected);
|
||||
}
|
||||
mSelected = selected;
|
||||
|
||||
|
@ -661,7 +661,7 @@ void ProjectFileIO::InSet(sqlite3_context *context, int argc, sqlite3_value **ar
|
||||
sqlite3_result_int(context, blockids->find(blockid) != blockids->end());
|
||||
}
|
||||
|
||||
bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
|
||||
bool ProjectFileIO::DeleteBlocks(const BlockIDs &blockids, bool complement)
|
||||
{
|
||||
auto db = DB();
|
||||
int rc;
|
||||
@ -673,15 +673,19 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids)
|
||||
});
|
||||
|
||||
// Add the function used to verify each row's blockid against the set of active blockids
|
||||
rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &blockids, InSet, nullptr, nullptr);
|
||||
const void *p = &blockids;
|
||||
rc = sqlite3_create_function(db, "inset", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, const_cast<void*>(p), InSet, nullptr, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogDebug(wxT("Unable to add 'inset' function"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete all rows that are orphaned
|
||||
rc = sqlite3_exec(db, "DELETE FROM sampleblocks WHERE NOT inset(blockid);", nullptr, nullptr, nullptr);
|
||||
// Delete all rows in the set, or not in it
|
||||
auto sql = wxString::Format(
|
||||
"DELETE FROM sampleblocks WHERE %sinset(blockid);",
|
||||
complement ? "NOT " : "" );
|
||||
rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
wxLogWarning(XO("Cleanup of orphan blocks failed").Translation());
|
||||
@ -708,24 +712,12 @@ Connection ProjectFileIO::CopyTo(const FilePath &destpath,
|
||||
auto pProject = &mProject;
|
||||
auto &tracklist = tracks ? *tracks : TrackList::Get(*pProject);
|
||||
|
||||
BlockIDs blockids;
|
||||
SampleBlockIDSet blockids;
|
||||
|
||||
// Collect all active blockids
|
||||
if (prune)
|
||||
{
|
||||
for (auto wt : tracklist.Any<const WaveTrack>())
|
||||
{
|
||||
// Scan all clips within current track
|
||||
for (const auto &clip : wt->GetAllClips())
|
||||
{
|
||||
// Scan all sample blocks within current clip
|
||||
auto blocks = clip->GetSequenceBlockArray();
|
||||
for (const auto &block : *blocks)
|
||||
{
|
||||
blockids.insert(block.sb->GetBlockID());
|
||||
}
|
||||
}
|
||||
}
|
||||
InspectBlocks( tracklist, {}, &blockids );
|
||||
}
|
||||
// Collect ALL blockids
|
||||
else
|
||||
@ -929,33 +921,13 @@ Connection ProjectFileIO::CopyTo(const FilePath &destpath,
|
||||
|
||||
bool ProjectFileIO::ShouldVacuum(const std::shared_ptr<TrackList> &tracks)
|
||||
{
|
||||
std::set<long long> active;
|
||||
SampleBlockIDSet active;
|
||||
unsigned long long current = 0;
|
||||
|
||||
// Scan all wave tracks
|
||||
for (auto wt : tracks->Any<const WaveTrack>())
|
||||
{
|
||||
// Scan all clips within current track
|
||||
for (const auto &clip : wt->GetAllClips())
|
||||
{
|
||||
// Scan all sample blocks within current clip
|
||||
auto blocks = clip->GetSequenceBlockArray();
|
||||
for (const auto &block : *blocks)
|
||||
{
|
||||
const auto &sb = block.sb;
|
||||
auto blockid = sb->GetBlockID();
|
||||
|
||||
// Accumulate space used by the block if the blockid has not
|
||||
// yet been seen
|
||||
if (active.count(blockid) == 0)
|
||||
{
|
||||
current += sb->GetSpaceUsage();
|
||||
|
||||
active.insert(blockid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
InspectBlocks( *tracks,
|
||||
BlockSpaceUsageAccumulator( current ),
|
||||
&active // Visit unique blocks only
|
||||
);
|
||||
|
||||
// Get the number of blocks and total length from the project file.
|
||||
unsigned long long blockcount = 0;
|
||||
@ -1868,7 +1840,7 @@ bool ProjectFileIO::LoadProject(const FilePath &fileName)
|
||||
// Check for orphans blocks...sets mRecovered if any were deleted
|
||||
if (blockids.size() > 0)
|
||||
{
|
||||
if (!CheckForOrphans(blockids))
|
||||
if (!DeleteBlocks(blockids, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2176,36 +2148,30 @@ AutoCommitTransaction::AutoCommitTransaction(ProjectFileIO &projectFileIO,
|
||||
mName(name)
|
||||
{
|
||||
mInTrans = mIO.TransactionStart(mName);
|
||||
// Must throw
|
||||
if ( !mInTrans )
|
||||
// To do, improve the message
|
||||
throw SimpleMessageBoxException( XO("Database error") );
|
||||
}
|
||||
|
||||
AutoCommitTransaction::~AutoCommitTransaction()
|
||||
{
|
||||
if (mInTrans)
|
||||
{
|
||||
// Can't check return status...should probably throw an exception here
|
||||
if (!Commit())
|
||||
if (!mIO.TransactionRollback(mName))
|
||||
{
|
||||
// must throw
|
||||
// Do not throw from a destructor!
|
||||
// This has to be a no-fail cleanup that does the best that it can.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AutoCommitTransaction::Commit()
|
||||
{
|
||||
wxASSERT(mInTrans);
|
||||
if ( !mInTrans )
|
||||
// Misuse of this class
|
||||
THROW_INCONSISTENCY_EXCEPTION;
|
||||
|
||||
mInTrans = !mIO.TransactionCommit(mName);
|
||||
|
||||
return mInTrans;
|
||||
}
|
||||
|
||||
bool AutoCommitTransaction::Rollback()
|
||||
{
|
||||
wxASSERT(mInTrans);
|
||||
|
||||
mInTrans = !mIO.TransactionCommit(mName);
|
||||
|
||||
return mInTrans;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ Paul Licameli split from AudacityProject.h
|
||||
#define __AUDACITY_PROJECT_FILE_IO__
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ClientData.h" // to inherit
|
||||
#include "Prefs.h" // to inherit
|
||||
@ -166,12 +166,15 @@ private:
|
||||
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema = "main");
|
||||
|
||||
// Application defined function to verify blockid exists is in set of blockids
|
||||
using BlockIDs = std::set<SampleBlockID>;
|
||||
using BlockIDs = std::unordered_set<SampleBlockID>;
|
||||
static void InSet(sqlite3_context *context, int argc, sqlite3_value **argv);
|
||||
|
||||
// Checks for orphan blocks. This will go away at a future date
|
||||
bool CheckForOrphans(BlockIDs &blockids);
|
||||
public:
|
||||
// In one SQL command, delete sample blocks with ids in the given set, or
|
||||
// (when complement is true), with ids not in the given set.
|
||||
bool DeleteBlocks(const BlockIDs &blockids, bool complement);
|
||||
|
||||
private:
|
||||
// Return a database connection if successful, which caller must close
|
||||
Connection CopyTo(const FilePath &destpath,
|
||||
const TranslatableString &msg,
|
||||
@ -213,10 +216,12 @@ private:
|
||||
|
||||
TranslatableString mLastError;
|
||||
TranslatableString mLibraryError;
|
||||
|
||||
friend AutoCommitTransaction;
|
||||
};
|
||||
|
||||
// Make a savepoint (a transaction, possibly nested) with the given name;
|
||||
// roll it back at destruction time, unless an explicit Commit() happened first.
|
||||
// Commit() must not be called again after one successful call.
|
||||
// An exception is thrown from the constructor if the transaction cannot open.
|
||||
class AutoCommitTransaction
|
||||
{
|
||||
public:
|
||||
@ -224,7 +229,6 @@ public:
|
||||
~AutoCommitTransaction();
|
||||
|
||||
bool Commit();
|
||||
bool Rollback();
|
||||
|
||||
private:
|
||||
ProjectFileIO &mIO;
|
||||
|
@ -748,6 +748,8 @@ void ProjectManager::OnCloseWindow(wxCloseEvent & event)
|
||||
|
||||
// Delete all the tracks to free up memory
|
||||
tracks.Clear();
|
||||
|
||||
trans.Commit();
|
||||
}
|
||||
|
||||
// We're all done with the project file, so close it now
|
||||
|
@ -382,7 +382,7 @@ bool ProjectSerializer::DictChanged() const
|
||||
return mDictChanged;
|
||||
}
|
||||
|
||||
// See ProjectFileIO::CheckForOrphans() for explanation of the blockids arg
|
||||
// See ProjectFileIO::LoadProject() for explanation of the blockids arg
|
||||
wxString ProjectSerializer::Decode(const wxMemoryBuffer &buffer, BlockIDs &blockids)
|
||||
{
|
||||
wxMemoryInputStream in(buffer.GetData(), buffer.GetDataLen());
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
#include <wx/mstream.h> // member variables
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "audacity/Types.h"
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
using SampleBlockID = long long;
|
||||
|
||||
// From ProjectFileiIO.h
|
||||
using BlockIDs = std::set<SampleBlockID>;
|
||||
using BlockIDs = std::unordered_set<SampleBlockID>;
|
||||
|
||||
///
|
||||
/// ProjectSerializer
|
||||
|
@ -47,7 +47,7 @@ public:
|
||||
|
||||
virtual void CloseLock() = 0;
|
||||
|
||||
virtual SampleBlockID GetBlockID() = 0;
|
||||
virtual SampleBlockID GetBlockID() const = 0;
|
||||
|
||||
// If !mayThrow and there is an error, ignores it and returns zero.
|
||||
// That may be appropriate when only attempting to display samples, not edit.
|
||||
@ -89,6 +89,15 @@ protected:
|
||||
virtual MinMaxRMS DoGetMinMaxRMS() const = 0;
|
||||
};
|
||||
|
||||
// Makes a useful function object
|
||||
inline std::function< void(const SampleBlock&) >
|
||||
BlockSpaceUsageAccumulator (unsigned long long &total)
|
||||
{
|
||||
return [&total]( const SampleBlock &block ){
|
||||
total += block.GetSpaceUsage();
|
||||
};
|
||||
};
|
||||
|
||||
///\brief abstract base class with methods to produce @ref SampleBlock objects
|
||||
class SampleBlockFactory
|
||||
{
|
||||
|
@ -36,7 +36,7 @@ public:
|
||||
|
||||
void Delete();
|
||||
|
||||
SampleBlockID GetBlockID() override;
|
||||
SampleBlockID GetBlockID() const override;
|
||||
|
||||
size_t DoGetSamples(samplePtr dest,
|
||||
sampleFormat destformat,
|
||||
@ -284,7 +284,7 @@ void SqliteSampleBlock::CloseLock()
|
||||
mLocked = true;
|
||||
}
|
||||
|
||||
SampleBlockID SqliteSampleBlock::GetBlockID()
|
||||
SampleBlockID SqliteSampleBlock::GetBlockID() const
|
||||
{
|
||||
return mBlockID;
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ wxDEFINE_EVENT(EVT_UNDO_OR_REDO, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_UNDO_RESET, wxCommandEvent);
|
||||
|
||||
using SampleBlockID = long long;
|
||||
using Set = std::unordered_set<SampleBlockID>;
|
||||
|
||||
struct UndoStackElem {
|
||||
|
||||
@ -94,35 +93,15 @@ UndoManager::~UndoManager()
|
||||
|
||||
namespace {
|
||||
SpaceArray::value_type
|
||||
CalculateUsage(const TrackList &tracks, Set &seen)
|
||||
CalculateUsage(const TrackList &tracks, SampleBlockIDSet &seen)
|
||||
{
|
||||
SpaceArray::value_type result = 0;
|
||||
|
||||
//TIMER_START( "CalculateSpaceUsage", space_calc );
|
||||
for (auto wt : tracks.Any< const WaveTrack >())
|
||||
{
|
||||
// Scan all clips within current track
|
||||
for(const auto &clip : wt->GetAllClips())
|
||||
{
|
||||
// Scan all sample blocks within current clip
|
||||
auto blocks = clip->GetSequenceBlockArray();
|
||||
for (const auto &block : *blocks)
|
||||
{
|
||||
const auto &sb = block.sb;
|
||||
|
||||
// Accumulate space used by the block if the block was not
|
||||
// yet seen
|
||||
if ( seen.count( sb->GetBlockID() ) == 0 )
|
||||
{
|
||||
unsigned long long usage{ sb->GetSpaceUsage() };
|
||||
result += usage;
|
||||
|
||||
seen.insert( sb->GetBlockID() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InspectBlocks(
|
||||
tracks,
|
||||
BlockSpaceUsageAccumulator( result ),
|
||||
&seen
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -132,7 +111,7 @@ void UndoManager::CalculateSpaceUsage()
|
||||
space.clear();
|
||||
space.resize(stack.size(), 0);
|
||||
|
||||
Set seen;
|
||||
SampleBlockIDSet seen;
|
||||
|
||||
// After copies and pastes, a block file may be used in more than
|
||||
// one place in one undo history state, and it may be used in more than
|
||||
@ -157,9 +136,9 @@ void UndoManager::CalculateSpaceUsage()
|
||||
|
||||
// Count the usage of the clipboard separately, using another set. Do not
|
||||
// multiple-count any block occurring multiple times within the clipboard.
|
||||
Set seen2;
|
||||
seen.clear();
|
||||
mClipboardSpaceUsage = CalculateUsage(
|
||||
Clipboard::Get().GetTracks(), seen2);
|
||||
Clipboard::Get().GetTracks(), seen);
|
||||
|
||||
//TIMER_STOP( space_calc );
|
||||
}
|
||||
@ -167,8 +146,6 @@ void UndoManager::CalculateSpaceUsage()
|
||||
wxLongLong_t UndoManager::GetLongDescription(
|
||||
unsigned int n, TranslatableString *desc, TranslatableString *size)
|
||||
{
|
||||
n -= 1; // 1 based to zero based
|
||||
|
||||
wxASSERT(n < stack.size());
|
||||
wxASSERT(space.size() == stack.size());
|
||||
|
||||
@ -181,8 +158,6 @@ wxLongLong_t UndoManager::GetLongDescription(
|
||||
|
||||
void UndoManager::GetShortDescription(unsigned int n, TranslatableString *desc)
|
||||
{
|
||||
n -= 1; // 1 based to zero based
|
||||
|
||||
wxASSERT(n < stack.size());
|
||||
|
||||
*desc = stack[n]->shortDescription;
|
||||
@ -228,7 +203,7 @@ unsigned int UndoManager::GetNumStates()
|
||||
|
||||
unsigned int UndoManager::GetCurrentState()
|
||||
{
|
||||
return current + 1; // the array is 0 based, the abstraction is 1 based
|
||||
return current;
|
||||
}
|
||||
|
||||
bool UndoManager::UndoAvailable()
|
||||
@ -332,8 +307,6 @@ void UndoManager::PushState(const TrackList * l,
|
||||
|
||||
void UndoManager::SetStateTo(unsigned int n, const Consumer &consumer)
|
||||
{
|
||||
n -= 1;
|
||||
|
||||
wxASSERT(n < stack.size());
|
||||
|
||||
current = n;
|
||||
|
@ -131,7 +131,6 @@ class AUDACITY_DLL_API UndoManager final
|
||||
const SelectedRegion &selectedRegion, const std::shared_ptr<Tags> &tags);
|
||||
void ClearStates();
|
||||
void RemoveStates(int num); // removes the 'num' oldest states
|
||||
void RemoveStateAt(int n); // removes the n'th state (1 is oldest)
|
||||
unsigned int GetNumStates();
|
||||
unsigned int GetCurrentState();
|
||||
|
||||
@ -168,6 +167,8 @@ class AUDACITY_DLL_API UndoManager final
|
||||
// void Debug(); // currently unused
|
||||
|
||||
private:
|
||||
void RemoveStateAt(int n);
|
||||
|
||||
AudacityProject &mProject;
|
||||
|
||||
int current;
|
||||
|
@ -2683,3 +2683,32 @@ void WaveTrack::AllClipsIterator::push( WaveClipHolders &clips )
|
||||
pClips = &(*first)->GetCutLines();
|
||||
}
|
||||
}
|
||||
|
||||
#include "SampleBlock.h"
|
||||
void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
|
||||
SampleBlockIDSet *pIDs)
|
||||
{
|
||||
for (auto wt : tracks.Any< const WaveTrack >()) {
|
||||
// Scan all clips within current track
|
||||
for(const auto &clip : wt->GetAllClips()) {
|
||||
// Scan all sample blocks within current clip
|
||||
auto blocks = clip->GetSequenceBlockArray();
|
||||
for (const auto &block : *blocks) {
|
||||
auto &pBlock = block.sb;
|
||||
if ( pBlock ) {
|
||||
if ( pIDs && !pIDs->insert(pBlock->GetBlockID()).second )
|
||||
continue;
|
||||
if ( visitor )
|
||||
visitor( *pBlock );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
|
||||
SampleBlockIDSet *pIDs)
|
||||
{
|
||||
VisitBlocks(
|
||||
const_cast<TrackList &>(tracks), std::move( inspector ), pIDs );
|
||||
}
|
||||
|
@ -626,4 +626,23 @@ private:
|
||||
int mNValidBuffers;
|
||||
};
|
||||
|
||||
#include <unordered_set>
|
||||
class SampleBlock;
|
||||
using SampleBlockID = long long;
|
||||
using SampleBlockIDSet = std::unordered_set<SampleBlockID>;
|
||||
class TrackList;
|
||||
using BlockVisitor = std::function< void(SampleBlock&) >;
|
||||
using BlockInspector = std::function< void(const SampleBlock&) >;
|
||||
|
||||
// Function to visit all sample blocks from a list of tracks.
|
||||
// If a set is supplied, then only visit once each unique block ID not already
|
||||
// in that set, and accumulate those into the set as a side-effect.
|
||||
// The visitor function may be null.
|
||||
void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
|
||||
SampleBlockIDSet *pIDs = nullptr);
|
||||
|
||||
// Non-mutating version of the above
|
||||
void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
|
||||
SampleBlockIDSet *pIDs = nullptr);
|
||||
|
||||
#endif // __AUDACITY_WAVETRACK__
|
||||
|
@ -1251,6 +1251,8 @@ bool Effect::DoEffect(double projectRate,
|
||||
// LastUsedDuration may have been modified by Preview.
|
||||
SetDuration(oldDuration);
|
||||
}
|
||||
else
|
||||
trans.Commit();
|
||||
|
||||
End();
|
||||
ReplaceProcessedTracks( false );
|
||||
|
Loading…
x
Reference in New Issue
Block a user