diff options
Diffstat (limited to 'chrome/browser')
40 files changed, 3257 insertions, 42 deletions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 39d0c49..af5a007 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -170,6 +170,10 @@ #include "chrome/browser/signin/signin_manager_factory.h" #endif +#if !defined(OS_ANDROID) +#include "chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h" +#endif + using base::FileDescriptor; using content::AccessTokenStore; using content::BrowserChildProcessHostIterator; @@ -2108,6 +2112,15 @@ void ChromeContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem( additional_allowed_schemes->push_back(extensions::kExtensionScheme); } +void ChromeContentBrowserClient::GetAdditionalFileSystemMountPointProviders( + const base::FilePath& storage_partition_path, + ScopedVector<fileapi::FileSystemMountPointProvider>* additional_providers) { +#if !defined(OS_ANDROID) + additional_providers->push_back(new MediaFileSystemMountPointProvider( + storage_partition_path)); +#endif +} + #if defined(OS_POSIX) && !defined(OS_MACOSX) void ChromeContentBrowserClient::GetAdditionalMappedFilesForChildProcess( const CommandLine& command_line, diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index 9b899a6..4a5f5c6 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h @@ -224,6 +224,10 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { content::WebContents* web_contents) OVERRIDE; virtual void GetAdditionalAllowedSchemesForFileSystem( std::vector<std::string>* additional_schemes) OVERRIDE; + virtual void GetAdditionalFileSystemMountPointProviders( + const base::FilePath& storage_partition_path, + ScopedVector<fileapi::FileSystemMountPointProvider>* + additional_providers) OVERRIDE; #if defined(OS_POSIX) && !defined(OS_MACOSX) virtual void GetAdditionalMappedFilesForChildProcess( diff --git a/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc new file mode 100644 index 0000000..1c7046c --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.cc @@ -0,0 +1,353 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/device_media_async_file_util.h" + +#include "base/callback.h" +#include "base/file_util.h" +#include "base/single_thread_task_runner.h" +#include "chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h" +#include "chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h" +#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h" +#include "webkit/fileapi/file_system_context.h" +#include "webkit/fileapi/file_system_operation_context.h" +#include "webkit/fileapi/file_system_task_runners.h" +#include "webkit/fileapi/file_system_url.h" +#include "webkit/fileapi/isolated_context.h" + +using fileapi::FileSystemOperationContext; +using fileapi::FileSystemURL; + +namespace chrome { + +namespace { + +const base::FilePath::CharType kDeviceMediaAsyncFileUtilTempDir[] = + FILE_PATH_LITERAL("DeviceMediaFileSystem"); + +// Returns true if the current thread is IO thread. +bool IsOnIOThread(FileSystemOperationContext* context) { + return context->file_system_context()->task_runners()-> + io_task_runner()->RunsTasksOnCurrentThread(); +} + +// Called on the IO thread. +MTPDeviceAsyncDelegate* GetMTPDeviceDelegate( + FileSystemOperationContext* context) { + DCHECK(IsOnIOThread(context)); + return MTPDeviceMapService::GetInstance()->GetMTPDeviceAsyncDelegate( + context->GetUserValue<std::string>( + MediaFileSystemMountPointProvider::kMTPDeviceDelegateURLKey)); +} + +// Called on a blocking pool thread to create a snapshot file to hold the +// contents of |device_file_path|. The snapshot file is created in +// "profile_path/kDeviceMediaAsyncFileUtilTempDir" directory. If the snapshot +// file is created successfully, |snapshot_file_path| will be a non-empty file +// path. In case of failure, the |snapshot_file_path| will be an empty file +// path. +void CreateSnapshotFileOnBlockingPool( + const base::FilePath& device_file_path, + const base::FilePath& profile_path, + base::FilePath* snapshot_file_path) { + DCHECK(snapshot_file_path); + base::FilePath isolated_media_file_system_dir_path = + profile_path.Append(kDeviceMediaAsyncFileUtilTempDir); + if (!file_util::CreateDirectory(isolated_media_file_system_dir_path) || + !file_util::CreateTemporaryFileInDir(isolated_media_file_system_dir_path, + snapshot_file_path)) { + LOG(WARNING) << "Could not create media snapshot file " + << isolated_media_file_system_dir_path.value(); + *snapshot_file_path = base::FilePath(); + } +} + +} // namespace + +DeviceMediaAsyncFileUtil::~DeviceMediaAsyncFileUtil() { +} + +// static +DeviceMediaAsyncFileUtil* DeviceMediaAsyncFileUtil::Create( + const base::FilePath& profile_path) { + DCHECK(!profile_path.empty()); + return new DeviceMediaAsyncFileUtil(profile_path); +} + +bool DeviceMediaAsyncFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) { + base::PlatformFile invalid_file = base::kInvalidPlatformFileValue; + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, + base::PassPlatformFile(&invalid_file), + false); + } + return true; +} + +bool DeviceMediaAsyncFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, false); + return true; +} + +bool DeviceMediaAsyncFileUtil::CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) { + DCHECK(IsOnIOThread(context)); + MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(context); + if (!delegate) { + OnGetFileInfoError(callback, url.path(), + base::PLATFORM_FILE_ERROR_NOT_FOUND); + return true; + } + delegate->GetFileInfo( + url.path(), + base::Bind(&DeviceMediaAsyncFileUtil::OnDidGetFileInfo, + weak_ptr_factory_.GetWeakPtr(), + callback, + url.path()), + base::Bind(&DeviceMediaAsyncFileUtil::OnGetFileInfoError, + weak_ptr_factory_.GetWeakPtr(), + callback, + url.path())); + return true; +} + +bool DeviceMediaAsyncFileUtil::ReadDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) { + DCHECK(IsOnIOThread(context)); + MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(context); + if (!delegate) { + OnReadDirectoryError(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND); + return true; + } + delegate->ReadDirectory( + url.path(), + base::Bind(&DeviceMediaAsyncFileUtil::OnDidReadDirectory, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&DeviceMediaAsyncFileUtil::OnReadDirectoryError, + weak_ptr_factory_.GetWeakPtr(), + callback)); + return true; +} + +bool DeviceMediaAsyncFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::CopyFileLocal( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::MoveFileLocal( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(IsOnIOThread(context)); + NOTIMPLEMENTED(); + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return true; +} + +bool DeviceMediaAsyncFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) { + DCHECK(IsOnIOThread(context)); + MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(context); + if (!delegate) { + OnCreateSnapshotFileError(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND); + return true; + } + base::FilePath* snapshot_file_path = new base::FilePath; + return context->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&CreateSnapshotFileOnBlockingPool, + url.path(), + profile_path_, + base::Unretained(snapshot_file_path)), + base::Bind(&DeviceMediaAsyncFileUtil::OnSnapshotFileCreatedRunTask, + weak_ptr_factory_.GetWeakPtr(), + context, + callback, + url.path(), + base::Owned(snapshot_file_path))); +} + +DeviceMediaAsyncFileUtil::DeviceMediaAsyncFileUtil( + const base::FilePath& profile_path) + : profile_path_(profile_path), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { +} + +void DeviceMediaAsyncFileUtil::OnDidGetFileInfo( + const AsyncFileUtil::GetFileInfoCallback& callback, + const base::FilePath& platform_path, + const base::PlatformFileInfo& file_info) { + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_OK, file_info, platform_path); +} + +void DeviceMediaAsyncFileUtil::OnGetFileInfoError( + const AsyncFileUtil::GetFileInfoCallback& callback, + const base::FilePath& platform_path, + base::PlatformFileError error) { + if (!callback.is_null()) + callback.Run(error, base::PlatformFileInfo(), platform_path); +} + +void DeviceMediaAsyncFileUtil::OnDidReadDirectory( + const AsyncFileUtil::ReadDirectoryCallback& callback, + const AsyncFileUtil::EntryList& file_list, + bool has_more) { + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_OK, file_list, has_more); +} + +void DeviceMediaAsyncFileUtil::OnReadDirectoryError( + const AsyncFileUtil::ReadDirectoryCallback& callback, + base::PlatformFileError error) { + if (!callback.is_null()) + callback.Run(error, AsyncFileUtil::EntryList(), false /*no more*/); +} + +void DeviceMediaAsyncFileUtil::OnDidCreateSnapshotFile( + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path) { + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_OK, file_info, platform_path, + fileapi::kSnapshotFileTemporary); +} + +void DeviceMediaAsyncFileUtil::OnCreateSnapshotFileError( + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + base::PlatformFileError error) { + if (!callback.is_null()) + callback.Run(error, base::PlatformFileInfo(), base::FilePath(), + fileapi::kSnapshotFileTemporary); +} + +void DeviceMediaAsyncFileUtil::OnSnapshotFileCreatedRunTask( + FileSystemOperationContext* context, + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + const base::FilePath& device_file_path, + base::FilePath* snapshot_file_path) { + DCHECK(IsOnIOThread(context)); + if (!snapshot_file_path || snapshot_file_path->empty()) { + OnCreateSnapshotFileError(callback, base::PLATFORM_FILE_ERROR_FAILED); + return; + } + MTPDeviceAsyncDelegate* delegate = GetMTPDeviceDelegate(context); + if (!delegate) { + OnCreateSnapshotFileError(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + delegate->CreateSnapshotFile( + device_file_path, + *snapshot_file_path, + base::Bind(&DeviceMediaAsyncFileUtil::OnDidCreateSnapshotFile, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&DeviceMediaAsyncFileUtil::OnCreateSnapshotFileError, + weak_ptr_factory_.GetWeakPtr(), + callback)); +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/device_media_async_file_util.h b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.h new file mode 100644 index 0000000..dde43a8 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/device_media_async_file_util.h @@ -0,0 +1,177 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_DEVICE_MEDIA_ASYNC_FILE_UTIL_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_DEVICE_MEDIA_ASYNC_FILE_UTIL_H_ + +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "webkit/fileapi/async_file_util.h" + +namespace base { +class Time; +} + +namespace fileapi { +class FileSystemOperationContext; +class FileSystemURL; +} + +namespace chrome { + +class DeviceMediaAsyncFileUtil : public fileapi::AsyncFileUtil { + public: + virtual ~DeviceMediaAsyncFileUtil(); + + // Returns an instance of DeviceMediaAsyncFileUtil. Returns NULL if + // asynchronous operation is not supported. Callers own the returned + // object. + static DeviceMediaAsyncFileUtil* Create(const base::FilePath& profile_path); + + // AsyncFileUtil overrides. + virtual bool CreateOrOpen( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) OVERRIDE; + virtual bool EnsureFileExists( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const EnsureFileExistsCallback& callback) OVERRIDE; + virtual bool CreateDirectory( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual bool GetFileInfo( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const GetFileInfoCallback& callback) OVERRIDE; + virtual bool ReadDirectory( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const ReadDirectoryCallback& callback) OVERRIDE; + virtual bool Touch( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual bool Truncate( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + int64 length, + const StatusCallback& callback) OVERRIDE; + virtual bool CopyFileLocal( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& src_url, + const fileapi::FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual bool MoveFileLocal( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& src_url, + const fileapi::FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual bool CopyInForeignFile( + fileapi::FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const fileapi::FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual bool DeleteFile( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual bool DeleteDirectory( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual bool CreateSnapshotFile( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const CreateSnapshotFileCallback& callback) OVERRIDE; + + private: + // Use Create() to get an instance of DeviceMediaAsyncFileUtil. + explicit DeviceMediaAsyncFileUtil(const base::FilePath& profile_path); + + // Called when GetFileInfo method call succeeds. |file_info| + // contains the |platform_path| file details. |callback| is invoked + // to complete the GetFileInfo request. + void OnDidGetFileInfo( + const AsyncFileUtil::GetFileInfoCallback& callback, + const base::FilePath& platform_path, + const base::PlatformFileInfo& file_info); + + // Called when GetFileInfo method call failed to get the details of file + // specified by the |platform_path|. |callback| is invoked to notify the + // caller about the platform file |error|. + void OnGetFileInfoError( + const AsyncFileUtil::GetFileInfoCallback& callback, + const base::FilePath& platform_path, + base::PlatformFileError error); + + // Called when ReadDirectory method call succeeds. |callback| is invoked to + // complete the ReadDirectory request. + // + // If the contents of the given directory are reported in one batch, then + // |file_list| will have the list of all files/directories in the given + // directory and |has_more| will be false. + // + // If the contents of the given directory are reported in multiple chunks, + // |file_list| will have only a subset of all contents (the subsets reported + // in any two calls are disjoint), and |has_more| will be true, except for + // the last chunk. + void OnDidReadDirectory( + const AsyncFileUtil::ReadDirectoryCallback& callback, + const AsyncFileUtil::EntryList& file_list, + bool has_more); + + // Called when ReadDirectory method call failed to enumerate the directory + // objects. |callback| is invoked to notify the caller about the |error| + // that occured while reading the directory objects. + void OnReadDirectoryError( + const AsyncFileUtil::ReadDirectoryCallback& callback, + base::PlatformFileError error); + + // Called when the snapshot file specified by the |platform_path| is + // successfully created. |file_info| contains the device media file details + // for which the snapshot file is created. |callback| is invoked to complete + // the CreateSnapshotFile request. + void OnDidCreateSnapshotFile( + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path); + + // Called when CreateSnapshotFile method call fails. |callback| is invoked to + // notify the caller about the |error|. + void OnCreateSnapshotFileError( + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + base::PlatformFileError error); + + // Called when the snapshot file specified by the |snapshot_file_path| is + // created to hold the contents of the |device_file_path|. If the snapshot + // file is successfully created, |snapshot_file_path| will be an non-empty + // file path. In case of failure, |snapshot_file_path| will be an empty file + // path. Forwards the CreateSnapshot request to the delegate to copy the + // contents of |device_file_path| to |snapshot_file_path|. + void OnSnapshotFileCreatedRunTask( + fileapi::FileSystemOperationContext* context, + const AsyncFileUtil::CreateSnapshotFileCallback& callback, + const base::FilePath& device_file_path, + base::FilePath* snapshot_file_path); + + // Profile path. + const base::FilePath profile_path_; + + // For callbacks that may run after destruction. + base::WeakPtrFactory<DeviceMediaAsyncFileUtil> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DeviceMediaAsyncFileUtil); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_DEVICE_MEDIA_ASYNC_FILE_UTIL_H_ diff --git a/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.cc b/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.cc new file mode 100644 index 0000000..cbb52b3 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#else +#include <cstring> + +#include "base/string_util.h" +#endif + +namespace chrome { + +namespace { + +// Used to skip the hidden folders and files. Returns true if the file specified +// by |path| should be skipped. +bool ShouldSkip(const base::FilePath& path) { + const base::FilePath& base_name = path.BaseName(); + if (base_name.empty()) + return false; + + // Dot files (aka hidden files) + if (base_name.value()[0] == '.') + return true; + + // Mac OS X file. + if (base_name.value() == FILE_PATH_LITERAL("__MACOSX")) + return true; + +#if defined(OS_WIN) + DWORD file_attributes = ::GetFileAttributes(path.value().c_str()); + if ((file_attributes != INVALID_FILE_ATTRIBUTES) && + ((file_attributes & FILE_ATTRIBUTE_HIDDEN) != 0)) + return true; +#else + // Windows always creates a recycle bin folder in the attached device to store + // all the deleted contents. On non-windows operating systems, there is no way + // to get the hidden attribute of windows recycle bin folders that are present + // on the attached device. Therefore, compare the file path name to the + // recycle bin name and exclude those folders. For more details, please refer + // to http://support.microsoft.com/kb/171694. + const char win_98_recycle_bin_name[] = "RECYCLED"; + const char win_xp_recycle_bin_name[] = "RECYCLER"; + const char win_vista_recycle_bin_name[] = "$Recycle.bin"; + if ((base::strncasecmp(base_name.value().c_str(), + win_98_recycle_bin_name, + strlen(win_98_recycle_bin_name)) == 0) || + (base::strncasecmp(base_name.value().c_str(), + win_xp_recycle_bin_name, + strlen(win_xp_recycle_bin_name)) == 0) || + (base::strncasecmp(base_name.value().c_str(), + win_vista_recycle_bin_name, + strlen(win_vista_recycle_bin_name)) == 0)) + return true; +#endif + return false; +} + +} // namespace + +FilteringFileEnumerator::FilteringFileEnumerator( + scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> + base_enumerator, + MediaPathFilter* filter) + : base_enumerator_(base_enumerator.Pass()), + filter_(filter) { + DCHECK(base_enumerator_.get()); + DCHECK(filter); +} + +FilteringFileEnumerator::~FilteringFileEnumerator() { +} + +base::FilePath FilteringFileEnumerator::Next() { + while (true) { + base::FilePath next = base_enumerator_->Next(); + if (ShouldSkip(next)) + continue; + + if (next.empty() || + base_enumerator_->IsDirectory() || + filter_->Match(next)) + return next; + } +} + +int64 FilteringFileEnumerator::Size() { + return base_enumerator_->Size(); +} + +base::Time FilteringFileEnumerator::LastModifiedTime() { + return base_enumerator_->LastModifiedTime(); +} + +bool FilteringFileEnumerator::IsDirectory() { + return base_enumerator_->IsDirectory(); +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h b/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h new file mode 100644 index 0000000..7e17dae --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_FILTERING_FILE_ENUMERATOR_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_FILTERING_FILE_ENUMERATOR_H_ + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" +#include "webkit/fileapi/file_system_file_util.h" + +namespace chrome { + +// This class wraps another file enumerator and filters out non-media files +// from its result, refering given MediaPathFilter. +class FilteringFileEnumerator + : public fileapi::FileSystemFileUtil::AbstractFileEnumerator { + public: + FilteringFileEnumerator( + scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> + base_enumerator, + MediaPathFilter* filter); + virtual ~FilteringFileEnumerator(); + + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + + private: + // The file enumerator to be wrapped. + scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> + base_enumerator_; + + // Path filter to filter out non-Media files. + MediaPathFilter* filter_; +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_FILTERING_FILE_ENUMERATOR_H_ diff --git a/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.cc b/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.cc new file mode 100644 index 0000000..6fb69c6 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.cc @@ -0,0 +1,222 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h" + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/sequenced_task_runner.h" +#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" +#include "chrome/browser/media_galleries/fileapi/native_media_file_util.h" +#include "webkit/blob/local_file_stream_reader.h" +#include "webkit/fileapi/async_file_util_adapter.h" +#include "webkit/fileapi/copy_or_move_file_validator.h" +#include "webkit/fileapi/file_system_callback_dispatcher.h" +#include "webkit/fileapi/file_system_context.h" +#include "webkit/fileapi/file_system_file_stream_reader.h" +#include "webkit/fileapi/file_system_operation_context.h" +#include "webkit/fileapi/file_system_task_runners.h" +#include "webkit/fileapi/file_system_types.h" +#include "webkit/fileapi/file_system_util.h" +#include "webkit/fileapi/isolated_context.h" +#include "webkit/fileapi/isolated_file_util.h" +#include "webkit/fileapi/local_file_stream_writer.h" +#include "webkit/fileapi/local_file_system_operation.h" +#include "webkit/fileapi/native_file_util.h" + +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) +#include "chrome/browser/media_galleries/fileapi/device_media_async_file_util.h" +#endif + +using fileapi::FileSystemContext; +using fileapi::FileSystemType; +using fileapi::FileSystemURL; + +namespace chrome { + +const char MediaFileSystemMountPointProvider::kMediaPathFilterKey[] = + "MediaPathFilterKey"; +const char MediaFileSystemMountPointProvider::kMTPDeviceDelegateURLKey[] = + "MTPDeviceDelegateKey"; + +MediaFileSystemMountPointProvider::MediaFileSystemMountPointProvider( + const base::FilePath& profile_path) + : profile_path_(profile_path), + media_path_filter_(new MediaPathFilter()), + native_media_file_util_( + new fileapi::AsyncFileUtilAdapter(new NativeMediaFileUtil())) { +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) + // TODO(kmadhusu): Initialize |device_media_file_util_| in + // initialization list. + device_media_async_file_util_.reset( + DeviceMediaAsyncFileUtil::Create(profile_path_)); +#endif +} + +MediaFileSystemMountPointProvider::~MediaFileSystemMountPointProvider() { +} + +bool MediaFileSystemMountPointProvider::CanHandleType( + FileSystemType type) const { + switch (type) { + case fileapi::kFileSystemTypeNativeMedia: + case fileapi::kFileSystemTypeDeviceMedia: + return true; + default: + return false; + } +} + +void MediaFileSystemMountPointProvider::ValidateFileSystemRoot( + const GURL& origin_url, + FileSystemType type, + bool create, + const ValidateFileSystemCallback& callback) { + // We never allow opening a new isolated FileSystem via usual OpenFileSystem. + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, base::PLATFORM_FILE_ERROR_SECURITY)); +} + +base::FilePath +MediaFileSystemMountPointProvider::GetFileSystemRootPathOnFileThread( + const FileSystemURL& url, + bool create) { + // This is not supposed to be used. + NOTREACHED(); + return base::FilePath(); +} + +fileapi::FileSystemFileUtil* MediaFileSystemMountPointProvider::GetFileUtil( + FileSystemType type) { + switch (type) { + case fileapi::kFileSystemTypeNativeMedia: + return native_media_file_util_->sync_file_util(); + default: + NOTREACHED(); + } + return NULL; +} + +fileapi::AsyncFileUtil* MediaFileSystemMountPointProvider::GetAsyncFileUtil( + FileSystemType type) { + switch (type) { + case fileapi::kFileSystemTypeNativeMedia: + return native_media_file_util_.get(); + case fileapi::kFileSystemTypeDeviceMedia: +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) + return device_media_async_file_util_.get(); +#endif + default: + NOTREACHED(); + } + return NULL; +} + +fileapi::CopyOrMoveFileValidatorFactory* +MediaFileSystemMountPointProvider::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) { + DCHECK(error_code); + *error_code = base::PLATFORM_FILE_OK; + switch (type) { + case fileapi::kFileSystemTypeNativeMedia: + case fileapi::kFileSystemTypeDeviceMedia: + if (!media_copy_or_move_file_validator_factory_) { + *error_code = base::PLATFORM_FILE_ERROR_SECURITY; + return NULL; + } + return media_copy_or_move_file_validator_factory_.get(); + default: + NOTREACHED(); + } + return NULL; +} + +void +MediaFileSystemMountPointProvider::InitializeCopyOrMoveFileValidatorFactory( + FileSystemType type, + scoped_ptr<fileapi::CopyOrMoveFileValidatorFactory> factory) { + switch (type) { + case fileapi::kFileSystemTypeNativeMedia: + case fileapi::kFileSystemTypeDeviceMedia: + if (!media_copy_or_move_file_validator_factory_) + media_copy_or_move_file_validator_factory_.reset(factory.release()); + break; + default: + NOTREACHED(); + } +} + +fileapi::FilePermissionPolicy +MediaFileSystemMountPointProvider::GetPermissionPolicy( + const FileSystemURL& url, int permissions) const { + // Access to media file systems should be checked using per-filesystem + // access permission. + return fileapi::FILE_PERMISSION_USE_FILESYSTEM_PERMISSION; +} + +fileapi::FileSystemOperation* +MediaFileSystemMountPointProvider::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const { + scoped_ptr<fileapi::FileSystemOperationContext> operation_context( + new fileapi::FileSystemOperationContext( + context, context->task_runners()->media_task_runner())); + + operation_context->SetUserValue(kMediaPathFilterKey, + media_path_filter_.get()); +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) + if (url.type() == fileapi::kFileSystemTypeDeviceMedia) { + operation_context->SetUserValue(kMTPDeviceDelegateURLKey, + url.filesystem_id()); + } +#endif + + return new fileapi::LocalFileSystemOperation(context, + operation_context.Pass()); +} + +scoped_ptr<webkit_blob::FileStreamReader> +MediaFileSystemMountPointProvider::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + return scoped_ptr<webkit_blob::FileStreamReader>( + new webkit_blob::LocalFileStreamReader( + context->task_runners()->file_task_runner(), + url.path(), offset, expected_modification_time)); +} + +scoped_ptr<fileapi::FileStreamWriter> +MediaFileSystemMountPointProvider::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + return scoped_ptr<fileapi::FileStreamWriter>( + new fileapi::LocalFileStreamWriter(url.path(), offset)); +} + +fileapi::FileSystemQuotaUtil* +MediaFileSystemMountPointProvider::GetQuotaUtil() { + // No quota support. + return NULL; +} + +void MediaFileSystemMountPointProvider::DeleteFileSystem( + const GURL& origin_url, + FileSystemType type, + FileSystemContext* context, + const DeleteFileSystemCallback& callback) { + NOTREACHED(); + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h b/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h new file mode 100644 index 0000000..27c6004 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h @@ -0,0 +1,95 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_FILE_SYSTEM_MOUNT_POINT_PROVIDER_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_FILE_SYSTEM_MOUNT_POINT_PROVIDER_H_ + +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h" +#include "webkit/fileapi/file_system_mount_point_provider.h" + +namespace fileapi { +class AsyncFileUtilAdapter; +} + +namespace chrome { + +class MediaPathFilter; + +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) +class DeviceMediaAsyncFileUtil; +#endif + +class MediaFileSystemMountPointProvider + : public fileapi::FileSystemMountPointProvider { + public: + static const char kMediaPathFilterKey[]; + static const char kMTPDeviceDelegateURLKey[]; + + explicit MediaFileSystemMountPointProvider( + const base::FilePath& profile_path); + virtual ~MediaFileSystemMountPointProvider(); + + // FileSystemMountPointProvider implementation. + virtual bool CanHandleType(fileapi::FileSystemType type) const OVERRIDE; + virtual void ValidateFileSystemRoot( + const GURL& origin_url, + fileapi::FileSystemType type, + bool create, + const ValidateFileSystemCallback& callback) OVERRIDE; + virtual base::FilePath GetFileSystemRootPathOnFileThread( + const fileapi::FileSystemURL& url, + bool create) OVERRIDE; + virtual fileapi::FileSystemFileUtil* GetFileUtil( + fileapi::FileSystemType type) OVERRIDE; + virtual fileapi::AsyncFileUtil* GetAsyncFileUtil( + fileapi::FileSystemType type) OVERRIDE; + virtual fileapi::CopyOrMoveFileValidatorFactory* + GetCopyOrMoveFileValidatorFactory( + fileapi::FileSystemType type, + base::PlatformFileError* error_code) OVERRIDE; + virtual void InitializeCopyOrMoveFileValidatorFactory( + fileapi::FileSystemType type, + scoped_ptr<fileapi::CopyOrMoveFileValidatorFactory> factory) OVERRIDE; + virtual fileapi::FilePermissionPolicy GetPermissionPolicy( + const fileapi::FileSystemURL& url, int permissions) const OVERRIDE; + virtual fileapi::FileSystemOperation* CreateFileSystemOperation( + const fileapi::FileSystemURL& url, + fileapi::FileSystemContext* context, + base::PlatformFileError* error_code) const OVERRIDE; + virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const fileapi::FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + fileapi::FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<fileapi::FileStreamWriter> CreateFileStreamWriter( + const fileapi::FileSystemURL& url, + int64 offset, + fileapi::FileSystemContext* context) const OVERRIDE; + virtual fileapi::FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + virtual void DeleteFileSystem( + const GURL& origin_url, + fileapi::FileSystemType type, + fileapi::FileSystemContext* context, + const DeleteFileSystemCallback& callback) OVERRIDE; + + private: + // Store the profile path. We need this to create temporary snapshot files. + const base::FilePath profile_path_; + + scoped_ptr<MediaPathFilter> media_path_filter_; + scoped_ptr<fileapi::CopyOrMoveFileValidatorFactory> + media_copy_or_move_file_validator_factory_; + + scoped_ptr<fileapi::AsyncFileUtilAdapter> native_media_file_util_; +#if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) + scoped_ptr<DeviceMediaAsyncFileUtil> device_media_async_file_util_; +#endif + + DISALLOW_COPY_AND_ASSIGN(MediaFileSystemMountPointProvider); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_FILE_SYSTEM_MOUNT_POINT_PROVIDER_H_ diff --git a/chrome/browser/media_galleries/fileapi/media_path_filter.cc b/chrome/browser/media_galleries/fileapi/media_path_filter.cc new file mode 100644 index 0000000..98f281b --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/media_path_filter.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" + +#include <algorithm> +#include <string> + +#include "base/string_util.h" +#include "net/base/mime_util.h" + +namespace chrome { + +namespace { + +const base::FilePath::CharType* const kExtraSupportedExtensions[] = { + FILE_PATH_LITERAL("3gp"), + FILE_PATH_LITERAL("3gpp"), + FILE_PATH_LITERAL("avi"), + FILE_PATH_LITERAL("flv"), + FILE_PATH_LITERAL("mov"), + FILE_PATH_LITERAL("mpeg"), + FILE_PATH_LITERAL("mpeg4"), + FILE_PATH_LITERAL("mpegps"), + FILE_PATH_LITERAL("mpg"), + FILE_PATH_LITERAL("wmv"), +}; + +bool IsUnsupportedExtension(const base::FilePath::StringType& extension) { + std::string mime_type; + return !net::GetMimeTypeFromExtension(extension, &mime_type) || + !net::IsSupportedMimeType(mime_type); +} + +} // namespace + +MediaPathFilter::MediaPathFilter() + : initialized_(false) { +} + +MediaPathFilter::~MediaPathFilter() { +} + +bool MediaPathFilter::Match(const base::FilePath& path) { + EnsureInitialized(); + return std::binary_search(media_file_extensions_.begin(), + media_file_extensions_.end(), + StringToLowerASCII(path.Extension())); +} + +void MediaPathFilter::EnsureInitialized() { + if (initialized_) + return; + + base::AutoLock lock(initialization_lock_); + if (initialized_) + return; + + net::GetExtensionsForMimeType("image/*", &media_file_extensions_); + net::GetExtensionsForMimeType("audio/*", &media_file_extensions_); + net::GetExtensionsForMimeType("video/*", &media_file_extensions_); + + MediaFileExtensionList::iterator new_end = + std::remove_if(media_file_extensions_.begin(), + media_file_extensions_.end(), + &IsUnsupportedExtension); + media_file_extensions_.erase(new_end, media_file_extensions_.end()); + + // Add other common extensions. + for (size_t i = 0; i < arraysize(kExtraSupportedExtensions); ++i) + media_file_extensions_.push_back(kExtraSupportedExtensions[i]); + + for (MediaFileExtensionList::iterator itr = media_file_extensions_.begin(); + itr != media_file_extensions_.end(); ++itr) + *itr = base::FilePath::kExtensionSeparator + *itr; + std::sort(media_file_extensions_.begin(), media_file_extensions_.end()); + + initialized_ = true; +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/media_path_filter.h b/chrome/browser/media_galleries/fileapi/media_path_filter.h new file mode 100644 index 0000000..c74d4d3 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/media_path_filter.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_PATH_FILTER_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_PATH_FILTER_H_ + +#include <vector> + +#include "base/files/file_path.h" +#include "base/synchronization/lock.h" + +namespace base { +class FilePath; +} + +namespace chrome { + +// This class holds the list of file path extensions that we should expose on +// media filesystem. +class MediaPathFilter { + public: + MediaPathFilter(); + ~MediaPathFilter(); + bool Match(const base::FilePath& path); + + private: + typedef std::vector<base::FilePath::StringType> MediaFileExtensionList; + + void EnsureInitialized(); + + bool initialized_; + base::Lock initialization_lock_; + MediaFileExtensionList media_file_extensions_; + + DISALLOW_COPY_AND_ASSIGN(MediaPathFilter); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MEDIA_PATH_FILTER_H_ diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h new file mode 100644 index 0000000..ba07d37 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h @@ -0,0 +1,85 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_ASYNC_DELEGATE_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_ASYNC_DELEGATE_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/platform_file.h" +#include "webkit/fileapi/async_file_util.h" + +namespace base { +class FilePath; +} + +namespace chrome { + +// Asynchronous delegate for media transfer protocol (MTP) device to perform +// media device isolated file system operations. Class that implements this +// delegate does the actual communication with the MTP device. +// ScopedMTPDeviceMapEntry class manages the lifetime of the delegate via +// MTPDeviceMapService class. Member functions and callbacks runs on the IO +// thread. +class MTPDeviceAsyncDelegate { + public: + // A callback to be called when GetFileInfo method call succeeds. + typedef base::Callback< + void(const base::PlatformFileInfo& file_info)> GetFileInfoSuccessCallback; + + // A callback to be called when ReadDirectory method call succeeds. + typedef base::Callback< + void(const fileapi::AsyncFileUtil::EntryList& file_list, + bool has_more)> ReadDirectorySuccessCallback; + + // A callback to be called when GetFileInfo/ReadDirectory/CreateSnapshot + // method call fails. + typedef base::Callback< + void(base::PlatformFileError error)> ErrorCallback; + + // A callback to be called when CreateSnapshotFile method call succeeds. + typedef base::Callback< + void(const base::PlatformFileInfo& file_info, + const base::FilePath& local_path)> CreateSnapshotFileSuccessCallback; + + // Gets information about the given |file_path| and invokes the appropriate + // callback asynchronously when complete. + virtual void GetFileInfo( + const base::FilePath& file_path, + const GetFileInfoSuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + // Enumerates the |root| directory contents and invokes the appropriate + // callback asynchronously when complete. + virtual void ReadDirectory( + const base::FilePath& root, + const ReadDirectorySuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + // Copy the contents of |device_file_path| to |local_path|. Invokes the + // appropriate callback asynchronously when complete. + virtual void CreateSnapshotFile( + const base::FilePath& device_file_path, + const base::FilePath& local_path, + const CreateSnapshotFileSuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + // Called when the + // (1) Browser application is in shutdown mode (or) + // (2) Last extension using this MTP device is destroyed (or) + // (3) Attached MTP device is removed (or) + // (4) User revoked the MTP device gallery permission. + // Ownership of |MTPDeviceAsyncDelegate| is handed off to the delegate + // implementation class by this call. This function should take care of + // cancelling all the pending tasks before deleting itself. + virtual void CancelPendingTasksAndDeleteDelegate() = 0; + + protected: + // Always destruct this object via CancelPendingTasksAndDeleteDelegate(). + virtual ~MTPDeviceAsyncDelegate() {} +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_ASYNC_DELEGATE_H_ diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h b/chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h new file mode 100644 index 0000000..1877de8 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_FILE_SYSTEM_CONFIG_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_FILE_SYSTEM_CONFIG_H_ + +#include "build/build_config.h" + +// Support MTP device file system for Windows, Mac, Linux and ChromeOS. +// Note that OS_LINUX implies OS_CHROMEOS. +// TODO(gbillock): remove this define and make this default. +#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) +#define SUPPORT_MTP_DEVICE_FILESYSTEM +#endif + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_FILE_SYSTEM_CONFIG_H_ diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_map_service.cc b/chrome/browser/media_galleries/fileapi/mtp_device_map_service.cc new file mode 100644 index 0000000..96c5b85 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/mtp_device_map_service.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h" + +#include <string> +#include <utility> + +#include "base/stl_util.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" +#include "webkit/fileapi/isolated_context.h" + +namespace chrome { + +namespace { + +base::LazyInstance<MTPDeviceMapService> g_mtp_device_map_service = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +MTPDeviceMapService* MTPDeviceMapService::GetInstance() { + return g_mtp_device_map_service.Pointer(); +} + +///////////////////////////////////////////////////////////////////////////// +// Following methods are used to manage MTPDeviceAsyncDelegate objects. // +///////////////////////////////////////////////////////////////////////////// +void MTPDeviceMapService::AddAsyncDelegate( + const base::FilePath::StringType& device_location, + MTPDeviceAsyncDelegate* delegate) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(delegate); + DCHECK(!device_location.empty()); + if (ContainsKey(async_delegate_map_, device_location)) + return; + async_delegate_map_[device_location] = delegate; +} + +void MTPDeviceMapService::RemoveAsyncDelegate( + const base::FilePath::StringType& device_location) { + DCHECK(thread_checker_.CalledOnValidThread()); + AsyncDelegateMap::iterator it = async_delegate_map_.find(device_location); + DCHECK(it != async_delegate_map_.end()); + it->second->CancelPendingTasksAndDeleteDelegate(); + async_delegate_map_.erase(it); +} + +MTPDeviceAsyncDelegate* MTPDeviceMapService::GetMTPDeviceAsyncDelegate( + const std::string& filesystem_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + base::FilePath device_path; + if (!fileapi::IsolatedContext::GetInstance()->GetRegisteredPath( + filesystem_id, &device_path)) { + return NULL; + } + + const base::FilePath::StringType& device_location = device_path.value(); + DCHECK(!device_location.empty()); + AsyncDelegateMap::const_iterator it = + async_delegate_map_.find(device_location); + return (it != async_delegate_map_.end()) ? it->second : NULL; +} + + +MTPDeviceMapService::MTPDeviceMapService() { +} + +MTPDeviceMapService::~MTPDeviceMapService() {} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/mtp_device_map_service.h b/chrome/browser/media_galleries/fileapi/mtp_device_map_service.h new file mode 100644 index 0000000..18b974f5 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/mtp_device_map_service.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_MAP_SERVICE_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_MAP_SERVICE_H_ + +#include <map> + +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/threading/thread_checker.h" + +namespace chrome { + +class MTPDeviceAsyncDelegate; + +// This class provides media transfer protocol (MTP) device delegate to +// complete media file system operations. ScopedMTPDeviceMapEntry class +// manages the device map entries. +class MTPDeviceMapService { + public: + static MTPDeviceMapService* GetInstance(); + + ///////////////////////////////////////////////////////////////////////////// + // Following methods are used to manage MTPDeviceAsyncDelegate objects. // + ///////////////////////////////////////////////////////////////////////////// + // Adds the MTP device delegate to the map service. |device_location| + // specifies the mount location of the MTP device. + // Called on the IO thread. + void AddAsyncDelegate(const base::FilePath::StringType& device_location, + MTPDeviceAsyncDelegate* delegate); + + // Removes the MTP device delegate from the map service. |device_location| + // specifies the mount location of the MTP device. + // Called on the IO thread. + void RemoveAsyncDelegate(const base::FilePath::StringType& device_location); + + // Gets the media device delegate associated with |filesystem_id|. + // Return NULL if the |filesystem_id| is no longer valid (e.g. because the + // corresponding device is detached, etc). + // Called on the IO thread. + MTPDeviceAsyncDelegate* GetMTPDeviceAsyncDelegate( + const std::string& filesystem_id); + + private: + friend struct base::DefaultLazyInstanceTraits<MTPDeviceMapService>; + + // Mapping of device_location and MTPDeviceAsyncDelegate* object. It is safe + // to store and access the raw pointer. This class operates on the IO thread. + typedef std::map<base::FilePath::StringType, MTPDeviceAsyncDelegate*> + AsyncDelegateMap; + + // Get access to this class using GetInstance() method. + MTPDeviceMapService(); + ~MTPDeviceMapService(); + + ///////////////////////////////////////////////////////////////////////////// + // Following member variables are used to manage asynchronous // + // MTP device delegate objects. // + ///////////////////////////////////////////////////////////////////////////// + // Map of attached mtp device async delegates. + AsyncDelegateMap async_delegate_map_; + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(MTPDeviceMapService); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_MTP_DEVICE_MAP_SERVICE_H_ diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util.cc b/chrome/browser/media_galleries/fileapi/native_media_file_util.cc new file mode 100644 index 0000000..19724f9 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/native_media_file_util.cc @@ -0,0 +1,229 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/native_media_file_util.h" + +#include "chrome/browser/media_galleries/fileapi/filtering_file_enumerator.h" +#include "chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h" +#include "chrome/browser/media_galleries/fileapi/media_path_filter.h" +#include "webkit/fileapi/file_system_operation_context.h" +#include "webkit/fileapi/native_file_util.h" + +using base::PlatformFile; +using base::PlatformFileError; +using base::PlatformFileInfo; +using fileapi::FileSystemOperationContext; +using fileapi::FileSystemURL; +using fileapi::NativeFileUtil; + +namespace chrome { + +namespace { + +MediaPathFilter* GetMediaPathFilter(FileSystemOperationContext* context) { + return context->GetUserValue<MediaPathFilter*>( + MediaFileSystemMountPointProvider::kMediaPathFilterKey); +} + +} // namespace + +NativeMediaFileUtil::NativeMediaFileUtil() { +} + +PlatformFileError NativeMediaFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + PlatformFile* file_handle, + bool* created) { + // Only called by NaCl, which should not have access to media file systems. + return base::PLATFORM_FILE_ERROR_SECURITY; +} + +PlatformFileError NativeMediaFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) { + base::FilePath file_path; + PlatformFileError error = GetFilteredLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::EnsureFileExists(file_path, created); +} + +scoped_ptr<fileapi::FileSystemFileUtil::AbstractFileEnumerator> +NativeMediaFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) { + DCHECK(context); + return make_scoped_ptr(new FilteringFileEnumerator( + IsolatedFileUtil::CreateFileEnumerator(context, root_url), + GetMediaPathFilter(context))) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); +} + +PlatformFileError NativeMediaFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::FilePath file_path; + PlatformFileError error = GetFilteredLocalFilePathForExistingFileOrDirectory( + context, + url, + // Touch fails for non-existent paths and filtered paths. + base::PLATFORM_FILE_ERROR_FAILED, + &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::Touch(file_path, last_access_time, last_modified_time); +} + +PlatformFileError NativeMediaFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) { + base::FilePath file_path; + PlatformFileError error = GetFilteredLocalFilePathForExistingFileOrDirectory( + context, + url, + // Cannot truncate paths that do not exist, or are filtered. + base::PLATFORM_FILE_ERROR_NOT_FOUND, + &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::Truncate(file_path, length); +} + +PlatformFileError NativeMediaFileUtil::CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) { + base::FilePath src_file_path; + PlatformFileError error = + GetFilteredLocalFilePathForExistingFileOrDirectory( + context, src_url, + base::PLATFORM_FILE_ERROR_NOT_FOUND, + &src_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + if (NativeFileUtil::DirectoryExists(src_file_path)) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + + base::FilePath dest_file_path; + error = GetLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + PlatformFileInfo file_info; + error = NativeFileUtil::GetFileInfo(dest_file_path, &file_info); + if (error != base::PLATFORM_FILE_OK && + error != base::PLATFORM_FILE_ERROR_NOT_FOUND) + return error; + if (error == base::PLATFORM_FILE_OK && file_info.is_directory) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + if (!GetMediaPathFilter(context)->Match(dest_file_path)) + return base::PLATFORM_FILE_ERROR_SECURITY; + + return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, copy); +} + +PlatformFileError NativeMediaFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) { + if (src_file_path.empty()) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + + base::FilePath dest_file_path; + PlatformFileError error = + GetFilteredLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, true); +} + +PlatformFileError NativeMediaFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + PlatformFileInfo file_info; + error = NativeFileUtil::GetFileInfo(file_path, &file_info); + if (error != base::PLATFORM_FILE_OK) + return error; + if (file_info.is_directory) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + if (!GetMediaPathFilter(context)->Match(file_path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + return NativeFileUtil::DeleteFile(file_path); +} + +PlatformFileError NativeMediaFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + PlatformFileInfo* file_info, + base::FilePath* platform_path) { + DCHECK(context); + DCHECK(GetMediaPathFilter(context)); + DCHECK(file_info); + DCHECK(platform_path); + + base::PlatformFileError error = + IsolatedFileUtil::GetFileInfo(context, url, file_info, platform_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + if (file_info->is_directory || + GetMediaPathFilter(context)->Match(*platform_path)) { + return base::PLATFORM_FILE_OK; + } + return base::PLATFORM_FILE_ERROR_NOT_FOUND; +} + +PlatformFileError NativeMediaFileUtil::GetFilteredLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) { + base::FilePath file_path; + PlatformFileError error = + IsolatedFileUtil::GetLocalFilePath(context, file_system_url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + if (!GetMediaPathFilter(context)->Match(file_path)) + return base::PLATFORM_FILE_ERROR_SECURITY; + + *local_file_path = file_path; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError +NativeMediaFileUtil::GetFilteredLocalFilePathForExistingFileOrDirectory( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + PlatformFileError failure_error, + base::FilePath* local_file_path) { + base::FilePath file_path; + PlatformFileError error = + GetLocalFilePath(context, file_system_url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + if (!file_util::PathExists(file_path)) + return failure_error; + PlatformFileInfo file_info; + if (!file_util::GetFileInfo(file_path, &file_info)) + return base::PLATFORM_FILE_ERROR_FAILED; + + if (!file_info.is_directory && + !GetMediaPathFilter(context)->Match(file_path)) { + return failure_error; + } + + *local_file_path = file_path; + return base::PLATFORM_FILE_OK; +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util.h b/chrome/browser/media_galleries/fileapi/native_media_file_util.h new file mode 100644 index 0000000..aa4ee62 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/native_media_file_util.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_NATIVE_MEDIA_FILE_UTIL_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_NATIVE_MEDIA_FILE_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "webkit/fileapi/isolated_file_util.h" + +namespace chrome { + +// This class handles native file system operations with media type filtering +// which is passed to each method via fileapi::FileSystemOperationContext as +// MediaPathFilter. +class NativeMediaFileUtil : public fileapi::IsolatedFileUtil { + public: + NativeMediaFileUtil(); + + virtual base::PlatformFileError CreateOrOpen( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + int file_flags, + base::PlatformFile* file_handle, + bool* created) OVERRIDE; + virtual base::PlatformFileError EnsureFileExists( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, bool* created) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& root_url) OVERRIDE; + virtual base::PlatformFileError Touch( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) OVERRIDE; + virtual base::PlatformFileError Truncate( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + int64 length) OVERRIDE; + virtual base::PlatformFileError CopyOrMoveFile( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& src_url, + const fileapi::FileSystemURL& dest_url, + bool copy) OVERRIDE; + virtual base::PlatformFileError CopyInForeignFile( + fileapi::FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const fileapi::FileSystemURL& dest_url) OVERRIDE; + virtual base::PlatformFileError DeleteFile( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url) OVERRIDE; + virtual base::PlatformFileError GetFileInfo( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) OVERRIDE; + + private: + // Like GetLocalFilePath(), but always take media_path_filter() into + // consideration. If the media_path_filter() check fails, return + // PLATFORM_FILE_ERROR_SECURITY. |local_file_path| does not have to exist. + base::PlatformFileError GetFilteredLocalFilePath( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& file_system_url, + base::FilePath* local_file_path); + + // Like GetLocalFilePath(), but if the file does not exist, then return + // |failure_error|. + // If |local_file_path| is a file, then take media_path_filter() into + // consideration. + // If the media_path_filter() check fails, return |failure_error|. + // If |local_file_path| is a directory, return PLATFORM_FILE_OK. + base::PlatformFileError GetFilteredLocalFilePathForExistingFileOrDirectory( + fileapi::FileSystemOperationContext* context, + const fileapi::FileSystemURL& file_system_url, + base::PlatformFileError failure_error, + base::FilePath* local_file_path); + + DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtil); +}; + +} // namespace chrome + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_NATIVE_MEDIA_FILE_UTIL_H_ diff --git a/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc b/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc new file mode 100644 index 0000000..32de0e4 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/native_media_file_util_unittest.cc @@ -0,0 +1,614 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <set> +#include <string> + +#include "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/message_loop.h" +#include "base/stringprintf.h" +#include "base/time.h" +#include "chrome/browser/media_galleries/fileapi/media_file_system_mount_point_provider.h" +#include "chrome/browser/media_galleries/fileapi/native_media_file_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/external_mount_points.h" +#include "webkit/fileapi/file_system_context.h" +#include "webkit/fileapi/file_system_mount_point_provider.h" +#include "webkit/fileapi/file_system_operation.h" +#include "webkit/fileapi/file_system_task_runners.h" +#include "webkit/fileapi/file_system_url.h" +#include "webkit/fileapi/isolated_context.h" +#include "webkit/fileapi/mock_file_system_options.h" +#include "webkit/fileapi/native_file_util.h" +#include "webkit/quota/mock_special_storage_policy.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +using fileapi::FileSystemOperation; +using fileapi::FileSystemURL; + +namespace chrome { + +namespace { + +typedef FileSystemOperation::FileEntryList FileEntryList; + +struct FilteringTestCase { + const base::FilePath::CharType* path; + bool is_directory; + bool visible; +}; + +const FilteringTestCase kFilteringTestCases[] = { + // Directory should always be visible. + { FPL("hoge"), true, true }, + { FPL("fuga.jpg"), true, true }, + { FPL("piyo.txt"), true, true }, + { FPL("moga.cod"), true, true }, + + // File should be visible if it's a supported media file. + { FPL("foo"), false, false }, // File without extension. + { FPL("bar.jpg"), false, true }, // Supported media file. + { FPL("baz.txt"), false, false }, // Non-media file. + { FPL("foobar.cod"), false, false }, // Unsupported media file. +}; + +void ExpectEqHelper(const std::string& test_name, + base::PlatformFileError expected, + base::PlatformFileError actual) { + EXPECT_EQ(expected, actual) << test_name; +} + +void ExpectMetadataEqHelper(const std::string& test_name, + base::PlatformFileError expected, + bool expected_is_directory, + base::PlatformFileError actual, + const base::PlatformFileInfo& file_info, + const base::FilePath& /*platform_path*/) { + EXPECT_EQ(expected, actual) << test_name; + if (actual == base::PLATFORM_FILE_OK) + EXPECT_EQ(expected_is_directory, file_info.is_directory) << test_name; +} + +void DidReadDirectory(std::set<base::FilePath::StringType>* content, + bool* completed, + base::PlatformFileError error, + const FileEntryList& file_list, + bool has_more) { + EXPECT_TRUE(!*completed); + *completed = !has_more; + for (FileEntryList::const_iterator itr = file_list.begin(); + itr != file_list.end(); ++itr) + EXPECT_TRUE(content->insert(itr->name).second); +} + +void PopulateDirectoryWithTestCases(const base::FilePath& dir, + const FilteringTestCase* test_cases, + size_t n) { + for (size_t i = 0; i < n; ++i) { + base::FilePath path = dir.Append(test_cases[i].path); + if (test_cases[i].is_directory) { + ASSERT_TRUE(file_util::CreateDirectory(path)); + } else { + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + fileapi::NativeFileUtil::EnsureFileExists(path, &created)); + ASSERT_TRUE(created); + } + } +} + +} // namespace + +class NativeMediaFileUtilTest : public testing::Test { + public: + NativeMediaFileUtilTest() + : file_util_(NULL) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_util::CreateDirectory(root_path())); + + scoped_refptr<quota::SpecialStoragePolicy> storage_policy = + new quota::MockSpecialStoragePolicy(); + + ScopedVector<fileapi::FileSystemMountPointProvider> additional_providers; + additional_providers.push_back(new MediaFileSystemMountPointProvider( + data_dir_.path())); + + file_system_context_ = + new fileapi::FileSystemContext( + fileapi::FileSystemTaskRunners::CreateMockTaskRunners(), + fileapi::ExternalMountPoints::CreateRefCounted().get(), + storage_policy, + NULL, + additional_providers.Pass(), + data_dir_.path(), + fileapi::CreateAllowFileAccessOptions()); + + file_util_ = file_system_context_->GetFileUtil( + fileapi::kFileSystemTypeNativeMedia); + + filesystem_id_ = isolated_context()->RegisterFileSystemForPath( + fileapi::kFileSystemTypeNativeMedia, root_path(), NULL); + + isolated_context()->AddReference(filesystem_id_); + } + + virtual void TearDown() { + isolated_context()->RemoveReference(filesystem_id_); + file_system_context_ = NULL; + } + + protected: + fileapi::FileSystemContext* file_system_context() { + return file_system_context_.get(); + } + + FileSystemURL CreateURL(const base::FilePath::CharType* test_case_path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin(), + fileapi::kFileSystemTypeIsolated, + GetVirtualPath(test_case_path)); + } + + fileapi::IsolatedContext* isolated_context() { + return fileapi::IsolatedContext::GetInstance(); + } + + base::FilePath root_path() { + return data_dir_.path().Append(FPL("Media Directory")); + } + + base::FilePath GetVirtualPath( + const base::FilePath::CharType* test_case_path) { + return base::FilePath::FromUTF8Unsafe(filesystem_id_). + Append(FPL("Media Directory")). + Append(base::FilePath(test_case_path)); + } + + fileapi::FileSystemFileUtil* file_util() { + return file_util_; + } + + GURL origin() { + return GURL("http://example.com"); + } + + fileapi::FileSystemType type() { + return fileapi::kFileSystemTypeNativeMedia; + } + + FileSystemOperation* NewOperation(const FileSystemURL& url) { + return file_system_context_->CreateFileSystemOperation(url, NULL); + } + + private: + MessageLoop message_loop_; + + base::ScopedTempDir data_dir_; + scoped_refptr<fileapi::FileSystemContext> file_system_context_; + + fileapi::FileSystemFileUtil* file_util_; + std::string filesystem_id_; + + DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtilTest); +}; + +TEST_F(NativeMediaFileUtilTest, DirectoryExistsAndFileExistsFiltering) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + FileSystemOperation* operation = NewOperation(url); + + base::PlatformFileError expectation = + kFilteringTestCases[i].visible ? + base::PLATFORM_FILE_OK : + base::PLATFORM_FILE_ERROR_NOT_FOUND; + + std::string test_name = + base::StringPrintf("DirectoryExistsAndFileExistsFiltering %" PRIuS, i); + if (kFilteringTestCases[i].is_directory) { + operation->DirectoryExists( + url, base::Bind(&ExpectEqHelper, test_name, expectation)); + } else { + operation->FileExists( + url, base::Bind(&ExpectEqHelper, test_name, expectation)); + } + MessageLoop::current()->RunUntilIdle(); + } +} + +TEST_F(NativeMediaFileUtilTest, ReadDirectoryFiltering) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + + std::set<base::FilePath::StringType> content; + FileSystemURL url = CreateURL(FPL("")); + bool completed = false; + NewOperation(url)->ReadDirectory( + url, base::Bind(&DidReadDirectory, &content, &completed)); + MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(completed); + EXPECT_EQ(5u, content.size()); + + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + base::FilePath::StringType name = + base::FilePath(kFilteringTestCases[i].path).BaseName().value(); + std::set<base::FilePath::StringType>::const_iterator found = + content.find(name); + EXPECT_EQ(kFilteringTestCases[i].visible, found != content.end()); + } +} + +TEST_F(NativeMediaFileUtilTest, CreateFileAndCreateDirectoryFiltering) { + // Run the loop twice. The second loop attempts to create files that are + // pre-existing. Though the result should be the same. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "CreateFileAndCreateDirectoryFiltering run %d, test %" PRIuS, + loop_count, i); + base::PlatformFileError expectation = + kFilteringTestCases[i].visible ? + base::PLATFORM_FILE_OK : + base::PLATFORM_FILE_ERROR_SECURITY; + if (kFilteringTestCases[i].is_directory) { + operation->CreateDirectory( + url, false, false, + base::Bind(&ExpectEqHelper, test_name, expectation)); + } else { + operation->CreateFile( + url, false, base::Bind(&ExpectEqHelper, test_name, expectation)); + } + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, CopySourceFiltering) { + base::FilePath dest_path = root_path().AppendASCII("dest"); + FileSystemURL dest_url = CreateURL(FPL("dest")); + + // Run the loop twice. The first run has no source files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + // Always start with an empty destination directory. + // Copying to a non-empty destination directory is an invalid operation. + ASSERT_TRUE(file_util::Delete(dest_path, true)); + ASSERT_TRUE(file_util::CreateDirectory(dest_path)); + + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "CopySourceFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // If the source does not exist or is not visible. + expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } else if (!kFilteringTestCases[i].is_directory) { + // Cannot copy a visible file to a directory. + expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } + operation->Copy( + url, dest_url, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, CopyDestFiltering) { + // Run the loop twice. The first run has no destination files. + // The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + // Reset the test directory between the two loops to remove old + // directories and create new ones that should pre-exist. + ASSERT_TRUE(file_util::Delete(root_path(), true)); + ASSERT_TRUE(file_util::CreateDirectory(root_path())); + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + + // Always create a dummy source data file. + base::FilePath src_path = root_path().AppendASCII("foo.jpg"); + FileSystemURL src_url = CreateURL(FPL("foo.jpg")); + static const char kDummyData[] = "dummy"; + ASSERT_TRUE(file_util::WriteFile(src_path, kDummyData, strlen(kDummyData))); + + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + if (loop_count == 0 && kFilteringTestCases[i].is_directory) { + // These directories do not exist in this case, so Copy() will not + // treat them as directories. Thus invalidating these test cases. + // Continue now to avoid creating a new |operation| below that goes + // unused. + continue; + } + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "CopyDestFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation; + if (loop_count == 0) { + // The destination path is a file here. The directory case has been + // handled above. + // If the destination path does not exist and is not visible, then + // creating it would be a security violation. + expectation = + kFilteringTestCases[i].visible ? + base::PLATFORM_FILE_OK : + base::PLATFORM_FILE_ERROR_SECURITY; + } else { + if (!kFilteringTestCases[i].visible) { + // If the destination path exist and is not visible, then to the copy + // operation, it looks like the file needs to be created, which is a + // security violation. + expectation = base::PLATFORM_FILE_ERROR_SECURITY; + } else if (kFilteringTestCases[i].is_directory) { + // Cannot copy a file to a directory. + expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } else { + // Copying from a file to a visible file that exists is ok. + expectation = base::PLATFORM_FILE_OK; + } + } + operation->Copy( + src_url, url, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, MoveSourceFiltering) { + base::FilePath dest_path = root_path().AppendASCII("dest"); + FileSystemURL dest_url = CreateURL(FPL("dest")); + + // Run the loop twice. The first run has no source files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + // Always start with an empty destination directory. + // Moving to a non-empty destination directory is an invalid operation. + ASSERT_TRUE(file_util::Delete(dest_path, true)); + ASSERT_TRUE(file_util::CreateDirectory(dest_path)); + + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "MoveSourceFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // If the source does not exist or is not visible. + expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } else if (!kFilteringTestCases[i].is_directory) { + // Cannot move a visible file to a directory. + expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } + operation->Move( + url, dest_url, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, MoveDestFiltering) { + // Run the loop twice. The first run has no destination files. + // The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + // Reset the test directory between the two loops to remove old + // directories and create new ones that should pre-exist. + ASSERT_TRUE(file_util::Delete(root_path(), true)); + ASSERT_TRUE(file_util::CreateDirectory(root_path())); + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + if (loop_count == 0 && kFilteringTestCases[i].is_directory) { + // These directories do not exist in this case, so Copy() will not + // treat them as directories. Thus invalidating these test cases. + // Continue now to avoid creating a new |operation| below that goes + // unused. + continue; + } + + // Create the source file for every test case because it might get moved. + base::FilePath src_path = root_path().AppendASCII("foo.jpg"); + FileSystemURL src_url = CreateURL(FPL("foo.jpg")); + static const char kDummyData[] = "dummy"; + ASSERT_TRUE( + file_util::WriteFile(src_path, kDummyData, strlen(kDummyData))); + + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "MoveDestFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation; + if (loop_count == 0) { + // The destination path is a file here. The directory case has been + // handled above. + // If the destination path does not exist and is not visible, then + // creating it would be a security violation. + expectation = + kFilteringTestCases[i].visible ? + base::PLATFORM_FILE_OK : + base::PLATFORM_FILE_ERROR_SECURITY; + } else { + if (!kFilteringTestCases[i].visible) { + // If the destination path exist and is not visible, then to the move + // operation, it looks like the file needs to be created, which is a + // security violation. + expectation = base::PLATFORM_FILE_ERROR_SECURITY; + } else if (kFilteringTestCases[i].is_directory) { + // Cannot move a file to a directory. + expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } else { + // Moving from a file to a visible file that exists is ok. + expectation = base::PLATFORM_FILE_OK; + } + } + operation->Move( + src_url, url, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, GetMetadataFiltering) { + // Run the loop twice. The first run has no files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "GetMetadataFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // Cannot get metadata from files that do not exist or are not visible. + expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + operation->GetMetadata(url, + base::Bind(&ExpectMetadataEqHelper, + test_name, + expectation, + kFilteringTestCases[i].is_directory)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, RemoveFiltering) { + // Run the loop twice. The first run has no files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "RemoveFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // Cannot remove files that do not exist or are not visible. + expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + operation->Remove( + url, false, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, TruncateFiltering) { + // Run the loop twice. The first run has no files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "TruncateFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // Cannot truncate files that do not exist or are not visible. + expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } else if (kFilteringTestCases[i].is_directory) { + // Cannot truncate directories. + expectation = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + } + operation->Truncate( + url, 0, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +TEST_F(NativeMediaFileUtilTest, TouchFileFiltering) { + base::Time time = base::Time::Now(); + + // Run the loop twice. The first run has no files. The second run does. + for (int loop_count = 0; loop_count < 2; ++loop_count) { + if (loop_count == 1) { + PopulateDirectoryWithTestCases(root_path(), + kFilteringTestCases, + arraysize(kFilteringTestCases)); + } + for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { + FileSystemURL root_url = CreateURL(FPL("")); + FileSystemOperation* operation = NewOperation(root_url); + + FileSystemURL url = CreateURL(kFilteringTestCases[i].path); + + std::string test_name = base::StringPrintf( + "TouchFileFiltering run %d test %" PRIuS, loop_count, i); + base::PlatformFileError expectation = base::PLATFORM_FILE_OK; + if (loop_count == 0 || !kFilteringTestCases[i].visible) { + // Files do not exists. Touch fails. + expectation = base::PLATFORM_FILE_ERROR_FAILED; + } + operation->TouchFile( + url, time, time, base::Bind(&ExpectEqHelper, test_name, expectation)); + MessageLoop::current()->RunUntilIdle(); + } + } +} + +} // namespace chrome diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.cc b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.cc new file mode 100644 index 0000000..bdfea21 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.cc @@ -0,0 +1,186 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h" + +#include <cstring> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace picasaimport { + +namespace { + +const size_t kPmpMaxFilesize = 50*1024*1024; // Maximum of 50 MB. + +} // namespace + +PmpColumnReader::PmpColumnReader() + : length_(0), + field_type_(PMP_TYPE_INVALID), + rows_(0) { } + +PmpColumnReader::~PmpColumnReader() { } + +bool PmpColumnReader::Init(const base::FilePath& filepath, uint32* rows_read) { + DCHECK(!data_.get()); + base::ThreadRestrictions::AssertIOAllowed(); + + int64 length = 0; // Signed temporary. + if (!file_util::GetFileSize(filepath, &length)) + return false; + + length_ = length; + + if (length_ < kPmpHeaderSize || length_ > kPmpMaxFilesize) + return false; + + data_.reset(new uint8[length_]); + + char* data_begin = reinterpret_cast<char*>(data_.get()); + + return file_util::ReadFile(filepath, data_begin, length_) && + ParseData(rows_read); +} + +bool PmpColumnReader::ReadString(const uint32 row, std::string* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row); + + if (field_type_ != PMP_TYPE_STRING || row >= rows_) + return false; + + *result = strings_[row]; + return true; +} + +bool PmpColumnReader::ReadUInt32(const uint32 row, uint32* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint32)); + + if (field_type_ != PMP_TYPE_UINT32 || row >= rows_) + return false; + + *result = reinterpret_cast<uint32*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadDouble64(const uint32 row, double* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(double)); + + if (field_type_ != PMP_TYPE_DOUBLE64 || row >= rows_) + return false; + + *result = reinterpret_cast<double*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt8(const uint32 row, uint8* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint8)); + + if (field_type_ != PMP_TYPE_UINT8 || row >= rows_) + return false; + + *result = reinterpret_cast<uint8*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt64(const uint32 row, uint64* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint64)); + + if (field_type_ != PMP_TYPE_UINT64 || row >= rows_) + return false; + + *result = reinterpret_cast<uint64*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ParseData(uint32* rows_read) { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + // Check all magic bytes. + if (memcmp(&kPmpMagic1, &data_[kPmpMagic1Offset], sizeof(kPmpMagic1)) != 0 || + memcmp(&kPmpMagic2, &data_[kPmpMagic2Offset], sizeof(kPmpMagic2)) != 0 || + memcmp(&kPmpMagic3, &data_[kPmpMagic3Offset], sizeof(kPmpMagic3)) != 0 || + memcmp(&kPmpMagic4, &data_[kPmpMagic4Offset], sizeof(kPmpMagic4)) != 0) { + return false; + } + + uint16 field_type_data = + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType1Offset])); + + // Verify if field type matches second declaration + if (field_type_data != + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType2Offset]))) { + return false; + } + + field_type_ = static_cast<PmpFieldType>(field_type_data); + + rows_ = *(reinterpret_cast<uint32*>(&data_[kPmpRowCountOffset])); + + size_t body_length = length_ - kPmpHeaderSize; + size_t expected_body_length = 0; + switch (field_type_) { + case PMP_TYPE_STRING: + expected_body_length = IndexStrings(); + break; + case PMP_TYPE_UINT32: + expected_body_length = rows_ * sizeof(uint32); + break; + case PMP_TYPE_DOUBLE64: + expected_body_length = rows_ * sizeof(double); + break; + case PMP_TYPE_UINT8: + expected_body_length = rows_ * sizeof(uint8); + break; + case PMP_TYPE_UINT64: + expected_body_length = rows_ * sizeof(uint64); + break; + default: + return false; + break; + } + + if (body_length == expected_body_length && rows_read) + *rows_read = rows_; + return body_length == expected_body_length; +} + +long PmpColumnReader::IndexStrings() { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + strings_.reserve(rows_); + + size_t bytes_parsed = kPmpHeaderSize; + const uint8* data_cursor = data_.get() + kPmpHeaderSize; + + while (strings_.size() < rows_) { + const uint8* string_end = static_cast<const uint8*>( + memchr(data_cursor, '\0', length_ - bytes_parsed)); + + // Fail if cannot find null termination. String runs on past file end. + if (string_end == NULL) + return -1; + + // Length of string. (+1 to include the termination character). + ptrdiff_t length_in_bytes = string_end - data_cursor + 1; + + strings_.push_back(reinterpret_cast<const char*>(data_cursor)); + data_cursor += length_in_bytes; + bytes_parsed += length_in_bytes; + } + + return bytes_parsed - kPmpHeaderSize; +} + +} // namespace picasaimport diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h new file mode 100644 index 0000000..280b91c --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_COLUMN_READER_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_COLUMN_READER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h" + +namespace base { +class FilePath; +} + +namespace picasaimport { + +// Reads a single PMP column from a file. +class PmpColumnReader { + public: + PmpColumnReader(); + virtual ~PmpColumnReader(); + + // Returns true if read successfully. + // |rows_read| is undefined if returns false. + bool Init(const base::FilePath& filepath, uint32* rows_read); + + // These functions read the value of that |row| into |result|. + // Functions return false if the column is of the wrong type or the row + // is out of range. + bool ReadString(const uint32 row, std::string* result) const; + bool ReadUInt32(const uint32 row, uint32* result) const; + bool ReadDouble64(const uint32 row, double* result) const; + bool ReadUInt8(const uint32 row, uint8* result) const; + bool ReadUInt64(const uint32 row, uint64* result) const; + + // Returns the native encoding of field_type. + PmpFieldType field_type() const { + return field_type_; + } + + private: + bool ParseData(uint32* rows_read); + // Returns the number of bytes parsed in the body, or, -1 on failure. + long IndexStrings(); + + // Source data + scoped_ptr<uint8[]> data_; + size_t length_; + + // Header data + PmpFieldType field_type_; + uint32 rows_; + + // Index of string start locations if fields are strings. Empty otherwise. + std::vector<const char*> strings_; + + DISALLOW_COPY_AND_ASSIGN(PmpColumnReader); +}; + +} // namespace picasaimport + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_COLUMN_READER_H_ diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader_unittest.cc b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader_unittest.cc new file mode 100644 index 0000000..3646113 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader_unittest.cc @@ -0,0 +1,179 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <vector> + +#include "base/file_util.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using picasaimport::PmpColumnReader; +using picasaimport::PmpTestHelper; + +// Overridden version of Read method to make test code templatable. +bool DoRead(const PmpColumnReader* reader, uint32 row, std::string* target) { + return reader->ReadString(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint32* target) { + return reader->ReadUInt32(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, double* target) { + return reader->ReadDouble64(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint8* target) { + return reader->ReadUInt8(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint64* target) { + return reader->ReadUInt64(row, target); +} + +// TestValid +template<class T> +void TestValid(const picasaimport::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader; + + uint32 rows_read = 0xFF; + + std::vector<uint8> data = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + ASSERT_TRUE(test_helper.InitColumnReaderFromBytes(&reader, data, &rows_read)); + EXPECT_EQ(elems.size(), rows_read); + + for (uint32 i = 0; i < elems.size() && i < rows_read; i++) { + T target; + EXPECT_TRUE(DoRead(&reader, i, &target)); + EXPECT_EQ(elems[i], target); + } +} + +template<class T> +void TestMalformed(const picasaimport::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader1, reader2, reader3, reader4; + + std::vector<uint8> data_too_few_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()-1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader1, data_too_few_declared_rows, NULL)); + + std::vector<uint8> data_too_many_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()+1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader2, data_too_many_declared_rows, NULL)); + + std::vector<uint8> data_truncated = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_truncated.resize(data_truncated.size()-10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader3, data_truncated, NULL)); + + std::vector<uint8> data_padded = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_padded.resize(data_padded.size()+10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader4, data_padded, NULL)); +} + +template<class T> +void TestPrimitive(const picasaimport::PmpFieldType field_type) { + // Make an ascending vector of the primitive. + uint32 n = 100; + std::vector<T> data(n, 0); + for (uint32 i = 0; i < n; i++) { + data[i] = i*3; + } + + TestValid<T>(field_type, data); + TestMalformed<T>(field_type, data); +} + + +TEST(PmpColumnReaderTest, HeaderParsingAndValidation) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader1, reader2, reader3, reader4, reader5; + + // Good header. + uint32 rows_read = 0xFF; + std::vector<uint8> good_header = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + ASSERT_TRUE(test_helper.InitColumnReaderFromBytes( + &reader1, good_header, &rows_read)); + EXPECT_EQ(0U, rows_read) << "Read non-zero rows from header-only data."; + + // Botch up elements of the header. + std::vector<uint8> bad_magic_byte = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + bad_magic_byte[0] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader2, bad_magic_byte, NULL)); + + // Corrupt means the type fields don't agree. + std::vector<uint8> corrupt_type = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + corrupt_type[picasaimport::kPmpFieldType1Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader3, corrupt_type, NULL)); + + std::vector<uint8> invalid_type = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + invalid_type[picasaimport::kPmpFieldType1Offset] = 0xff; + invalid_type[picasaimport::kPmpFieldType2Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader4, invalid_type, NULL)); + + std::vector<uint8> incomplete_header = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + incomplete_header.resize(10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader5, incomplete_header, NULL)); +} + +TEST(PmpColumnReaderTest, StringParsing) { + std::vector<std::string> empty_strings(100, ""); + + // Test empty strings read okay. + TestValid(picasaimport::PMP_TYPE_STRING, empty_strings); + + std::vector<std::string> mixed_strings; + mixed_strings.push_back(""); + mixed_strings.push_back("Hello"); + mixed_strings.push_back("World"); + mixed_strings.push_back(""); + mixed_strings.push_back("123123"); + mixed_strings.push_back("Q"); + mixed_strings.push_back(""); + + // Test that a mixed set of strings read correctly. + TestValid(picasaimport::PMP_TYPE_STRING, mixed_strings); + + // Test with the data messed up in a variety of ways. + TestMalformed(picasaimport::PMP_TYPE_STRING, mixed_strings); +} + +TEST(PmpColumnReaderTest, PrimitiveParsing) { + TestPrimitive<uint32>(picasaimport::PMP_TYPE_UINT32); + TestPrimitive<double>(picasaimport::PMP_TYPE_DOUBLE64); + TestPrimitive<uint8>(picasaimport::PMP_TYPE_UINT8); + TestPrimitive<uint64>(picasaimport::PMP_TYPE_UINT64); +} + +} // namespace diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h b/chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h new file mode 100644 index 0000000..521c31a --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h @@ -0,0 +1,46 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_CONSTANTS_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_CONSTANTS_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace picasaimport { + +// PMP file format. +// Info derived from: http://sbktech.blogspot.com/2011/12/picasa-pmp-format.html + +const char* const kPmpExtension = "pmp"; + +const size_t kPmpHeaderSize = 20; + +const int kPmpMagic1Offset = 0; +const int kPmpMagic2Offset = 6; +const int kPmpMagic3Offset = 8; +const int kPmpMagic4Offset = 14; + +const uint32 kPmpMagic1 = 0x3fcccccd; +const uint16 kPmpMagic2 = 0x1332; +const uint32 kPmpMagic3 = 0x00000002; +const uint16 kPmpMagic4 = 0x1332; + +const int kPmpFieldType1Offset = 4; +const int kPmpFieldType2Offset = 12; +const int kPmpRowCountOffset = 16; + +enum PmpFieldType { + PMP_TYPE_STRING = 0x00, + PMP_TYPE_UINT32 = 0x01, + PMP_TYPE_DOUBLE64 = 0x02, + PMP_TYPE_UINT8 = 0x03, + PMP_TYPE_UINT64 = 0x04, + PMP_TYPE_INVALID = 0xff +}; + +} // namespace picasaimport + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_CONSTANTS_H_ diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.cc b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.cc new file mode 100644 index 0000000..6a510b6 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.cc @@ -0,0 +1,88 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.h" + +#include <algorithm> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h" + +namespace picasaimport { + +namespace { + +COMPILE_ASSERT(sizeof(double) == 8, double_must_be_8_bytes_long); + +} // namespace + +PmpTableReader::PmpTableReader() : column_readers_(), max_row_count_(0) { } + +PmpTableReader::~PmpTableReader() { } + +bool PmpTableReader::Init(const std::string& table_name, + const base::FilePath& directory_path, + const std::vector<std::string>& columns) { + DCHECK(!columns.empty()); + + if (!column_readers_.empty()) + return false; + + if (!file_util::DirectoryExists(directory_path)) + return false; + + std::string table_prefix = table_name + "_"; + std::string indicator_file_name = table_prefix + "0"; + + base::FilePath indicator_file = directory_path.Append( + base::FilePath::FromUTF8Unsafe(indicator_file_name)); + + // Look for the indicator_file file, indicating table existence. + if (!file_util::PathExists(indicator_file) || + file_util::DirectoryExists(indicator_file)) { + return false; + } + + ScopedVector<PmpColumnReader> column_readers; + uint32 max_row_count = 0; + + for (std::vector<std::string>::const_iterator it = columns.begin(); + it != columns.end(); ++it) { + std::string filename = table_prefix + *it + "." + kPmpExtension; + + base::FilePath column_file_path = directory_path.Append( + base::FilePath::FromUTF8Unsafe(filename)); + + PmpColumnReader* column_reader = new PmpColumnReader(); + column_readers.push_back(column_reader); + + uint32 row_cnt; + + if (!column_reader->Init(column_file_path, &row_cnt)) + return false; + + max_row_count = std::max(max_row_count, row_cnt); + } + + column_readers_ = column_readers.Pass(); + max_row_count_ = max_row_count; + + return true; +} + +uint32 PmpTableReader::RowCount() const { + return max_row_count_; +} + +std::vector<const PmpColumnReader*> PmpTableReader::GetColumns() const { + std::vector<const PmpColumnReader*> readers( + column_readers_.begin(), column_readers_.end()); + return readers; +} + +} // namespace picasaimport diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.h b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.h new file mode 100644 index 0000000..3cee4f7 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.h @@ -0,0 +1,47 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TABLE_READER_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TABLE_READER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_vector.h" + +namespace base { +class FilePath; +} + +namespace picasaimport { + +class PmpColumnReader; + +class PmpTableReader { + public: + PmpTableReader(); + + virtual ~PmpTableReader(); + + // |columns| parameter will define, in-order, the columns returned by + // subsequent columns to GetColumns() if Init() succeeds. + bool Init(const std::string& table_name, + const base::FilePath& directory_path, + const std::vector<std::string>& columns); + + // Returns a const "view" of the current contents of |column_readers_|. + std::vector<const PmpColumnReader*> GetColumns() const; + + uint32 RowCount() const; + + private: + ScopedVector<PmpColumnReader> column_readers_; + uint32 max_row_count_; + + DISALLOW_COPY_AND_ASSIGN(PmpTableReader); +}; + +} // namespace picasaimport + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TABLE_READER_H_ diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader_unittest.cc b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader_unittest.cc new file mode 100644 index 0000000..dd6ece7 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <vector> + +#include "base/file_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_table_reader.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using picasaimport::PmpTestHelper; + +TEST(PmpTableReaderTest, RowCountAndFieldType) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + std::string table_name = "testtable"; + + std::vector<std::string> column_names; + column_names.push_back("strings"); + column_names.push_back("uint32s"); + column_names.push_back("doubles"); + + const std::vector<std::string> strings_vector(10, "Hello"); + const std::vector<uint32> uint32s_vector(30, 42); + const std::vector<double> doubles_vector(20, 0.5); + + picasaimport::PmpFieldType column_field_types[] = { + picasaimport::PMP_TYPE_STRING, + picasaimport::PMP_TYPE_UINT32, + picasaimport::PMP_TYPE_DOUBLE64 + }; + + const uint32 max_rows = uint32s_vector.size(); + + base::FilePath indicator_path = test_helper.GetTempDirPath().Append( + base::FilePath::FromUTF8Unsafe(table_name + "_0")); + + ASSERT_EQ(0, file_util::WriteFile(indicator_path, NULL, 0)); + // Write three column files, one each for strings, uint32s, and doubles. + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[0], column_field_types[0], strings_vector)); + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[1], column_field_types[1], uint32s_vector)); + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[2], column_field_types[2], doubles_vector)); + + picasaimport::PmpTableReader table_reader; + ASSERT_TRUE(table_reader.Init( + table_name, test_helper.GetTempDirPath(), column_names)); + + EXPECT_EQ(max_rows, table_reader.RowCount()); + + const std::vector<const picasaimport::PmpColumnReader*> column_readers = + table_reader.GetColumns(); + + for (int i = 0; i < 3; i++) { + EXPECT_EQ(column_field_types[i], column_readers[i]->field_type()); + } +} + +} // namespace diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.cc b/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.cc new file mode 100644 index 0000000..f426d96 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.cc @@ -0,0 +1,172 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h" + +#include <algorithm> +#include <iterator> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_column_reader.h" + +namespace picasaimport { + +namespace { + +bool WriteToFile(const base::FilePath& path, std::vector<uint8> data) { + // Cast for usage in WriteFile function + const char* data_char = reinterpret_cast<const char*>(&data[0]); + size_t bytes_written = file_util::WriteFile(path, data_char, data.size()); + return (bytes_written == data.size()); +} + +// Flatten a vector of elements into an array of bytes. +template<class T> +std::vector<uint8> Flatten(const std::vector<T>& elems) { + const uint8* elems0 = reinterpret_cast<const uint8*>(&elems[0]); + std::vector<uint8> data_body(elems0, elems0 + sizeof(T)*elems.size()); + + return data_body; +} + +// Custom specialization for std::string. +template<> +std::vector<uint8> Flatten(const std::vector<std::string>& strings) { + std::vector<uint8> totalchars; + + for (std::vector<std::string>::const_iterator it = strings.begin(); + it != strings.end(); ++it) { + std::copy(it->begin(), it->end(), std::back_inserter(totalchars)); + totalchars.push_back('\0'); // Add the null termination too. + } + + return totalchars; +} + +// Returns a new vector with the concatenated contents of |a| and |b|. +std::vector<uint8> CombinedVectors(const std::vector<uint8>& a, + const std::vector<uint8>& b) { + std::vector<uint8> total; + + std::copy(a.begin(), a.end(), std::back_inserter(total)); + std::copy(b.begin(), b.end(), std::back_inserter(total)); + + return total; +} + +} // namespace + +PmpTestHelper::PmpTestHelper() { } + +bool PmpTestHelper::Init() { + return temp_dir_.CreateUniqueTempDir(); +} + +base::FilePath PmpTestHelper::GetTempDirPath() { + DCHECK(temp_dir_.IsValid()); + return temp_dir_.path(); +} + +template<class T> +bool PmpTestHelper::WriteColumnFileFromVector( + const std::string& table_name, const std::string& column_name, + const PmpFieldType field_type, const std::vector<T>& elements_vector) { + DCHECK(temp_dir_.IsValid()); + + std::string file_name = table_name + "_" + column_name + "." + kPmpExtension; + + base::FilePath path = temp_dir_.path().Append( + base::FilePath::FromUTF8Unsafe(file_name)); + + std::vector<uint8> data = PmpTestHelper::MakeHeaderAndBody( + field_type, elements_vector.size(), elements_vector); + + return WriteToFile(path, data); +} + +// Explicit Instantiation for all the valid types. +template bool PmpTestHelper::WriteColumnFileFromVector<std::string>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<std::string>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint32>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint32>&); +template bool PmpTestHelper::WriteColumnFileFromVector<double>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<double>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint8>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint8>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint64>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint64>&); + +bool PmpTestHelper::InitColumnReaderFromBytes( + PmpColumnReader* const reader, const std::vector<uint8>& data, + uint32* rows_read) { + DCHECK(temp_dir_.IsValid()); + + base::FilePath temp_path; + + if (!file_util::CreateTemporaryFileInDir(temp_dir_.path(), &temp_path) || + !WriteToFile(temp_path, data)) { + return false; + } + + bool success = reader->Init(temp_path, rows_read); + + file_util::Delete(temp_path, true); + + return success; + +} + +// Return a vector so we don't have to worry about memory management. +std::vector<uint8> PmpTestHelper::MakeHeader(const PmpFieldType field_type, + const uint32 row_count) { + std::vector<uint8> header(picasaimport::kPmpHeaderSize); + + // Copy in magic bytes. + memcpy(&header[picasaimport::kPmpMagic1Offset], &picasaimport::kPmpMagic1, + sizeof(picasaimport::kPmpMagic1)); + memcpy(&header[picasaimport::kPmpMagic2Offset], &picasaimport::kPmpMagic2, + sizeof(picasaimport::kPmpMagic2)); + memcpy(&header[picasaimport::kPmpMagic3Offset], &picasaimport::kPmpMagic3, + sizeof(picasaimport::kPmpMagic3)); + memcpy(&header[picasaimport::kPmpMagic4Offset], &picasaimport::kPmpMagic4, + sizeof(picasaimport::kPmpMagic4)); + + // Copy in field type. + memcpy(&header[picasaimport::kPmpFieldType1Offset], &field_type, 2); + memcpy(&header[picasaimport::kPmpFieldType2Offset], &field_type, 2); + + // Copy in row count. + memcpy(&header[picasaimport::kPmpRowCountOffset], &row_count, 4); + + return header; +} + +template<class T> +std::vector<uint8> PmpTestHelper::MakeHeaderAndBody( + const PmpFieldType field_type, const uint32 row_count, + const std::vector<T>& elems) { + return CombinedVectors(PmpTestHelper::MakeHeader(field_type, row_count), + Flatten(elems)); +} + +// Explicit Instantiation for all the valid types. +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<std::string>( + const PmpFieldType, const uint32, const std::vector<std::string>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint32>( + const PmpFieldType, const uint32, const std::vector<uint32>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<double>( + const PmpFieldType, const uint32, const std::vector<double>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint8>( + const PmpFieldType, const uint32, const std::vector<uint8>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint64>( + const PmpFieldType, const uint32, const std::vector<uint64>&); + +} // namespace picasaimport diff --git a/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h b/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h new file mode 100644 index 0000000..d675773 --- /dev/null +++ b/chrome/browser/media_galleries/fileapi/picasa/pmp_test_helper.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TEST_HELPER_H_ +#define CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TEST_HELPER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/files/scoped_temp_dir.h" +#include "chrome/browser/media_galleries/fileapi/picasa/pmp_constants.h" + +namespace base { +class FilePath; +} // namespace base + +namespace picasaimport { + +class PmpColumnReader; + +// A helper class used for unit tests only +class PmpTestHelper { + public: + PmpTestHelper(); + + bool Init(); + + base::FilePath GetTempDirPath(); + + template<class T> + bool WriteColumnFileFromVector(const std::string& table_name, + const std::string& column_name, + const PmpFieldType field_type, + const std::vector<T>& elements_vector); + + bool InitColumnReaderFromBytes(PmpColumnReader* const reader, + const std::vector<uint8>& data, + uint32* rows_read); + + static std::vector<uint8> MakeHeader(const PmpFieldType field_type, + const uint32 row_count); + + template<class T> + static std::vector<uint8> MakeHeaderAndBody(const PmpFieldType field_type, + const uint32 row_count, + const std::vector<T>& elems); + + private: + base::ScopedTempDir temp_dir_; +}; + +} // namespace picasaimport + +#endif // CHROME_BROWSER_MEDIA_GALLERIES_FILEAPI_PICASA_PMP_TEST_HELPER_H_ diff --git a/chrome/browser/media_galleries/linux/mtp_device_delegate_impl_linux.h b/chrome/browser/media_galleries/linux/mtp_device_delegate_impl_linux.h index 01bc9d1..7483b17 100644 --- a/chrome/browser/media_galleries/linux/mtp_device_delegate_impl_linux.h +++ b/chrome/browser/media_galleries/linux/mtp_device_delegate_impl_linux.h @@ -12,9 +12,9 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/platform_file.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" #include "chrome/browser/media_galleries/mtp_device_delegate_impl.h" #include "webkit/fileapi/async_file_util.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" namespace base { class FilePath; @@ -30,7 +30,7 @@ struct SnapshotRequestInfo; // MTPDeviceDelegateImplLinux lives on the IO thread. // MTPDeviceDelegateImplLinux does a call-and-reply to the UI thread // to dispatch the requests to MediaTransferProtocolManager. -class MTPDeviceDelegateImplLinux : public fileapi::MTPDeviceAsyncDelegate { +class MTPDeviceDelegateImplLinux : public MTPDeviceAsyncDelegate { private: friend void CreateMTPDeviceAsyncDelegate( const std::string&, diff --git a/chrome/browser/media_galleries/linux/mtp_device_task_helper.h b/chrome/browser/media_galleries/linux/mtp_device_task_helper.h index d817212..f1389fe 100644 --- a/chrome/browser/media_galleries/linux/mtp_device_task_helper.h +++ b/chrome/browser/media_galleries/linux/mtp_device_task_helper.h @@ -12,9 +12,9 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/platform_file.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" #include "device/media_transfer_protocol/mtp_file_entry.pb.h" #include "webkit/fileapi/async_file_util.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" namespace chrome { @@ -31,13 +31,13 @@ class MTPDeviceTaskHelper { public: typedef base::Callback<void(bool succeeded)> OpenStorageCallback; - typedef fileapi::MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback + typedef MTPDeviceAsyncDelegate::GetFileInfoSuccessCallback GetFileInfoSuccessCallback; typedef base::Callback<void(const fileapi::AsyncFileUtil::EntryList&)> ReadDirectorySuccessCallback; - typedef fileapi::MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback; + typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback; MTPDeviceTaskHelper(); ~MTPDeviceTaskHelper(); diff --git a/chrome/browser/media_galleries/linux/snapshot_file_details.cc b/chrome/browser/media_galleries/linux/snapshot_file_details.cc index 2d85ccb..8f2e709 100644 --- a/chrome/browser/media_galleries/linux/snapshot_file_details.cc +++ b/chrome/browser/media_galleries/linux/snapshot_file_details.cc @@ -15,9 +15,9 @@ namespace chrome { SnapshotRequestInfo::SnapshotRequestInfo( const std::string& device_file_path, const base::FilePath& snapshot_file_path, - const fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& + const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& success_callback, - const fileapi::MTPDeviceAsyncDelegate::ErrorCallback& error_callback) + const MTPDeviceAsyncDelegate::ErrorCallback& error_callback) : device_file_path(device_file_path), snapshot_file_path(snapshot_file_path), success_callback(success_callback), diff --git a/chrome/browser/media_galleries/linux/snapshot_file_details.h b/chrome/browser/media_galleries/linux/snapshot_file_details.h index c2157fc..e358b39 100644 --- a/chrome/browser/media_galleries/linux/snapshot_file_details.h +++ b/chrome/browser/media_galleries/linux/snapshot_file_details.h @@ -11,7 +11,7 @@ #include "base/callback.h" #include "base/files/file_path.h" #include "base/platform_file.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" namespace chrome { @@ -20,9 +20,9 @@ struct SnapshotRequestInfo { SnapshotRequestInfo( const std::string& device_file_path, const base::FilePath& snapshot_file_path, - const fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& + const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& success_callback, - const fileapi::MTPDeviceAsyncDelegate::ErrorCallback& error_callback); + const MTPDeviceAsyncDelegate::ErrorCallback& error_callback); ~SnapshotRequestInfo(); // MTP device file path. @@ -32,11 +32,11 @@ struct SnapshotRequestInfo { const base::FilePath snapshot_file_path; // A callback to be called when CreateSnapshotFile() succeeds. - const fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback + const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback; // A callback to be called when CreateSnapshotFile() fails. - const fileapi::MTPDeviceAsyncDelegate::ErrorCallback error_callback; + const MTPDeviceAsyncDelegate::ErrorCallback error_callback; }; // SnapshotFileDetails tracks the current state of the snapshot file (e.g how @@ -65,12 +65,12 @@ class SnapshotFileDetails { return file_info_; } - const fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback + const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback() const { return request_info_.success_callback; } - const fileapi::MTPDeviceAsyncDelegate::ErrorCallback error_callback() const { + const MTPDeviceAsyncDelegate::ErrorCallback error_callback() const { return request_info_.error_callback; } diff --git a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h index 201b314..bc3ac6f 100644 --- a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h +++ b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h @@ -13,7 +13,7 @@ #include "base/hash_tables.h" #include "base/memory/weak_ptr.h" #include "base/platform_file.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" namespace chrome { @@ -26,7 +26,7 @@ namespace chrome { // on any thread, but then mutates all state on the UI thread. The async // delegate interface can be invoked on any thread, as it simply forwards calls // to the UI thread. -class MTPDeviceDelegateImplMac : public fileapi::MTPDeviceAsyncDelegate { +class MTPDeviceDelegateImplMac : public MTPDeviceAsyncDelegate { public: MTPDeviceDelegateImplMac(const std::string& device_id, const base::FilePath::StringType& synthetic_path); diff --git a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm index 5f9a719..4a1de47 100644 --- a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm +++ b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.mm @@ -19,7 +19,6 @@ namespace { int kReadDirectoryTimeLimitSeconds = 20; -using fileapi::MTPDeviceAsyncDelegate; typedef MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback CreateSnapshotFileSuccessCallback; typedef MTPDeviceAsyncDelegate::ErrorCallback ErrorCallback; diff --git a/chrome/browser/media_galleries/media_file_system_context.h b/chrome/browser/media_galleries/media_file_system_context.h index ccc0371..eebcd2d 100644 --- a/chrome/browser/media_galleries/media_file_system_context.h +++ b/chrome/browser/media_galleries/media_file_system_context.h @@ -10,8 +10,8 @@ #include <string> #include "base/memory/ref_counted.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h" #include "chrome/browser/media_galleries/scoped_mtp_device_map_entry.h" -#include "webkit/fileapi/media/mtp_device_file_system_config.h" namespace base { class FilePath; diff --git a/chrome/browser/media_galleries/media_file_system_registry.h b/chrome/browser/media_galleries/media_file_system_registry.h index 7ce269a..6843c3a 100644 --- a/chrome/browser/media_galleries/media_file_system_registry.h +++ b/chrome/browser/media_galleries/media_file_system_registry.h @@ -17,9 +17,9 @@ #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/prefs/pref_change_registrar.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h" #include "chrome/browser/media_galleries/media_galleries_preferences.h" #include "chrome/browser/storage_monitor/removable_storage_observer.h" -#include "webkit/fileapi/media/mtp_device_file_system_config.h" #if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) #include "chrome/browser/media_galleries/mtp_device_delegate_impl.h" diff --git a/chrome/browser/media_galleries/mtp_device_delegate_impl.h b/chrome/browser/media_galleries/mtp_device_delegate_impl.h index 632c024..3490c6e 100644 --- a/chrome/browser/media_galleries/mtp_device_delegate_impl.h +++ b/chrome/browser/media_galleries/mtp_device_delegate_impl.h @@ -5,7 +5,7 @@ #ifndef CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_H_ #define CHROME_BROWSER_MEDIA_GALLERIES_MTP_DEVICE_DELEGATE_IMPL_H_ -#include "webkit/fileapi/media/mtp_device_file_system_config.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h" #if !defined(SUPPORT_MTP_DEVICE_FILESYSTEM) #error "Media file system is not supported for this platform." @@ -13,7 +13,7 @@ #include "base/callback_forward.h" #include "base/files/file_path.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" namespace base { class SequencedTaskRunner; @@ -21,7 +21,7 @@ class SequencedTaskRunner; namespace chrome { -typedef base::Callback<void(fileapi::MTPDeviceAsyncDelegate*)> +typedef base::Callback<void(MTPDeviceAsyncDelegate*)> CreateMTPDeviceAsyncDelegateCallback; void CreateMTPDeviceAsyncDelegate( diff --git a/chrome/browser/media_galleries/scoped_mtp_device_map_entry.cc b/chrome/browser/media_galleries/scoped_mtp_device_map_entry.cc index b91d641..2444f0a 100644 --- a/chrome/browser/media_galleries/scoped_mtp_device_map_entry.cc +++ b/chrome/browser/media_galleries/scoped_mtp_device_map_entry.cc @@ -4,15 +4,16 @@ #include "chrome/browser/media_galleries/scoped_mtp_device_map_entry.h" -#include "webkit/fileapi/media/mtp_device_file_system_config.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_file_system_config.h" #if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) #include "base/bind.h" #include "base/threading/sequenced_worker_pool.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h" #include "chrome/browser/media_galleries/mtp_device_delegate_impl.h" #include "content/public/browser/browser_thread.h" #include "webkit/fileapi/file_system_task_runners.h" -#include "webkit/fileapi/media/mtp_device_map_service.h" + #endif namespace chrome { @@ -37,7 +38,7 @@ scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { void OnDeviceAsyncDelegateDestroyed( const base::FilePath::StringType& device_location) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - fileapi::MTPDeviceMapService::GetInstance()->RemoveAsyncDelegate( + MTPDeviceMapService::GetInstance()->RemoveAsyncDelegate( device_location); } @@ -82,10 +83,10 @@ ScopedMTPDeviceMapEntry::~ScopedMTPDeviceMapEntry() { } void ScopedMTPDeviceMapEntry::OnMTPDeviceAsyncDelegateCreated( - fileapi::MTPDeviceAsyncDelegate* delegate) { + MTPDeviceAsyncDelegate* delegate) { #if defined(SUPPORT_MTP_DEVICE_FILESYSTEM) DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - fileapi::MTPDeviceMapService::GetInstance()->AddAsyncDelegate( + MTPDeviceMapService::GetInstance()->AddAsyncDelegate( device_location_, delegate); #endif } diff --git a/chrome/browser/media_galleries/scoped_mtp_device_map_entry.h b/chrome/browser/media_galleries/scoped_mtp_device_map_entry.h index 8cf24a2..abcf042 100644 --- a/chrome/browser/media_galleries/scoped_mtp_device_map_entry.h +++ b/chrome/browser/media_galleries/scoped_mtp_device_map_entry.h @@ -15,12 +15,10 @@ #include "base/sequenced_task_runner_helpers.h" #include "content/public/browser/browser_thread.h" -namespace fileapi { -class MTPDeviceAsyncDelegate; -} - namespace chrome { +class MTPDeviceAsyncDelegate; + class ScopedMTPDeviceMapEntry : public base::RefCountedThreadSafe< ScopedMTPDeviceMapEntry, content::BrowserThread::DeleteOnUIThread> { @@ -50,8 +48,7 @@ class ScopedMTPDeviceMapEntry // Callback to add the managed MTPDeviceAsyncDelegate to the // MTPDeviceMapService on the IO thread. - void OnMTPDeviceAsyncDelegateCreated( - fileapi::MTPDeviceAsyncDelegate* delegate); + void OnMTPDeviceAsyncDelegateCreated(MTPDeviceAsyncDelegate* delegate); // The MTP or PTP device location. const base::FilePath::StringType device_location_; diff --git a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h index cdd6bdf..afae427 100644 --- a/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h +++ b/chrome/browser/media_galleries/win/mtp_device_delegate_impl_win.h @@ -14,9 +14,9 @@ #include "base/platform_file.h" #include "base/string16.h" #include "base/win/scoped_comptr.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" #include "chrome/browser/media_galleries/mtp_device_delegate_impl.h" #include "webkit/fileapi/async_file_util.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" namespace base { class FilePath; @@ -36,7 +36,7 @@ struct SnapshotRequestInfo; // is instantiated per MTP device storage partition using // CreateMTPDeviceAsyncDelegate(). MTPDeviceDelegateImplWin lives on the IO // thread. -class MTPDeviceDelegateImplWin : public fileapi::MTPDeviceAsyncDelegate { +class MTPDeviceDelegateImplWin : public MTPDeviceAsyncDelegate { public: // Structure used to represent MTP device storage partition details. struct StorageDeviceInfo { diff --git a/chrome/browser/media_galleries/win/snapshot_file_details.cc b/chrome/browser/media_galleries/win/snapshot_file_details.cc index 6b69418..073c97d 100644 --- a/chrome/browser/media_galleries/win/snapshot_file_details.cc +++ b/chrome/browser/media_galleries/win/snapshot_file_details.cc @@ -8,8 +8,6 @@ namespace chrome { -using fileapi::MTPDeviceAsyncDelegate; - /////////////////////////////////////////////////////////////////////////////// // SnapshotRequestInfo // /////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/media_galleries/win/snapshot_file_details.h b/chrome/browser/media_galleries/win/snapshot_file_details.h index d6f00ea..7c39ea3 100644 --- a/chrome/browser/media_galleries/win/snapshot_file_details.h +++ b/chrome/browser/media_galleries/win/snapshot_file_details.h @@ -8,7 +8,7 @@ #include "base/files/file_path.h" #include "base/platform_file.h" #include "base/win/scoped_comptr.h" -#include "webkit/fileapi/media/mtp_device_async_delegate.h" +#include "chrome/browser/media_galleries/fileapi/mtp_device_async_delegate.h" namespace chrome { @@ -17,9 +17,9 @@ struct SnapshotRequestInfo { SnapshotRequestInfo( const base::FilePath& device_file_path, const base::FilePath& snapshot_file_path, - const fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& + const MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback& success_callback, - const fileapi::MTPDeviceAsyncDelegate::ErrorCallback& error_callback); + const MTPDeviceAsyncDelegate::ErrorCallback& error_callback); // Device file path. base::FilePath device_file_path; @@ -28,11 +28,11 @@ struct SnapshotRequestInfo { base::FilePath snapshot_file_path; // A callback to be called when CreateSnapshotFile() succeeds. - fileapi::MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback + MTPDeviceAsyncDelegate::CreateSnapshotFileSuccessCallback success_callback; // A callback to be called when CreateSnapshotFile() fails. - fileapi::MTPDeviceAsyncDelegate::ErrorCallback error_callback; + MTPDeviceAsyncDelegate::ErrorCallback error_callback; }; // Provides the details for the the creation of snapshot file. |