vdr
1.7.27
|
00001 /* 00002 * filetransfer.c: The video file transfer facilities 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: $ 00008 */ 00009 00010 #include "videodir.h" 00011 #include "filetransfer.h" 00012 00013 static cString StripLastDirectory(const char *DirName) 00014 { 00015 if (DirName && *DirName) { 00016 cString s(DirName); 00017 int l = strlen(*s); 00018 const char *p = *s + l; 00019 while (l > 0) { 00020 if (*p-- == '/') 00021 break; 00022 l--; 00023 } 00024 if (l) 00025 s = s.Truncate(l); 00026 return s; 00027 } 00028 return NULL; 00029 } 00030 00031 // --- cCopyingThread -------------------------------------------------------- 00032 00033 class cCopyingThread : public cThread { 00034 private: 00035 const char *error; 00036 bool deleteSource; 00037 cString source; 00038 cString target; 00039 protected: 00040 virtual void Action(void); 00041 public: 00042 cCopyingThread(const char *SourceName, const char *ToFileName, bool DeleteSource = false); 00043 virtual ~cCopyingThread(); 00044 const char *Error(void) { return error; } 00045 }; 00046 00047 cCopyingThread::cCopyingThread(const char *SourceName, const char *TargetName, bool DeleteSource) 00048 :cThread("copying"), 00049 error(NULL), 00050 deleteSource(DeleteSource), 00051 source(SourceName), 00052 target(TargetName) 00053 { 00054 // add missing directory delimiters 00055 const char *delim = "/"; 00056 if (!endswith(*source, delim)) 00057 source = cString::sprintf("%s%s", *source, delim); 00058 if (!endswith(*target, delim)) 00059 target = cString::sprintf("%s%s", *target, delim); 00060 00061 Start(); 00062 } 00063 00064 cCopyingThread::~cCopyingThread() 00065 { 00066 Cancel(3); 00067 } 00068 00069 void cCopyingThread::Action(void) 00070 { 00071 SetPriority(19); 00072 SetIOPriority(7); 00073 00074 if (strcmp(*source, *target)) { 00075 // validate target directory 00076 if (strstr(*target, *source)) { 00077 error = "invalid target"; 00078 return; 00079 } 00080 00081 // recordings methods require the last directory delimiter to be stripped off 00082 cString recname = target; 00083 recname.Truncate(strlen(*recname) - 1); 00084 Recordings.AddByName(*recname, false); 00085 00086 RemoveFileOrDir(*target); 00087 if (!MakeDirs(*target, true)) { 00088 error = "MakeDirs"; 00089 return; 00090 } 00091 00092 if (deleteSource && EntriesOnSameFileSystem(*source, *target)) { 00093 if (rename(*source, *target) == -1) { 00094 error = "rename"; 00095 return; 00096 } 00097 // delete all empty source directories 00098 recname = source; 00099 recname.Truncate(strlen(*recname) - 1); 00100 recname = StripLastDirectory(*recname); 00101 do { 00102 if (!RemoveEmptyDirectories(*recname, true)) 00103 break; 00104 recname = StripLastDirectory(*recname); 00105 } 00106 while (strcmp(*recname, VideoDirectory)); 00107 } 00108 else { 00109 int required = DirSizeMB(*source); 00110 int available = FreeDiskSpaceMB(*target); 00111 00112 // validate free space 00113 if (required < available) { 00114 cReadDir d(*source); 00115 struct dirent *e; 00116 bool success = true; 00117 00118 // allocate copying buffer 00119 const int len = 1024 * 1024; 00120 char *buffer = MALLOC(char, len); 00121 if (!buffer) { 00122 error = "MALLOC"; 00123 return; 00124 } 00125 00126 // loop through all files, but skip all sub-directories 00127 while (Running() && (e = d.Next()) != NULL) { 00128 // skip generic entries 00129 if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..") && strcmp(e->d_name, "lost+found")) { 00130 cString sourceFile = cString::sprintf("%s%s", *source, e->d_name); 00131 cString targetFile = cString::sprintf("%s%s", *target, e->d_name); 00132 00133 // copy only regular files 00134 struct stat sts; 00135 if (!stat(*sourceFile, &sts) && S_ISREG(sts.st_mode)) { 00136 int r = -1, w = -1; 00137 cUnbufferedFile *inputFile = cUnbufferedFile::Create(*sourceFile, O_RDONLY | O_LARGEFILE); 00138 cUnbufferedFile *outputFile = cUnbufferedFile::Create(*targetFile, O_RDWR | O_CREAT | O_LARGEFILE); 00139 00140 // validate files 00141 if (!inputFile || !outputFile) { 00142 success = false; 00143 break; 00144 } 00145 00146 // do actual copy 00147 do { 00148 r = inputFile->Read(buffer, len); 00149 if (r > 0) 00150 w = outputFile->Write(buffer, r); 00151 else 00152 w = 0; 00153 } while (Running() && r > 0 && w > 0); 00154 DELETENULL(inputFile); 00155 DELETENULL(outputFile); 00156 00157 // validate result 00158 if (!Running() || r < 0 || w < 0) { 00159 success = false; 00160 break; 00161 } 00162 } 00163 } 00164 } 00165 00166 // release allocated buffer 00167 free(buffer); 00168 00169 // delete all created target files and directories 00170 if (!success) { 00171 target = StripLastDirectory(*target); 00172 RemoveFileOrDir(*target, true); 00173 target = StripLastDirectory(*target); 00174 RemoveEmptyDirectories(*target, true); 00175 error = "copy failed"; 00176 return; 00177 } 00178 } 00179 else { 00180 // delete all created empty target directories 00181 recname = target; 00182 recname.Truncate(strlen(*recname) - 1); 00183 recname = StripLastDirectory(*recname); 00184 do { 00185 if (!RemoveEmptyDirectories(*recname, true)) 00186 break; 00187 recname = StripLastDirectory(*recname); 00188 } 00189 while (strcmp(*recname, VideoDirectory)); 00190 error = "insufficient free space"; 00191 return; 00192 } 00193 } 00194 00195 if (deleteSource) { 00196 // Recordings' methods require the last directory delimiter to be stripped off 00197 source.Truncate(strlen(*source) - 1); 00198 cRecording *recording = Recordings.GetByName(*source); 00199 if (recording->Delete()) 00200 Recordings.DelByName(*source, false); 00201 } 00202 else 00203 Recordings.TouchUpdate(); 00204 } 00205 } 00206 00207 // --- cFileTransfer ---------------------------------------------------------------- 00208 00209 cMutex cFileTransfer::mutex; 00210 char *cFileTransfer::copiedVersionName = NULL; 00211 cCopyingThread *cFileTransfer::copyingThread = NULL; 00212 bool cFileTransfer::error = false; 00213 bool cFileTransfer::ended = false; 00214 00215 bool cFileTransfer::Start(cRecording *Recording, const char *FileName, bool CopyOnly) 00216 { 00217 cMutexLock MutexLock(&mutex); 00218 if (!copyingThread) { 00219 cString NewName = NewVideoFileName(Recording->FileName(), FileName); 00220 error = false; 00221 ended = false; 00222 if (strlen(*NewName)) { 00223 copiedVersionName = strdup(*NewName); 00224 copyingThread = new cCopyingThread(Recording->FileName(), copiedVersionName, !CopyOnly); 00225 return true; 00226 } 00227 } 00228 return false; 00229 } 00230 00231 void cFileTransfer::Stop(void) 00232 { 00233 cMutexLock MutexLock(&mutex); 00234 bool Interrupted = copyingThread && copyingThread->Active(); 00235 const char *Error = copyingThread ? copyingThread->Error() : NULL; 00236 DELETENULL(copyingThread); 00237 if ((Interrupted || Error)) { 00238 if (Interrupted) 00239 isyslog("file transfer has been interrupted"); 00240 if (Error) 00241 esyslog("ERROR: '%s' during file transfer", Error); 00242 RemoveVideoFile(copiedVersionName); //XXX what if this file is currently being replayed? 00243 Recordings.DelByName(copiedVersionName); 00244 free(copiedVersionName); 00245 copiedVersionName = NULL; 00246 } 00247 } 00248 00249 bool cFileTransfer::Active(void) 00250 { 00251 cMutexLock MutexLock(&mutex); 00252 if (copyingThread) { 00253 if (copyingThread->Active()) 00254 return true; 00255 error = copyingThread->Error(); 00256 Stop(); 00257 free(copiedVersionName); 00258 copiedVersionName = NULL; 00259 ended = true; 00260 } 00261 return false; 00262 } 00263 00264 bool cFileTransfer::Error(void) 00265 { 00266 cMutexLock MutexLock(&mutex); 00267 bool result = error; 00268 error = false; 00269 return result; 00270 } 00271 00272 bool cFileTransfer::Ended(void) 00273 { 00274 cMutexLock MutexLock(&mutex); 00275 bool result = ended; 00276 ended = false; 00277 return result; 00278 }