mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-14 04:18:36 +00:00
- Renamed MetadataValue -> MetadataKeyValue
- Cleaned up some of the requery logic in CategoryListView, TrackListView - Started working on the message-queue related functionality that will be required for clean callback logic - Reduced the main loop timeout delay
This commit is contained in:
parent
45e8cf956e
commit
1a0d297b30
50
src/core/library/metadata/MetadataKeyValue.cpp
Normal file
50
src/core/library/metadata/MetadataKeyValue.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License Agreement:
|
||||
//
|
||||
// The following are Copyright © 2008, Daniel Önnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "pch.hpp"
|
||||
#include <core/library/metadata/MetadataKeyValue.h>
|
||||
|
||||
using namespace musik::core;
|
||||
|
||||
MetadataKeyValue::MetadataKeyValue(const DBID newId, const char *value)
|
||||
: id (newId) {
|
||||
if (value) {
|
||||
this->value = value;
|
||||
}
|
||||
}
|
||||
|
||||
MetadataKeyValue::~MetadataKeyValue() {
|
||||
}
|
58
src/core/library/metadata/MetadataKeyValue.h
Normal file
58
src/core/library/metadata/MetadataKeyValue.h
Normal file
@ -0,0 +1,58 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License Agreement:
|
||||
//
|
||||
// The following are Copyright © 2008, Daniel Önnerby
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/config.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace musik { namespace core {
|
||||
|
||||
class MetadataKeyValue {
|
||||
public:
|
||||
MetadataKeyValue(const DBID newId, const char *value);
|
||||
~MetadataKeyValue();
|
||||
|
||||
DBID id;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<MetadataKeyValue> MetadataKeyValuePtr;
|
||||
typedef std::vector<MetadataKeyValuePtr> MetadataKeyValueList;
|
||||
|
||||
} }
|
@ -12,16 +12,20 @@ using musik::core::IQuery;
|
||||
|
||||
CategoryListView::CategoryListView(LibraryPtr library, IWindow *parent)
|
||||
: ListWindow(parent) {
|
||||
this->SetContentColor(BOX_COLOR_WHITE_ON_BLACK);
|
||||
this->library = library;
|
||||
this->adapter = new Adapter(*this);
|
||||
this->activeQuery.reset(new CategoryListQuery());
|
||||
this->library->Enqueue(activeQuery);
|
||||
}
|
||||
|
||||
CategoryListView::~CategoryListView() {
|
||||
delete adapter;
|
||||
}
|
||||
|
||||
void CategoryListView::Requery() {
|
||||
this->activeQuery.reset(new CategoryListQuery());
|
||||
this->library->Enqueue(activeQuery);
|
||||
}
|
||||
|
||||
void CategoryListView::OnIdle() {
|
||||
if (activeQuery && activeQuery->GetStatus() == IQuery::Finished) {
|
||||
this->metadata = activeQuery->GetResult();
|
||||
|
@ -18,6 +18,7 @@ class CategoryListView : public ListWindow, public sigslot::has_slots<> {
|
||||
~CategoryListView(); /* non-virtual for now*/
|
||||
|
||||
void OnIdle();
|
||||
void Requery();
|
||||
|
||||
protected:
|
||||
virtual IScrollAdapter& GetScrollAdapter();
|
||||
|
15
src/musikbox/IWindowMessage.h
Executable file
15
src/musikbox/IWindowMessage.h
Executable file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "IWindow.h"
|
||||
#include <memory>
|
||||
|
||||
class IWindowMessage {
|
||||
public:
|
||||
virtual IWindowPtr Target() = 0;
|
||||
virtual int MessageType() = 0;
|
||||
virtual int64 UserData1() = 0;
|
||||
virtual int64 UserData2() = 0;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<IWindowMessage> IWindowMessagePtr;
|
@ -6,7 +6,7 @@
|
||||
LibraryLayout::LibraryLayout(LibraryPtr library)
|
||||
: LayoutBase() {
|
||||
this->library = library;
|
||||
this->InitializeViews();
|
||||
this->InitializeWindows();
|
||||
}
|
||||
|
||||
LibraryLayout::~LibraryLayout() {
|
||||
@ -26,16 +26,28 @@ void LibraryLayout::Layout() {
|
||||
this->trackList->SetFocusOrder(1);
|
||||
}
|
||||
|
||||
void LibraryLayout::OnIdle() {
|
||||
this->albumList->OnIdle();
|
||||
}
|
||||
|
||||
void LibraryLayout::InitializeViews() {
|
||||
void LibraryLayout::InitializeWindows() {
|
||||
this->albumList.reset(new CategoryListView(library));
|
||||
this->trackList.reset(new TrackListView());
|
||||
this->trackList.reset(new TrackListView(library));
|
||||
|
||||
this->AddWindow(this->albumList);
|
||||
this->AddWindow(this->trackList);
|
||||
|
||||
this->Layout();
|
||||
}
|
||||
|
||||
void LibraryLayout::OnIdle() {
|
||||
this->UpdateWindows();
|
||||
}
|
||||
|
||||
void LibraryLayout::Show() {
|
||||
LayoutBase::Show();
|
||||
this->trackList->Requery();
|
||||
this->albumList->Requery();
|
||||
}
|
||||
|
||||
void LibraryLayout::UpdateWindows() {
|
||||
/* update() or repaint() or whatever */
|
||||
this->albumList->OnIdle();
|
||||
this->trackList->OnIdle();
|
||||
}
|
@ -15,9 +15,11 @@ class LibraryLayout : public LayoutBase {
|
||||
|
||||
virtual void Layout();
|
||||
virtual void OnIdle();
|
||||
virtual void Show();
|
||||
|
||||
private:
|
||||
void InitializeViews();
|
||||
void UpdateWindows();
|
||||
void InitializeWindows();
|
||||
|
||||
LibraryPtr library;
|
||||
std::shared_ptr<CategoryListView> albumList;
|
||||
|
@ -53,7 +53,7 @@
|
||||
#ifdef WIN32
|
||||
#undef MOUSE_MOVED
|
||||
|
||||
#define IDLE_TIMEOUT_MS 500
|
||||
#define IDLE_TIMEOUT_MS 0
|
||||
|
||||
struct WindowState {
|
||||
ILayout* layout;
|
||||
|
@ -9,10 +9,8 @@
|
||||
typedef IScrollAdapter::EntryPtr EntryPtr;
|
||||
|
||||
OutputWindow::OutputWindow(IWindow *parent)
|
||||
: ScrollableWindow(parent)
|
||||
{
|
||||
: ScrollableWindow(parent) {
|
||||
this->SetContentColor(BOX_COLOR_BLACK_ON_GREY);
|
||||
|
||||
this->adapter = new SimpleScrollAdapter();
|
||||
this->adapter->SetDisplaySize(this->GetContentWidth(), this->GetContentHeight());
|
||||
this->adapter->SetMaxEntries(500);
|
||||
|
@ -1,13 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Colors.h"
|
||||
#include "SingleLineEntry.h"
|
||||
#include "TrackListView.h"
|
||||
|
||||
TrackListView::TrackListView(IWindow *parent)
|
||||
: Window(parent) {
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using musik::core::IQuery;
|
||||
|
||||
TrackListView::TrackListView(LibraryPtr library, IWindow *parent)
|
||||
: ListWindow(parent) {
|
||||
this->SetContentColor(BOX_COLOR_WHITE_ON_BLACK);
|
||||
this->library = library;
|
||||
this->adapter = new Adapter(*this);
|
||||
}
|
||||
|
||||
TrackListView::~TrackListView() {
|
||||
|
||||
}
|
||||
|
||||
void TrackListView::Requery() {
|
||||
this->query.reset(new TracklistQuery());
|
||||
this->library->Enqueue(this->query);
|
||||
}
|
||||
|
||||
void TrackListView::OnIdle() {
|
||||
if (query && query->GetStatus() == IQuery::Finished) {
|
||||
this->metadata = query->GetResult();
|
||||
query.reset();
|
||||
this->OnAdapterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
IScrollAdapter& TrackListView::GetScrollAdapter() {
|
||||
return *this->adapter;
|
||||
}
|
||||
|
||||
TrackListView::Adapter::Adapter(TrackListView &parent)
|
||||
: parent(parent) {
|
||||
}
|
||||
|
||||
size_t TrackListView::Adapter::GetEntryCount() {
|
||||
return 23847;//parent.metadata ? parent.metadata->size() : 0;
|
||||
}
|
||||
|
||||
IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(size_t index) {
|
||||
int64 attrs = (index == parent.GetSelectedIndex()) ? COLOR_PAIR(BOX_COLOR_BLACK_ON_GREEN) : -1;
|
||||
std::string text = boost::str(boost::format("%1% %2%") % index % "need to fill in the metadata");
|
||||
IScrollAdapter::EntryPtr entry(new SingleLineEntry(/*parent.metadata->at(index)*/ text));
|
||||
entry->SetAttrs(attrs);
|
||||
|
||||
return entry;
|
||||
}
|
@ -1,12 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "curses_config.h"
|
||||
#include "Window.h"
|
||||
|
||||
class TrackListView : public Window {
|
||||
#include "ListWindow.h"
|
||||
#include "TracklistQuery.h"
|
||||
#include "ScrollAdapterBase.h"
|
||||
|
||||
#include <core/library/ILibrary.h>
|
||||
|
||||
using musik::core::QueryPtr;
|
||||
using musik::core::LibraryPtr;
|
||||
|
||||
class TrackListView : public ListWindow {
|
||||
public:
|
||||
TrackListView(IWindow *parent = NULL);
|
||||
TrackListView(LibraryPtr library, IWindow *parent = NULL);
|
||||
~TrackListView();
|
||||
|
||||
void OnIdle();
|
||||
void Requery();
|
||||
|
||||
protected:
|
||||
virtual IScrollAdapter& GetScrollAdapter();
|
||||
|
||||
class Adapter : public ScrollAdapterBase {
|
||||
public:
|
||||
Adapter(TrackListView &parent);
|
||||
|
||||
virtual size_t GetEntryCount();
|
||||
virtual EntryPtr GetEntry(size_t index);
|
||||
|
||||
private:
|
||||
TrackListView &parent;
|
||||
IScrollAdapter::ScrollPosition spos;
|
||||
};
|
||||
|
||||
private:
|
||||
std::shared_ptr<TracklistQuery> query;
|
||||
std::shared_ptr<std::vector<TrackPtr>> metadata;
|
||||
Adapter* adapter;
|
||||
LibraryPtr library;
|
||||
};
|
@ -27,16 +27,15 @@ bool TracklistQuery::OnRun(Connection& db) {
|
||||
}
|
||||
|
||||
std::string query =
|
||||
"SELECT DISTINCT albums.name "
|
||||
"FROM albums, tracks "
|
||||
"WHERE albums.id = tracks.album_id "
|
||||
"ORDER BY albums.sort_order;";
|
||||
"SELECT DISTINCT track.name "
|
||||
"FROM tracks "
|
||||
"ORDER BY track.name;";
|
||||
|
||||
Statement stmt(query.c_str(), db);
|
||||
|
||||
//while (stmt.Step() == Row) {
|
||||
// result->push_back(stmt.ColumnText(0));
|
||||
//}
|
||||
while (stmt.Step() == Row) {
|
||||
//result->push_back(stmt.ColumnText(0));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -3,26 +3,32 @@
|
||||
#include <core/library/query/QueryBase.h>
|
||||
#include <core/db/Connection.h>
|
||||
#include <core/library/track/Track.h>
|
||||
#include "CategoryListQuery.h"
|
||||
|
||||
using musik::core::query::QueryBase;
|
||||
using musik::core::db::Connection;
|
||||
using musik::core::TrackPtr;
|
||||
|
||||
class TracklistQuery : public QueryBase {
|
||||
public:
|
||||
typedef std::shared_ptr<std::vector<TrackPtr>> Result;
|
||||
public:
|
||||
typedef std::shared_ptr<std::vector<TrackPtr>> Result;
|
||||
typedef std::shared_ptr<CategoryListQuery> Category;
|
||||
|
||||
TracklistQuery();
|
||||
~TracklistQuery();
|
||||
TracklistQuery();
|
||||
~TracklistQuery();
|
||||
|
||||
std::string Name() {
|
||||
return "TracklistQuery";
|
||||
}
|
||||
std::string Name() {
|
||||
return "TracklistQuery";
|
||||
}
|
||||
|
||||
virtual Result GetResult();
|
||||
virtual Result GetResult();
|
||||
|
||||
protected:
|
||||
virtual bool OnRun(Connection &db);
|
||||
protected:
|
||||
virtual bool OnRun(Connection &db);
|
||||
|
||||
Result result;
|
||||
Result result;
|
||||
|
||||
private:
|
||||
std::vector<Category> categories;
|
||||
|
||||
};
|
31
src/musikbox/WindowMessage.cpp
Executable file
31
src/musikbox/WindowMessage.cpp
Executable file
@ -0,0 +1,31 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "WindowMessage.h"
|
||||
|
||||
WindowMessage::WindowMessage(
|
||||
IWindowPtr target,
|
||||
int messageType,
|
||||
int64 data1,
|
||||
int64 data2)
|
||||
{
|
||||
this->target = target;
|
||||
this->messageType = messageType;
|
||||
this->data1 = data1;
|
||||
this->data2 = data2;
|
||||
}
|
||||
|
||||
IWindowPtr WindowMessage::Target() {
|
||||
return this->target;
|
||||
}
|
||||
|
||||
int WindowMessage::MessageType() {
|
||||
return this->messageType;
|
||||
}
|
||||
|
||||
int64 WindowMessage::UserData1() {
|
||||
return this->data1;
|
||||
}
|
||||
|
||||
int64 WindowMessage::UserData2() {
|
||||
return this->data2;
|
||||
}
|
22
src/musikbox/WindowMessage.h
Executable file
22
src/musikbox/WindowMessage.h
Executable file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "IWindowMessage.h"
|
||||
|
||||
class WindowMessage : public IWindowMessage {
|
||||
public:
|
||||
WindowMessage(
|
||||
IWindowPtr target,
|
||||
int messageType,
|
||||
int64 data1,
|
||||
int64 data2);
|
||||
|
||||
virtual IWindowPtr Target();
|
||||
virtual int MessageType();
|
||||
virtual int64 UserData1();
|
||||
virtual int64 UserData2();
|
||||
|
||||
private:
|
||||
IWindowPtr target;
|
||||
int messageType;
|
||||
int64 data1, data2;
|
||||
};
|
1
src/musikbox/WindowMessageQueue.cpp
Executable file
1
src/musikbox/WindowMessageQueue.cpp
Executable file
@ -0,0 +1 @@
|
||||
#include "stdafx.h"
|
7
src/musikbox/WindowMessageQueue.h
Executable file
7
src/musikbox/WindowMessageQueue.h
Executable file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IWindowMessage.h"
|
||||
|
||||
class WindowMessageQueue {
|
||||
public:
|
||||
};
|
@ -125,6 +125,7 @@
|
||||
<ClCompile Include="MultiLineEntry.cpp" />
|
||||
<ClCompile Include="ScrollableWindow.cpp" />
|
||||
<ClCompile Include="SingleLineEntry.cpp" />
|
||||
<ClCompile Include="TracklistQuery.cpp" />
|
||||
<ClCompile Include="TrackListView.cpp" />
|
||||
<ClCompile Include="Window.cpp" />
|
||||
<ClCompile Include="Colors.cpp" />
|
||||
@ -143,12 +144,15 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TransportWindow.cpp" />
|
||||
<ClCompile Include="WindowMessage.cpp" />
|
||||
<ClCompile Include="WindowMessageQueue.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CategoryListQuery.h" />
|
||||
<ClInclude Include="CategoryListView.h" />
|
||||
<ClInclude Include="IDisplayable.h" />
|
||||
<ClInclude Include="IWindowGroup.h" />
|
||||
<ClInclude Include="IWindowMessage.h" />
|
||||
<ClInclude Include="LayoutBase.h" />
|
||||
<ClInclude Include="ScrollAdapterBase.h" />
|
||||
<ClInclude Include="IScrollable.h" />
|
||||
@ -158,6 +162,7 @@
|
||||
<ClInclude Include="ScrollableWindow.h" />
|
||||
<ClInclude Include="ListWindow.h" />
|
||||
<ClInclude Include="SingleLineEntry.h" />
|
||||
<ClInclude Include="TracklistQuery.h" />
|
||||
<ClInclude Include="TrackListView.h" />
|
||||
<ClInclude Include="Window.h" />
|
||||
<ClInclude Include="Colors.h" />
|
||||
@ -173,6 +178,8 @@
|
||||
<ClInclude Include="SystemInfo.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="TransportWindow.h" />
|
||||
<ClInclude Include="WindowMessage.h" />
|
||||
<ClInclude Include="WindowMessageQueue.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\3rdparty\3rdparty.vcxproj">
|
||||
|
@ -72,6 +72,15 @@
|
||||
<ClCompile Include="LayoutBase.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TracklistQuery.cpp">
|
||||
<Filter>query</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowMessage.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowMessageQueue.cpp">
|
||||
<Filter>curses</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h" />
|
||||
@ -156,6 +165,18 @@
|
||||
<ClInclude Include="IDisplayable.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TracklistQuery.h">
|
||||
<Filter>query</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IWindowMessage.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowMessage.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowMessageQueue.h">
|
||||
<Filter>curses</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="curses">
|
||||
|
Loading…
x
Reference in New Issue
Block a user