changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / filesystem / ZeroconfDirectory.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "ZeroconfDirectory.h"
22 #include <stdexcept>
23
24 #include "URL.h"
25 #include "utils/URIUtils.h"
26 #include "FileItem.h"
27 #include "network/ZeroconfBrowser.h"
28 #include "Directory.h"
29 #include "utils/log.h"
30
31 using namespace XFILE;
32
33 CZeroconfDirectory::CZeroconfDirectory()
34 {
35   CZeroconfBrowser::GetInstance()->Start();
36 }
37
38 CZeroconfDirectory::~CZeroconfDirectory()
39 {
40 }
41
42 namespace
43 {
44   CStdString GetHumanReadableProtocol(std::string const& fcr_service_type)
45   {
46     if(fcr_service_type == "_smb._tcp.")
47       return "SAMBA";
48     else if(fcr_service_type == "_ftp._tcp.")
49       return "FTP";
50     else if(fcr_service_type == "_htsp._tcp.")
51       return "HTS";
52     else if(fcr_service_type == "_daap._tcp.")
53       return "iTunes Music Sharing";
54     else if(fcr_service_type == "_webdav._tcp.")
55       return "WebDAV";   
56     else if(fcr_service_type == "_nfs._tcp.")
57       return "NFS";   
58     else if(fcr_service_type == "_afpovertcp._tcp.")
59       return "AFP";   
60     else if(fcr_service_type == "_sftp-ssh._tcp.")
61       return "SFTP";
62     //fallback, just return the received type
63     return fcr_service_type;
64   }
65   bool GetXBMCProtocol(std::string const& fcr_service_type, CStdString& fr_protocol)
66   {
67     if(fcr_service_type == "_smb._tcp.")
68       fr_protocol = "smb";
69     else if(fcr_service_type == "_ftp._tcp.")
70       fr_protocol = "ftp";
71     else if(fcr_service_type == "_htsp._tcp.")
72       fr_protocol = "htsp";
73     else if(fcr_service_type == "_daap._tcp.")
74       fr_protocol = "daap";
75     else if(fcr_service_type == "_webdav._tcp.")
76       fr_protocol = "dav";
77     else if(fcr_service_type == "_nfs._tcp.")
78       fr_protocol = "nfs";      
79     else if(fcr_service_type == "_afpovertcp._tcp.")
80       fr_protocol = "afp";      
81     else if(fcr_service_type == "_sftp-ssh._tcp.")
82       fr_protocol = "sftp";
83     else
84       return false;
85     return true;
86   }
87 }
88
89 bool GetDirectoryFromTxtRecords(CZeroconfBrowser::ZeroconfService zeroconf_service, CURL& url, CFileItemList &items)
90 {
91   bool ret = false;
92
93   //get the txt-records from this service
94   CZeroconfBrowser::ZeroconfService::tTxtRecordMap txtRecords=zeroconf_service.GetTxtRecords();
95
96   //if we have some records
97   if(!txtRecords.empty())
98   {
99     CStdString path;
100     CStdString username;
101     CStdString password;
102   
103     //search for a path key entry
104     CZeroconfBrowser::ZeroconfService::tTxtRecordMap::iterator it = txtRecords.find(TXT_RECORD_PATH_KEY);
105
106     //if we found the key - be sure there is a value there
107     if( it != txtRecords.end() && !it->second.empty() )
108     {
109       //from now on we treat the value as a path - everything else would mean
110       //a missconfigured zeroconf server.
111       path=it->second;
112     }
113     
114     //search for a username key entry
115     it = txtRecords.find(TXT_RECORD_USERNAME_KEY);
116
117     //if we found the key - be sure there is a value there
118     if( it != txtRecords.end() && !it->second.empty() )
119     {
120       username=it->second;
121       url.SetUserName(username);
122     }
123     
124     //search for a password key entry
125     it = txtRecords.find(TXT_RECORD_PASSWORD_KEY);
126
127     //if we found the key - be sure there is a value there
128     if( it != txtRecords.end() && !it->second.empty() )
129     {
130       password=it->second;
131       url.SetPassword(password);
132     }
133     
134     //if we got a path - add a item - else at least we maybe have set username and password to theurl
135     if( !path.empty())
136     {
137       CFileItemPtr item(new CFileItem("", true));
138       CStdString urlStr(url.Get());
139       //if path has a leading slash (sure it should have one)
140       if( path.at(0) == '/' )
141       {
142         URIUtils::RemoveSlashAtEnd(urlStr);//we don't need the slash at and of url then
143       }
144       else//path doesn't start with slash - 
145       {//this is some kind of missconfiguration - we fix it by adding a slash to the url
146         URIUtils::AddSlashAtEnd(urlStr);
147       }
148       
149       //add slash at end of path since it has to be a folder
150       URIUtils::AddSlashAtEnd(path);
151       //this is the full path includeing remote stuff (e.x. nfs://ip/path
152       item->SetPath(urlStr + path);
153       //remove the slash at the end of the path or GetFileName will not give the last dir
154       URIUtils::RemoveSlashAtEnd(path);
155       //set the label to the last directory in path
156       if( !URIUtils::GetFileName(path).empty() )
157         item->SetLabel(URIUtils::GetFileName(path));
158       else
159         item->SetLabel("/");
160
161       item->SetLabelPreformated(true);
162       //just set the default folder icon
163       item->FillInDefaultIcon();
164       item->m_bIsShareOrDrive=true;
165       items.Add(item);
166       ret = true;
167     }
168   }
169   return ret;
170 }
171
172 bool CZeroconfDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
173 {
174   assert(strPath.substr(0, 11) == "zeroconf://");
175   CStdString path = strPath.substr(11, strPath.length());
176   URIUtils::RemoveSlashAtEnd(path);
177   if(path.empty())
178   {
179     std::vector<CZeroconfBrowser::ZeroconfService> found_services = CZeroconfBrowser::GetInstance()->GetFoundServices();
180     for(std::vector<CZeroconfBrowser::ZeroconfService>::iterator it = found_services.begin(); it != found_services.end(); ++it)
181     {
182       //only use discovered services we can connect to through directory
183       CStdString tmp;
184       if(GetXBMCProtocol(it->GetType(), tmp))
185       {
186         CFileItemPtr item(new CFileItem("", true));
187         CURL url;
188         url.SetProtocol("zeroconf");
189         CStdString service_path = CZeroconfBrowser::ZeroconfService::toPath(*it);
190         CURL::Encode(service_path);
191         url.SetFileName(service_path);
192         item->SetPath(url.Get());
193
194         //now do the formatting
195         CStdString protocol = GetHumanReadableProtocol(it->GetType());
196         item->SetLabel(it->GetName() + " (" + protocol  + ")");
197         item->SetLabelPreformated(true);
198         //just set the default folder icon
199         item->FillInDefaultIcon();
200         items.Add(item);
201       }
202     }
203     return true;
204   } 
205   else
206   {
207     //decode the path first
208     CStdString decoded = path;
209     CURL::Decode(decoded);
210     try
211     {
212       CZeroconfBrowser::ZeroconfService zeroconf_service = CZeroconfBrowser::ZeroconfService::fromPath(decoded);
213
214       if(!CZeroconfBrowser::GetInstance()->ResolveService(zeroconf_service))
215       {
216         CLog::Log(LOGINFO, "CZeroconfDirectory::GetDirectory service ( %s ) could not be resolved in time", zeroconf_service.GetName().c_str());
217         return false;
218       }
219       else
220       {
221         assert(!zeroconf_service.GetIP().empty());
222         CURL service;
223         service.SetPort(zeroconf_service.GetPort());
224         service.SetHostName(zeroconf_service.GetIP());
225         //do protocol conversion (_smb._tcp -> smb)
226         //ToDo: try automatic conversion -> remove leading '_' and '._tcp'?
227         CStdString protocol;
228         if(!GetXBMCProtocol(zeroconf_service.GetType(), protocol))
229         {
230           CLog::Log(LOGERROR, "CZeroconfDirectory::GetDirectory Unknown service type (%s), skipping; ", zeroconf_service.GetType().c_str());
231           return false;
232         }
233         
234         service.SetProtocol(protocol);
235         
236         //first try to show the txt-record defined path if any
237         if(GetDirectoryFromTxtRecords(zeroconf_service, service, items))
238         {
239           return true;
240         }
241         else//no txt record path - so let the CDirectory handler show the folders
242         {          
243           return CDirectory::GetDirectory(service.Get(), items, "", DIR_FLAG_ALLOW_PROMPT); 
244         }
245       }
246     } catch (std::runtime_error& e) {
247       CLog::Log(LOGERROR, "CZeroconfDirectory::GetDirectory failed getting directory: '%s'. Error: '%s'", decoded.c_str(), e.what());
248       return false;
249     }
250   }
251 }