src/app/lib/file_cache_handler.coffee
async = require 'async'
DesignDocuments = require '../replicator/design_documents'
fs = require '../replicator/filesystem'
log = require('./persistent_log')
prefix: "FileCacheHandler"
date: true
instance = null
module.exports = class FileCacheHandler
constructor: (@localDb, @replicateDb, @requestCozy) ->
return instance if instance
instance = @
@cache = Object.create(null)
@localDb ?= app.init.database.localDb
@replicateDb ?= app.init.database.replicateDb
@requestCozy ?= app.init.requestCozy
fs.initialize (err, downloads) =>
return log.error err if err
@downloads = downloads
load: (callback) ->
log.debug 'load'
@localDb.query DesignDocuments.FILES_AND_FOLDER_CACHE, (err, results) =>
if results?.rows
for cozyFile in results.rows
@cache[cozyFile.id] = cozyFile.value
callback()
getFileName: (cozyFile) ->
log.debug 'getFileName'
return encodeURIComponent cozyFile.name if cozyFile.name
log.warn JSON.stringify cozyFile
throw new Error 'cozyFile hasn\'t name field'
getFolderName: (cozyFile) ->
return cozyFile._id if cozyFile._id
log.warn JSON.stringify cozyFile
throw new Error 'cozyFile hasn\'t _id field'
isCached: (cozyFile) ->
@cache[@getFolderName cozyFile]?
isSameBinary: (cozyFile) ->
log.debug 'isSameBinary'
@cache[@getFolderName cozyFile]?.downloaded is \
cozyFile.binary?.file?.rev
isDownloaded: (cozyFile) ->
log.debug 'isDownloaded'
cacheFile = @cache[@getFolderName cozyFile]
cacheFile isnt undefined and cacheFile.downloaded isnt false
isSameName: (cozyFile) ->
log.debug 'isSameName'
@cache[@getFolderName cozyFile]?.name is @getFileName cozyFile
saveInCache: (cozyFile, downloaded, callback) ->
log.debug 'saveInCache'
folderName = @getFolderName cozyFile
fileName = @getFileName cozyFile
downloadFile =
_id: folderName
docType: 'cache'
fileName: fileName
binary_id: cozyFile.binary?.file?.id
binary_rev: cozyFile.binary?.file?.rev
downloaded: if downloaded then cozyFile.binary?.file?.rev else false
@cache[folderName] =
version: downloadFile.binary_rev
name: fileName
downloaded: downloadFile.downloaded
@localDb.get folderName, (err, doc) =>
unless err
# this cozyFile exist on cache
downloadFile._rev = doc._rev
unless downloaded
downloadFile.downloaded = doc.downloaded
@cache[folderName].downloaded = doc.downloaded
@localDb.put downloadFile, callback
downloadUnsynchronizedFiles: (callback) ->
progressback = ->
async.forEachOfSeries @cache, (cacheFile, id, cb) =>
return cb() if cacheFile.downloaded is cacheFile.version
@replicateDb.get id, (err, cozyFile) =>
return cb err if err
@downloadBinary cozyFile, progressback, cb
, callback
removeInCache: (cozyFile, callback) ->
log.debug 'removeInCache'
folderName = @getFolderName cozyFile
delete @cache[folderName]
@localDb.get folderName, (err, doc) =>
return callback() if err and err.status is 404
return callback err if err
@localDb.remove doc, callback
getBinaryUrl: (cozyFile, callback) ->
folderName = @getFolderName cozyFile
fs.getOrCreateSubFolder @downloads, folderName, (err, binaryFolder) =>
if err and err.code isnt FileError.PATH_EXISTS_ERR
return callback err
fileName = @getFileName cozyFile
if device.platform is "Android"
fileName = decodeURIComponent fileName
fs.getFile binaryFolder, fileName, (err, entry) ->
# file already exist
return callback null, entry.toURL() if entry
return callback err
# Get or download the binary of the specified file in cache.
# @param cozyFile cozy File document
# @param progressback progress callback.
getBinary: (cozyFile, progressback, callback) ->
log.debug 'getBinary'
getUrl = =>
@getBinaryUrl cozyFile, (err, url) =>
return callback null, url if url
@cache[@getFolderName cozyFile].downloaded = false
@getBinary cozyFile, progressback, callback
return getUrl() if @isSameBinary cozyFile
@downloadBinary cozyFile, progressback, (err, url) =>
return callback null, url if url
return getUrl() if @isDownloaded cozyFile
callback err
# Download the binary of the specified file in cache.
# @param cozyFile cozy File document
# @param progressback progress callback.
downloadBinary: (cozyFile, progressback, callback) ->
log.debug 'downloadBinary'
folderName = @getFolderName cozyFile
fs.getOrCreateSubFolder @downloads, folderName, (err, binaryFolder) =>
if err and err.code isnt FileError.PATH_EXISTS_ERR
return callback err
fileName = @getFileName cozyFile
path = "/data/#{cozyFile._id}/binaries/file"
androidPath = binaryFolder.toURL() + fileName
options = @requestCozy.getDataSystemOption path, true
options.path = androidPath + '_download'
fs.download options, progressback, (err, entry) =>
if entry
fileName = cozyFile.name
fs.moveTo entry, binaryFolder, fileName, (err, entry) =>
return callback err if err
log.info "Binary #{fileName} is downloaded."
@saveInCache cozyFile, true, (err) ->
log.error err if err
callback null, decodeURIComponent entry.toURL()
else
callback err
getBinaryDirectory: (folderName, callback) ->
log.debug "getBinaryDirectory"
fs.getOrCreateSubFolder @downloads, folderName, callback
# Remove from cache specified file.
# @param file a cozy file document.
removeLocal: (cozyFile, callback) ->
log.info "remove #{cozyFile.name} from cache."
folderName = @getFolderName cozyFile
fs.getDirectory @downloads, folderName, (err, binaryFolder) =>
# code 1: is NOT_FOUND_ERR
return callback err if err and err.code is not 1
@removeInCache cozyFile, (err) ->
return callback err if err
return callback() unless binaryFolder
fs.rmrf binaryFolder, (err) ->
return callback err if err and err.code is not 1
callback()
open: (url, callback) ->
success = (entry) ->
entry.file (file) ->
fileName = entry.toURL()
if device.platform is "Android"
fileName = decodeURIComponent fileName
cordova.plugins.fileOpener2.open fileName, file.type,
success: callback, # do nothing
error: callback
url = encodeURI(url).replace /#/g, '%23'
resolveLocalFileSystemURL url, success, callback