/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ var Montage = require("montage/core/core").Montage, pickerNavigatorReel = require("js/io/ui/file-picker/picker-navigator.reel").PickerNavigator, filePickerModelModule = require("js/io/ui/file-picker/file-picker-model"), Popup = require("montage/ui/popup/popup.reel").Popup; //singleton with functions to create a new file picker instance and utilities to format or filter the model data var FilePickerController = exports.FilePickerController = Montage.create(require("montage/ui/component").Component, { /** * Register a listener for file open event */ deserializedFromTemplate:{ writable:false, enumerable:true, value:function(){ this.eventManager.addEventListener("openFilePicker", this, false); } }, pickerNavChoices:{ enumerable: true, value: null }, filePickerPopupType:{ enumerable: false, value: "filePicker" }, handleOpenFilePicker: { value: function(evt) { this.showFilePicker(evt.detail); } }, /** *this function is used to create an instance of a file picker * * parameters: * settings is an object containing : * callback [optional]: the call back function which will be used to send the selected URIs back. If undefined then an event is fired with the selected uri * callbackScope : required if callback is set * pickerMode [optional]: ["read", "write"] : specifies if the file picker is opened to read a file/folder or to save a file * currentFilter [optional]: if a current filter needs to be applied [ex: .psd] * allFileFilters [optional]: list of filters that user can use to filter the view * inFileMode [optional]: true => allow file selection , false => allow directory selection * allowNewFileCreation [optional]: flag to specify whether or not it should return URI(s) to item(s) that do not exist. i.e. a user can type a filename to a new file that doesn't yet exist in the file system. * allowMultipleSelections [optional]: allowMultipleSelections * pickerName: name for montage custom popup * * return: none */ showFilePicker:{ writable:false, enumerable:true, value:function(settings){ var callback, callbackScope, pickerMode, currentFilter, allFileFilters, inFileMode, allowNewFileCreation, allowMultipleSelections, pickerName; if(!!settings){ if(typeof settings.callback !== "undefined"){callback = settings.callback;} if(typeof settings.pickerMode !== "undefined"){pickerMode = settings.pickerMode;} if(typeof settings.currentFilter !== "undefined"){currentFilter = settings.currentFilter;} if(typeof settings.allFileFilters !== "undefined"){allFileFilters = settings.allFileFilters;} if(typeof settings.inFileMode !== "undefined"){inFileMode = settings.inFileMode;} if(typeof settings.allowNewFileCreation !== "undefined"){allowNewFileCreation = settings.allowNewFileCreation;} if(typeof settings.allowMultipleSelections !== "undefined"){allowMultipleSelections = settings.allowMultipleSelections;} if(typeof settings.pickerName !== "undefined"){this.filePickerPopupType = settings.pickerName;} } if(settings.pickerName === "saveAsDirectoryPicker"){//need to set the picker mode in a better way pickerMode = "write"; }else{ pickerMode = "read"; } var aModel = filePickerModelModule.FilePickerModel.create(); var topLevelDirectories = null; var driveData = this.application.ninja.coreIoApi.getDirectoryContents({uri:"", recursive:false, returnType:"all"}); if(driveData.success){ topLevelDirectories = (JSON.parse(driveData.content)).children; }else{ var errorCause = ""; if(driveData.status === null){ errorCause = "Service Unavailable" }else{ errorCause = driveData.status; } aModel.fatalError = " ** Unable to get files [Error: "+ errorCause +"]"; } //dummy data - TODO:remove after testing //aModel.currentFilter = "*.html, *.png"; //aModel.currentFilter = "*.jpg"; aModel.currentFilter = "*.*"; aModel.inFileMode = true; aModel.fileFilters = [".html, .htm", ".jpg, .jpeg, .png, .gif", ".js, .json", ".css", ".txt, .rtf", ".doc, .docx", ".pdf", ".avi, .mov, .mpeg, .ogg, .webm", "*.*"]; //-end - dummy data if(!!currentFilter){aModel.currentFilter = currentFilter;} if(typeof inFileMode !== "undefined"){aModel.inFileMode = inFileMode;} aModel.topLevelDirectories = topLevelDirectories; if(!!topLevelDirectories && !!topLevelDirectories[0]){ aModel.currentRoot = aModel.currentLogicalDrive = topLevelDirectories[0].uri; } //populate the last opened folder first, if none then populate default root var storedUri = null; var sessionStorage = window.sessionStorage; try{ if(pickerMode === "write"){ storedUri = sessionStorage.getItem("lastSavedFolderURI"); }else if(inFileMode === true){ storedUri = sessionStorage.getItem("lastOpenedFolderURI_fileSelection"); }else if(inFileMode === false){ storedUri = sessionStorage.getItem("lastOpenedFolderURI_folderSelection"); } }catch(e){ if(e.code == 22){ sessionStorage.clear(); } } if(!!storedUri){ aModel.currentRoot = decodeURI(storedUri); } if(!!allFileFilters){aModel.fileFilters = allFileFilters;} if(!!callback){aModel.callback = callback;} if(!!callbackScope){aModel.callbackScope = callbackScope;} if(typeof pickerMode !== "undefined"){aModel.pickerMode = pickerMode;} //logic: get file content data onDemand from the REST api for the default or last opened root. Cache the data in page [in local cache ? dirty fs? ]. Filter on client side to reduce network calls. this.openFilePickerAsModal(callback, aModel); //to open this on another modal dialog, make it a popup instead above the modal dialog container layer } }, openFilePickerAsModal:{ writable:false, enumerable:true, value:function(callback, aModel){ var pickerNavChoices = this.pickerNavChoices = Montage.create(pickerNavigatorReel); var initUri = aModel.currentRoot; //remove extra / at the end if((initUri.length > 1) && (initUri.charAt(initUri.length - 1) === "/")){ initUri = initUri.substring(0, (initUri.length - 1)); } pickerNavChoices.mainContentData = this.prepareContentList(initUri, aModel); pickerNavChoices.pickerModel = aModel; var popup = Popup.create(); popup.content = pickerNavChoices; popup.modal = true; popup.type = this.filePickerPopupType;//should be set always to not default to the single custom popup layer popup.show(); pickerNavChoices.popup = popup;//handle to be used for hiding the popup } }, openFilePickerAsPopup:{ writable:false, enumerable:true, value:function(){} }, expandDirectory:{ writable:false, enumerable:true, value: function(root, currentFilter, inFileMode){ //populate children in dom } }, refreshDirectoryCache:{ writable:false, enumerable:true, value:function(directoryUri){ if(directoryContentCache[directoryUri] !== null){ directoryContentCache[directoryUri] = null; //invalidate the cached content //fetch fresh content } } }, /** * queries the cache to build contents array. If not found queries the file system * * parameters: * folderUri * aModel: model instance per picker instance, containing */ prepareContentList:{ writable: false, enumerable:true, value:function(folderUri, aModel, fromCache, checkStaleness){ var contentList = [], childrenArray = []; var folderContent = null; // query filesystem and populate cache if(((typeof fromCache !== "undefined") && (fromCache === false)) || !this._directoryContentCache[folderUri] || !this._directoryContentCache[folderUri].children){ //get data using IO api try{ var iodata = this.application.ninja.coreIoApi.getDirectoryContents({uri:folderUri, recursive:false, returnType:"all"}); //console.log("IO:getDirectoryContents:Response:\n"+"uri="+folderUri+"\n status="+iodata.status+"\n content= "+iodata.content); if(iodata.success && (iodata.status === 200) && (iodata.content !== null)){ folderContent = JSON.parse(iodata.content); } }catch(e){ console.error("Error to IO uri: "+folderUri+"\n"+e.message); } if(!!folderContent){ //contentList = folderContent.children;//need to apply filters and mode this.cacheContentForRandomAccess(folderUri, folderContent); } } //now from cache - apply filters and mode if((!!this._directoryContentCache[folderUri]) && (this._directoryContentCache[folderUri].type === "directory") && (typeof this._directoryContentCache[folderUri].children !== "undefined") && (this._directoryContentCache[folderUri].children !== null)){ //console.log("$$$ this._directoryContentCache"); //console.log(this._directoryContentCache); //check for directory staleness.... if stale query filesystem if((typeof checkStaleness === "undefined") || (checkStaleness === true)){ this.checkIfStale(folderUri); } childrenArray = this._directoryContentCache[folderUri].children; //prepare content array for folder uri childrenArray.forEach(function(item){ if(this._directoryContentCache[item]){ //apply mode and filtering here if(aModel.inFileMode){// if in file selection mode, do filtering if((this._directoryContentCache[item].type === "directory") || !aModel.currentFilter){//no filetering contentList.push(this._directoryContentCache[item]); } else if(aModel.currentFilter){ if(this.applyFilter(this._directoryContentCache[item].name, aModel.currentFilter)){ contentList.push(this._directoryContentCache[item]); } } }else{// if in folder selection mode if(this._directoryContentCache[item].type === "directory"){ contentList.push(this._directoryContentCache[item]); } } } }, this); } else if((typeof this._directoryContentCache[folderUri] !== 'undefined') && (this._directoryContentCache[folderUri].type === "file")){//if the uri is for a file //check for directory staleness.... if stale query filesystem if((typeof checkStaleness === "undefined") || (checkStaleness === true)){ this.checkIfStale(folderUri); } contentList.push(this._directoryContentCache[folderUri]); } //end - from cache return contentList; } }, /** * populates/updates cache for a uri */ cacheContentForRandomAccess:{ writable:false, enumerable:true, value: function(directoryUri, directoryContents){ var that = this; //assumption: directoryContents will have only its direct files and subfolders //uri is the unique identifier //check if the directoryUri exists in cache //if not add uri content object, prepare children's uri array,then add/update objects for children if(!this._directoryContentCache[directoryUri]){//uri not in cache... so add it //add uri content object this._directoryContentCache[directoryUri] = {"type":directoryContents.type,"name":directoryContents.name,"uri":directoryUri}; if(!!directoryContents.size){ this._directoryContentCache[directoryUri].size = directoryContents.size; } if(!!directoryContents.creationDate){ this._directoryContentCache[directoryUri].creationDate = directoryContents.creationDate; } if(!!directoryContents.modifiedDate){ this._directoryContentCache[directoryUri].modifiedDate = directoryContents.modifiedDate; } //store the current queried time for refreshing cache logic this._directoryContentCache[directoryUri].queriedTimeStamp = (new Date()).getTime(); if(!!directoryContents.children && directoryContents.children.length > 0){ this._directoryContentCache[directoryUri].children = []; //add the uri to this._directoryContentCache[directoryUri].children, and add the child's description objects directoryContents.children.forEach(function(obj){ //add uri to parent's children list that._directoryContentCache[directoryUri].children.push(obj.uri); //add the child object that._directoryContentCache[obj.uri] = obj; //store the current queried time for refreshing cache logic that._directoryContentCache[obj.uri].queriedTimeStamp = (new Date()).getTime(); } ,this); } }else{//uri in cache... so update it AND its children this._directoryContentCache[directoryUri].type = directoryContents.type; this._directoryContentCache[directoryUri].name = directoryContents.name; if(!!directoryContents.size){ this._directoryContentCache[directoryUri].size = directoryContents.size; } if(!!directoryContents.creationDate){ this._directoryContentCache[directoryUri].creationDate = directoryContents.creationDate; } if(!!directoryContents.modifiedDate){ this._directoryContentCache[directoryUri].modifiedDate = directoryContents.modifiedDate; } //store the current queried time for refreshing cache logic this._directoryContentCache[directoryUri].queriedTimeStamp = (new Date()).getTime(); if(!!directoryContents.children && directoryContents.children.length > 0){ // logic to clear off objects from cache if they no longer exist in the filesystem //better logic - use isUpdatedFlag for a folder .. then compare modified date //hack for now - clear up the old children and add new ones var tempArr = this._directoryContentCache[directoryUri].children; if( !!tempArr && Array.isArray(tempArr) && tempArr.length>0){ tempArr.forEach(function(uriString){ if(!!that._directoryContentCache[uriString]){ delete that._directoryContentCache[uriString]; } }); } this._directoryContentCache[directoryUri].children = []; //add the uri to this._directoryContentCache[directoryUri].children, and add the child's description objects directoryContents.children.forEach(function(obj){ //add uri to parent's children list that._directoryContentCache[directoryUri].children.push(obj.uri); //add the child object that._directoryContentCache[obj.uri] = obj; //store the current queried time for refreshing cache logic that._directoryContentCache[obj.uri].queriedTimeStamp = (new Date()).getTime(); } ,this); }else{ this._directoryContentCache[directoryUri].children = []; } } //console.log("$$$ "+directoryUri+" modifiedDate = "+this._directoryContentCache[directoryUri].modifiedDate); //console.log("$$$ "+directoryUri+" queriedTimeStamp = "+this._directoryContentCache[directoryUri].queriedTimeStamp); } }, applyFilter:{ writable: false, enumerable:true, value:function(fileName , filters){ if(filters.indexOf("*.*") !== -1){return true;} //console.log(""+fileName); var filtersArr = filters.split(","); var passed = false; for(var i=0; i< filtersArr.length; i++){ filtersArr[i] = filtersArr[i].trim(); //console.log(filtersArr[i]); var fileType = filtersArr[i].substring(filtersArr[i].indexOf(".") ); //console.log(""+fileType); //ignore uppercase fileName = fileName.toLowerCase(); fileType = fileType.toLowerCase(); if(fileName.indexOf(fileType, fileName.length - fileType.length) !== -1){//ends with file type passed = true; break; } } return passed; } }, /** * Stale Time (ms) for each resource * Logic: the last queried time for a resource is compared to stale time. If stale, then file system is queried */ cacheStaleTime:{ writable: false, enumerable: false, value: 5000 }, checkIfStale: { writable: false, enumerable: true, value: function(folderUri){ var wasStale = false; var folderContent = null; //check for directory staleness.... if stale query filesystem if((new Date()).getTime() > (this._directoryContentCache[folderUri].queriedTimeStamp + this.cacheStaleTime)){ try{ var ifModifiedResponse = this.application.ninja.coreIoApi.isDirectoryModified({uri:folderUri, recursive:false, returnType:"all"}, this._directoryContentCache[folderUri].queriedTimeStamp); //console.log("ifModifiedResponse"); //console.log(ifModifiedResponse); }catch(e){ console.error("Error to IO uri with isDirectoryModified: "+folderUri+"\n"+e.message); } if(ifModifiedResponse && ifModifiedResponse.status === 304){ //do nothing since the uri has not changed }else if(ifModifiedResponse && (ifModifiedResponse.status === 200)){ wasStale = true; //uri has changed. so update cache try{ var iodata = this.application.ninja.coreIoApi.getDirectoryContents({uri:folderUri, recursive:false, returnType:"all"}); //console.log("IO:getDirectoryContents:Response:\n"+"uri="+folderUri+"\n status="+iodata.status+"\n content= "+iodata.content); if(iodata.success && (iodata.status === 200) && (iodata.content !== null)){ folderContent = JSON.parse(iodata.content); } }catch(e){ console.error("Error to IO uri: "+folderUri+"\n"+e.message); } if(!!folderContent){ this.cacheContentForRandomAccess(folderUri, folderContent); } } } return wasStale; } }, /** * This will store the directory content per session * check session storage for this */ _directoryContentCache:{ writable:true, enumerable:false, value:{} }, clearCache:{ writable:false, enumerable: true, value: function(){ this._directoryContentCache = {}; } } });