void test_ms_file_scan_1(void) { #ifdef WIN32 char valid_file[MAX_PATH_STR_LEN] = "data\\video\\bars-mpeg4-aac.m4v"; char invalid_file[MAX_PATH_STR_LEN] = "data\\video\\notafile.m4v"; #else char valid_file[MAX_PATH_STR_LEN] = "data/video/bars-mpeg4-aac.m4v"; char invalid_file[MAX_PATH_STR_LEN] = "data/video/notafile.m4v"; #endif MediaScan *s = ms_create(); CU_ASSERT_FATAL(s != NULL); // Check null mediascan oject ms_errno = 0; ms_scan_file(NULL, invalid_file, TYPE_VIDEO); CU_ASSERT(ms_errno == MSENO_NULLSCANOBJ); // Check for no callback set ms_errno = 0; ms_scan_file(s, invalid_file, TYPE_VIDEO); CU_ASSERT(ms_errno == MSENO_NORESULTCALLBACK); CU_ASSERT(s->on_result == NULL); ms_set_result_callback(s, my_result_callback_1); CU_ASSERT(s->on_result == my_result_callback_1); // Now scan an invalid file, without an error handler ms_errno = 0; ms_scan_file(s, invalid_file, TYPE_VIDEO); CU_ASSERT(ms_errno == MSENO_NOERRORCALLBACK); // Set up an error callback CU_ASSERT(s->on_error == NULL); ms_set_error_callback(s, my_error_callback); CU_ASSERT(s->on_error == my_error_callback); // Now scan an invalid file, with an error handler ms_errno = 0; error_called = FALSE; ms_scan_file(s, invalid_file, TYPE_VIDEO); CU_ASSERT(ms_errno != MSENO_NOERRORCALLBACK); CU_ASSERT(error_called == TRUE); // Now scan a valid video file, with an error handler ms_errno = 0; error_called = FALSE; ms_scan_file(s, valid_file, TYPE_VIDEO); // Need more info to run this test ms_destroy(s); } /* test_ms_file_scan_1 */
void test_ms_file_asf_audio(void) { #ifdef WIN32 char asf_file[MAX_PATH_STR_LEN] = "data\\audio\\wmv92-with-audio.wmv"; #else char asf_file[MAX_PATH_STR_LEN] = "data/audio/wmv92-with-audio.wmv"; #endif MediaScan *s = ms_create(); CU_ASSERT_FATAL(s != NULL); CU_ASSERT(s->on_result == NULL); ms_set_result_callback(s, my_result_callback_1); CU_ASSERT(s->on_result == my_result_callback_1); // Set up an error callback CU_ASSERT(s->on_error == NULL); ms_set_error_callback(s, my_error_callback); CU_ASSERT(s->on_error == my_error_callback); // Now scan a valid video file, with an error handler ms_errno = 0; error_called = FALSE; ms_scan_file(s, asf_file, TYPE_VIDEO); // Need more info to run this test ms_destroy(s); } /* test_ms_file_asf_audio */
void generate_thumbnails() { FILE *tfp; char file[MAX_PATH_STR_LEN]; int i, n; MediaScanImage *thumb; Buffer *dbuf; MediaScan *s = ms_create(); ms_set_result_callback(s, my_result_callback); ms_set_error_callback(s, my_error_callback); ms_add_thumbnail_spec(s, THUMB_PNG, 32,32, TRUE, 0, 90); for(i = 0; expected_results[i].filename; i++) { ms_scan_file(s, expected_results[i].filename, TYPE_IMAGE); if (!strcmp("JPEG", result._thumbs[0]->codec)) sprintf(file, "%s.jpg", expected_results[i].thumb_filename); else sprintf(file, "%s.png",expected_results[i].thumb_filename); write_png_file(file); } ms_destroy(s); }
void test_ms_db(void) { #ifdef WIN32 char valid_file[MAX_PATH_STR_LEN] = "data\\video\\bars-mpeg4-aac.m4v"; #else char valid_file[MAX_PATH_STR_LEN] = "data/video/bars-mpeg4-aac.m4v"; #endif MediaScan *s = ms_create(); CU_ASSERT_FATAL(s != NULL); CU_ASSERT(s->npaths == 0); CU_ASSERT(s->on_result == NULL); ms_set_result_callback(s, my_result_callback); CU_ASSERT(s->on_result == my_result_callback); CU_ASSERT(s->on_error == NULL); ms_set_error_callback(s, my_error_callback); CU_ASSERT(s->on_error == my_error_callback); ms_errno = 0; error_called = FALSE; // Okay scan a file result_called = FALSE; ms_scan_file(s, valid_file, TYPE_VIDEO); CU_ASSERT(result_called == TRUE); // now scan the same file again and make sure it is skipped result_called = FALSE; ms_scan_file(s, valid_file, TYPE_VIDEO); CU_ASSERT(result_called == FALSE); // now scan the same file again, this time with a different modification time and make sure it is scanned TouchFile(valid_file); result_called = FALSE; ms_scan_file(s, valid_file, TYPE_VIDEO); CU_ASSERT(result_called == TRUE); ms_destroy(s); } /* test_ms_db() */
void test_thumbnailing(void) { int i = 0; MediaScan *s = ms_create(); CU_ASSERT_FATAL(s != NULL); CU_ASSERT(s->on_result == NULL); ms_set_result_callback(s, my_result_callback); CU_ASSERT(s->on_result == my_result_callback); CU_ASSERT(s->on_error == NULL); ms_set_error_callback(s, my_error_callback); CU_ASSERT(s->on_error == my_error_callback); ms_add_thumbnail_spec(s, THUMB_PNG, 32,32, TRUE, 0, 90); ms_scan_file(s, expected_results[0].filename, TYPE_IMAGE); // TODO: Read and compare presaved and approved thumbnails ms_destroy(s); } /* test_thumbnailing() */
// Called by ms_scan either in a thread or synchronously static void *do_scan(void *userdata) { MediaScan *s = ((thread_data_type *)userdata)->s; int i; struct dirq *dir_head = (struct dirq *)s->_dirq; struct dirq_entry *dir_entry = NULL; struct fileq *file_head = NULL; struct fileq_entry *file_entry = NULL; char tmp_full_path[MAX_PATH_STR_LEN]; // Initialize the cache database if (!init_bdb(s)) { MediaScanError *e = error_create("", MS_ERROR_CACHE, "Unable to initialize libmediascan cache"); send_error(s, e); goto out; } if (s->flags & MS_CLEARDB) { reset_bdb(s); } if (s->progress == NULL) { MediaScanError *e = error_create("", MS_ERROR_TYPE_INVALID_PARAMS, "Progress object not created"); send_error(s, e); goto out; } // Build a list of all directories and paths // We do this first so we can present an accurate scan eta later progress_start_phase(s->progress, "Discovering"); for (i = 0; i < s->npaths; i++) { LOG_INFO("Scanning %s\n", s->paths[i]); recurse_dir(s, s->paths[i], 0); } // Scan all files found progress_start_phase(s->progress, "Scanning"); while (!SIMPLEQ_EMPTY(dir_head)) { dir_entry = SIMPLEQ_FIRST(dir_head); file_head = dir_entry->files; while (!SIMPLEQ_EMPTY(file_head)) { // check if the scan has been aborted if (s->_want_abort) { LOG_DEBUG("Aborting scan\n"); goto aborted; } file_entry = SIMPLEQ_FIRST(file_head); // Construct full path strcpy(tmp_full_path, dir_entry->dir); #ifdef WIN32 strcat(tmp_full_path, "\\"); #else strcat(tmp_full_path, "/"); #endif strcat(tmp_full_path, file_entry->file); ms_scan_file(s, tmp_full_path, file_entry->type); // Send progress update if necessary if (s->on_progress) { s->progress->done++; if (progress_update(s->progress, tmp_full_path)) send_progress(s); } SIMPLEQ_REMOVE_HEAD(file_head, entries); free(file_entry->file); free(file_entry); } SIMPLEQ_REMOVE_HEAD(dir_head, entries); free(dir_entry->dir); free(dir_entry->files); free(dir_entry); } // Send final progress callback if (s->on_progress) { progress_update(s->progress, NULL); send_progress(s); } LOG_DEBUG("Finished scanning\n"); out: if (s->on_finish) send_finish(s); aborted: if (s->async) { LOG_MEM("destroy thread_data @ %p\n", userdata); free(userdata); } return NULL; }
void WatchDirectory(void *thread_data) { // Copy thread inputs to local variables MediaScan *s = ((thread_data_type *)thread_data)->s; LPTSTR lpDir = ((thread_data_type *)thread_data)->lpDir; // Data variables for the windows directory change notification OVERLAPPED oOverlap; FILE_NOTIFY_INFORMATION Buffer[FILE_BUFFER_SZ]; char buf[256]; char full_path[MAX_PATH_STR_LEN]; DWORD BytesReturned; // Overlapped I/O variables WSAOVERLAPPED RecvOverlapped; HANDLE hDir; DWORD dwWaitStatus; DWORD dwBytesRead; BOOL bResult; WSABUF DataBuf; DWORD RecvBytes, Flags; char buffer[DATA_BUFSIZE]; int rc = 0; int err = 0; char *pBase = 0; // Thread state variables int ThreadRunning = TRUE; // Initialize the cache database if (!init_bdb(s)) { MediaScanError *e = error_create("", MS_ERROR_CACHE, "Unable to initialize libmediascan cache"); send_error(s, e); } SecureZeroMemory(buffer, DATA_BUFSIZE); // Make sure the RecvOverlapped struct is zeroed out SecureZeroMemory((PVOID) & oOverlap, sizeof(OVERLAPPED)); // Create the event that will get fired when a directory changes oOverlap.hEvent = CreateEvent(NULL, // default security attributes TRUE, // manual-reset event FALSE, // initial state is nonsignaled TEXT("MyFileChangeEvent")); // "FileChangeEvent" name // Make sure the RecvOverlapped struct is zeroed out SecureZeroMemory((PVOID) & RecvOverlapped, sizeof(WSAOVERLAPPED)); // Create an event handle and setup an overlapped structure. RecvOverlapped.hEvent = WSACreateEvent(); if (RecvOverlapped.hEvent == NULL) { LOG_ERROR("WSACreateEvent failed: %d\n", WSAGetLastError()); return; } DataBuf.len = DATA_BUFSIZE; DataBuf.buf = buffer; // Open the directory that we are going to have windows monitor, note the share modes hDir = CreateFile(lpDir, // pointer to the file name FILE_LIST_DIRECTORY, // access (read/write) mode FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, // share mode NULL, // security descriptor OPEN_EXISTING, // how to create FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // file attributes NULL); // file with attributes to copy // Tell windows to monitor the folder asyncroniously using overlapped I/O ReadDirectoryChangesW(hDir, // handle to directory &Buffer, // read results buffer FILE_BUFFER_SZ * sizeof(FILE_NOTIFY_INFORMATION), // length of buffer TRUE, // Monitor sub-directories = TRUE DETECTION_FILTER, // filter conditions &BytesReturned, // bytes returned &oOverlap, // We are using overlapped I/O NULL); // Not using completion routine Flags = 0; /* XXX FIXME, reqpipe is gone rc = WSARecv(s->thread->reqpipe[0], &DataBuf, 1, &RecvBytes, &Flags, &RecvOverlapped, NULL); if ((rc == SOCKET_ERROR) && (WSA_IO_PENDING != (err = WSAGetLastError()))) { LOG_ERROR("WSARecv failed with error: %d\n", err); return; } */ // Run until we are told to stop. It is important to let the thread clean up after itself so // there is shutdown code at the bottom of this function. while (ThreadRunning) { // Set up the events are going to wait for HANDLE event_list[2] = { oOverlap.hEvent, RecvOverlapped.hEvent }; FILE_NOTIFY_INFORMATION *fni; LOG_LEVEL(1, "\nWaiting for notification...\n"); // Wait forever for notification. dwWaitStatus = WaitForMultipleObjects(2, // number of objects in array event_list, // array of objects FALSE, // wait for any object INFINITE); // infinite wait switch (dwWaitStatus) { // This is for the event oOverlap.hEvent case WAIT_OBJECT_0: bResult = GetOverlappedResult(hDir, &oOverlap, &dwBytesRead, TRUE); pBase = (char *)Buffer; do { fni = (FILE_NOTIFY_INFORMATION *) pBase; // Note pRecord->FileName is in UTF-16, have to convert it to 8 bits WideCharToMultiByte(CP_UTF8, 0, fni->FileName, // the string you have fni->FileNameLength / 2, // length of the string buf, // output _countof(buf), // size of the buffer in bytes - if you leave it zero the return value is the length required for the output buffer NULL, NULL); buf[fni->FileNameLength / 2] = 0; // Set up a full path to the file to be scanned and call ms_scan_fileCall scan here with the file changed strcpy(full_path, lpDir); strcat(full_path, "\\"); strcat(full_path, buf); LOG_INFO("Found File Changed: %s", full_path); switch (fni->Action) { case FILE_ACTION_ADDED: LOG_INFO(" file was added\n"); // We get notifications even while a file is being copied. This will wait 60 seconds for the file to finish. if (WaitForFile(full_path, 10)) { ms_scan_file(s, full_path, TYPE_UNKNOWN); } else { LOG_ERROR("A file found by the background scanner never finished copying"); } break; case FILE_ACTION_REMOVED: LOG_INFO(" file was removed\n"); HandleRemovedFile(s, full_path); break; case FILE_ACTION_MODIFIED: LOG_INFO(" file was modified\n"); // This can be a change in the time stamp or attributes."; // We get notifications even while a file is being copied. This will wait 60 seconds for the file to finish. if (WaitForFile(full_path, 10)) { ms_scan_file(s, full_path, TYPE_UNKNOWN); } else { LOG_ERROR("A file found by the background scanner never finished copying"); } break; /* case FILE_ACTION_RENAMED_OLD_NAME: LOG_INFO(" file was renamed and this is the old name\n"); break; case FILE_ACTION_RENAMED_NEW_NAME: LOG_INFO(" file was renamed and this is the new name\n"); break; */ } if (!fni->NextEntryOffset) break; pBase += fni->NextEntryOffset; } while (TRUE); SecureZeroMemory((PVOID) Buffer, sizeof(Buffer)); ReadDirectoryChangesW(hDir, // handle to directory &Buffer, // read results buffer sizeof(Buffer), // length of buffer TRUE, // Monitor sub-directories = TRUE DETECTION_FILTER, // filter conditions &BytesReturned, // bytes returned &oOverlap, // We are using overlapped I/O NULL); // Not using completion routine break; /* WAIT_OBJECT_0: */ // This is for the event s->ghSignalEvent case WAIT_OBJECT_0 + 1: ThreadRunning = FALSE; break; /* WAIT_OBJECT_0 + 1: */ case WAIT_TIMEOUT: // A timeout occurred, this would happen if some value other // than INFINITE is used in the Wait call and no changes occur. // In a single-threaded environment you might not want an // INFINITE wait. LOG_INFO("\nNo changes in the timeout period.\n"); break; default: LOG_ERROR("\n ERROR: Unhandled dwWaitStatus.\n"); ExitThread(GetLastError()); break; } } // Free the data that was passed to this thread on the heap if (thread_data != NULL) { free(thread_data); thread_data = NULL; // Ensure address is not reused. } CancelIo(hDir); if (!HasOverlappedIoCompleted(&oOverlap)) { SleepEx(5, TRUE); } WSACloseEvent(RecvOverlapped.hEvent); CloseHandle(oOverlap.hEvent); ExitThread(GetLastError()); } /* WatchDirectory() */
void test_image_reading(void) { int i = 0; MediaScan *s = ms_create(); CU_ASSERT_FATAL(s != NULL); CU_ASSERT(s->on_result == NULL); ms_set_result_callback(s, my_result_callback); CU_ASSERT(s->on_result == my_result_callback); CU_ASSERT(s->on_error == NULL); ms_set_error_callback(s, my_error_callback); CU_ASSERT(s->on_error == my_error_callback); for(i = 0; expected_results[i].filename; i++) { error_called = FALSE; result_called = FALSE; ms_scan_file(s, expected_results[i].filename, TYPE_IMAGE); if(expected_results[i].failure) { CU_ASSERT(error_called); continue; } else { CU_ASSERT(result_called); } CU_ASSERT_PTR_NOT_NULL(result.mime_type); if(result.mime_type) { CU_ASSERT_STRING_EQUAL(result.mime_type, expected_results[i].mime_type); } CU_ASSERT_PTR_NOT_NULL(result.image); if(result.image) { CU_ASSERT_STRING_EQUAL(result.image->codec, expected_results[i].codec); CU_ASSERT(result.image->width == expected_results[i].width); CU_ASSERT(result.image->height == expected_results[i].height); CU_ASSERT(result.image->channels == expected_results[i].channels); if(!strcmp(expected_results[i].codec, "GIF") ) { CU_ASSERT(result.image->_jpeg == NULL); CU_ASSERT(result.image->_png == NULL); CU_ASSERT(result.image->_bmp == NULL); // CU_ASSERT(result.image->_gif != NULL); } else if(!strcmp(expected_results[i].codec, "JPEG") ) { CU_ASSERT(result.image->_jpeg != NULL); CU_ASSERT(result.image->_png == NULL); CU_ASSERT(result.image->_bmp == NULL); // CU_ASSERT(result.image->_gif == NULL); } else if(!strcmp(expected_results[i].codec, "PNG") ) { CU_ASSERT(result.image->_jpeg == NULL); CU_ASSERT(result.image->_png != NULL); CU_ASSERT(result.image->_bmp == NULL); // CU_ASSERT(result.image->_gif == NULL); } else if(!strcmp(expected_results[i].codec, "BMP") ) { CU_ASSERT(result.image->_jpeg == NULL); CU_ASSERT(result.image->_png == NULL); CU_ASSERT(result.image->_bmp != NULL); // CU_ASSERT(result.image->_gif == NULL); } } } ms_destroy(s); } /* test_image_reading() */