aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSascha Montellese <sascha.montellese@gmail.com>2013-08-06 00:30:24 -0700
committerSascha Montellese <sascha.montellese@gmail.com>2013-08-06 00:30:24 -0700
commit3f422a00208e1338694ffedb8be1f5c68b6aa031 (patch)
tree0300d315b3823951186ee3223ff639a7233131cf
parent8b6a71faf9e0cb16db1b1cfcf7e780063597966c (diff)
parent995c5208a66580e30abbafa492262c8de04772d4 (diff)
Merge pull request #2988 from Montellese/xsp_extensions
smartplaylists: support for virtual folders
-rw-r--r--language/English/strings.po7
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp28
-rw-r--r--xbmc/filesystem/SmartPlaylistDirectory.cpp25
-rw-r--r--xbmc/playlists/SmartPlayList.cpp132
-rw-r--r--xbmc/playlists/SmartPlayList.h21
-rw-r--r--xbmc/utils/DatabaseUtils.h1
6 files changed, 175 insertions, 39 deletions
diff --git a/language/English/strings.po b/language/English/strings.po
index 6b7cbc66a1..830d3d2812 100644
--- a/language/English/strings.po
+++ b/language/English/strings.po
@@ -2531,7 +2531,12 @@ msgctxt "#613"
msgid "Sample rate"
msgstr ""
-#empty strings from id 614 to 619
+#: xbmc/playlists/SmartPlayList.cpp
+msgctxt "#614"
+msgid "Virtual folder"
+msgstr ""
+
+#empty strings from id 615 to 619
#: system/settings/settings.xml
msgctxt "#620"
diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
index aa06f377df..41be313636 100644
--- a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
+++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
@@ -103,7 +103,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
videodatabase.Open();
std::string basePath;
- if (m_type.Equals("songs") || m_type.Equals("albums") || m_type.Equals("artists") || m_type.Equals("mixed"))
+ if (CSmartPlaylist::IsMusicType(m_type))
basePath = "musicdb://";
else
basePath = "videodb://";
@@ -153,7 +153,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
}
else if (m_rule.m_field == FieldArtist || m_rule.m_field == FieldAlbumArtist)
{
- if (m_type.Equals("songs") || m_type.Equals("mixed") || m_type.Equals("albums") || m_type.Equals("artists"))
+ if (CSmartPlaylist::IsMusicType(m_type))
database.GetArtistsNav("musicdb://artists/", items, m_rule.m_field == FieldAlbumArtist, -1);
if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
{
@@ -165,7 +165,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
}
else if (m_rule.m_field == FieldAlbum)
{
- if (m_type.Equals("songs") || m_type.Equals("mixed") || m_type.Equals("albums"))
+ if (CSmartPlaylist::IsMusicType(m_type))
database.GetAlbumsNav("musicdb://albums/", items);
if (m_type.Equals("musicvideos") || m_type.Equals("mixed"))
{
@@ -182,9 +182,9 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
}
else if (m_rule.m_field == FieldYear)
{
- if (m_type.Equals("songs") || m_type.Equals("mixed") || m_type.Equals("albums"))
+ if (CSmartPlaylist::IsMusicType(m_type))
database.GetYearsNav("musicdb://years/", items);
- if (!m_type.Equals("songs") && !m_type.Equals("albums"))
+ if (!m_type.Equals("songs") && !m_type.Equals("albums") && !m_type.Equals("artists"))
{
CFileItemList items2;
videodatabase.GetYearsNav(basePath + "years/", items2, type);
@@ -242,7 +242,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
else
assert(false);
}
- else if (m_rule.m_field == FieldPlaylist)
+ else if (m_rule.m_field == FieldPlaylist || m_rule.m_field == FieldVirtualFolder)
{
// use filebrowser to grab another smart playlist
@@ -257,7 +257,19 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
{
CFileItemPtr item = items[i];
CSmartPlaylist playlist;
- if (playlist.OpenAndReadName(item->GetPath()))
+ // don't list unloadable smartplaylists or any referencable smartplaylists
+ // which do not match the type of the current smartplaylist
+ if (!playlist.Load(item->GetPath()) ||
+ (m_rule.m_field == FieldPlaylist &&
+ (!CSmartPlaylist::CheckTypeCompatibility(m_type, playlist.GetType()) ||
+ (!playlist.GetGroup().empty() || playlist.IsGroupMixed()))))
+ {
+ items.Remove(i);
+ i -= 1;
+ continue;
+ }
+
+ if (!playlist.GetName().empty())
item->SetLabel(playlist.GetName());
}
iLabel = 559;
@@ -316,7 +328,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
CStdString strHeading;
strHeading.Format(g_localizeStrings.Get(13401),g_localizeStrings.Get(iLabel));
pDialog->SetHeading(strHeading);
- pDialog->SetMultiSelection(m_rule.m_field != FieldPlaylist);
+ pDialog->SetMultiSelection(m_rule.m_field != FieldPlaylist && m_rule.m_field != FieldVirtualFolder);
if (!m_rule.m_parameter.empty())
pDialog->SetSelected(m_rule.m_parameter);
diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp
index 2b779da4b8..553772472d 100644
--- a/xbmc/filesystem/SmartPlaylistDirectory.cpp
+++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp
@@ -24,6 +24,7 @@
#include "FileItem.h"
#include "filesystem/Directory.h"
#include "filesystem/File.h"
+#include "filesystem/FileDirectoryFactory.h"
#include "music/MusicDatabase.h"
#include "playlists/SmartPlayList.h"
#include "settings/Settings.h"
@@ -64,19 +65,33 @@ namespace XFILE
bool CSmartPlaylistDirectory::GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items, const CStdString &strBaseDir /* = "" */, bool filter /* = false */)
{
bool success = false, success2 = false;
- std::set<CStdString> playlists;
+ std::vector<CStdString> virtualFolders;
SortDescription sorting;
sorting.limitEnd = playlist.GetLimit();
sorting.sortBy = playlist.GetOrder();
sorting.sortOrder = playlist.GetOrderAscending() ? SortOrderAscending : SortOrderDescending;
+ sorting.sortAttributes = playlist.GetOrderAttributes();
if (CSettings::Get().GetBool("filelists.ignorethewhensorting"))
- sorting.sortAttributes = SortAttributeIgnoreArticle;
+ sorting.sortAttributes = (SortAttribute)(sorting.sortAttributes | SortAttributeIgnoreArticle);
+ items.SetSortIgnoreFolders((sorting.sortAttributes & SortAttributeIgnoreFolders) == SortAttributeIgnoreFolders);
std::string option = !filter ? "xsp" : "filter";
const CStdString& group = playlist.GetGroup();
bool isGrouped = !group.empty() && !StringUtils::EqualsNoCase(group, "none") && !playlist.IsGroupMixed();
+ // get all virtual folders and add them to the item list
+ playlist.GetVirtualFolders(virtualFolders);
+ for (std::vector<CStdString>::const_iterator virtualFolder = virtualFolders.begin(); virtualFolder != virtualFolders.end(); virtualFolder++)
+ {
+ CFileItemPtr pItem = CFileItemPtr(new CFileItem(*virtualFolder, true));
+ if (CFileDirectoryFactory::Create(*virtualFolder, pItem.get()) != NULL)
+ {
+ pItem->SetSpecialSort(SortSpecialOnTop);
+ items.Add(pItem);
+ }
+ }
+
if (playlist.GetType().Equals("movies") ||
playlist.GetType().Equals("tvshows") ||
playlist.GetType().Equals("episodes"))
@@ -145,9 +160,7 @@ namespace XFILE
items.SetProperty(PROPERTY_PATH_DB, videoUrl.ToString());
}
}
- else if (playlist.GetType().Equals("artists") ||
- playlist.GetType().Equals("albums") ||
- playlist.GetType().Equals("songs") || playlist.GetType().Equals("mixed") || playlist.GetType().IsEmpty())
+ else if (playlist.IsMusicType() || playlist.GetType().IsEmpty())
{
CMusicDatabase db;
if (db.Open())
@@ -314,7 +327,7 @@ namespace XFILE
{
CFileItemList list;
bool filesExist = false;
- if (playlistType == "songs" || playlistType == "albums" || playlistType == "artists")
+ if (CSmartPlaylist::IsMusicType(playlistType))
filesExist = CDirectory::GetDirectory("special://musicplaylists/", list, ".xsp", false);
else // all others are video
filesExist = CDirectory::GetDirectory("special://videoplaylists/", list, ".xsp", false);
diff --git a/xbmc/playlists/SmartPlayList.cpp b/xbmc/playlists/SmartPlayList.cpp
index f728b7840b..2815b036d0 100644
--- a/xbmc/playlists/SmartPlayList.cpp
+++ b/xbmc/playlists/SmartPlayList.cpp
@@ -101,7 +101,8 @@ static const translateField fields[] = {
{ "audiolanguage", FieldAudioLanguage, SortByAudioLanguage, CSmartPlaylistRule::TEXTIN_FIELD, false, 21447 },
{ "subtitlelanguage", FieldSubtitleLanguage, SortBySubtitleLanguage, CSmartPlaylistRule::TEXTIN_FIELD, false, 21448 },
{ "random", FieldRandom, SortByRandom, CSmartPlaylistRule::TEXT_FIELD, false, 590 },
- { "playlist", FieldPlaylist, SortByPlaylistOrder, CSmartPlaylistRule::PLAYLIST_FIELD, false, 559 },
+ { "playlist", FieldPlaylist, SortByPlaylistOrder, CSmartPlaylistRule::PLAYLIST_FIELD, true, 559 },
+ { "virtualfolder", FieldVirtualFolder, SortByNone, CSmartPlaylistRule::PLAYLIST_FIELD, true, 614 },
{ "tag", FieldTag, SortByNone, CSmartPlaylistRule::TEXT_FIELD, true, 20459 },
{ "instruments", FieldInstruments, SortByNone, CSmartPlaylistRule::TEXT_FIELD, false, 21892 },
{ "biography", FieldBiography, SortByNone, CSmartPlaylistRule::TEXT_FIELD, false, 21887 },
@@ -552,6 +553,7 @@ vector<Field> CSmartPlaylistRule::GetFields(const CStdString &type)
fields.push_back(FieldVideoAspectRatio);
}
fields.push_back(FieldPlaylist);
+ fields.push_back(FieldVirtualFolder);
return fields;
}
@@ -1131,6 +1133,11 @@ CStdString CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, co
// translate the rules into SQL
for (CSmartPlaylistRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
{
+ // don't include playlists that are meant to be displayed
+ // as a virtual folders in the SQL WHERE clause
+ if (it->m_field == FieldVirtualFolder)
+ continue;
+
if (!rule.empty())
rule += m_type == CombinationAnd ? " AND " : " OR ";
rule += "(";
@@ -1142,20 +1149,22 @@ CStdString CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, co
{
referencedPlaylists.insert(playlistFile);
CSmartPlaylist playlist;
- playlist.Load(playlistFile);
- CStdString playlistQuery;
- // only playlists of same type will be part of the query
- if (playlist.GetType().Equals(strType) || (playlist.GetType().Equals("mixed") && (strType == "songs" || strType == "musicvideos")) || playlist.GetType().IsEmpty())
- {
- playlist.SetType(strType);
- playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
- }
- if (playlist.GetType().Equals(strType))
+ if (playlist.Load(playlistFile))
{
- if (it->m_operator == CSmartPlaylistRule::OPERATOR_DOES_NOT_EQUAL)
- currentRule.Format("NOT (%s)", playlistQuery.c_str());
- else
- currentRule = playlistQuery;
+ CStdString playlistQuery;
+ // only playlists of same type will be part of the query
+ if (playlist.GetType().Equals(strType) || (playlist.GetType().Equals("mixed") && (strType == "songs" || strType == "musicvideos")) || playlist.GetType().IsEmpty())
+ {
+ playlist.SetType(strType);
+ playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
+ }
+ if (playlist.GetType().Equals(strType))
+ {
+ if (it->m_operator == CSmartPlaylistRule::OPERATOR_DOES_NOT_EQUAL)
+ currentRule.Format("NOT (%s)", playlistQuery.c_str());
+ else
+ currentRule = playlistQuery;
+ }
}
}
}
@@ -1171,6 +1180,35 @@ CStdString CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, co
return rule;
}
+void CSmartPlaylistRuleCombination::GetVirtualFolders(const CStdString& strType, std::vector<CStdString> &virtualFolders) const
+{
+ for (vector<CSmartPlaylistRuleCombination>::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
+ it->GetVirtualFolders(strType, virtualFolders);
+
+ for (CSmartPlaylistRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
+ {
+ if ((it->m_field != FieldVirtualFolder && it->m_field != FieldPlaylist) || it->m_operator != CSmartPlaylistRule::OPERATOR_EQUALS)
+ continue;
+
+ CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName(it->m_parameter.at(0), strType);
+ if (playlistFile.empty())
+ continue;
+
+ if (it->m_field == FieldVirtualFolder)
+ virtualFolders.push_back(playlistFile);
+ else
+ {
+ // look for any virtual folders in the expanded playlists
+ CSmartPlaylist playlist;
+ if (!playlist.Load(playlistFile))
+ continue;
+
+ if (CSmartPlaylist::CheckTypeCompatibility(playlist.GetType(), strType))
+ playlist.GetVirtualFolders(virtualFolders);
+ }
+ }
+}
+
bool CSmartPlaylistRuleCombination::Load(const CVariant &obj)
{
if (!obj.isObject() && !obj.isArray())
@@ -1402,8 +1440,12 @@ bool CSmartPlaylist::Load(const CVariant &obj)
// and order
if (obj.isMember("order") && obj["order"].isMember("method") && obj["order"]["method"].isString())
{
- if (obj["order"].isMember("direction") && obj["order"]["direction"].isString())
- m_orderDirection = StringUtils::EqualsNoCase(obj["order"]["direction"].asString(), "ascending") ? SortOrderAscending : SortOrderDescending;
+ const CVariant &order = obj["order"];
+ if (order.isMember("direction") && order["direction"].isString())
+ m_orderDirection = StringUtils::EqualsNoCase(order["direction"].asString(), "ascending") ? SortOrderAscending : SortOrderDescending;
+
+ if (order.isMember("ignorefolders") && obj["ignorefolders"].isBoolean())
+ m_orderAttributes = obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
m_orderField = CSmartPlaylistRule::TranslateOrder(obj["order"]["method"].asString().c_str());
}
@@ -1456,6 +1498,11 @@ bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const CStdString &encodi
const char *direction = order->Attribute("direction");
if (direction)
m_orderDirection = StringUtils::EqualsNoCase(direction, "ascending") ? SortOrderAscending : SortOrderDescending;
+
+ const char *ignorefolders = order->Attribute("ignorefolders");
+ if (ignorefolders != NULL)
+ m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true") ? SortAttributeIgnoreFolders : SortAttributeNone;
+
m_orderField = CSmartPlaylistRule::TranslateOrder(order->FirstChild()->Value());
}
return true;
@@ -1513,6 +1560,8 @@ bool CSmartPlaylist::Save(const CStdString &path) const
TiXmlText order(CSmartPlaylistRule::TranslateOrder(m_orderField).c_str());
TiXmlElement nodeOrder("order");
nodeOrder.SetAttribute("direction", m_orderDirection == SortOrderDescending ? "descending" : "ascending");
+ if (m_orderAttributes & SortAttributeIgnoreFolders)
+ nodeOrder.SetAttribute("ignorefolders", "true");
nodeOrder.InsertEndChild(order);
pRoot->InsertEndChild(nodeOrder);
}
@@ -1550,6 +1599,7 @@ bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
obj["order"] = CVariant(CVariant::VariantTypeObject);
obj["order"]["method"] = CSmartPlaylistRule::TranslateOrder(m_orderField);
obj["order"]["direction"] = m_orderDirection == SortOrderDescending ? "descending" : "ascending";
+ obj["order"]["ignorefolders"] = (m_orderAttributes & SortAttributeIgnoreFolders);
}
return true;
@@ -1573,6 +1623,7 @@ void CSmartPlaylist::Reset()
m_limit = 0;
m_orderField = SortByNone;
m_orderDirection = SortOrderNone;
+ m_orderAttributes = SortAttributeNone;
m_playlistType = "songs"; // sane default
m_group.clear();
m_groupMixed = false;
@@ -1588,17 +1639,44 @@ void CSmartPlaylist::SetType(const CStdString &type)
m_playlistType = type;
}
+bool CSmartPlaylist::IsVideoType() const
+{
+ return IsVideoType(m_playlistType);
+}
+
+bool CSmartPlaylist::IsMusicType() const
+{
+ return IsMusicType(m_playlistType);
+}
+
+bool CSmartPlaylist::IsVideoType(const CStdString &type)
+{
+ return type == "movies" || type == "tvshows" || type == "episodes" ||
+ type == "musicvideos" || type == "mixed";
+}
+
+bool CSmartPlaylist::IsMusicType(const CStdString &type)
+{
+ return type == "artists" || type == "albums" ||
+ type == "songs" || type == "mixed";
+}
+
CStdString CSmartPlaylist::GetWhereClause(const CDatabase &db, set<CStdString> &referencedPlaylists) const
{
return m_ruleCombination.GetWhereClause(db, GetType(), referencedPlaylists);
}
+void CSmartPlaylist::GetVirtualFolders(std::vector<CStdString> &virtualFolders) const
+{
+ m_ruleCombination.GetVirtualFolders(GetType(), virtualFolders);
+}
+
CStdString CSmartPlaylist::GetSaveLocation() const
{
- if (m_playlistType == "songs" || m_playlistType == "albums" || m_playlistType == "artists")
- return "music";
- else if (m_playlistType == "mixed")
+ if (m_playlistType == "mixed")
return "mixed";
+ if (IsMusicType())
+ return "music";
// all others are video
return "video";
}
@@ -1630,3 +1708,19 @@ bool CSmartPlaylist::IsEmpty(bool ignoreSortAndLimit /* = true */) const
return empty;
}
+
+bool CSmartPlaylist::CheckTypeCompatibility(const CStdString &typeLeft, const CStdString &typeRight)
+{
+ if (typeLeft.Equals(typeRight))
+ return true;
+
+ if (typeLeft.Equals("mixed") &&
+ (typeRight.Equals("songs") || typeRight.Equals("musicvideos")))
+ return true;
+
+ if (typeRight.Equals("mixed") &&
+ (typeLeft.Equals("songs") || typeLeft.Equals("musicvideos")))
+ return true;
+
+ return false;
+}
diff --git a/xbmc/playlists/SmartPlayList.h b/xbmc/playlists/SmartPlayList.h
index 0e2da8c357..1dc337417b 100644
--- a/xbmc/playlists/SmartPlayList.h
+++ b/xbmc/playlists/SmartPlayList.h
@@ -79,7 +79,12 @@ public:
virtual bool Save(TiXmlNode *parent) const;
virtual bool Save(CVariant &obj) const;
+ CStdString GetParameter() const;
+ void SetParameter(const CStdString &value);
+ void SetParameter(const std::vector<CStdString> &values);
+ CStdString GetLocalizedRule() const;
CStdString GetWhereClause(const CDatabase &db, const CStdString& strType) const;
+
static Field TranslateField(const char *field);
static CStdString TranslateField(Field field);
static SortBy TranslateOrder(const char *order);
@@ -100,10 +105,6 @@ public:
static FIELD_TYPE GetFieldType(Field field);
static bool IsFieldBrowseable(Field field);
- CStdString GetLocalizedRule() const;
- CStdString GetParameter() const;
- void SetParameter(const CStdString &value);
- void SetParameter(const std::vector<CStdString> &values);
Field m_field;
SEARCH_OPERATOR m_operator;
@@ -136,6 +137,7 @@ public:
virtual bool Save(CVariant &obj) const;
CStdString GetWhereClause(const CDatabase &db, const CStdString& strType, std::set<CStdString> &referencedPlaylists) const;
+ void GetVirtualFolders(const CStdString& strType, std::vector<CStdString> &virtualFolders) const;
std::string TranslateCombinationType() const;
Combination GetType() const { return m_type; }
@@ -177,6 +179,8 @@ public:
void SetType(const CStdString &type); // music, video, mixed
const CStdString& GetName() const { return m_playlistName; };
const CStdString& GetType() const { return m_playlistType; };
+ bool IsVideoType() const;
+ bool IsMusicType() const;
void SetMatchAllRules(bool matchAll) { m_ruleCombination.SetType(matchAll ? CSmartPlaylistRuleCombination::CombinationAnd : CSmartPlaylistRuleCombination::CombinationOr); }
bool GetMatchAllRules() const { return m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationAnd; }
@@ -186,10 +190,11 @@ public:
void SetOrder(SortBy order) { m_orderField = order; };
SortBy GetOrder() const { return m_orderField; };
-
void SetOrderAscending(bool orderAscending) { m_orderDirection = orderAscending ? SortOrderAscending : SortOrderDescending; };
bool GetOrderAscending() const { return m_orderDirection != SortOrderDescending; };
SortOrder GetOrderDirection() const { return m_orderDirection; }
+ void SetOrderAttributes(SortAttribute attributes) { m_orderAttributes = attributes; }
+ SortAttribute GetOrderAttributes() const { return m_orderAttributes; }
void SetGroup(const CStdString &group) { m_group = group; }
const CStdString& GetGroup() const { return m_group; }
@@ -205,12 +210,17 @@ public:
\param needWhere whether we need to prepend the where clause with "WHERE "
*/
CStdString GetWhereClause(const CDatabase &db, std::set<CStdString> &referencedPlaylists) const;
+ void GetVirtualFolders(std::vector<CStdString> &virtualFolders) const;
CStdString GetSaveLocation() const;
static void GetAvailableFields(const std::string &type, std::vector<std::string> &fieldList);
static void GetAvailableOperators(std::vector<std::string> &operatorList);
+ static bool IsVideoType(const CStdString &type);
+ static bool IsMusicType(const CStdString &type);
+ static bool CheckTypeCompatibility(const CStdString &typeLeft, const CStdString &typeRight);
+
bool IsEmpty(bool ignoreSortAndLimit = true) const;
private:
friend class CGUIDialogSmartPlaylistEditor;
@@ -229,6 +239,7 @@ private:
unsigned int m_limit;
SortBy m_orderField;
SortOrder m_orderDirection;
+ SortAttribute m_orderAttributes;
CStdString m_group;
bool m_groupMixed;
diff --git a/xbmc/utils/DatabaseUtils.h b/xbmc/utils/DatabaseUtils.h
index 67e6e761dc..ed7ee757cb 100644
--- a/xbmc/utils/DatabaseUtils.h
+++ b/xbmc/utils/DatabaseUtils.h
@@ -54,6 +54,7 @@ typedef enum {
FieldBitrate,
FieldListeners,
FieldPlaylist,
+ FieldVirtualFolder,
FieldRandom,
FieldDateTaken,