Merge branch 'single_query_playcounts'
authorJonathan Marshall <jmarshall@never.you.mind>
Sun, 29 May 2011 21:28:03 +0000 (09:28 +1200)
committerJonathan Marshall <jmarshall@never.you.mind>
Sun, 29 May 2011 21:51:18 +0000 (09:51 +1200)
* single_query_playcounts:
  changed: use specialised dropIndex function call for dropping indexes.
  added: specialised dropIndex function for managing the difference between DROP INDEX calls on sqlite and mysql.
  switch to mysql-compatible DROP INDEX statements, and alter sqlite3 db layer so that it converts these to sqlite-friendly statements
  remove the IF EXISTS from the DROP INDEX - mysql doesn't support it
  remote old pre-addons xml import code that wasn't ever going to work
  fix grammar in comments
  speed up retrieval of library info when browsing by file
  update comment with note regarding items in a folder with content set that aren't yet in the db
  add an extra column to the video database with the parent path id to allow faster lookup of items by folder
  change AddPath so it checks for a previously existing path and make it public
  make sure paths with content set are imported from xml prior to importing media, ensuring we can set the basepath correctly
  use a single query for retrieving playcounts to speed up file listings

Conflicts:
xbmc/video/windows/GUIWindowVideoNav.cpp

12 files changed:
xbmc/dbwrappers/dataset.cpp
xbmc/dbwrappers/dataset.h
xbmc/dbwrappers/mysqldataset.cpp
xbmc/dbwrappers/mysqldataset.h
xbmc/dbwrappers/sqlitedataset.cpp
xbmc/dbwrappers/sqlitedataset.h
xbmc/video/VideoDatabase.cpp
xbmc/video/VideoDatabase.h
xbmc/video/VideoInfoScanner.cpp
xbmc/video/VideoInfoTag.cpp
xbmc/video/VideoInfoTag.h
xbmc/video/windows/GUIWindowVideoNav.cpp

index e9b8944..3c8bd17 100644 (file)
@@ -63,6 +63,33 @@ int Database::connectFull(const char *newHost, const char *newPort, const char *
   return connect(true);
 }
 
+string Database::prepare(const char *format, ...)
+{
+  string result = "";
+
+  va_list args;
+  va_start(args, format);
+  result = vprepare(format, args);
+  va_end(args);
+
+  return result;
+}
+
+string Database::vprepare(const char *format, va_list args)
+{
+  char *p = NULL;
+  string result = "";
+
+  vsprintf(p, format, args);
+
+  if ( p )
+  {
+    result = p;
+    free(p);
+  }
+
+  return result;
+}
 
 
 
index 190e076..efc1171 100644 (file)
@@ -133,7 +133,20 @@ public:
   virtual void rollback_transaction() {};
 
 /* virtual methods for formatting */
-  virtual std::string vprepare(const char *format, va_list args) { return std::string(""); };
+
+  /*! \brief Prepare a SQL statement for execution or querying using C printf nomenclature.
+   \param format - C printf compliant format string
+   \param ... - optional comma seperated list of variables for substitution in format string placeholders.
+   \return escaped and formatted string.
+   */
+  virtual std::string prepare(const char *format, ...);
+
+  /*! \brief Prepare a SQL statement for execution or querying using C printf nomenclature
+   \param format - C printf compliant format string
+   \param args - va_list of variables for substitution in format string placeholders.
+   \return escaped and formatted string.
+   */
+  virtual std::string vprepare(const char *format, va_list args);
 
   virtual bool in_transaction() {return false;};
 
@@ -284,6 +297,13 @@ public:
 /* Refresh dataset (reopen it and set the same cursor position) */
   virtual void refresh();
 
+  /*! \brief Drop an index from the database table, provided it exists.
+   \param table - name of the table the index to be dropped is associated with
+   \param index - name of the index to be dropped
+   \return true when the index is guaranteed to no longer exist in the database.
+   */
+  virtual bool dropIndex(const char *table, const char *index) { return false; }
+
 /* Go to record No (starting with 0) */
   virtual bool seek(int pos=0);
 /* Go to record No (starting with 1) */
index 0b7638a..9364c0d 100644 (file)
@@ -1221,7 +1221,29 @@ void MysqlDataset::fill_fields() {
 
 
 //------------- public functions implementation -----------------//
-//FILE* file;
+bool MysqlDataset::dropIndex(const char *table, const char *index)
+{
+  string sql;
+  string sql_prepared;
+
+  sql = "SELECT * FROM information_schema.statistics WHERE TABLE_SCHEMA=DATABASE() AND table_name='%s' AND index_name='%s'";
+  sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
+
+  if (!query(sql_prepared))
+    return false;
+
+  if (num_rows())
+  {
+    sql = "ALTER TABLE %s DROP INDEX %s";
+    sql_prepared = static_cast<MysqlDatabase*>(db)->prepare(sql.c_str(), table, index);
+
+    if (exec(sql_prepared) != MYSQL_OK)
+      return false;
+  }
+
+  return true;
+}
+
 int MysqlDataset::exec(const string &sql) {
   if (!handle()) throw DbErrors("No Database Connection");
   string qry = sql;
@@ -1230,6 +1252,7 @@ int MysqlDataset::exec(const string &sql) {
 
   // enforce the "auto_increment" keyword to be appended to "integer primary key"
   size_t loc;
+
   if ( (loc=qry.find("integer primary key")) != string::npos)
   {
     qry = qry.insert(loc + 19, " auto_increment ");
@@ -1240,9 +1263,8 @@ int MysqlDataset::exec(const string &sql) {
   {
     qry += " CHARACTER SET utf8 COLLATE utf8_general_ci";
   }
-
   // sqlite3 requires the BEGIN and END pragmas when creating triggers. mysql does not.
-  if ( qry.find("CREATE TRIGGER") != string::npos )
+  else if ( qry.find("CREATE TRIGGER") != string::npos )
   {
     if ( (loc=qry.find("BEGIN ")) != string::npos )
     {
index c005c13..8137180 100644 (file)
@@ -93,7 +93,6 @@ private:
   void mysqlStrAccumReset(StrAccum *p);
   void mysqlStrAccumInit(StrAccum *p, char *zBase, int n, int mx);
   char *mysql_vmprintf(const char *zFormat, va_list ap);
-
 };
 
 
@@ -174,6 +173,7 @@ or insert() operations default = false) */
 /* Go to record No (starting with 0) */
   virtual bool seek(int pos=0);
 
+  virtual bool dropIndex(const char *table, const char *index);
 };
 } //namespace
 #endif
index d92e968..f7b308c 100644 (file)
@@ -471,6 +471,15 @@ void SqliteDataset::fill_fields() {
 
 
 //------------- public functions implementation -----------------//
+bool SqliteDataset::dropIndex(const char *table, const char *index)
+{
+  string sql;
+
+  sql = static_cast<SqliteDatabase*>(db)->prepare("DROP INDEX IF EXISTS %s", index);
+
+  return (exec(sql) == SQLITE_OK);
+}
+
 
 int SqliteDataset::exec(const string &sql) {
   if (!handle()) throw DbErrors("No Database Connection");
@@ -504,6 +513,17 @@ int SqliteDataset::exec(const string &sql) {
       }
     }
   }
+  // Strip ON table from DROP INDEX statements:
+  // before: DROP INDEX foo ON table
+  // after:  DROP INDEX foo
+  size_t pos = qry.find("DROP INDEX ");
+  if ( pos != string::npos )
+  {
+    pos = qry.find(" ON ", pos+1);
+
+    if ( pos != string::npos )
+      qry = qry.substr(0, pos);
+  }
 
   if((res = db->setErr(sqlite3_exec(handle(),qry.c_str(),&callback,&exec_res,&errmsg),qry.c_str())) == SQLITE_OK)
     return res;
index 9152547..8203d6e 100644 (file)
@@ -167,7 +167,7 @@ or insert() operations default = false) */
 /* Go to record No (starting with 0) */
   virtual bool seek(int pos=0);
 
-
+  virtual bool dropIndex(const char *table, const char *index);
 };
 } //namespace
 #endif
index a97cb36..45cca98 100644 (file)
@@ -303,10 +303,10 @@ bool CVideoDatabase::CreateTables()
     m_pDS->exec("CREATE UNIQUE INDEX ix_setlinkmovie_2 ON setlinkmovie ( idMovie, idSet)\n");
 
     // create basepath indices
-    m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c22(255) )");
-    m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c13(255) )");
-    m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c18(255) )");
-    m_pDS->exec("CREATE INDEX ixTVShowBasePath on tvshow ( c16(255) )");
+    m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
+    m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
+    m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
+    m_pDS->exec("CREATE INDEX ixTVShowBasePath on tvshow ( c17(12) )");
   }
   catch (...)
   {
@@ -533,7 +533,10 @@ int CVideoDatabase::AddPath(const CStdString& strPath)
   CStdString strSQL;
   try
   {
-    int idPath;
+    int idPath = GetPathId(strPath);
+    if (idPath >= 0)
+      return idPath; // already have the path
+
     if (NULL == m_pDB.get()) return -1;
     if (NULL == m_pDS.get()) return -1;
 
@@ -590,10 +593,7 @@ int CVideoDatabase::AddFile(const CStdString& strFileNameAndPath)
     CStdString strFileName, strPath;
     SplitPath(strFileNameAndPath,strPath,strFileName);
 
-    int idPath=GetPathId(strPath);
-    if (idPath < 0)
-      idPath = AddPath(strPath);
-
+    int idPath = AddPath(strPath);
     if (idPath < 0)
       return -1;
 
@@ -639,9 +639,7 @@ bool CVideoDatabase::SetPathHash(const CStdString &path, const CStdString &hash)
       if (!CDirectory::Exists(path))
         return false;
     }
-    int idPath = GetPathId(path);
-    if (idPath < 0)
-      idPath = AddPath(path);
+    int idPath = AddPath(path);
     if (idPath < 0) return false;
 
     CStdString strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
@@ -1002,9 +1000,7 @@ int CVideoDatabase::AddTvShow(const CStdString& strPath)
     m_pDS->exec(strSQL.c_str());
     int idTvShow = (int)m_pDS->lastinsertid();
 
-    int idPath = GetPathId(strPath);
-    if (idPath < 0)
-      idPath = AddPath(strPath);
+    int idPath = AddPath(strPath);
     strSQL=PrepareSQL("insert into tvshowlinkpath values (%i,%i)",idTvShow,idPath);
     m_pDS->exec(strSQL.c_str());
 
@@ -3297,12 +3293,10 @@ void CVideoDatabase::SetScraperForPath(const CStdString& filePath, const Scraper
   {
     if (NULL == m_pDB.get()) return ;
     if (NULL == m_pDS.get()) return ;
-    int idPath = GetPathId(filePath);
+
+    int idPath = AddPath(filePath);
     if (idPath < 0)
-    { // no path found - we have to add one
-      idPath = AddPath(filePath);
-      if (idPath < 0) return ;
-    }
+      return;
 
     // Update
     CStdString strSQL;
@@ -3469,6 +3463,26 @@ bool CVideoDatabase::UpdateOldVersion(int iVersion)
       m_pDS->exec("ALTER TABLE actorlinktvshow ADD iOrder integer");
       m_pDS->exec("ALTER TABLE actorlinkepisode ADD iOrder integer");
     }
+    if (iVersion < 52)
+    { // Add basepath link to path table for faster content retrieval, and indicies
+      m_pDS->exec("ALTER table movie add c23 text");
+      m_pDS->exec("ALTER table episode add c23 text");
+      m_pDS->exec("ALTER table musicvideo add c23 text");
+      m_pDS->exec("ALTER table tvshow add c23 text");
+      m_pDS->dropIndex("movie", "ixMovieBasePath");
+      m_pDS->dropIndex("musicvideo", "ixMusicVideoBasePath");
+      m_pDS->dropIndex("episode", "ixEpisodeBasePath");
+      m_pDS->dropIndex("tvshow", "ixTVShowBasePath");
+      m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
+      m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
+      m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
+      m_pDS->exec("CREATE INDEX ixTVShowBasePath ON tvshow ( c17(12) )");
+      // now update the base path links
+      UpdateBasePathID("movie", "idMovie", VIDEODB_ID_BASEPATH, VIDEODB_ID_PARENTPATHID);
+      UpdateBasePathID("musicvideo", "idMVideo", VIDEODB_ID_MUSICVIDEO_BASEPATH, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
+      UpdateBasePathID("episode", "idEpisode", VIDEODB_ID_EPISODE_BASEPATH, VIDEODB_ID_EPISODE_PARENTPATHID);
+      UpdateBasePathID("tvshow", "idShow", VIDEODB_ID_TV_BASEPATH, VIDEODB_ID_TV_PARENTPATHID);
+    }
   }
   catch (...)
   {
@@ -3480,6 +3494,16 @@ bool CVideoDatabase::UpdateOldVersion(int iVersion)
   return true;
 }
 
+bool CVideoDatabase::LookupByFolders(const CStdString &path, bool shows)
+{
+  SScanSettings settings;
+  bool foundDirectly = false;
+  ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
+  if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
+    return false; // episodes
+  return settings.parent_name_root; // shows, movies, musicvids
+}
+
 void CVideoDatabase::UpdateBasePath(const char *table, const char *id, int column, bool shows)
 {
   CStdString query;
@@ -3496,13 +3520,7 @@ void CVideoDatabase::UpdateBasePath(const char *table, const char *id, int colum
     map<CStdString, bool>::iterator i = paths.find(path);
     if (i == paths.end())
     {
-      SScanSettings settings;
-      bool foundDirectly = false;
-      ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
-      if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
-        paths.insert(make_pair(path, false)); // episodes
-      else
-        paths.insert(make_pair(path, settings.parent_name_root)); // shows, movies, musicvids
+      paths.insert(make_pair(path, LookupByFolders(path, shows)));
       i = paths.find(path);
     }
     CStdString filename;
@@ -3519,6 +3537,62 @@ void CVideoDatabase::UpdateBasePath(const char *table, const char *id, int colum
   m_pDS2->close();
 }
 
+void CVideoDatabase::UpdateBasePathID(const char *table, const char *id, int column, int idColumn)
+{
+  CStdString query = PrepareSQL("SELECT %s,c%02d from %s", id, column, table);
+  m_pDS2->query(query.c_str());
+  while (!m_pDS2->eof())
+  {
+    int rowID = m_pDS2->fv(0).get_asInt();
+    CStdString path(m_pDS2->fv(1).get_asString());
+    // find the parent path of this item
+    int pathID = AddPath(URIUtils::GetParentPath(path));
+    if (pathID >= 0)
+    {
+      CStdString sql = PrepareSQL("UPDATE %s SET c%02d=%d WHERE %s=%d", table, idColumn, pathID, id, rowID);
+      m_pDS->exec(sql.c_str());
+    }
+    m_pDS2->next();
+  }
+  m_pDS2->close();
+}
+
+bool CVideoDatabase::GetPlayCounts(CFileItemList &items)
+{
+  int pathID = GetPathId(items.m_strPath);
+  if (pathID < 0)
+    return false; // path (and thus files) aren't in the database
+
+  try
+  {
+    // error!
+    if (NULL == m_pDB.get()) return false;
+    if (NULL == m_pDS.get()) return false;
+
+    // TODO: also test a single query for the above and below
+    CStdString sql = PrepareSQL("select strFilename,playCount from files where idPath=%i", pathID);
+    if (RunQuery(sql) <= 0)
+      return false;
+
+    items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
+    while (!m_pDS->eof())
+    {
+      CStdString path;
+      ConstructPath(path, items.m_strPath, m_pDS->fv(0).get_asString());
+      CFileItemPtr item = items.Get(path);
+      if (item)
+        item->GetVideoInfoTag()->m_playCount = m_pDS->fv(1).get_asInt();
+      m_pDS->next();
+    }
+    return true;
+  }
+  catch (...)
+  {
+    CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
+  }
+  return false;
+}
+
 int CVideoDatabase::GetPlayCount(const CFileItem &item)
 {
   int id = GetFileId(item);
@@ -7354,6 +7428,34 @@ void CVideoDatabase::ImportFromXML(const CStdString &path)
     CStdString tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
     CVideoInfoScanner scanner;
     set<CStdString> actors;
+    // add paths first (so we have scraper settings available)
+    TiXmlElement *path = root->FirstChildElement("paths");
+    path = path->FirstChildElement();
+    while (path)
+    {
+      CStdString strPath;
+      if (XMLUtils::GetString(path,"url",strPath))
+        AddPath(strPath);
+
+      CStdString content;
+      if (XMLUtils::GetString(path,"content", content))
+      { // check the scraper exists, if so store the path
+        AddonPtr addon;
+        CStdString uuid;
+        XMLUtils::GetString(path,"scraperID",uuid);
+        if (CAddonMgr::Get().GetAddon(uuid, addon))
+        {
+          SScanSettings settings;
+          ScraperPtr scraper = boost::dynamic_pointer_cast<CScraper>(addon);
+          // FIXME: scraper settings are not exported?
+          scraper->SetPathSettings(TranslateContent(content), "");
+          XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
+          XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
+          SetScraperForPath(strPath,scraper,settings);
+        }
+      }
+      path = path->NextSiblingElement();
+    }
     movie = root->FirstChildElement();
     while (movie)
     {
@@ -7362,7 +7464,8 @@ void CVideoDatabase::ImportFromXML(const CStdString &path)
       {
         info.Load(movie);
         CFileItem item(info);
-        scanner.AddVideo(&item,CONTENT_MOVIES);
+        bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.m_strPath) : false;
+        scanner.AddVideo(&item, CONTENT_MOVIES, useFolders);
         SetPlayCount(item, info.m_playCount, info.m_lastPlayed);
         CStdString strFileName(info.m_strTitle);
         if (GetExportVersion() >= 1 && info.m_iYear > 0)
@@ -7378,7 +7481,8 @@ void CVideoDatabase::ImportFromXML(const CStdString &path)
       {
         info.Load(movie);
         CFileItem item(info);
-        scanner.AddVideo(&item,CONTENT_MUSICVIDEOS);
+        bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.m_strPath) : false;
+        scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders);
         SetPlayCount(item, info.m_playCount, info.m_lastPlayed);
         CStdString strFileName(info.m_strArtist + "." + info.m_strTitle);
         if (GetExportVersion() >= 1 && info.m_iYear > 0)
@@ -7395,7 +7499,8 @@ void CVideoDatabase::ImportFromXML(const CStdString &path)
         URIUtils::AddSlashAtEnd(info.m_strPath);
         DeleteTvShow(info.m_strPath);
         CFileItem item(info);
-        int showID = scanner.AddVideo(&item,CONTENT_TVSHOWS);
+        bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.m_strPath, true) : false;
+        int showID = scanner.AddVideo(&item, CONTENT_TVSHOWS, useFolders);
         current++;
         CStdString showDir(GetSafeFile(tvshowsDir, info.m_strTitle));
         CFile::Cache(URIUtils::AddFileToFolder(showDir, "folder.jpg"), item.GetCachedVideoThumb());
@@ -7422,40 +7527,6 @@ void CVideoDatabase::ImportFromXML(const CStdString &path)
         // and fetch season thumbs
         scanner.FetchSeasonThumbs(showID, showDir, false, true);
       }
-      else if (strnicmp(movie->Value(), "paths", 5) == 0)
-      {
-        const TiXmlElement* path = movie->FirstChildElement("path");
-        while (path)
-        {
-          CStdString strPath;
-          XMLUtils::GetString(path,"url",strPath);
-          CStdString content;
-
-          if (XMLUtils::GetString(path,"content", content))
-          { // check the scraper exists, if so store the path
-            AddonPtr addon;
-            CStdString uuid;
-
-            if (!XMLUtils::GetString(path,"scraperID",uuid))
-            { // support pre addons exports
-              XMLUtils::GetString(path, "scraperpath", uuid);
-              uuid = URIUtils::GetFileName(uuid);
-            }
-
-            if (CAddonMgr::Get().GetAddon(uuid, addon))
-            {
-              SScanSettings settings;
-              ScraperPtr scraper = boost::dynamic_pointer_cast<CScraper>(addon);
-              // FIXME: scraper settings are not exported?
-              scraper->SetPathSettings(TranslateContent(content), "");
-              XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
-              XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
-              SetScraperForPath(strPath,scraper,settings);
-            }
-          }
-          path = path->NextSiblingElement();
-        }
-      }
       movie = movie->NextSiblingElement();
       if (progress && total)
       {
@@ -7725,3 +7796,39 @@ bool CVideoDatabase::GetItemForPath(const CStdString &content, const CStdString
   }
   return false;
 }
+
+bool CVideoDatabase::GetItemsForPath(const CStdString &content, const CStdString &strPath, CFileItemList &items)
+{
+  CStdString path(strPath);
+  
+  if(URIUtils::IsMultiPath(path))
+    path = CMultiPathDirectory::GetFirstPath(path);
+  
+  int pathID = GetPathId(path);
+  if (pathID < 0)
+    return false;
+
+  if (content == "movies")
+  {
+    CStdString where = PrepareSQL("where c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID);
+    GetMoviesByWhere("", where, "", items);
+  }
+  else if (content == "episodes")
+  {
+    CStdString where = PrepareSQL("where c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID);
+    GetEpisodesByWhere("", where, items);
+  }
+  else if (content == "tvshows")
+  {
+    CStdString where = PrepareSQL("where c%02d=%d", VIDEODB_ID_TV_PARENTPATHID, pathID);
+    GetTvShowsByWhere("", where, items);
+  }
+  else if (content == "musicvideos")
+  {
+    CStdString where = PrepareSQL("where c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID);
+    GetMusicVideosByWhere("", where, items);
+  }
+  for (int i = 0; i < items.Size(); i++)
+    items[i]->m_strPath = items[i]->GetVideoInfoTag()->m_basePath;
+  return items.Size() > 0;
+}
index 713d51b..e3fcde3 100644 (file)
@@ -59,7 +59,7 @@ namespace VIDEO
 
 // these defines are based on how many columns we have and which column certain data is going to be in
 // when we do GetDetailsForMovie()
-#define VIDEODB_MAX_COLUMNS 23
+#define VIDEODB_MAX_COLUMNS 24
 #define VIDEODB_DETAILS_FILEID                 1
 #define VIDEODB_DETAILS_FILE                   VIDEODB_MAX_COLUMNS + 2
 #define VIDEODB_DETAILS_PATH                   VIDEODB_MAX_COLUMNS + 3
@@ -118,6 +118,7 @@ typedef enum // this enum MUST match the offset struct further down!! and make s
   VIDEODB_ID_FANART = 20,
   VIDEODB_ID_COUNTRY = 21,
   VIDEODB_ID_BASEPATH = 22,
+  VIDEODB_ID_PARENTPATHID = 23,
   VIDEODB_ID_MAX
 } VIDEODB_IDS;
 
@@ -149,7 +150,8 @@ const struct SDbTableOffsets
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strTrailer) },
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_fanart.m_xml) },
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strCountry) },
-  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) }
+  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) },
+  { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_parentPathID) }
 };
 
 typedef enum // this enum MUST match the offset struct further down!! and make sure to keep min and max at -1 and sizeof(offsets)
@@ -172,6 +174,7 @@ typedef enum // this enum MUST match the offset struct further down!! and make s
   VIDEODB_ID_TV_STUDIOS = 14,
   VIDEODB_ID_TV_SORTTITLE = 15,
   VIDEODB_ID_TV_BASEPATH = 16,
+  VIDEODB_ID_TV_PARENTPATHID = 17,
   VIDEODB_ID_TV_MAX
 } VIDEODB_TV_IDS;
 
@@ -193,7 +196,8 @@ const struct SDbTableOffsets DbTvShowOffsets[] =
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strMPAARating)},
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strStudio)},
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strSortTitle)},
-  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) }
+  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) },
+  { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_parentPathID) }
 };
 
 typedef enum // this enum MUST match the offset struct further down!! and make sure to keep min and max at -1 and sizeof(offsets)
@@ -218,6 +222,7 @@ typedef enum // this enum MUST match the offset struct further down!! and make s
   VIDEODB_ID_EPISODE_SORTEPISODE = 16,
   VIDEODB_ID_EPISODE_BOOKMARK = 17,
   VIDEODB_ID_EPISODE_BASEPATH = 18,
+  VIDEODB_ID_EPISODE_PARENTPATHID = 19,
   VIDEODB_ID_EPISODE_MAX
 } VIDEODB_EPISODE_IDS;
 
@@ -241,7 +246,8 @@ const struct SDbTableOffsets DbEpisodeOffsets[] =
   { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_iSpecialSortSeason) },
   { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_iSpecialSortEpisode) },
   { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_iBookmarkId) },
-  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) }
+  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) },
+  { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_parentPathID) }
 };
 
 typedef enum // this enum MUST match the offset struct further down!! and make sure to keep min and max at -1 and sizeof(offsets)
@@ -261,6 +267,7 @@ typedef enum // this enum MUST match the offset struct further down!! and make s
   VIDEODB_ID_MUSICVIDEO_GENRE = 11,
   VIDEODB_ID_MUSICVIDEO_TRACK = 12,
   VIDEODB_ID_MUSICVIDEO_BASEPATH = 13,
+  VIDEODB_ID_MUSICVIDEO_PARENTPATHID = 14,
   VIDEODB_ID_MUSICVIDEO_MAX
 } VIDEODB_MUSICVIDEO_IDS;
 
@@ -279,7 +286,8 @@ const struct SDbTableOffsets DbMusicVideoOffsets[] =
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strArtist) },
   { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_strGenre) },
   { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_iTrack) },
-  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) }
+  { VIDEODB_TYPE_STRING, my_offsetof(CVideoInfoTag,m_basePath) },
+  { VIDEODB_TYPE_INT, my_offsetof(CVideoInfoTag,m_parentPathID) }
 };
 
 #define COMPARE_PERCENTAGE     0.90f // 90%
@@ -328,24 +336,30 @@ public:
   /*! \brief Increment the playcount of an item
    Increments the playcount and updates the last played date
    \param item CFileItem to increment the playcount for
-   \sa GetPlayCount, SetPlayCount
+   \sa GetPlayCount, SetPlayCount, GetPlayCounts
    */
   void IncrementPlayCount(const CFileItem &item);
 
   /*! \brief Get the playcount of an item
    \param item CFileItem to get the playcount for
    \return the playcount of the item, or -1 on error
-   \sa SetPlayCount, IncrementPlayCount
+   \sa SetPlayCount, IncrementPlayCount, GetPlayCounts
    */
   int GetPlayCount(const CFileItem &item);
 
   /*! \brief Update the last played time of an item
    Updates the last played date
    \param item CFileItem to update the last played time for
-   \sa GetPlayCount, SetPlayCount, IncrementPlayCount
+   \sa GetPlayCount, SetPlayCount, IncrementPlayCount, GetPlayCounts
    */
   void UpdateLastPlayed(const CFileItem &item);
 
+  /*! \brief Get the playcount of a list of items
+   \param items CFileItemList to fetch the playcounts for
+   \sa GetPlayCount, SetPlayCount, IncrementPlayCount
+   */
+  bool GetPlayCounts(CFileItemList &items);
+
   void UpdateMovieTitle(int idMovie, const CStdString& strNewMovieTitle, VIDEODB_CONTENT_TYPE iType=VIDEODB_CONTENT_MOVIES);
 
   bool HasMovieInfo(const CStdString& strFilenameAndPath);
@@ -444,6 +458,14 @@ public:
    */
   bool GetItemForPath(const CStdString &content, const CStdString &path, CFileItem &item);
 
+  /*! \brief Get videos of the given content type from the given path
+   \param content the content type to fetch.
+   \param path the path to fetch videos from.
+   \param items the returned items
+   \return true if items are found, false otherwise.
+   */
+  bool GetItemsForPath(const CStdString &content, const CStdString &path, CFileItemList &items);
+
   /*! \brief Check whether a given scraper is in use.
    \param scraperID the scraper to check for.
    \return true if the scraper is in use, false otherwise.
@@ -534,7 +556,7 @@ public:
   void CleanDatabase(VIDEO::IVideoInfoScannerObserver* pObserver=NULL, const std::vector<int>* paths=NULL);
 
   /*! \brief Add a file to the database, if necessary
-   If the file is already in the database, we simply return it's id.
+   If the file is already in the database, we simply return its id.
    \param url - full path of the file to add.
    \return id of the file, -1 if it could not be added.
    */
@@ -547,6 +569,13 @@ public:
    */
   int AddFile(const CFileItem& item);
 
+  /*! \brief Add a path to the database, if necessary
+   If the path is already in the database, we simply return its id.
+   \param strPath the path to add
+   \return id of the file, -1 if it could not be added.
+   */
+  int AddPath(const CStdString& strPath);
+
   void ExportToXML(const CStdString &path, bool singleFiles = false, bool images=false, bool actorThumbs=false, bool overwrite=false);
   bool ExportSkipEntry(const CStdString &nfoFile);
   void ExportActorThumbs(const CStdString &path, const CVideoInfoTag& tag, bool singleFiles, bool overwrite=false);
@@ -603,7 +632,6 @@ protected:
    */
   int GetFileId(const CStdString& url);
 
-  int AddPath(const CStdString& strPath);
   int AddToTable(const CStdString& table, const CStdString& firstField, const CStdString& secondField, const CStdString& value);
   int AddGenre(const CStdString& strGenre1);
   int AddActor(const CStdString& strActor, const CStdString& strThumb);
@@ -683,7 +711,22 @@ private:
    */
   void UpdateBasePath(const char *table, const char *id, int column, bool shows = false);
 
-  virtual int GetMinVersion() const { return 51; };
+  /*! \brief Update routine for base path id of videos
+   Only required for videodb version < 52
+   \param table the table to update
+   \param id the primary id in the given table
+   \param column the column of the basepath
+   \param idColumn the column of the parent path id to update
+   */
+  void UpdateBasePathID(const char *table, const char *id, int column, int idColumn);
+
+  /*! \brief Determine whether the path is using lookup using folders
+   \param path the path to check
+   \param shows whether this path is from a tvshow (defaults to false)
+   */
+  bool LookupByFolders(const CStdString &path, bool shows = false);
+
+  virtual int GetMinVersion() const { return 52; };
   virtual int GetExportVersion() const { return 1; };
   const char *GetBaseDBName() const { return "MyVideos"; };
 
index c4ea8f2..c8327cb 100644 (file)
@@ -1060,7 +1060,9 @@ namespace VIDEO
     long lResult = -1;
 
     CVideoInfoTag &movieDetails = *pItem->GetVideoInfoTag();
-    movieDetails.m_basePath = pItem->GetBaseMoviePath(videoFolder);
+    if (movieDetails.m_basePath.IsEmpty())
+      movieDetails.m_basePath = pItem->GetBaseMoviePath(videoFolder);
+    movieDetails.m_parentPathID = m_database.AddPath(URIUtils::GetParentPath(movieDetails.m_basePath));
 
     if (content == CONTENT_MOVIES)
     {
index 6f4fda0..d15c1ea 100644 (file)
@@ -79,6 +79,7 @@ void CVideoInfoTag::Reset()
   m_playCount = 0;
   m_fEpBookmark = 0;
   m_basePath = "";
+  m_parentPathID = -1;
 }
 
 bool CVideoInfoTag::Save(TiXmlNode *node, const CStdString &tag, bool savePathInfo)
@@ -305,6 +306,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
     ar << m_strShowLink;
     ar << m_fEpBookmark;
     ar << m_basePath;
+    ar << m_parentPathID;
   }
   else
   {
@@ -371,6 +373,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
     ar >> m_strShowLink;
     ar >> m_fEpBookmark;
     ar >> m_basePath;
+    ar >> m_parentPathID;
   }
 }
 
index f7b37f7..dd4535d 100644 (file)
@@ -51,6 +51,7 @@ public:
   bool IsEmpty() const;
 
   CStdString m_basePath; // the base path of the video, for folder-based lookups
+  int m_parentPathID;      // the parent path id where the base path of the video lies
   CStdString m_strDirector;
   CStdString m_strWritingCredits;
   CStdString m_strGenre;
index ff5aeae..971ff01 100644 (file)
@@ -419,39 +419,52 @@ void CGUIWindowVideoNav::LoadVideoInfo(CFileItemList &items)
                 !items.IsVirtualDirectoryRoot() &&
                 m_stackingAvailable);
 
+  CFileItemList dbItems;
+  if (content.IsEmpty())
+    m_database.GetPlayCounts(items);
+  else
+  {
+    m_database.GetItemsForPath(content, items.m_strPath, dbItems);
+    dbItems.SetFastLookup(true);
+  }
   for (int i = 0; i < items.Size(); i++)
   {
     CFileItemPtr pItem = items[i];
-    CFileItem item;
-    if (!content.IsEmpty() && m_database.GetItemForPath(content, pItem->m_strPath, item))
-    { // copy info across
+    CFileItemPtr match;
+    if (!content.IsEmpty())
+      match = dbItems.Get(pItem->m_strPath);
+    if (match)
+    {
       CStdString label (pItem->GetLabel ());
       CStdString label2(pItem->GetLabel2());
-      pItem->UpdateInfo(item);
-      
+      pItem->UpdateInfo(*match);
+
       if(g_settings.m_videoStacking && m_stackingAvailable)
       {
-        pItem->m_strPath = item.m_strPath;
+        if (match->m_bIsFolder)
+          pItem->m_strPath = match->GetVideoInfoTag()->m_strPath;
+        else
+          pItem->m_strPath = match->GetVideoInfoTag()->m_strFileNameAndPath;
         // if we switch from a file to a folder item it means we really shouldn't be sorting files and
         // folders separately
-        if (pItem->m_bIsFolder != item.m_bIsFolder)
+        if (pItem->m_bIsFolder != match->m_bIsFolder)
           items.SetSortIgnoreFolders(true);
-        pItem->m_bIsFolder = item.m_bIsFolder;
+        pItem->m_bIsFolder = match->m_bIsFolder;
       }
       else
       {
-        if (CFile::Exists(item.GetCachedFanart()))
-          pItem->SetProperty("fanart_image", item.GetCachedFanart());
+        if (CFile::Exists(match->GetCachedFanart()))
+          pItem->SetProperty("fanart_image", match->GetCachedFanart());
         pItem->SetLabel (label);
         pItem->SetLabel2(label);
       }
-
     }
     else
-    { // grab the playcount and clean the label
-      int playCount = m_database.GetPlayCount(*pItem);
-      if (playCount >= 0)
-        pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, playCount > 0);
+    { // set the watched overlay (note: items in a folder with content set that aren't in the db
+      //                                won't get picked up here - in the future all items will be returned)
+      // and clean the label
+      if (pItem->HasVideoInfoTag())
+        pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, pItem->GetVideoInfoTag()->m_playCount > 0);
       if (clean)
         pItem->CleanString();
     }