changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / filesystem / NFSFile.cpp
1 /*
2  *      Copyright (C) 2011-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 // FileNFS.cpp: implementation of the CNFSFile class.
22 //
23 //////////////////////////////////////////////////////////////////////
24 #include "system.h"
25
26 #ifdef HAS_FILESYSTEM_NFS
27 #include "DllLibNfs.h"
28 #include "NFSFile.h"
29 #include "threads/SingleLock.h"
30 #include "utils/log.h"
31 #include "utils/URIUtils.h"
32 #include "utils/StringUtils.h"
33 #include "network/DNSNameCache.h"
34 #include "threads/SystemClock.h"
35
36 #include <nfsc/libnfs-raw-mount.h>
37
38 #ifdef TARGET_WINDOWS
39 #include <fcntl.h>
40 #include <sys\stat.h>
41 #endif
42
43 //KEEP_ALIVE_TIMEOUT is decremented every half a second
44 //360 * 0.5s == 180s == 3mins
45 //so when no read was done for 3mins and files are open
46 //do the nfs keep alive for the open files
47 #define KEEP_ALIVE_TIMEOUT 360
48
49 //6 mins (360s) cached context timeout
50 #define CONTEXT_TIMEOUT 360000
51
52 //return codes for getContextForExport
53 #define CONTEXT_INVALID  0    //getcontext failed
54 #define CONTEXT_NEW      1    //new context created
55 #define CONTEXT_CACHED   2    //context cached and therefore already mounted (no new mount needed)
56
57 using namespace XFILE;
58
59 CNfsConnection::CNfsConnection()
60 : m_pNfsContext(NULL)
61 , m_exportPath("")
62 , m_hostName("")
63 , m_resolvedHostName("")
64 , m_readChunkSize(0)
65 , m_writeChunkSize(0)
66 , m_OpenConnections(0)
67 , m_IdleTimeout(0)
68 , m_lastAccessedTime(0)
69 , m_pLibNfs(new DllLibNfs())
70 {
71 }
72
73 CNfsConnection::~CNfsConnection()
74 {
75   Deinit();
76   delete m_pLibNfs;
77 }
78
79 void CNfsConnection::resolveHost(const CURL &url)
80
81   //resolve if hostname has changed
82   CDNSNameCache::Lookup(url.GetHostName(), m_resolvedHostName);
83 }
84
85 std::list<std::string> CNfsConnection::GetExportList(const CURL &url)
86 {
87     std::list<std::string> retList;
88
89     if(HandleDyLoad())
90     {
91       struct exportnode *exportlist, *tmp;
92       exportlist = m_pLibNfs->mount_getexports(m_resolvedHostName);
93       tmp = exportlist;
94
95       for(tmp = exportlist; tmp!=NULL; tmp=tmp->ex_next)
96       {
97         std::string exportStr = std::string(tmp->ex_dir);
98         URIUtils::AddSlashAtEnd(exportStr);
99         retList.push_back(exportStr);
100       }      
101
102       gNfsConnection.GetImpl()->mount_free_export_list(exportlist);
103     }
104     
105     return retList;
106 }
107
108 bool CNfsConnection::HandleDyLoad()
109 {
110   bool ret = true;
111   
112   if(!m_pLibNfs->IsLoaded())
113   {
114     if(!m_pLibNfs->Load())
115     {
116       CLog::Log(LOGERROR,"NFS: Error loading libnfs (%s).",__FUNCTION__);    
117       ret = false; //fatal
118     }    
119   }
120   return ret;
121 }
122
123 void CNfsConnection::clearMembers()
124 {
125     m_exportPath.clear();
126     m_hostName.clear();
127     m_exportList.clear();
128     m_writeChunkSize = 0;
129     m_readChunkSize = 0;  
130     m_pNfsContext = NULL;
131     m_KeepAliveTimeouts.clear();
132 }
133
134 void CNfsConnection::destroyOpenContexts()
135 {
136   CSingleLock lock(openContextLock);
137   for(tOpenContextMap::iterator it = m_openContextMap.begin();it!=m_openContextMap.end();it++)
138   {
139     m_pLibNfs->nfs_destroy_context(it->second.pContext);
140   }
141   m_openContextMap.clear();
142 }
143
144 void CNfsConnection::destroyContext(const CStdString &exportName)
145 {
146   CSingleLock lock(openContextLock);
147   tOpenContextMap::iterator it = m_openContextMap.find(exportName.c_str());
148   if (it != m_openContextMap.end()) 
149   {
150       m_pLibNfs->nfs_destroy_context(it->second.pContext);
151       m_openContextMap.erase(it);
152   }
153 }
154
155 struct nfs_context *CNfsConnection::getContextFromMap(const CStdString &exportname, bool forceCacheHit/* = false*/)
156 {
157   struct nfs_context *pRet = NULL;
158   CSingleLock lock(openContextLock);
159
160   tOpenContextMap::iterator it = m_openContextMap.find(exportname.c_str());
161   if(it != m_openContextMap.end())
162   {
163     //check if context has timed out already
164     uint64_t now = XbmcThreads::SystemClockMillis();
165     if((now - it->second.lastAccessedTime) < CONTEXT_TIMEOUT || forceCacheHit)
166     {
167       //its not timedout yet or caller wants the cached entry regardless of timeout
168       //refresh access time of that
169       //context and return it
170       if (!forceCacheHit) // only log it if this isn't the resetkeepalive on each read ;)
171         CLog::Log(LOGDEBUG, "NFS: Refreshing context for %s, old: %"PRId64", new: %"PRId64, exportname.c_str(), it->second.lastAccessedTime, now);
172       it->second.lastAccessedTime = now;
173       pRet = it->second.pContext;
174     }
175     else 
176     {
177       //context is timed out
178       //destroy it and return NULL
179       CLog::Log(LOGDEBUG, "NFS: Old context timed out - destroying it");
180       m_pLibNfs->nfs_destroy_context(it->second.pContext);
181       m_openContextMap.erase(it);
182     }
183   }
184   return pRet;
185 }
186
187 int CNfsConnection::getContextForExport(const CStdString &exportname)
188 {
189   int ret = CONTEXT_INVALID; 
190     
191   if(HandleDyLoad())
192   {
193     clearMembers();  
194     
195     m_pNfsContext = getContextFromMap(exportname);
196
197     if(!m_pNfsContext)
198     {
199       CLog::Log(LOGDEBUG,"NFS: Context for %s not open - get a new context.", exportname.c_str());
200       m_pNfsContext = m_pLibNfs->nfs_init_context();
201     
202       if(!m_pNfsContext) 
203       {
204         CLog::Log(LOGERROR,"NFS: Error initcontext in getContextForExport.");
205       }
206       else 
207       {
208         struct contextTimeout tmp;
209         CSingleLock lock(openContextLock);        
210         tmp.pContext = m_pNfsContext;
211         tmp.lastAccessedTime = XbmcThreads::SystemClockMillis();
212         m_openContextMap[exportname] = tmp; //add context to list of all contexts      
213         ret = CONTEXT_NEW;
214       }
215     }
216     else
217     {
218       ret = CONTEXT_CACHED;
219       CLog::Log(LOGDEBUG,"NFS: Using cached context.");
220     }
221     m_lastAccessedTime = XbmcThreads::SystemClockMillis(); //refresh last access time of m_pNfsContext
222   }
223   return ret;
224 }
225
226 bool CNfsConnection::splitUrlIntoExportAndPath(const CURL& url, CStdString &exportPath, CStdString &relativePath)
227 {
228     bool ret = false;
229     
230     //refresh exportlist if empty or hostname change
231     if(m_exportList.empty() || !url.GetHostName().Equals(m_hostName,false))
232     {
233       m_exportList = GetExportList(url);
234     }
235
236     if(!m_exportList.empty())
237     {
238       relativePath = "";
239       exportPath = "";
240       
241       CStdString path = url.GetFileName();
242       
243       //GetFileName returns path without leading "/"
244       //but we need it because the export paths start with "/"
245       //and path.Find(*it) wouldn't work else
246       if(!path.empty() && path[0] != '/')
247       {
248         path = "/" + path;
249       }
250       
251       std::list<std::string>::iterator it;
252       
253       for(it=m_exportList.begin();it!=m_exportList.end();it++)
254       {
255         //if path starts with the current export path
256         if(StringUtils::StartsWith(path, *it))
257         {
258           exportPath = *it;
259           //handle special case where root is exported
260           //in that case we don't want to stripp off to
261           //much from the path
262           if( exportPath == "/" )
263             relativePath = "//" + path.substr(exportPath.length()-1);
264           else
265             relativePath = "//" + path.substr(exportPath.length());
266           ret = true;
267           break;          
268         }
269       }
270     }
271     return ret;
272 }
273
274 bool CNfsConnection::Connect(const CURL& url, CStdString &relativePath)
275 {
276   CSingleLock lock(*this);
277   bool ret = false;
278   int nfsRet = 0;
279   CStdString exportPath = "";
280
281   resolveHost(url);
282   ret = splitUrlIntoExportAndPath(url, exportPath, relativePath);
283   
284   if( (ret && (!exportPath.Equals(m_exportPath,true)  || 
285       !url.GetHostName().Equals(m_hostName,false)))    ||
286       (XbmcThreads::SystemClockMillis() - m_lastAccessedTime) > CONTEXT_TIMEOUT )
287   {
288     int contextRet = getContextForExport(url.GetHostName() + exportPath);
289     
290     if(contextRet == CONTEXT_INVALID)//we need a new context because sharename or hostname has changed
291     {
292       return false;
293     }
294     
295     if(contextRet == CONTEXT_NEW) //new context was created - we need to mount it
296     {
297       //we connect to the directory of the path. This will be the "root" path of this connection then.
298       //So all fileoperations are relative to this mountpoint...
299       nfsRet = m_pLibNfs->nfs_mount(m_pNfsContext, m_resolvedHostName.c_str(), exportPath.c_str());
300
301       if(nfsRet != 0) 
302       {
303         CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
304         destroyContext(url.GetHostName() + exportPath);
305         return false;
306       }
307       CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s\n", url.GetHostName().c_str(), exportPath.c_str());
308     }
309     m_exportPath = exportPath;
310     m_hostName = url.GetHostName();
311     //read chunksize only works after mount
312     m_readChunkSize = m_pLibNfs->nfs_get_readmax(m_pNfsContext);
313     m_writeChunkSize = m_pLibNfs->nfs_get_writemax(m_pNfsContext);
314
315     if(contextRet == CONTEXT_NEW)
316     {
317       CLog::Log(LOGDEBUG,"NFS: chunks: r/w %i/%i\n", (int)m_readChunkSize,(int)m_writeChunkSize);          
318     }
319   }
320   return ret; 
321 }
322
323 void CNfsConnection::Deinit()
324 {
325   if(m_pNfsContext && m_pLibNfs->IsLoaded())
326   {
327     destroyOpenContexts();
328     m_pNfsContext = NULL;
329     m_pLibNfs->Unload();    
330   }        
331   clearMembers();
332 }
333
334 /* This is called from CApplication::ProcessSlow() and is used to tell if nfs have been idle for too long */
335 void CNfsConnection::CheckIfIdle()
336 {
337   /* We check if there are open connections. This is done without a lock to not halt the mainthread. It should be thread safe as
338    worst case scenario is that m_OpenConnections could read 0 and then changed to 1 if this happens it will enter the if wich will lead to another check, wich is locked.  */
339   if (m_OpenConnections == 0 && m_pNfsContext != NULL)
340   { /* I've set the the maxiumum IDLE time to be 1 min and 30 sec. */
341     CSingleLock lock(*this);
342     if (m_OpenConnections == 0 /* check again - when locked */)
343     {
344       if (m_IdleTimeout > 0)
345       {
346         m_IdleTimeout--;
347       }
348       else
349       {
350         CLog::Log(LOGNOTICE, "NFS is idle. Closing the remaining connections.");
351         gNfsConnection.Deinit();
352       }
353     }
354   }
355   
356   if( m_pNfsContext != NULL )
357   {
358     CSingleLock lock(keepAliveLock);
359     //handle keep alive on opened files
360     for( tFileKeepAliveMap::iterator it = m_KeepAliveTimeouts.begin();it!=m_KeepAliveTimeouts.end();it++)
361     {
362       if(it->second.refreshCounter > 0)
363       {
364         it->second.refreshCounter--;
365       }
366       else
367       {
368         keepAlive(it->second.exportPath, it->first);
369         //reset timeout
370         resetKeepAlive(it->second.exportPath, it->first);
371       }
372     }
373   }
374 }
375
376 //remove file handle from keep alive list on file close
377 void CNfsConnection::removeFromKeepAliveList(struct nfsfh  *_pFileHandle)
378 {
379   CSingleLock lock(keepAliveLock);
380   m_KeepAliveTimeouts.erase(_pFileHandle);
381 }
382
383 //reset timeouts on read
384 void CNfsConnection::resetKeepAlive(std::string _exportPath, struct nfsfh  *_pFileHandle)
385 {
386   CSingleLock lock(keepAliveLock);
387   //refresh last access time of the context aswell
388   getContextFromMap(_exportPath, true);
389   //adds new keys - refreshs existing ones
390   m_KeepAliveTimeouts[_pFileHandle].exportPath = _exportPath;
391   m_KeepAliveTimeouts[_pFileHandle].refreshCounter = KEEP_ALIVE_TIMEOUT;
392 }
393
394 //keep alive the filehandles nfs connection
395 //by blindly doing a read 32bytes - seek back to where
396 //we were before
397 void CNfsConnection::keepAlive(std::string _exportPath, struct nfsfh  *_pFileHandle)
398 {
399   uint64_t offset = 0;
400   char buffer[32];
401   // this also refreshs the last accessed time for the context
402   // true forces a cachehit regardless the context is timedout
403   // on this call we are sure its not timedout even if the last accessed
404   // time suggests it.
405   struct nfs_context *pContext = getContextFromMap(_exportPath, true);
406   
407   if (!pContext)// this should normally never happen - paranoia
408     pContext = m_pNfsContext;
409   
410   CLog::Log(LOGNOTICE, "NFS: sending keep alive after %i s.",KEEP_ALIVE_TIMEOUT/2);
411   CSingleLock lock(*this);
412   m_pLibNfs->nfs_lseek(pContext, _pFileHandle, 0, SEEK_CUR, &offset);
413   m_pLibNfs->nfs_read(pContext, _pFileHandle, 32, buffer);
414   m_pLibNfs->nfs_lseek(pContext, _pFileHandle, offset, SEEK_SET, &offset);
415 }
416
417 int CNfsConnection::stat(const CURL &url, struct stat *statbuff)
418 {
419   CSingleLock lock(*this);
420   int nfsRet = 0;
421   CStdString exportPath;
422   CStdString relativePath;
423   struct nfs_context *pTmpContext = NULL;
424   
425   if(!HandleDyLoad())
426   {
427     return -1;
428   }
429   
430   resolveHost(url);
431   
432   if(splitUrlIntoExportAndPath(url, exportPath, relativePath))
433   {    
434     pTmpContext = m_pLibNfs->nfs_init_context();
435     
436     if(pTmpContext)
437     {  
438       //we connect to the directory of the path. This will be the "root" path of this connection then.
439       //So all fileoperations are relative to this mountpoint...
440       nfsRet = m_pLibNfs->nfs_mount(pTmpContext, m_resolvedHostName.c_str(), exportPath.c_str());
441       
442       if(nfsRet == 0) 
443       {
444         nfsRet = m_pLibNfs->nfs_stat(pTmpContext, relativePath.c_str(), statbuff);      
445       }
446       else
447       {
448         CLog::Log(LOGERROR,"NFS: Failed to mount nfs share: %s (%s)\n", exportPath.c_str(), m_pLibNfs->nfs_get_error(m_pNfsContext));
449       }
450       
451       m_pLibNfs->nfs_destroy_context(pTmpContext);
452       CLog::Log(LOGDEBUG,"NFS: Connected to server %s and export %s in tmpContext\n", url.GetHostName().c_str(), exportPath.c_str());
453     }
454   }
455   return nfsRet;
456 }
457
458 /* The following two function is used to keep track on how many Opened files/directories there are.
459 needed for unloading the dylib*/
460 void CNfsConnection::AddActiveConnection()
461 {
462   CSingleLock lock(*this);
463   m_OpenConnections++;
464 }
465
466 void CNfsConnection::AddIdleConnection()
467 {
468   CSingleLock lock(*this);
469   m_OpenConnections--;
470   /* If we close a file we reset the idle timer so that we don't have any wierd behaviours if a user
471    leaves the movie paused for a long while and then press stop */
472   m_IdleTimeout = 180;
473 }
474
475 CNfsConnection gNfsConnection;
476
477 CNFSFile::CNFSFile()
478 : m_fileSize(0)
479 , m_pFileHandle(NULL)
480 , m_pNfsContext(NULL)
481 {
482   gNfsConnection.AddActiveConnection();
483 }
484
485 CNFSFile::~CNFSFile()
486 {
487   Close();
488   gNfsConnection.AddIdleConnection();
489 }
490
491 int64_t CNFSFile::GetPosition()
492 {
493   int ret = 0;
494   uint64_t offset = 0;
495   CSingleLock lock(gNfsConnection);
496   
497   if (gNfsConnection.GetNfsContext() == NULL || m_pFileHandle == NULL) return 0;
498   
499   ret = (int)gNfsConnection.GetImpl()->nfs_lseek(gNfsConnection.GetNfsContext(), m_pFileHandle, 0, SEEK_CUR, &offset);
500   
501   if (ret < 0) 
502   {
503     CLog::Log(LOGERROR, "NFS: Failed to lseek(%s)",gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
504   }
505   return offset;
506 }
507
508 int64_t CNFSFile::GetLength()
509 {
510   if (m_pFileHandle == NULL) return 0;
511   return m_fileSize;
512 }
513
514 bool CNFSFile::Open(const CURL& url)
515 {
516   int ret = 0;
517   Close();
518   // we can't open files like nfs://file.f or nfs://server/file.f
519   // if a file matches the if below return false, it can't exist on a nfs share.
520   if (!IsValidFile(url.GetFileName()))
521   {
522     CLog::Log(LOGNOTICE,"NFS: Bad URL : '%s'",url.GetFileName().c_str());
523     return false;
524   }
525   
526   CStdString filename = "";
527    
528   CSingleLock lock(gNfsConnection);
529   
530   if(!gNfsConnection.Connect(url, filename))
531     return false;
532   
533   m_pNfsContext = gNfsConnection.GetNfsContext(); 
534   m_exportPath = gNfsConnection.GetContextMapId();
535   
536   ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDONLY, &m_pFileHandle);
537   
538   if (ret != 0) 
539   {
540     CLog::Log(LOGINFO, "CNFSFile::Open: Unable to open file : '%s'  error : '%s'", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
541     m_pNfsContext = NULL;
542     m_exportPath.clear();
543     return false;
544   } 
545   
546   CLog::Log(LOGDEBUG,"CNFSFile::Open - opened %s",url.GetFileName().c_str());
547   m_url=url;
548   
549   struct __stat64 tmpBuffer;
550
551   if( Stat(&tmpBuffer) )
552   {
553     m_url.Reset();
554     Close();
555     return false;
556   }
557   
558   m_fileSize = tmpBuffer.st_size;//cache the size of this file
559   // We've successfully opened the file!
560   return true;
561 }
562
563
564 bool CNFSFile::Exists(const CURL& url)
565 {
566   return Stat(url,NULL) == 0;
567 }
568
569 int CNFSFile::Stat(struct __stat64* buffer)
570 {
571   return Stat(m_url,buffer);
572 }
573
574
575 int CNFSFile::Stat(const CURL& url, struct __stat64* buffer)
576 {
577   int ret = 0;
578   CSingleLock lock(gNfsConnection);
579   CStdString filename = "";
580   
581   if(!gNfsConnection.Connect(url,filename))
582     return -1;
583    
584
585   struct stat tmpBuffer = {0};
586
587   ret = gNfsConnection.GetImpl()->nfs_stat(gNfsConnection.GetNfsContext(), filename.c_str(), &tmpBuffer);
588   
589   //if buffer == NULL we where called from Exists - in that case don't spam the log with errors
590   if (ret != 0 && buffer != NULL) 
591   {
592     CLog::Log(LOGERROR, "NFS: Failed to stat(%s) %s\n", url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
593     ret = -1;
594   }
595   else
596   {  
597     if(buffer)
598     {
599       memset(buffer, 0, sizeof(struct __stat64));
600       buffer->st_dev = tmpBuffer.st_dev;
601       buffer->st_ino = tmpBuffer.st_ino;
602       buffer->st_mode = tmpBuffer.st_mode;
603       buffer->st_nlink = tmpBuffer.st_nlink;
604       buffer->st_uid = tmpBuffer.st_uid;
605       buffer->st_gid = tmpBuffer.st_gid;
606       buffer->st_rdev = tmpBuffer.st_rdev;
607       buffer->st_size = tmpBuffer.st_size;
608       buffer->st_atime = tmpBuffer.st_atime;
609       buffer->st_mtime = tmpBuffer.st_mtime;
610       buffer->st_ctime = tmpBuffer.st_ctime;
611     }
612   }
613   return ret;
614 }
615
616 unsigned int CNFSFile::Read(void *lpBuf, int64_t uiBufSize)
617 {
618   int numberOfBytesRead = 0;
619   CSingleLock lock(gNfsConnection);
620   
621   if (m_pFileHandle == NULL || m_pNfsContext == NULL ) return 0;
622
623   numberOfBytesRead = gNfsConnection.GetImpl()->nfs_read(m_pNfsContext, m_pFileHandle, uiBufSize, (char *)lpBuf);  
624
625   lock.Leave();//no need to keep the connection lock after that
626   
627   gNfsConnection.resetKeepAlive(m_exportPath, m_pFileHandle);//triggers keep alive timer reset for this filehandle
628   
629   //something went wrong ...
630   if (numberOfBytesRead < 0) 
631   {
632     CLog::Log(LOGERROR, "%s - Error( %d, %s )", __FUNCTION__, numberOfBytesRead, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
633     return 0;
634   }
635   return (unsigned int)numberOfBytesRead;
636 }
637
638 int64_t CNFSFile::Seek(int64_t iFilePosition, int iWhence)
639 {
640   int ret = 0;
641   uint64_t offset = 0;
642
643   CSingleLock lock(gNfsConnection);  
644   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
645   
646  
647   ret = (int)gNfsConnection.GetImpl()->nfs_lseek(m_pNfsContext, m_pFileHandle, iFilePosition, iWhence, &offset);
648   if (ret < 0) 
649   {
650     CLog::Log(LOGERROR, "%s - Error( seekpos: %"PRId64", whence: %i, fsize: %"PRId64", %s)", __FUNCTION__, iFilePosition, iWhence, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
651     return -1;
652   }
653   return (int64_t)offset;
654 }
655
656 int CNFSFile::Truncate(int64_t iSize)
657 {
658   int ret = 0;
659   
660   CSingleLock lock(gNfsConnection);  
661   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
662   
663   
664   ret = (int)gNfsConnection.GetImpl()->nfs_ftruncate(m_pNfsContext, m_pFileHandle, iSize);
665   if (ret < 0) 
666   {
667     CLog::Log(LOGERROR, "%s - Error( ftruncate: %"PRId64", fsize: %"PRId64", %s)", __FUNCTION__, iSize, m_fileSize, gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
668     return -1;
669   }
670   return ret;
671 }
672
673 void CNFSFile::Close()
674 {
675   CSingleLock lock(gNfsConnection);
676   
677   if (m_pFileHandle != NULL && m_pNfsContext != NULL)
678   {
679     int ret = 0;
680     CLog::Log(LOGDEBUG,"CNFSFile::Close closing file %s", m_url.GetFileName().c_str());
681     // remove it from keep alive list before closing
682     // so keep alive code doens't process it anymore
683     gNfsConnection.removeFromKeepAliveList(m_pFileHandle);
684     ret = gNfsConnection.GetImpl()->nfs_close(m_pNfsContext, m_pFileHandle);
685         
686           if (ret < 0) 
687     {
688       CLog::Log(LOGERROR, "Failed to close(%s) - %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));
689     }
690     m_pFileHandle = NULL;
691     m_pNfsContext = NULL;    
692     m_fileSize = 0;
693     m_exportPath.clear();
694   }
695 }
696
697 //this was a bitch!
698 //for nfs write to work we have to write chunked
699 //otherwise this could crash on big files
700 int CNFSFile::Write(const void* lpBuf, int64_t uiBufSize)
701 {
702   int numberOfBytesWritten = 0;
703   int writtenBytes = 0;
704   int64_t leftBytes = uiBufSize;
705   //clamp max write chunksize to 32kb - fixme - this might be superfluious with future libnfs versions
706   int64_t chunkSize = gNfsConnection.GetMaxWriteChunkSize() > 32768 ? 32768 : gNfsConnection.GetMaxWriteChunkSize();
707   
708   CSingleLock lock(gNfsConnection);
709   
710   if (m_pFileHandle == NULL || m_pNfsContext == NULL) return -1;
711   
712   //write as long as some bytes are left to be written
713   while( leftBytes )
714   {
715     //the last chunk could be smalle than chunksize
716     if(leftBytes < chunkSize)
717     {
718       chunkSize = leftBytes;//write last chunk with correct size
719     }
720     //write chunk
721     writtenBytes = gNfsConnection.GetImpl()->nfs_write(m_pNfsContext,
722                                   m_pFileHandle, 
723                                   chunkSize, 
724                                   (char *)lpBuf + numberOfBytesWritten);
725     //decrease left bytes
726     leftBytes-= writtenBytes;
727     //increase overall written bytes
728     numberOfBytesWritten += writtenBytes;
729         
730     //danger - something went wrong
731     if (writtenBytes < 0) 
732     {
733       CLog::Log(LOGERROR, "Failed to pwrite(%s) %s\n", m_url.GetFileName().c_str(), gNfsConnection.GetImpl()->nfs_get_error(m_pNfsContext));        
734       break;
735     }     
736   }
737   //return total number of written bytes
738   return numberOfBytesWritten;
739 }
740
741 bool CNFSFile::Delete(const CURL& url)
742 {
743   int ret = 0;
744   CSingleLock lock(gNfsConnection);
745   CStdString filename = "";
746   
747   if(!gNfsConnection.Connect(url, filename))
748     return false;
749   
750   
751   ret = gNfsConnection.GetImpl()->nfs_unlink(gNfsConnection.GetNfsContext(), filename.c_str());
752   
753   if(ret != 0)
754   {
755     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
756   }
757   return (ret == 0);
758 }
759
760 bool CNFSFile::Rename(const CURL& url, const CURL& urlnew)
761 {
762   int ret = 0;
763   CSingleLock lock(gNfsConnection);
764   CStdString strFile = "";
765   
766   if(!gNfsConnection.Connect(url,strFile))
767     return false;
768   
769   CStdString strFileNew;
770   CStdString strDummy;
771   gNfsConnection.splitUrlIntoExportAndPath(urlnew, strDummy, strFileNew);
772   
773   ret = gNfsConnection.GetImpl()->nfs_rename(gNfsConnection.GetNfsContext() , strFile.c_str(), strFileNew.c_str());
774   
775   if(ret != 0)
776   {
777     CLog::Log(LOGERROR, "%s - Error( %s )", __FUNCTION__, gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
778   } 
779   return (ret == 0);
780 }
781
782 bool CNFSFile::OpenForWrite(const CURL& url, bool bOverWrite)
783
784   int ret = 0;
785   // we can't open files like nfs://file.f or nfs://server/file.f
786   // if a file matches the if below return false, it can't exist on a nfs share.
787   if (!IsValidFile(url.GetFileName())) return false;
788   
789   Close();
790   CSingleLock lock(gNfsConnection);
791   CStdString filename = "";
792   
793   if(!gNfsConnection.Connect(url,filename))
794     return false;
795   
796   m_pNfsContext = gNfsConnection.GetNfsContext();
797   m_exportPath = gNfsConnection.GetContextMapId();
798   
799   if (bOverWrite)
800   {
801     CLog::Log(LOGWARNING, "FileNFS::OpenForWrite() called with overwriting enabled! - %s", filename.c_str());
802     //create file with proper permissions
803     ret = gNfsConnection.GetImpl()->nfs_creat(m_pNfsContext, filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, &m_pFileHandle);    
804     //if file was created the file handle isn't valid ... so close it and open later
805     if(ret == 0)
806     {
807       gNfsConnection.GetImpl()->nfs_close(m_pNfsContext,m_pFileHandle);
808       m_pFileHandle = NULL;          
809     }
810   }
811
812   ret = gNfsConnection.GetImpl()->nfs_open(m_pNfsContext, filename.c_str(), O_RDWR, &m_pFileHandle);
813   
814   if (ret || m_pFileHandle == NULL)
815   {
816     // write error to logfile
817     CLog::Log(LOGERROR, "CNFSFile::Open: Unable to open file : '%s' error : '%s'", filename.c_str(), gNfsConnection.GetImpl()->nfs_get_error(gNfsConnection.GetNfsContext()));
818     m_pNfsContext = NULL;
819     m_exportPath.clear();
820     return false;
821   }
822   m_url=url;
823   
824   struct __stat64 tmpBuffer = {0};
825
826   //only stat if file was not created
827   if(!bOverWrite) 
828   {
829     if(Stat(&tmpBuffer))
830     {
831       m_url.Reset();
832       Close();
833       return false;
834     }
835     m_fileSize = tmpBuffer.st_size;//cache filesize of this file    
836   }
837   else//file was created - filesize is zero
838   {
839     m_fileSize = 0;    
840   }
841   
842   // We've successfully opened the file!
843   return true;
844 }
845
846 bool CNFSFile::IsValidFile(const CStdString& strFileName)
847 {
848   if (strFileName.find('/') == std::string::npos || /* doesn't have sharename */
849       StringUtils::EndsWith(strFileName, "/.") || /* not current folder */
850       StringUtils::EndsWith(strFileName, "/.."))  /* not parent folder */
851     return false;
852   return true;
853 }
854 #endif//HAS_FILESYSTEM_NFS
855