1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/media_galleries/fileapi/picasa_file_util.h"
6
7#include <string>
8#include <vector>
9
10#include "base/basictypes.h"
11#include "base/bind_helpers.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/sys_string_conversions.h"
15#include "base/strings/utf_string_conversions.h"
16#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
17#include "chrome/browser/media_galleries/fileapi/picasa_data_provider.h"
18#include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
19#include "chrome/common/media_galleries/picasa_types.h"
20#include "content/public/browser/browser_thread.h"
21#include "storage/browser/fileapi/file_system_operation_context.h"
22#include "storage/browser/fileapi/file_system_url.h"
23#include "storage/browser/fileapi/native_file_util.h"
24#include "storage/common/fileapi/file_system_util.h"
25
26using base::FilePath;
27using storage::DirectoryEntry;
28using storage::FileSystemOperationContext;
29using storage::FileSystemURL;
30
31namespace picasa {
32
33namespace {
34
35base::File::Error FindAlbumInfo(const std::string& key,
36                                const AlbumMap* map,
37                                AlbumInfo* album_info) {
38  if (!map)
39    return base::File::FILE_ERROR_FAILED;
40
41  AlbumMap::const_iterator it = map->find(key);
42
43  if (it == map->end())
44    return base::File::FILE_ERROR_NOT_FOUND;
45
46  if (album_info != NULL)
47    *album_info = it->second;
48
49  return base::File::FILE_OK;
50}
51
52std::vector<std::string> GetVirtualPathComponents(
53    const storage::FileSystemURL& url) {
54  ImportedMediaGalleryRegistry* imported_registry =
55      ImportedMediaGalleryRegistry::GetInstance();
56  base::FilePath root = imported_registry->ImportedRoot().AppendASCII("picasa");
57
58  DCHECK(root.IsParent(url.path()) || root == url.path());
59  base::FilePath virtual_path;
60  root.AppendRelativePath(url.path(), &virtual_path);
61
62  std::vector<std::string> result;
63  storage::VirtualPath::GetComponentsUTF8Unsafe(virtual_path, &result);
64  return result;
65}
66
67PicasaDataProvider::DataType GetDataTypeForURL(
68    const storage::FileSystemURL& url) {
69  std::vector<std::string> components = GetVirtualPathComponents(url);
70  if (components.size() >= 2 && components[0] == kPicasaDirAlbums)
71    return PicasaDataProvider::ALBUMS_IMAGES_DATA;
72
73  return PicasaDataProvider::LIST_OF_ALBUMS_AND_FOLDERS_DATA;
74}
75
76}  // namespace
77
78const char kPicasaDirAlbums[]  = "albums";
79const char kPicasaDirFolders[] = "folders";
80
81PicasaFileUtil::PicasaFileUtil(MediaPathFilter* media_path_filter)
82    : NativeMediaFileUtil(media_path_filter),
83      weak_factory_(this) {
84}
85
86PicasaFileUtil::~PicasaFileUtil() {}
87
88void PicasaFileUtil::GetFileInfoOnTaskRunnerThread(
89    scoped_ptr<storage::FileSystemOperationContext> context,
90    const storage::FileSystemURL& url,
91    const GetFileInfoCallback& callback) {
92  PicasaDataProvider* data_provider = GetDataProvider();
93  // |data_provider| may be NULL if the file system was revoked before this
94  // operation had a chance to run.
95  if (!data_provider) {
96    GetFileInfoWithFreshDataProvider(context.Pass(), url, callback, false);
97  } else {
98    data_provider->RefreshData(
99        GetDataTypeForURL(url),
100        base::Bind(&PicasaFileUtil::GetFileInfoWithFreshDataProvider,
101                   weak_factory_.GetWeakPtr(),
102                   base::Passed(&context),
103                   url,
104                   callback));
105  }
106}
107
108void PicasaFileUtil::ReadDirectoryOnTaskRunnerThread(
109    scoped_ptr<storage::FileSystemOperationContext> context,
110    const storage::FileSystemURL& url,
111    const ReadDirectoryCallback& callback) {
112  PicasaDataProvider* data_provider = GetDataProvider();
113  // |data_provider| may be NULL if the file system was revoked before this
114  // operation had a chance to run.
115  if (!data_provider) {
116    ReadDirectoryWithFreshDataProvider(context.Pass(), url, callback, false);
117  } else {
118    data_provider->RefreshData(
119        GetDataTypeForURL(url),
120        base::Bind(&PicasaFileUtil::ReadDirectoryWithFreshDataProvider,
121                   weak_factory_.GetWeakPtr(),
122                   base::Passed(&context),
123                   url,
124                   callback));
125  }
126}
127
128base::File::Error PicasaFileUtil::GetFileInfoSync(
129    FileSystemOperationContext* context, const FileSystemURL& url,
130    base::File::Info* file_info, base::FilePath* platform_path) {
131  DCHECK(context);
132  DCHECK(file_info);
133
134  if (platform_path)
135    *platform_path = base::FilePath();
136
137  std::vector<std::string> components = GetVirtualPathComponents(url);
138
139  switch (components.size()) {
140    case 0:
141      // Root directory.
142      file_info->is_directory = true;
143      return base::File::FILE_OK;
144    case 1:
145      if (components[0] == kPicasaDirAlbums ||
146          components[0] == kPicasaDirFolders) {
147        file_info->is_directory = true;
148        return base::File::FILE_OK;
149      }
150
151      break;
152    case 2:
153      if (components[0] == kPicasaDirAlbums) {
154        scoped_ptr<AlbumMap> album_map = GetDataProvider()->GetAlbums();
155        base::File::Error error =
156            FindAlbumInfo(components[1], album_map.get(), NULL);
157        if (error != base::File::FILE_OK)
158          return error;
159
160        file_info->is_directory = true;
161        return base::File::FILE_OK;
162      }
163
164      if (components[0] == kPicasaDirFolders) {
165        return NativeMediaFileUtil::GetFileInfoSync(context, url, file_info,
166                                                    platform_path);
167      }
168      break;
169    case 3:
170      // NativeMediaFileUtil::GetInfo calls into virtual function
171      // PicasaFileUtil::GetLocalFilePath, and that will handle both
172      // album contents and folder contents.
173      base::File::Error result = NativeMediaFileUtil::GetFileInfoSync(
174          context, url, file_info, platform_path);
175
176      DCHECK(components[0] == kPicasaDirAlbums ||
177             components[0] == kPicasaDirFolders ||
178             result == base::File::FILE_ERROR_NOT_FOUND);
179
180      return result;
181  }
182
183  return base::File::FILE_ERROR_NOT_FOUND;
184}
185
186base::File::Error PicasaFileUtil::ReadDirectorySync(
187    storage::FileSystemOperationContext* context,
188    const storage::FileSystemURL& url,
189    EntryList* file_list) {
190  DCHECK(context);
191  DCHECK(file_list);
192  DCHECK(file_list->empty());
193
194  base::File::Info file_info;
195  base::FilePath platform_directory_path;
196  base::File::Error error = GetFileInfoSync(
197      context, url, &file_info, &platform_directory_path);
198
199  if (error != base::File::FILE_OK)
200    return error;
201
202  if (!file_info.is_directory)
203    return base::File::FILE_ERROR_NOT_A_DIRECTORY;
204
205  std::vector<std::string> components = GetVirtualPathComponents(url);
206  switch (components.size()) {
207    case 0: {
208      // Root directory.
209      file_list->push_back(
210          DirectoryEntry(kPicasaDirAlbums, DirectoryEntry::DIRECTORY, 0,
211                         base::Time()));
212      file_list->push_back(
213          DirectoryEntry(kPicasaDirFolders, DirectoryEntry::DIRECTORY, 0,
214                         base::Time()));
215      break;
216    }
217    case 1:
218      if (components[0] == kPicasaDirAlbums) {
219        scoped_ptr<AlbumMap> albums = GetDataProvider()->GetAlbums();
220        if (!albums)
221          return base::File::FILE_ERROR_NOT_FOUND;
222
223        for (AlbumMap::const_iterator it = albums->begin();
224             it != albums->end(); ++it) {
225          file_list->push_back(
226              DirectoryEntry(it->first, DirectoryEntry::DIRECTORY, 0,
227                             it->second.timestamp));
228        }
229      } else if (components[0] == kPicasaDirFolders) {
230        scoped_ptr<AlbumMap> folders = GetDataProvider()->GetFolders();
231        if (!folders)
232          return base::File::FILE_ERROR_NOT_FOUND;
233
234        for (AlbumMap::const_iterator it = folders->begin();
235             it != folders->end(); ++it) {
236          file_list->push_back(
237              DirectoryEntry(it->first, DirectoryEntry::DIRECTORY, 0,
238                             it->second.timestamp));
239        }
240      }
241      break;
242    case 2:
243      if (components[0] == kPicasaDirAlbums) {
244        scoped_ptr<AlbumMap> album_map = GetDataProvider()->GetAlbums();
245        AlbumInfo album_info;
246        base::File::Error error =
247            FindAlbumInfo(components[1], album_map.get(), &album_info);
248        if (error != base::File::FILE_OK)
249          return error;
250
251        scoped_ptr<AlbumImages> album_images =
252            GetDataProvider()->FindAlbumImages(album_info.uid, &error);
253        if (error != base::File::FILE_OK)
254          return error;
255
256        for (AlbumImages::const_iterator it = album_images->begin();
257             it != album_images->end();
258             ++it) {
259          storage::DirectoryEntry entry;
260          base::File::Info info;
261
262          // Simply skip files that we can't get info on.
263          if (storage::NativeFileUtil::GetFileInfo(it->second, &info) !=
264              base::File::FILE_OK) {
265            continue;
266          }
267
268          file_list->push_back(DirectoryEntry(
269              it->first, DirectoryEntry::FILE, info.size, info.last_modified));
270        }
271      }
272
273      if (components[0] == kPicasaDirFolders) {
274        EntryList super_list;
275        base::File::Error error =
276            NativeMediaFileUtil::ReadDirectorySync(context, url, &super_list);
277        if (error != base::File::FILE_OK)
278          return error;
279
280        for (EntryList::const_iterator it = super_list.begin();
281             it != super_list.end(); ++it) {
282          if (!it->is_directory)
283            file_list->push_back(*it);
284        }
285      }
286
287      break;
288  }
289
290  return base::File::FILE_OK;
291}
292
293base::File::Error PicasaFileUtil::DeleteDirectorySync(
294    storage::FileSystemOperationContext* context,
295    const storage::FileSystemURL& url) {
296  return base::File::FILE_ERROR_SECURITY;
297}
298
299base::File::Error PicasaFileUtil::DeleteFileSync(
300    storage::FileSystemOperationContext* context,
301    const storage::FileSystemURL& url) {
302  return base::File::FILE_ERROR_SECURITY;
303}
304
305base::File::Error PicasaFileUtil::GetLocalFilePath(
306    FileSystemOperationContext* context, const FileSystemURL& url,
307    base::FilePath* local_file_path) {
308  DCHECK(local_file_path);
309  DCHECK(url.is_valid());
310  std::vector<std::string> components = GetVirtualPathComponents(url);
311
312  switch (components.size()) {
313    case 2:
314      if (components[0] == kPicasaDirFolders) {
315        scoped_ptr<AlbumMap> album_map = GetDataProvider()->GetFolders();
316        AlbumInfo album_info;
317        base::File::Error error =
318            FindAlbumInfo(components[1], album_map.get(), &album_info);
319        if (error != base::File::FILE_OK)
320          return error;
321
322        *local_file_path = album_info.path;
323        return base::File::FILE_OK;
324      }
325      break;
326    case 3:
327      if (components[0] == kPicasaDirAlbums) {
328        scoped_ptr<AlbumMap> album_map = GetDataProvider()->GetAlbums();
329        AlbumInfo album_info;
330        base::File::Error error =
331            FindAlbumInfo(components[1], album_map.get(), &album_info);
332        if (error != base::File::FILE_OK)
333          return error;
334
335        scoped_ptr<AlbumImages> album_images =
336            GetDataProvider()->FindAlbumImages(album_info.uid, &error);
337        if (error != base::File::FILE_OK)
338          return error;
339
340        AlbumImages::const_iterator it = album_images->find(components[2]);
341        if (it == album_images->end())
342          return base::File::FILE_ERROR_NOT_FOUND;
343
344        *local_file_path = it->second;
345        return base::File::FILE_OK;
346      }
347
348      if (components[0] == kPicasaDirFolders) {
349        scoped_ptr<AlbumMap> album_map = GetDataProvider()->GetFolders();
350        AlbumInfo album_info;
351        base::File::Error error =
352            FindAlbumInfo(components[1], album_map.get(), &album_info);
353        if (error != base::File::FILE_OK)
354          return error;
355
356        // Not part of this class's mandate to check that it actually exists.
357        *local_file_path = album_info.path.Append(url.path().BaseName());
358        return base::File::FILE_OK;
359      }
360
361      return base::File::FILE_ERROR_NOT_FOUND;
362      break;
363  }
364
365  // All other cases don't have a local path. The valid cases should be
366  // intercepted by GetFileInfo()/CreateFileEnumerator(). Invalid cases
367  // return a NOT_FOUND error.
368  return base::File::FILE_ERROR_NOT_FOUND;
369}
370
371void PicasaFileUtil::GetFileInfoWithFreshDataProvider(
372    scoped_ptr<storage::FileSystemOperationContext> context,
373    const storage::FileSystemURL& url,
374    const GetFileInfoCallback& callback,
375    bool success) {
376  if (!success) {
377    content::BrowserThread::PostTask(
378        content::BrowserThread::IO,
379        FROM_HERE,
380        base::Bind(callback, base::File::FILE_ERROR_IO, base::File::Info()));
381    return;
382  }
383  NativeMediaFileUtil::GetFileInfoOnTaskRunnerThread(
384      context.Pass(), url, callback);
385}
386
387void PicasaFileUtil::ReadDirectoryWithFreshDataProvider(
388    scoped_ptr<storage::FileSystemOperationContext> context,
389    const storage::FileSystemURL& url,
390    const ReadDirectoryCallback& callback,
391    bool success) {
392  if (!success) {
393    content::BrowserThread::PostTask(
394        content::BrowserThread::IO,
395        FROM_HERE,
396        base::Bind(callback, base::File::FILE_ERROR_IO, EntryList(), false));
397    return;
398  }
399  NativeMediaFileUtil::ReadDirectoryOnTaskRunnerThread(
400      context.Pass(), url, callback);
401}
402
403PicasaDataProvider* PicasaFileUtil::GetDataProvider() {
404  return ImportedMediaGalleryRegistry::PicasaDataProvider();
405}
406
407}  // namespace picasa
408