pre version bump

This commit is contained in:
Martin Donnelly 2016-06-01 16:08:26 +01:00
parent ee73adae39
commit 8066c76dfd
258 changed files with 49298 additions and 1 deletions

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "sensortoy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "http://gitlab.silvrtree.co.uk/martind2000/sensortoy.git"
},
"author": "Martin Donnelly <martind2000@gmail.com>",
"license": "ISC",
"devDependencies": {
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.0",
"gulp-cache": "^0.4.5",
"gulp-concat": "^2.6.0",
"gulp-cssnano": "^2.1.2"
}
}

View File

@ -0,0 +1,94 @@
/* Progress Bar */
.progress {
position: relative;
height: 4px;
display: block;
width: 100%;
background-color: #acece6;
border-radius: 2px;
background-clip: padding-box;
/* margin: 0.5rem 0 1rem 0; */
overflow: hidden; }
.progress .determinate {
position: absolute;
background-color: inherit;
top: 0;
bottom: 0;
background-color: #26a69a;
transition: width .3s linear; }
.progress .indeterminate {
background-color: #26a69a; }
.progress .indeterminate:before {
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; }
.progress .indeterminate:after {
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s; }
@-webkit-keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
60% {
left: 100%;
right: -90%; }
100% {
left: 100%;
right: -90%; }
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
60% {
left: 100%;
right: -90%; }
100% {
left: 100%;
right: -90%; }
}
@-webkit-keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
60% {
left: 107%;
right: -8%; }
100% {
left: 107%;
right: -8%; }
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
60% {
left: 107%;
right: -8%; }
100% {
left: 107%;
right: -8%; }
}

View File

@ -0,0 +1,195 @@
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-moz-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-ms-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-moz-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-o-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
.uil-ripple-css {
background: none;
position: relative;
width: 200px;
height: 200px;
}
.uil-ripple-css div {
position: absolute;
top: 50%;
left: 50%;
margin: 0;
width: 0;
height: 0;
opacity: 0;
border-radius: 50%;
border-width: 12px;
border-style: solid;
-ms-animation: uil-ripple 2s ease-out infinite;
-moz-animation: uil-ripple 2s ease-out infinite;
-webkit-animation: uil-ripple 2s ease-out infinite;
-o-animation: uil-ripple 2s ease-out infinite;
animation: uil-ripple 2s ease-out infinite;
}
.uil-ripple-css div:nth-of-type(1) {
border-color: #afafb7;
}
.uil-ripple-css div:nth-of-type(2) {
border-color: #5cffd6;
-ms-animation-delay: 1s;
-moz-animation-delay: 1s;
-webkit-animation-delay: 1s;
-o-animation-delay: 1s;
animation-delay: 1s;
}

View File

@ -0,0 +1,41 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(Material_Icons-normal-400.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
src: url(Ubuntu-normal-300.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
src: url(Ubuntu-normal-400.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 500;
src: url(Ubuntu-normal-500.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
src: url(Ubuntu-normal-700.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu Condensed';
font-style: normal;
font-weight: 400;
src: url(Ubuntu_Condensed-normal-400.woff) format('woff');
}

View File

@ -0,0 +1,144 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_HUM = function(p) {
'use strict';
this.name = 'Humidity';
this.deviceID = p.deviceID || null;
this.target = p.target || null;
this.capabilityID = 'F000AA20-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA20-0451-4000-B000-000000000000',
data: 'F000AA21-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA22-0451-4000-B000-000000000000',
period: 'F000AA23-0451-4000-B000-000000000000'
};
this.data = {temp: [], humidity: []};
this.$result = {temp: null, humidity: null};
this.setFrame();
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Humidity Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.data,
this.onHumidityData.bind(this),
this.onError);
//Turn on barometer
var humidityConfig = new Uint8Array(1);
humidityConfig[0] = 0x01;
ble.write(this.deviceID,
this.serviceDef.service,
this.serviceDef.configuration,
humidityConfig.buffer,
function() { console.log('Started Humidity.'); },
this.onError);
}
};
this.onHumidityData = function(data) {
var hStr;
var tStr;
// Console.log(data);
var message;
var raw = new Uint16Array(data);
//-- calculate temperature [°C]
var temp = (raw[0] / 65536) * 165 - 40;
//-- calculate relative humidity [%RH]
var hum = (raw[1] / 65536) * 100;
tStr = temp.toFixed(2) + '°C';
hStr = hum.toFixed(2) + '%RH';
message = 'Temperature <br/>' + tStr + 'Humidity <br/>' + hStr;
this.data.temp = this.storeData(temp, this.data.temp);
this.data.humidity = this.storeData(hum, this.data.humidity);
this.$result.temp.text(tStr);
this.$result.humidity.text(hStr);
this.state = message;
// Console.log('Barometer:', this.state);
};
this.animateGraph = function() {
this.simpleGraph(this.data.temp, 'temp');
this.simpleGraph(this.data.humidity, 'humidity');
};
this.insertFrame = function() {
var self = this;
var blankChart;
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var temp = this.frameID + '-t';
var humidity = this.frameID + '-h';
var row = $('<div />', {class: 'mui-row'});
$('<div />',
{class: 'mui-col-xs-3 mui--text-accent', text: 'Temp:'}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-dark', id: temp}).appendTo(row);
$('<div />',
{
class: 'mui-col-xs-3 mui--text-accent',
text: 'Humidity:'
}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-dark', id: humidity}).appendTo(row);
this.$id.append(row);
var tabBody = $('<ul>',{class: 'mui-tabs__bar mui-tabs__bar--justified'});
$('<li>',{class: 'mui--is-active tabOnWhite'}).append($('<a>',{text: 'Temperature','data-mui-toggle': 'tab', 'data-mui-controls': (temp + '-pane')})).appendTo(tabBody);
$('<li>',{class: 'tabOnWhite'}).append($('<a>',{text: 'Humidity','data-mui-toggle': 'tab', 'data-mui-controls': (humidity + '-pane')})).appendTo(tabBody);
this.$id.append(tabBody);
blankChart = this.generateBlankGraph('temp');
this.$id.append($('<div>',{class: 'mui-tabs__pane mui--is-active',id: (temp + '-pane')}).append(blankChart));
blankChart = this.generateBlankGraph('humidity');
this.$id.append($('<div>',{class: 'mui-tabs__pane',id: (humidity + '-pane')}).append(blankChart));
this.$result.temp = $('#' + temp);
this.$result.humidity = $('#' + humidity);
};
};
inheritsFrom(CC2650_HUM, CAPABILITY);
capabilityManager.register({id:'F000AA20-0451-4000-B000-000000000000', module:CC2650_HUM});

View File

@ -0,0 +1,148 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_TMP = function(p) {
'use strict';
this.name = 'Thermopile';
this.deviceID = p.deviceID || null;
this.target = p.target || null;
this.capabilityID = 'F000AA00-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA00-0451-4000-B000-000000000000',
data: 'F000AA01-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA02-0451-4000-B000-000000000000',
period: 'F000AA03-0451-4000-B000-000000000000'
};
this.data = {temp: [], ambient: []};
this.$result = {temp: null, ambient: null};
this.setFrame();
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Thermopile Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.data,
this.onThermData.bind(this),
this.onError);
//Turn on thermopile
var tmpConfig = new Uint8Array(1);
tmpConfig[0] = 0x01;
ble.write(this.deviceID,
this.serviceDef.service,
this.serviceDef.configuration,
tmpConfig.buffer,
function() { console.log('Started Thermopile.'); },
this.onError);
}
};
this.onThermData = function(data) {
var ambTemp;
var objTemp;
var scale_lsb = 0.03125;
var raw = new Uint16Array(data);
var it = raw[0] >> 2;
objTemp = it * scale_lsb;
it = raw[1] >> 2;
ambTemp = it * scale_lsb;
var tStr = objTemp.toFixed(2) + '°C';
var aStr = ambTemp.toFixed(2) + '°C';
this.state = ['Temp: ', tStr, ', Ambient:', aStr].join(' ');
this.data.temp = this.storeData(objTemp, this.data.temp);
this.data.ambient = this.storeData(ambTemp, this.data.ambient);
this.$result.temp.text(tStr);
this.$result.ambient.text(aStr);
};
this.animateGraph = function() {
this.simpleGraph(this.data.temp, 'temp');
this.simpleGraph(this.data.ambient, 'ambient');
};
this.insertFrame = function() {
var blankChart;
var self = this;
// Console.log('Overloading...');
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var temp = this.frameID + '-t';
var amb = this.frameID + '-a';
var row = $('<div />', {class: 'mui-row'});
$('<div />',
{class: 'mui-col-xs-3 mui--text-accent', text: 'Temp:'}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-dark', id: temp}).appendTo(row);
$('<div />',
{
class: 'mui-col-xs-3 mui--text-accent',
text: 'Ambient:'
}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-dark', id: amb}).appendTo(row);
this.$id.append(row);
var tabBody = $('<ul>',{class: 'mui-tabs__bar mui-tabs__bar--justified'});
$('<li>',{class: 'mui--is-active tabOnWhite'}).append($('<a>',{ text: 'Temperature','data-mui-toggle': 'tab', 'data-mui-controls': (temp + '-pane')})).appendTo(tabBody);
$('<li>', {class: 'tabOnWhite'}).append($('<a>',{ text: 'Ambient','data-mui-toggle': 'tab', 'data-mui-controls': (amb + '-pane')})).appendTo(tabBody);
this.$id.append(tabBody);
blankChart = this.generateBlankGraph('temp');
this.$id.append($('<div>',{class: 'mui-tabs__pane mui--is-active',id: (temp + '-pane')}).append(blankChart));
blankChart = this.generateBlankGraph('ambient');
this.$id.append($('<div>',{class: 'mui-tabs__pane',id: (amb + '-pane')}).append(blankChart));
this.$result.temp = $('#' + temp);
this.$result.ambient = $('#' + amb);
};
};
inheritsFrom(CC2650_TMP, CAPABILITY);
capabilityManager.register({id: 'F000AA00-0451-4000-B000-000000000000', module: CC2650_TMP});

View File

@ -0,0 +1,69 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-24
* Time: 14:21
*
*/
var MANUFACTUREDECODER = function() {
'use strict';
this.getManID = function(data) {
return ('0000' + ((data[1] << 8) | data[0]).toString(16)).slice(-4).toUpperCase();
},
this.decodeIbeacon = function(data) {
// Not decoding anything yet.
// https://support.kontakt.io/hc/en-gb/articles/201492492-iBeacon-advertising-packet-structure
var bin = data;
var obj = { msg: '(iBeacon)'};
// obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
var uuid = [];
uuid.push(bin[4].toString(16) + bin[5].toString(16) + bin[6].toString(16) + bin[7].toString(16)) ;
uuid.push(bin[8].toString(16) + bin[9].toString(16)) ;
uuid.push(bin[10].toString(16) + bin[11].toString(16)) ;
uuid.push(bin[12].toString(16) + bin[13].toString(16)) ;
uuid.push(bin[14].toString(16) + bin[15].toString(16) + bin[16].toString(16) + bin[17].toString(16) + bin[18].toString(16) + bin[19].toString(16)) ;
obj.uuid = uuid.join('-');
return obj;
};
this.decodeSiliconLabsSensorPuck = function(data) {
var bin = data;
var obj = {data:{}};
//obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
obj.a = (bin[3] << 8) | bin[2];
obj.b = (bin[5] << 8) | bin[4];
obj.data.humidity = (bin[7] << 8) | bin[6];
obj.data.temp = (bin[9] << 8) | bin[8];
obj.c = (bin[11] << 8) | bin[10];
obj.d = (bin[13] << 8) | bin[12];
obj.msg = 'Humidity: ' + (obj.data.humidity / 10) + ', temp: ' + (obj.data.temp / 10);
return obj;
};
this.decodeSansible = function(data) {
var bin = data;
var obj = {data:{}};
// obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
obj.data.p1 = ((bin[2] << 16) | bin[3] << 8 | bin[4]);
obj.data.p2 = ((bin[5] << 16) | bin[6] << 8 | bin[7]);
obj.msg = 'Left: ' + (obj.data.p1 / 100) + ' hPa, Right: ' + (obj.data.p2 / 100) + ' hPa';
return obj;
};
};

View File

@ -53,9 +53,9 @@ var BATTERY = function() {
this.onError);
}
};
};
inheritsFrom(BATTERY, CAPABILITY);
capabilityManager.register({id: '180F', module: BATTERY});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
/**
*
* User: Martin Donnelly
* Date: 2016-06-01
* Time: 13:30
*
*/
var GADGET = function(p) {
'use strict';
this._gadget = p;
};

View File

@ -0,0 +1,803 @@
// MIT License:
//
// Copyright (c) 2010-2013, Joe Walnes
// 2013-2014, Drew Noakes
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* Smoothie Charts - http://smoothiecharts.org/
* (c) 2010-2013, Joe Walnes
* 2013-2014, Drew Noakes
*
* v1.0: Main charting library, by Joe Walnes
* v1.1: Auto scaling of axis, by Neil Dunn
* v1.2: fps (frames per second) option, by Mathias Petterson
* v1.3: Fix for divide by zero, by Paul Nikitochkin
* v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
* v1.5: Set default frames per second to 50... smoother.
* .start(), .stop() methods for conserving CPU, by Dmitry Vyal
* options.interpolation = 'bezier' or 'line', by Dmitry Vyal
* options.maxValue to fix scale, by Dmitry Vyal
* v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla
* v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin
* Smooth rescaling, by Kostas Michalopoulos
* v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni
* v1.9: Display timestamps along the bottom, by Nick and Stev-io
* (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D)
* Refactored by Krishna Narni, to support timestamp formatting function
* v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh
* v1.11: options.grid.sharpLines option added, by @drewnoakes
* Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes
* v1.12: Support for horizontalLines added, by @drewnoakes
* Support for yRangeFunction callback added, by @drewnoakes
* v1.13: Fixed typo (#32), by @alnikitich
* v1.14: Timer cleared when last TimeSeries removed (#23), by @davidgaleano
* Fixed diagonal line on chart at start/end of data stream, by @drewnoakes
* v1.15: Support for npm package (#18), by @dominictarr
* Fixed broken removeTimeSeries function (#24) by @davidgaleano
* Minor performance and tidying, by @drewnoakes
* v1.16: Bug fix introduced in v1.14 relating to timer creation/clearance (#23), by @drewnoakes
* TimeSeries.append now deals with out-of-order timestamps, and can merge duplicates, by @zacwitte (#12)
* Documentation and some local variable renaming for clarity, by @drewnoakes
* v1.17: Allow control over font size (#10), by @drewnoakes
* Timestamp text won't overlap, by @drewnoakes
* v1.18: Allow control of max/min label precision, by @drewnoakes
* Added 'borderVisible' chart option, by @drewnoakes
* Allow drawing series with fill but no stroke (line), by @drewnoakes
* v1.19: Avoid unnecessary repaints, and fixed flicker in old browsers having multiple charts in document (#40), by @asbai
* v1.20: Add SmoothieChart.getTimeSeriesOptions and SmoothieChart.bringToFront functions, by @drewnoakes
* v1.21: Add 'step' interpolation mode, by @drewnoakes
* v1.22: Add support for different pixel ratios. Also add optional y limit formatters, by @copacetic
* v1.23: Fix bug introduced in v1.22 (#44), by @drewnoakes
* v1.24: Fix bug introduced in v1.23, re-adding parseFloat to y-axis formatter defaults, by @siggy_sf
* v1.25: Fix bug seen when adding a data point to TimeSeries which is older than the current data, by @Nking92
* Draw time labels on top of series, by @comolosabia
* Add TimeSeries.clear function, by @drewnoakes
* v1.26: Add support for resizing on high device pixel ratio screens, by @copacetic
* v1.27: Fix bug introduced in v1.26 for non whole number devicePixelRatio values, by @zmbush
* v1.28: Add 'minValueScale' option, by @megawac
*/
;(function(exports) {
var Util = {
extend: function() {
arguments[0] = arguments[0] || {};
for (var i = 1; i < arguments.length; i++)
{
for (var key in arguments[i])
{
if (arguments[i].hasOwnProperty(key))
{
if (typeof(arguments[i][key]) === 'object') {
if (arguments[i][key] instanceof Array) {
arguments[0][key] = arguments[i][key];
} else {
arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]);
}
} else {
arguments[0][key] = arguments[i][key];
}
}
}
}
return arguments[0];
}
};
/**
* Initialises a new <code>TimeSeries</code> with optional data options.
*
* Options are of the form (defaults shown):
*
* <pre>
* {
* resetBounds: true, // enables/disables automatic scaling of the y-axis
* resetBoundsInterval: 3000 // the period between scaling calculations, in millis
* }
* </pre>
*
* Presentation options for TimeSeries are specified as an argument to <code>SmoothieChart.addTimeSeries</code>.
*
* @constructor
*/
function TimeSeries(options) {
this.options = Util.extend({}, TimeSeries.defaultOptions, options);
this.clear();
}
TimeSeries.defaultOptions = {
resetBoundsInterval: 3000,
resetBounds: true
};
/**
* Clears all data and state from this TimeSeries object.
*/
TimeSeries.prototype.clear = function() {
this.data = [];
this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries.
this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries.
};
/**
* Recalculate the min/max values for this <code>TimeSeries</code> object.
*
* This causes the graph to scale itself in the y-axis.
*/
TimeSeries.prototype.resetBounds = function() {
if (this.data.length) {
// Walk through all data points, finding the min/max value
this.maxValue = this.data[0][1];
this.minValue = this.data[0][1];
for (var i = 1; i < this.data.length; i++) {
var value = this.data[i][1];
if (value > this.maxValue) {
this.maxValue = value;
}
if (value < this.minValue) {
this.minValue = value;
}
}
} else {
// No data exists, so set min/max to NaN
this.maxValue = Number.NaN;
this.minValue = Number.NaN;
}
};
/**
* Adds a new data point to the <code>TimeSeries</code>, preserving chronological order.
*
* @param timestamp the position, in time, of this data point
* @param value the value of this data point
* @param sumRepeatedTimeStampValues if <code>timestamp</code> has an exact match in the series, this flag controls
* whether it is replaced, or the values summed (defaults to false.)
*/
TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) {
// Rewind until we hit an older timestamp
var i = this.data.length - 1;
while (i >= 0 && this.data[i][0] > timestamp) {
i--;
}
if (i === -1) {
// This new item is the oldest data
this.data.splice(0, 0, [timestamp, value]);
} else if (this.data.length > 0 && this.data[i][0] === timestamp) {
// Update existing values in the array
if (sumRepeatedTimeStampValues) {
// Sum this value into the existing 'bucket'
this.data[i][1] += value;
value = this.data[i][1];
} else {
// Replace the previous value
this.data[i][1] = value;
}
} else if (i < this.data.length - 1) {
// Splice into the correct position to keep timestamps in order
this.data.splice(i + 1, 0, [timestamp, value]);
} else {
// Add to the end of the array
this.data.push([timestamp, value]);
}
this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value);
this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value);
};
TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) {
// We must always keep one expired data point as we need this to draw the
// line that comes into the chart from the left, but any points prior to that can be removed.
var removeCount = 0;
while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) {
removeCount++;
}
if (removeCount !== 0) {
this.data.splice(0, removeCount);
}
};
/**
* Initialises a new <code>SmoothieChart</code>.
*
* Options are optional, and should be of the form below. Just specify the values you
* need and the rest will be given sensible defaults as shown:
*
* <pre>
* {
* minValue: undefined, // specify to clamp the lower y-axis to a given value
* maxValue: undefined, // specify to clamp the upper y-axis to a given value
* maxValueScale: 1, // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
* minValueScale: 1, // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
* yRangeFunction: undefined, // function({min: , max: }) { return {min: , max: }; }
* scaleSmoothing: 0.125, // controls the rate at which y-value zoom animation occurs
* millisPerPixel: 20, // sets the speed at which the chart pans by
* enableDpiScaling: true, // support rendering at different DPI depending on the device
* yMinFormatter: function(min, precision) { // callback function that formats the min y value label
* return parseFloat(min).toFixed(precision);
* },
* yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
* return parseFloat(max).toFixed(precision);
* },
* maxDataSetLength: 2,
* interpolation: 'bezier' // one of 'bezier', 'linear', or 'step'
* timestampFormatter: null, // optional function to format time stamps for bottom of chart
* // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
* scrollBackwards: false, // reverse the scroll direction of the chart
* horizontalLines: [], // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
* grid:
* {
* fillStyle: '#000000', // the background colour of the chart
* lineWidth: 1, // the pixel width of grid lines
* strokeStyle: '#777777', // colour of grid lines
* millisPerLine: 1000, // distance between vertical grid lines
* sharpLines: false, // controls whether grid lines are 1px sharp, or softened
* verticalSections: 2, // number of vertical sections marked out by horizontal grid lines
* borderVisible: true // whether the grid lines trace the border of the chart or not
* },
* labels
* {
* disabled: false, // enables/disables labels showing the min/max values
* fillStyle: '#ffffff', // colour for text of labels,
* fontSize: 15,
* fontFamily: 'sans-serif',
* precision: 2
* }
* }
* </pre>
*
* @constructor
*/
function SmoothieChart(options) {
this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options);
this.seriesSet = [];
this.currentValueRange = 1;
this.currentVisMinValue = 0;
this.lastRenderTimeMillis = 0;
}
SmoothieChart.defaultChartOptions = {
millisPerPixel: 20,
enableDpiScaling: true,
yMinFormatter: function(min, precision) {
return parseFloat(min).toFixed(precision);
},
yMaxFormatter: function(max, precision) {
return parseFloat(max).toFixed(precision);
},
maxValueScale: 1,
minValueScale: 1,
interpolation: 'bezier',
scaleSmoothing: 0.125,
maxDataSetLength: 2,
scrollBackwards: false,
grid: {
fillStyle: '#000000',
strokeStyle: '#777777',
lineWidth: 1,
sharpLines: false,
millisPerLine: 1000,
verticalSections: 2,
borderVisible: true
},
labels: {
fillStyle: '#ffffff',
disabled: false,
fontSize: 10,
fontFamily: 'monospace',
precision: 2
},
horizontalLines: []
};
// Based on http://inspirit.github.com/jsfeat/js/compatibility.js
SmoothieChart.AnimateCompatibility = (function() {
var requestAnimationFrame = function(callback, element) {
var requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(function() {
callback(new Date().getTime());
}, 16);
};
return requestAnimationFrame.call(window, callback, element);
},
cancelAnimationFrame = function(id) {
var cancelAnimationFrame =
window.cancelAnimationFrame ||
function(id) {
clearTimeout(id);
};
return cancelAnimationFrame.call(window, id);
};
return {
requestAnimationFrame: requestAnimationFrame,
cancelAnimationFrame: cancelAnimationFrame
};
})();
SmoothieChart.defaultSeriesPresentationOptions = {
lineWidth: 1,
strokeStyle: '#ffffff'
};
/**
* Adds a <code>TimeSeries</code> to this chart, with optional presentation options.
*
* Presentation options should be of the form (defaults shown):
*
* <pre>
* {
* lineWidth: 1,
* strokeStyle: '#ffffff',
* fillStyle: undefined
* }
* </pre>
*/
SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)});
if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) {
timeSeries.resetBoundsTimerId = setInterval(
function() {
timeSeries.resetBounds();
},
timeSeries.options.resetBoundsInterval
);
}
};
/**
* Removes the specified <code>TimeSeries</code> from the chart.
*/
SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
// Find the correct timeseries to remove, and remove it
var numSeries = this.seriesSet.length;
for (var i = 0; i < numSeries; i++) {
if (this.seriesSet[i].timeSeries === timeSeries) {
this.seriesSet.splice(i, 1);
break;
}
}
// If a timer was operating for that timeseries, remove it
if (timeSeries.resetBoundsTimerId) {
// Stop resetting the bounds, if we were
clearInterval(timeSeries.resetBoundsTimerId);
}
};
/**
* Gets render options for the specified <code>TimeSeries</code>.
*
* As you may use a single <code>TimeSeries</code> in multiple charts with different formatting in each usage,
* these settings are stored in the chart.
*/
SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) {
// Find the correct timeseries to remove, and remove it
var numSeries = this.seriesSet.length;
for (var i = 0; i < numSeries; i++) {
if (this.seriesSet[i].timeSeries === timeSeries) {
return this.seriesSet[i].options;
}
}
};
/**
* Brings the specified <code>TimeSeries</code> to the top of the chart. It will be rendered last.
*/
SmoothieChart.prototype.bringToFront = function(timeSeries) {
// Find the correct timeseries to remove, and remove it
var numSeries = this.seriesSet.length;
for (var i = 0; i < numSeries; i++) {
if (this.seriesSet[i].timeSeries === timeSeries) {
var set = this.seriesSet.splice(i, 1);
this.seriesSet.push(set[0]);
break;
}
}
};
/**
* Instructs the <code>SmoothieChart</code> to start rendering to the provided canvas, with specified delay.
*
* @param canvas the target canvas element
* @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series
* from appearing on screen, with new values flashing into view, at the expense of some latency.
*/
SmoothieChart.prototype.streamTo = function(canvas, delayMillis) {
this.canvas = canvas;
this.delay = delayMillis;
this.start();
};
/**
* Make sure the canvas has the optimal resolution for the device's pixel ratio.
*/
SmoothieChart.prototype.resize = function() {
// TODO this function doesn't handle the value of enableDpiScaling changing during execution
if (!this.options.enableDpiScaling || !window || window.devicePixelRatio === 1)
return;
var dpr = window.devicePixelRatio;
var width = parseInt(this.canvas.getAttribute('width'));
var height = parseInt(this.canvas.getAttribute('height'));
if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) {
this.originalWidth = width;
this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString());
this.canvas.style.width = width + 'px';
this.canvas.getContext('2d').scale(dpr, dpr);
}
if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) {
this.originalHeight = height;
this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString());
this.canvas.style.height = height + 'px';
this.canvas.getContext('2d').scale(dpr, dpr);
}
};
/**
* Starts the animation of this chart.
*/
SmoothieChart.prototype.start = function() {
if (this.frame) {
// We're already running, so just return
return;
}
// Renders a frame, and queues the next frame for later rendering
var animate = function() {
this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() {
this.render();
animate();
}.bind(this));
}.bind(this);
animate();
};
/**
* Stops the animation of this chart.
*/
SmoothieChart.prototype.stop = function() {
if (this.frame) {
SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame);
delete this.frame;
}
};
SmoothieChart.prototype.updateValueRange = function() {
// Calculate the current scale of the chart, from all time series.
var chartOptions = this.options,
chartMaxValue = Number.NaN,
chartMinValue = Number.NaN;
for (var d = 0; d < this.seriesSet.length; d++) {
// TODO(ndunn): We could calculate / track these values as they stream in.
var timeSeries = this.seriesSet[d].timeSeries;
if (!isNaN(timeSeries.maxValue)) {
chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue;
}
if (!isNaN(timeSeries.minValue)) {
chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue;
}
}
// Scale the chartMaxValue to add padding at the top if required
if (chartOptions.maxValue != null) {
chartMaxValue = chartOptions.maxValue;
} else {
chartMaxValue *= chartOptions.maxValueScale;
}
// Set the minimum if we've specified one
if (chartOptions.minValue != null) {
chartMinValue = chartOptions.minValue;
} else {
chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue);
}
// If a custom range function is set, call it
if (this.options.yRangeFunction) {
var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue});
chartMinValue = range.min;
chartMaxValue = range.max;
}
if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) {
var targetValueRange = chartMaxValue - chartMinValue;
var valueRangeDiff = (targetValueRange - this.currentValueRange);
var minValueDiff = (chartMinValue - this.currentVisMinValue);
this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1;
this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff;
this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff;
}
this.valueRange = { min: chartMinValue, max: chartMaxValue };
};
SmoothieChart.prototype.render = function(canvas, time) {
var nowMillis = new Date().getTime();
if (!this.isAnimatingScale) {
// We're not animating. We can use the last render time and the scroll speed to work out whether
// we actually need to paint anything yet. If not, we can return immediately.
// Render at least every 1/6th of a second. The canvas may be resized, which there is
// no reliable way to detect.
var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel);
if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) {
return;
}
}
this.resize();
this.lastRenderTimeMillis = nowMillis;
canvas = canvas || this.canvas;
time = time || nowMillis - (this.delay || 0);
// Round time down to pixel granularity, so motion appears smoother.
time -= time % this.options.millisPerPixel;
var context = canvas.getContext('2d'),
chartOptions = this.options,
dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight },
// Calculate the threshold time for the oldest data points.
oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel),
valueToYPixel = function(value) {
var offset = value - this.currentVisMinValue;
return this.currentValueRange === 0
? dimensions.height
: dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height));
}.bind(this),
timeToXPixel = function(t) {
if(chartOptions.scrollBackwards) {
return Math.round((time - t) / chartOptions.millisPerPixel);
}
return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel));
};
this.updateValueRange();
context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily;
// Save the state of the canvas context, any transformations applied in this method
// will get removed from the stack at the end of this method when .restore() is called.
context.save();
// Move the origin.
context.translate(dimensions.left, dimensions.top);
// Create a clipped rectangle - anything we draw will be constrained to this rectangle.
// This prevents the occasional pixels from curves near the edges overrunning and creating
// screen cheese (that phrase should need no explanation).
context.beginPath();
context.rect(0, 0, dimensions.width, dimensions.height);
context.clip();
// Clear the working area.
context.save();
context.fillStyle = chartOptions.grid.fillStyle;
context.clearRect(0, 0, dimensions.width, dimensions.height);
context.fillRect(0, 0, dimensions.width, dimensions.height);
context.restore();
// Grid lines...
context.save();
context.lineWidth = chartOptions.grid.lineWidth;
context.strokeStyle = chartOptions.grid.strokeStyle;
// Vertical (time) dividers.
if (chartOptions.grid.millisPerLine > 0) {
context.beginPath();
for (var t = time - (time % chartOptions.grid.millisPerLine);
t >= oldestValidTime;
t -= chartOptions.grid.millisPerLine) {
var gx = timeToXPixel(t);
if (chartOptions.grid.sharpLines) {
gx -= 0.5;
}
context.moveTo(gx, 0);
context.lineTo(gx, dimensions.height);
}
context.stroke();
context.closePath();
}
// Horizontal (value) dividers.
for (var v = 1; v < chartOptions.grid.verticalSections; v++) {
var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections);
if (chartOptions.grid.sharpLines) {
gy -= 0.5;
}
context.beginPath();
context.moveTo(0, gy);
context.lineTo(dimensions.width, gy);
context.stroke();
context.closePath();
}
// Bounding rectangle.
if (chartOptions.grid.borderVisible) {
context.beginPath();
context.strokeRect(0, 0, dimensions.width, dimensions.height);
context.closePath();
}
context.restore();
// Draw any horizontal lines...
if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) {
for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) {
var line = chartOptions.horizontalLines[hl],
hly = Math.round(valueToYPixel(line.value)) - 0.5;
context.strokeStyle = line.color || '#ffffff';
context.lineWidth = line.lineWidth || 1;
context.beginPath();
context.moveTo(0, hly);
context.lineTo(dimensions.width, hly);
context.stroke();
context.closePath();
}
}
// For each data set...
for (var d = 0; d < this.seriesSet.length; d++) {
context.save();
var timeSeries = this.seriesSet[d].timeSeries,
dataSet = timeSeries.data,
seriesOptions = this.seriesSet[d].options;
// Delete old data that's moved off the left of the chart.
timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength);
// Set style for this dataSet.
context.lineWidth = seriesOptions.lineWidth;
context.strokeStyle = seriesOptions.strokeStyle;
// Draw the line...
context.beginPath();
// Retain lastX, lastY for calculating the control points of bezier curves.
var firstX = 0, lastX = 0, lastY = 0;
for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) {
var x = timeToXPixel(dataSet[i][0]),
y = valueToYPixel(dataSet[i][1]);
if (i === 0) {
firstX = x;
context.moveTo(x, y);
} else {
switch (chartOptions.interpolation) {
case "linear":
case "line": {
context.lineTo(x,y);
break;
}
case "bezier":
default: {
// Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
//
// Assuming A was the last point in the line plotted and B is the new point,
// we draw a curve with control points P and Q as below.
//
// A---P
// |
// |
// |
// Q---B
//
// Importantly, A and P are at the same y coordinate, as are B and Q. This is
// so adjacent curves appear to flow as one.
//
context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
x, y); // endPoint (B)
break;
}
case "step": {
context.lineTo(x,lastY);
context.lineTo(x,y);
break;
}
}
}
lastX = x; lastY = y;
}
if (dataSet.length > 1) {
if (seriesOptions.fillStyle) {
// Close up the fill region.
context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
context.fillStyle = seriesOptions.fillStyle;
context.fill();
}
if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') {
context.stroke();
}
context.closePath();
}
context.restore();
}
// Draw the axis values on the chart.
if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) {
var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision),
minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision),
labelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2;
context.fillStyle = chartOptions.labels.fillStyle;
context.fillText(maxValueString, labelPos, chartOptions.labels.fontSize);
context.fillText(minValueString, labelPos, dimensions.height - 2);
}
// Display timestamps along x-axis at the bottom of the chart.
if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) {
var textUntilX = chartOptions.scrollBackwards
? context.measureText(minValueString).width
: dimensions.width - context.measureText(minValueString).width + 4;
for (var t = time - (time % chartOptions.grid.millisPerLine);
t >= oldestValidTime;
t -= chartOptions.grid.millisPerLine) {
var gx = timeToXPixel(t);
// Only draw the timestamp if it won't overlap with the previously drawn one.
if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) {
// Formats the timestamp based on user specified formatting function
// SmoothieChart.timeFormatter function above is one such formatting option
var tx = new Date(t),
ts = chartOptions.timestampFormatter(tx),
tsWidth = context.measureText(ts).width;
textUntilX = chartOptions.scrollBackwards
? gx + tsWidth + 2
: gx - tsWidth - 2;
context.fillStyle = chartOptions.labels.fillStyle;
if(chartOptions.scrollBackwards) {
context.fillText(ts, gx, dimensions.height - 2);
} else {
context.fillText(ts, gx - tsWidth, dimensions.height - 2);
}
}
}
}
context.restore(); // See .save() above.
};
// Sample timestamp formatting function
SmoothieChart.timeFormatter = function(date) {
function pad2(number) { return (number < 10 ? '0' : '') + number }
return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds());
};
exports.TimeSeries = TimeSeries;
exports.SmoothieChart = SmoothieChart;
})(typeof exports === 'undefined' ? this : exports);

View File

@ -0,0 +1,120 @@
cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileError = require('./FileError'),
DirectoryReader = require('./DirectoryReader');
/**
* An interface representing a directory on the file system.
*
* {boolean} isFile always false (readonly)
* {boolean} isDirectory always true (readonly)
* {DOMString} name of the directory, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the directory (readonly)
* {FileSystem} filesystem on which the directory resides (readonly)
*/
var DirectoryEntry = function(name, fullPath, fileSystem, nativeURL) {
// add trailing slash if it is missing
if ((fullPath) && !/\/$/.test(fullPath)) {
fullPath += "/";
}
// add trailing slash if it is missing
if (nativeURL && !/\/$/.test(nativeURL)) {
nativeURL += "/";
}
DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
};
utils.extend(DirectoryEntry, Entry);
/**
* Creates a new DirectoryReader to read entries from this directory
*/
DirectoryEntry.prototype.createReader = function() {
return new DirectoryReader(this.toInternalURL());
};
/**
* Creates or looks up a directory
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
* @param {Flags} options to create or exclusively create the directory
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getDirectory", [this.toInternalURL(), path, options]);
};
/**
* Deletes a directory and all of it's contents
*
* @param {Function} successCallback is called with no parameters
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "removeRecursively", [this.toInternalURL()]);
};
/**
* Creates or looks up a file
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
* @param {Flags} options to create or exclusively create the file
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var FileEntry = require('./FileEntry');
var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFile", [this.toInternalURL(), path, options]);
};
module.exports = DirectoryEntry;
});

View File

@ -0,0 +1,76 @@
cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError') ;
/**
* An interface that lists the files and directories in a directory.
*/
function DirectoryReader(localURL) {
this.localURL = localURL || null;
this.hasReadEntries = false;
}
/**
* Returns a list of entries from a directory.
*
* @param {Function} successCallback is called with a list of entries
* @param {Function} errorCallback is called with a FileError
*/
DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
// If we've already read and passed on this directory's entries, return an empty list.
if (this.hasReadEntries) {
successCallback([]);
return;
}
var reader = this;
var win = typeof successCallback !== 'function' ? null : function(result) {
var retVal = [];
for (var i=0; i<result.length; i++) {
var entry = null;
if (result[i].isDirectory) {
entry = new (require('./DirectoryEntry'))();
}
else if (result[i].isFile) {
entry = new (require('./FileEntry'))();
}
entry.isDirectory = result[i].isDirectory;
entry.isFile = result[i].isFile;
entry.name = result[i].name;
entry.fullPath = result[i].fullPath;
entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
entry.nativeURL = result[i].nativeURL;
retVal.push(entry);
}
reader.hasReadEntries = true;
successCallback(retVal);
};
var fail = typeof errorCallback !== 'function' ? null : function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "readEntries", [this.localURL]);
};
module.exports = DirectoryReader;
});

View File

@ -0,0 +1,265 @@
cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
exec = require('cordova/exec'),
FileError = require('./FileError'),
Metadata = require('./Metadata');
/**
* Represents a file or directory on the local file system.
*
* @param isFile
* {boolean} true if Entry is a file (readonly)
* @param isDirectory
* {boolean} true if Entry is a directory (readonly)
* @param name
* {DOMString} name of the file or directory, excluding the path
* leading to it (readonly)
* @param fullPath
* {DOMString} the absolute full path to the file or directory
* (readonly)
* @param fileSystem
* {FileSystem} the filesystem on which this entry resides
* (readonly)
* @param nativeURL
* {DOMString} an alternate URL which can be used by native
* webview controls, for example media players.
* (optional, readonly)
*/
function Entry(isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
this.isFile = !!isFile;
this.isDirectory = !!isDirectory;
this.name = name || '';
this.fullPath = fullPath || '';
this.filesystem = fileSystem || null;
this.nativeURL = nativeURL || null;
}
/**
* Look up the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
*/
Entry.prototype.getMetadata = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
var success = successCallback && function(entryMetadata) {
var metadata = new Metadata({
size: entryMetadata.size,
modificationTime: entryMetadata.lastModifiedDate
});
successCallback(metadata);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(success, fail, "File", "getFileMetadata", [this.toInternalURL()]);
};
/**
* Set the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
* @param metadataObject
* {Object} keys and values to set
*/
Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) {
argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
exec(successCallback, errorCallback, "File", "setMetadata", [this.toInternalURL(), metadataObject]);
};
/**
* Move a file or directory to a new location.
*
* @param parent
* {DirectoryEntry} the directory to which to move this entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new DirectoryEntry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "moveTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Copy a directory to a different location.
*
* @param parent
* {DirectoryEntry} the directory to which to copy the entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new Entry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
// success callback
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "copyTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Return a URL that can be passed across the bridge to identify this entry.
*/
Entry.prototype.toInternalURL = function() {
if (this.filesystem && this.filesystem.__format__) {
return this.filesystem.__format__(this.fullPath, this.nativeURL);
}
};
/**
* Return a URL that can be used to identify this entry.
* Use a URL that can be used to as the src attribute of a <video> or
* <audio> tag. If that is not possible, construct a cdvfile:// URL.
*/
Entry.prototype.toURL = function() {
if (this.nativeURL) {
return this.nativeURL;
}
// fullPath attribute may contain the full URL in the case that
// toInternalURL fails.
return this.toInternalURL() || "file://localhost" + this.fullPath;
};
/**
* Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
* cdvfile:// URL, and this method was necessary to obtain URLs usable by the
* webview.
* See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
* and CB-6300.
*/
Entry.prototype.toNativeURL = function() {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Returns a URI that can be used to identify this entry.
*
* @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
* @return uri
*/
Entry.prototype.toURI = function(mimeType) {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Remove a file or directory. It is an error to attempt to delete a
* directory that is not empty. It is an error to attempt to delete a
* root directory of a file system.
*
* @param successCallback {Function} called with no parameters
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.remove = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.remove', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "remove", [this.toInternalURL()]);
};
/**
* Look up the parent DirectoryEntry of this entry.
*
* @param successCallback {Function} called with the parent DirectoryEntry object
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.getParent = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getParent', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var DirectoryEntry = require('./DirectoryEntry');
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getParent", [this.toInternalURL()]);
};
module.exports = Entry;
});

View File

@ -0,0 +1,82 @@
cordova.define("cordova-plugin-file.File", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Constructor.
* name {DOMString} name of the file, without path information
* fullPath {DOMString} the full path of the file, including the name
* type {DOMString} mime type
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
var File = function(name, localURL, type, lastModifiedDate, size){
this.name = name || '';
this.localURL = localURL || null;
this.type = type || null;
this.lastModified = lastModifiedDate || null;
// For backwards compatibility, store the timestamp in lastModifiedDate as well
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
// These store the absolute start and end for slicing the file.
this.start = 0;
this.end = this.size;
};
/**
* Returns a "slice" of the file. Since Cordova Files don't contain the actual
* content, this really returns a File with adjusted start and end.
* Slices of slices are supported.
* start {Number} The index at which to start the slice (inclusive).
* end {Number} The index at which to end the slice (exclusive).
*/
File.prototype.slice = function(start, end) {
var size = this.end - this.start;
var newStart = 0;
var newEnd = size;
if (arguments.length) {
if (start < 0) {
newStart = Math.max(size + start, 0);
} else {
newStart = Math.min(size, start);
}
}
if (arguments.length >= 2) {
if (end < 0) {
newEnd = Math.max(size + end, 0);
} else {
newEnd = Math.min(end, size);
}
}
var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
newFile.start = this.start + newStart;
newFile.end = this.start + newEnd;
return newFile;
};
module.exports = File;
});

View File

@ -0,0 +1,96 @@
cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileWriter = require('./FileWriter'),
File = require('./File'),
FileError = require('./FileError');
/**
* An interface representing a file on the file system.
*
* {boolean} isFile always true (readonly)
* {boolean} isDirectory always false (readonly)
* {DOMString} name of the file, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the file (readonly)
* {FileSystem} filesystem on which the file resides (readonly)
*/
var FileEntry = function(name, fullPath, fileSystem, nativeURL) {
// remove trailing slash if it is present
if (fullPath && /\/$/.test(fullPath)) {
fullPath = fullPath.substring(0, fullPath.length - 1);
}
if (nativeURL && /\/$/.test(nativeURL)) {
nativeURL = nativeURL.substring(0, nativeURL.length - 1);
}
FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
};
utils.extend(FileEntry, Entry);
/**
* Creates a new FileWriter associated with the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new FileWriter
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.createWriter = function(successCallback, errorCallback) {
this.file(function(filePointer) {
var writer = new FileWriter(filePointer);
if (writer.localURL === null || writer.localURL === "") {
if (errorCallback) {
errorCallback(new FileError(FileError.INVALID_STATE_ERR));
}
} else {
if (successCallback) {
successCallback(writer);
}
}
}, errorCallback);
};
/**
* Returns a File that represents the current state of the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new File object
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.file = function(successCallback, errorCallback) {
var localURL = this.toInternalURL();
var win = successCallback && function(f) {
var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
successCallback(file);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFileMetadata", [localURL]);
};
module.exports = FileEntry;
});

View File

@ -0,0 +1,49 @@
cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileError
*/
function FileError(error) {
this.code = error || null;
}
// File error codes
// Found in DOMException
FileError.NOT_FOUND_ERR = 1;
FileError.SECURITY_ERR = 2;
FileError.ABORT_ERR = 3;
// Added by File API specification
FileError.NOT_READABLE_ERR = 4;
FileError.ENCODING_ERR = 5;
FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
FileError.INVALID_STATE_ERR = 7;
FileError.SYNTAX_ERR = 8;
FileError.INVALID_MODIFICATION_ERR = 9;
FileError.QUOTA_EXCEEDED_ERR = 10;
FileError.TYPE_MISMATCH_ERR = 11;
FileError.PATH_EXISTS_ERR = 12;
module.exports = FileError;
});

View File

@ -0,0 +1,292 @@
cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper'),
utils = require('cordova/utils'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent'),
origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
/**
* This class reads the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To read from the SD card, the file name is "sdcard/my_file.txt"
* @constructor
*/
var FileReader = function() {
this._readyState = 0;
this._error = null;
this._result = null;
this._progress = null;
this._localURL = '';
this._realReader = origFileReader ? new origFileReader() : {};
};
/**
* Defines the maximum size to read at a time via the native API. The default value is a compromise between
* minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
* (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
* OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
*/
FileReader.READ_CHUNK_SIZE = 256*1024;
// States
FileReader.EMPTY = 0;
FileReader.LOADING = 1;
FileReader.DONE = 2;
utils.defineGetter(FileReader.prototype, 'readyState', function() {
return this._localURL ? this._readyState : this._realReader.readyState;
});
utils.defineGetter(FileReader.prototype, 'error', function() {
return this._localURL ? this._error: this._realReader.error;
});
utils.defineGetter(FileReader.prototype, 'result', function() {
return this._localURL ? this._result: this._realReader.result;
});
function defineEvent(eventName) {
utils.defineGetterSetter(FileReader.prototype, eventName, function() {
return this._realReader[eventName] || null;
}, function(value) {
this._realReader[eventName] = value;
});
}
defineEvent('onloadstart'); // When the read starts.
defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
defineEvent('onload'); // When the read has successfully completed.
defineEvent('onerror'); // When the read has failed (see errors).
defineEvent('onloadend'); // When the request has completed (either in success or failure).
defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method.
function initRead(reader, file) {
// Already loading something
if (reader.readyState == FileReader.LOADING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
reader._result = null;
reader._error = null;
reader._progress = 0;
reader._readyState = FileReader.LOADING;
if (typeof file.localURL == 'string') {
reader._localURL = file.localURL;
} else {
reader._localURL = '';
return true;
}
if (reader.onloadstart) {
reader.onloadstart(new ProgressEvent("loadstart", {target:reader}));
}
}
/**
* Callback used by the following read* functions to handle incremental or final success.
* Must be bound to the FileReader's this along with all but the last parameter,
* e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
* @param readType The name of the read function to call.
* @param encoding Text encoding, or null if this is not a text type read.
* @param offset Starting offset of the read.
* @param totalSize Total number of bytes or chars to read.
* @param accumulate A function that takes the callback result and accumulates it in this._result.
* @param r Callback result returned by the last read exec() call, or null to begin reading.
*/
function readSuccessCallback(readType, encoding, offset, totalSize, accumulate, r) {
if (this._readyState === FileReader.DONE) {
return;
}
if (typeof r !== "undefined") {
accumulate(r);
this._progress = Math.min(this._progress + FileReader.READ_CHUNK_SIZE, totalSize);
if (typeof this.onprogress === "function") {
this.onprogress(new ProgressEvent("progress", {loaded:this._progress, total:totalSize}));
}
}
if (typeof r === "undefined" || this._progress < totalSize) {
var execArgs = [
this._localURL,
offset + this._progress,
offset + this._progress + Math.min(totalSize - this._progress, FileReader.READ_CHUNK_SIZE)];
if (encoding) {
execArgs.splice(1, 0, encoding);
}
exec(
readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
readFailureCallback.bind(this),
"File", readType, execArgs);
} else {
this._readyState = FileReader.DONE;
if (typeof this.onload === "function") {
this.onload(new ProgressEvent("load", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
}
/**
* Callback used by the following read* functions to handle errors.
* Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
*/
function readFailureCallback(e) {
if (this._readyState === FileReader.DONE) {
return;
}
this._readyState = FileReader.DONE;
this._result = null;
this._error = new FileError(e);
if (typeof this.onerror === "function") {
this.onerror(new ProgressEvent("error", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
/**
* Abort reading file.
*/
FileReader.prototype.abort = function() {
if (origFileReader && !this._localURL) {
return this._realReader.abort();
}
this._result = null;
if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) {
return;
}
this._readyState = FileReader.DONE;
// If abort callback
if (typeof this.onabort === 'function') {
this.onabort(new ProgressEvent('abort', {target:this}));
}
// If load end callback
if (typeof this.onloadend === 'function') {
this.onloadend(new ProgressEvent('loadend', {target:this}));
}
};
/**
* Read text file.
*
* @param file {File} File object containing file properties
* @param encoding [Optional] (see http://www.iana.org/assignments/character-sets)
*/
FileReader.prototype.readAsText = function(file, encoding) {
if (initRead(this, file)) {
return this._realReader.readAsText(file, encoding);
}
// Default encoding is UTF-8
var enc = encoding ? encoding : "UTF-8";
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsText", enc, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a base64 encoded data url.
* A data url is of the form:
* data:[<mediatype>][;base64],<data>
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsDataURL = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsDataURL(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsDataURL", null, file.start, totalSize, function(r) {
var commaIndex = r.indexOf(',');
if (this._progress === 0) {
this._result = r;
} else {
this._result += r.substring(commaIndex + 1);
}
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsBinaryString = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsBinaryString(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsBinaryString", null, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsArrayBuffer = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsArrayBuffer(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsArrayBuffer", null, file.start, totalSize, function(r) {
var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
resultArray.set(new Uint8Array(r), this._progress);
this._result = resultArray.buffer;
}.bind(this));
};
module.exports = FileReader;
});

View File

@ -0,0 +1,58 @@
cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var DirectoryEntry = require('./DirectoryEntry');
/**
* An interface representing a file system
*
* @constructor
* {DOMString} name the unique name of the file system (readonly)
* {DirectoryEntry} root directory of the file system (readonly)
*/
var FileSystem = function(name, root) {
this.name = name;
if (root) {
this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
} else {
this.root = new DirectoryEntry(this.name, '/', this);
}
};
FileSystem.prototype.__format__ = function(fullPath, nativeUrl) {
return fullPath;
};
FileSystem.prototype.toJSON = function() {
return "<FileSystem: " + this.name + ">";
};
// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
FileSystem.encodeURIPath = function(path) {
// Because # is a valid filename character, it must be encoded to prevent part of the
// path from being parsed as a URI fragment.
return encodeURI(path).replace(/#/g, '%23');
};
module.exports = FileSystem;
});

View File

@ -0,0 +1,44 @@
cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Options to customize the HTTP request used to upload files.
* @constructor
* @param fileKey {String} Name of file request parameter.
* @param fileName {String} Filename to be used by the server. Defaults to image.jpg.
* @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg.
* @param params {Object} Object with key: value params to send to the server.
* @param headers {Object} Keys are header names, values are header values. Multiple
* headers of the same name are not supported.
*/
var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
this.headers = headers || null;
this.httpMethod = httpMethod || null;
};
module.exports = FileUploadOptions;
});

View File

@ -0,0 +1,32 @@
cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileUploadResult
* @constructor
*/
module.exports = function FileUploadResult(size, code, content) {
this.bytesSent = size;
this.responseCode = code;
this.response = content;
};
});

View File

@ -0,0 +1,327 @@
cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent');
/**
* This class writes to the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To write to the SD card, the file name is "sdcard/my_file.txt"
*
* @constructor
* @param file {File} File object containing file properties
* @param append if true write to the end of the file, otherwise overwrite the file
*/
var FileWriter = function(file) {
this.fileName = "";
this.length = 0;
if (file) {
this.localURL = file.localURL || file;
this.length = file.size || 0;
}
// default is to write at the beginning of the file
this.position = 0;
this.readyState = 0; // EMPTY
this.result = null;
// Error
this.error = null;
// Event handlers
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
// States
FileWriter.INIT = 0;
FileWriter.WRITING = 1;
FileWriter.DONE = 2;
/**
* Abort writing file.
*/
FileWriter.prototype.abort = function() {
// check for invalid state
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// set error
this.error = new FileError(FileError.ABORT_ERR);
this.readyState = FileWriter.DONE;
// If abort callback
if (typeof this.onabort === "function") {
this.onabort(new ProgressEvent("abort", {"target":this}));
}
// If write end callback
if (typeof this.onwriteend === "function") {
this.onwriteend(new ProgressEvent("writeend", {"target":this}));
}
};
/**
* Writes data to the file
*
* @param data text or blob to be written
* @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
*/
FileWriter.prototype.write = function(data, isPendingBlobReadResult) {
var that=this;
var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
var isProxySupportBlobNatively = (cordova.platformId === "windows8" || cordova.platformId === "windows");
var isBinary;
// Check to see if the incoming data is a blob
if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
var fileReader = new FileReader();
fileReader.onload = function() {
// Call this method again, with the arraybuffer as argument
FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
};
fileReader.onerror = function () {
// DONE state
that.readyState = FileWriter.DONE;
// Save error
that.error = this.error;
// If onerror callback
if (typeof that.onerror === "function") {
that.onerror(new ProgressEvent("error", {"target":that}));
}
// If onwriteend callback
if (typeof that.onwriteend === "function") {
that.onwriteend(new ProgressEvent("writeend", {"target":that}));
}
};
// WRITING state
this.readyState = FileWriter.WRITING;
if (supportsBinary) {
fileReader.readAsArrayBuffer(data);
} else {
fileReader.readAsText(data);
}
return;
}
// Mark data type for safer transport over the binary bridge
isBinary = supportsBinary && (data instanceof ArrayBuffer);
if (isBinary && cordova.platformId === "windowsphone") {
// create a plain array, using the keys from the Uint8Array view so that we can serialize it
data = Array.apply(null, new Uint8Array(data));
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":me}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// position always increases by bytes written because file would be extended
me.position += r;
// The length of the file is now where we are done writing.
me.length = me.position;
// DONE state
me.readyState = FileWriter.DONE;
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "write", [this.localURL, data, this.position, isBinary]);
};
/**
* Moves the file pointer to the location specified.
*
* If the offset is a negative number the position of the file
* pointer is rewound. If the offset is greater than the file
* size the position is set to the end of the file.
*
* @param offset is the location to move the file pointer to.
*/
FileWriter.prototype.seek = function(offset) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
if (!offset && offset !== 0) {
return;
}
// See back from end of file.
if (offset < 0) {
this.position = Math.max(offset + this.length, 0);
}
// Offset is bigger than file size so set position
// to the end of the file.
else if (offset > this.length) {
this.position = this.length;
}
// Offset is between 0 and file size so set the position
// to start writing.
else {
this.position = offset;
}
};
/**
* Truncates the file to the size specified.
*
* @param size to chop the file at.
*/
FileWriter.prototype.truncate = function(size) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":this}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Update the length of the file
me.length = r;
me.position = Math.min(me.position, r);
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "truncate", [this.localURL, size]);
};
module.exports = FileWriter;
});

View File

@ -0,0 +1,39 @@
cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Supplies arguments to methods that lookup or create files and directories.
*
* @param create
* {boolean} file or directory if it doesn't exist
* @param exclusive
* {boolean} used with create; if true the command will fail if
* target path exists
*/
function Flags(create, exclusive) {
this.create = create || false;
this.exclusive = exclusive || false;
}
module.exports = Flags;
});

View File

@ -0,0 +1,26 @@
cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
exports.TEMPORARY = 0;
exports.PERSISTENT = 1;
});

View File

@ -0,0 +1,43 @@
cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Information about the state of the file or directory
*
* {Date} modificationTime (readonly)
*/
var Metadata = function(metadata) {
if (typeof metadata == "object") {
this.modificationTime = new Date(metadata.modificationTime);
this.size = metadata.size || 0;
} else if (typeof metadata == "undefined") {
this.modificationTime = null;
this.size = 0;
} else {
/* Backwards compatiblity with platforms that only return a timestamp */
this.modificationTime = new Date(metadata);
}
};
module.exports = Metadata;
});

View File

@ -0,0 +1,70 @@
cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
// Feature test: See if we can instantiate a native ProgressEvent;
// if so, use that approach,
// otherwise fill-in with our own implementation.
//
// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
var ProgressEvent = (function() {
/*
var createEvent = function(data) {
var event = document.createEvent('Events');
event.initEvent('ProgressEvent', false, false);
if (data) {
for (var i in data) {
if (data.hasOwnProperty(i)) {
event[i] = data[i];
}
}
if (data.target) {
// TODO: cannot call <some_custom_object>.dispatchEvent
// need to first figure out how to implement EventTarget
}
}
return event;
};
try {
var ev = createEvent({type:"abort",target:document});
return function ProgressEvent(type, data) {
data.type = type;
return createEvent(data);
};
} catch(e){
*/
return function ProgressEvent(type, dict) {
this.type = type;
this.bubbles = false;
this.cancelBubble = false;
this.cancelable = false;
this.lengthComputable = false;
this.loaded = dict && dict.loaded ? dict.loaded : 0;
this.total = dict && dict.total ? dict.total : 0;
this.target = dict && dict.target ? dict.target : null;
};
//}
})();
module.exports = ProgressEvent;
});

View File

@ -0,0 +1,52 @@
cordova.define("cordova-plugin-file.androidFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
FILESYSTEM_PROTOCOL = "cdvfile";
module.exports = {
__format__: function(fullPath, nativeUrl) {
var path;
var contentUrlMatch = /^content:\/\//.exec(nativeUrl);
if (contentUrlMatch) {
// When available, use the path from a native content URL, which was already encoded by Android.
// This is necessary because JavaScript's encodeURI() does not encode as many characters as
// Android, which can result in permission exceptions when the encoding of a content URI
// doesn't match the string for which permission was originally granted.
path = nativeUrl.substring(contentUrlMatch[0].length - 1);
} else {
path = FileSystem.encodeURIPath(fullPath);
if (!/^\//.test(path)) {
path = '/' + path;
}
var m = /\?.*/.exec(nativeUrl);
if (m) {
path += m[0];
}
}
return FILESYSTEM_PROTOCOL + '://localhost/' + this.name + path;
}
};
});

View File

@ -0,0 +1,29 @@
cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
module.exports = function () {
// window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
// possibly a good flag to indicate that we're running in Chrome
return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
};
});

View File

@ -0,0 +1,66 @@
cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var channel = require('cordova/channel');
exports.file = {
// Read-only directory where the application is installed.
applicationDirectory: null,
// Root of app's private writable storage
applicationStorageDirectory: null,
// Where to put app-specific data files.
dataDirectory: null,
// Cached files that should survive app restarts.
// Apps should not rely on the OS to delete files in here.
cacheDirectory: null,
// Android: the application space on external storage.
externalApplicationStorageDirectory: null,
// Android: Where to put app-specific data files on external storage.
externalDataDirectory: null,
// Android: the application cache on external storage.
externalCacheDirectory: null,
// Android: the external storage (SD card) root.
externalRootDirectory: null,
// iOS: Temp directory that the OS can clear at will.
tempDirectory: null,
// iOS: Holds app-specific files that should be synced (e.g. to iCloud).
syncedDataDirectory: null,
// iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
documentsDirectory: null,
// BlackBerry10: Files globally available to all apps
sharedDirectory: null
};
channel.waitForInitialization('onFileSystemPathsReady');
channel.onCordovaReady.subscribe(function() {
function after(paths) {
for (var k in paths) {
exports.file[k] = paths[k];
}
channel.initializationComplete('onFileSystemPathsReady');
}
exec(after, null, 'File', 'requestAllPaths', []);
});
});

View File

@ -0,0 +1,48 @@
cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Map of fsName -> FileSystem.
var fsMap = null;
var FileSystem = require('./FileSystem');
var exec = require('cordova/exec');
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
require('./fileSystems').getFs = function(name, callback) {
function success(response) {
fsMap = {};
for (var i = 0; i < response.length; ++i) {
var fsRoot = response[i];
var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
fsMap[fs.name] = fs;
}
callback(fsMap[name]);
}
if (fsMap) {
callback(fsMap[name]);
} else {
exec(success, null, "File", "requestAllFileSystems", []);
}
};
});

View File

@ -0,0 +1,28 @@
cordova.define("cordova-plugin-file.fileSystems", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
module.exports.getFs = function(name, callback) {
callback(null);
};
});

View File

@ -0,0 +1,85 @@
cordova.define("cordova-plugin-file.requestFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
(function() {
//For browser platform: not all browsers use this file.
function checkBrowser() {
if (cordova.platformId === "browser" && require('./isChrome')()) {
module.exports = window.requestFileSystem || window.webkitRequestFileSystem;
return true;
}
return false;
}
if (checkBrowser()) {
return;
}
var argscheck = require('cordova/argscheck'),
FileError = require('./FileError'),
FileSystem = require('./FileSystem'),
exec = require('cordova/exec');
var fileSystems = require('./fileSystems');
/**
* Request a file system in which to store application data.
* @param type local file system type
* @param size indicates how much storage space, in bytes, the application expects to need
* @param successCallback invoked with a FileSystem object
* @param errorCallback invoked if error occurs retrieving file system
*/
var requestFileSystem = function(type, size, successCallback, errorCallback) {
argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
var fail = function(code) {
if (errorCallback) {
errorCallback(new FileError(code));
}
};
if (type < 0) {
fail(FileError.SYNTAX_ERR);
} else {
// if successful, return a FileSystem object
var success = function(file_system) {
if (file_system) {
if (successCallback) {
fileSystems.getFs(file_system.name, function(fs) {
// This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
if (!fs) {
fs = new FileSystem(file_system.name, file_system.root);
}
successCallback(fs);
});
}
}
else {
// no FileSystem object returned
fail(FileError.NOT_FOUND_ERR);
}
};
exec(success, fail, "File", "requestFileSystem", [type, size]);
}
};
module.exports = requestFileSystem;
})();
});

View File

@ -0,0 +1,95 @@
cordova.define("cordova-plugin-file.resolveLocalFileSystemURI", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
(function() {
//For browser platform: not all browsers use overrided `resolveLocalFileSystemURL`.
function checkBrowser() {
if (cordova.platformId === "browser" && require('./isChrome')()) {
module.exports.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL;
return true;
}
return false;
}
if (checkBrowser()) {
return;
}
var argscheck = require('cordova/argscheck'),
DirectoryEntry = require('./DirectoryEntry'),
FileEntry = require('./FileEntry'),
FileError = require('./FileError'),
exec = require('cordova/exec');
var fileSystems = require('./fileSystems');
/**
* Look up file system Entry referred to by local URI.
* @param {DOMString} uri URI referring to a local file or directory
* @param successCallback invoked with Entry object corresponding to URI
* @param errorCallback invoked if error occurs retrieving file system entry
*/
module.exports.resolveLocalFileSystemURL = module.exports.resolveLocalFileSystemURL || function(uri, successCallback, errorCallback) {
argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments);
// error callback
var fail = function(error) {
if (errorCallback) {
errorCallback(new FileError(error));
}
};
// sanity check for 'not:valid:filename' or '/not:valid:filename'
// file.spec.12 window.resolveLocalFileSystemURI should error (ENCODING_ERR) when resolving invalid URI with leading /.
if(!uri || uri.split(":").length > 2) {
setTimeout( function() {
fail(FileError.ENCODING_ERR);
},0);
return;
}
// if successful, return either a file or directory entry
var success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var fsName = entry.filesystemName || (entry.filesystem && entry.filesystem.name) || (entry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary');
fileSystems.getFs(fsName, function(fs) {
// This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
if (!fs) {
fs = new FileSystem(fsName, {name:"", fullPath:"/"});
}
var result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath, fs, entry.nativeURL) : new FileEntry(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
});
}
}
else {
// no Entry object returned
fail(FileError.NOT_FOUND_ERR);
}
};
exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]);
};
module.exports.resolveLocalFileSystemURI = function() {
console.log("resolveLocalFileSystemURI is deprecated. Please call resolveLocalFileSystemURL instead.");
module.exports.resolveLocalFileSystemURL.apply(this, arguments);
};
})();
});

View File

@ -0,0 +1,120 @@
cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileError = require('./FileError'),
DirectoryReader = require('./DirectoryReader');
/**
* An interface representing a directory on the file system.
*
* {boolean} isFile always false (readonly)
* {boolean} isDirectory always true (readonly)
* {DOMString} name of the directory, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the directory (readonly)
* {FileSystem} filesystem on which the directory resides (readonly)
*/
var DirectoryEntry = function(name, fullPath, fileSystem, nativeURL) {
// add trailing slash if it is missing
if ((fullPath) && !/\/$/.test(fullPath)) {
fullPath += "/";
}
// add trailing slash if it is missing
if (nativeURL && !/\/$/.test(nativeURL)) {
nativeURL += "/";
}
DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
};
utils.extend(DirectoryEntry, Entry);
/**
* Creates a new DirectoryReader to read entries from this directory
*/
DirectoryEntry.prototype.createReader = function() {
return new DirectoryReader(this.toInternalURL());
};
/**
* Creates or looks up a directory
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
* @param {Flags} options to create or exclusively create the directory
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getDirectory", [this.toInternalURL(), path, options]);
};
/**
* Deletes a directory and all of it's contents
*
* @param {Function} successCallback is called with no parameters
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "removeRecursively", [this.toInternalURL()]);
};
/**
* Creates or looks up a file
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
* @param {Flags} options to create or exclusively create the file
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var FileEntry = require('./FileEntry');
var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFile", [this.toInternalURL(), path, options]);
};
module.exports = DirectoryEntry;
});

View File

@ -0,0 +1,76 @@
cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError') ;
/**
* An interface that lists the files and directories in a directory.
*/
function DirectoryReader(localURL) {
this.localURL = localURL || null;
this.hasReadEntries = false;
}
/**
* Returns a list of entries from a directory.
*
* @param {Function} successCallback is called with a list of entries
* @param {Function} errorCallback is called with a FileError
*/
DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
// If we've already read and passed on this directory's entries, return an empty list.
if (this.hasReadEntries) {
successCallback([]);
return;
}
var reader = this;
var win = typeof successCallback !== 'function' ? null : function(result) {
var retVal = [];
for (var i=0; i<result.length; i++) {
var entry = null;
if (result[i].isDirectory) {
entry = new (require('./DirectoryEntry'))();
}
else if (result[i].isFile) {
entry = new (require('./FileEntry'))();
}
entry.isDirectory = result[i].isDirectory;
entry.isFile = result[i].isFile;
entry.name = result[i].name;
entry.fullPath = result[i].fullPath;
entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
entry.nativeURL = result[i].nativeURL;
retVal.push(entry);
}
reader.hasReadEntries = true;
successCallback(retVal);
};
var fail = typeof errorCallback !== 'function' ? null : function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "readEntries", [this.localURL]);
};
module.exports = DirectoryReader;
});

View File

@ -0,0 +1,265 @@
cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
exec = require('cordova/exec'),
FileError = require('./FileError'),
Metadata = require('./Metadata');
/**
* Represents a file or directory on the local file system.
*
* @param isFile
* {boolean} true if Entry is a file (readonly)
* @param isDirectory
* {boolean} true if Entry is a directory (readonly)
* @param name
* {DOMString} name of the file or directory, excluding the path
* leading to it (readonly)
* @param fullPath
* {DOMString} the absolute full path to the file or directory
* (readonly)
* @param fileSystem
* {FileSystem} the filesystem on which this entry resides
* (readonly)
* @param nativeURL
* {DOMString} an alternate URL which can be used by native
* webview controls, for example media players.
* (optional, readonly)
*/
function Entry(isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
this.isFile = !!isFile;
this.isDirectory = !!isDirectory;
this.name = name || '';
this.fullPath = fullPath || '';
this.filesystem = fileSystem || null;
this.nativeURL = nativeURL || null;
}
/**
* Look up the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
*/
Entry.prototype.getMetadata = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
var success = successCallback && function(entryMetadata) {
var metadata = new Metadata({
size: entryMetadata.size,
modificationTime: entryMetadata.lastModifiedDate
});
successCallback(metadata);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(success, fail, "File", "getFileMetadata", [this.toInternalURL()]);
};
/**
* Set the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
* @param metadataObject
* {Object} keys and values to set
*/
Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) {
argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
exec(successCallback, errorCallback, "File", "setMetadata", [this.toInternalURL(), metadataObject]);
};
/**
* Move a file or directory to a new location.
*
* @param parent
* {DirectoryEntry} the directory to which to move this entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new DirectoryEntry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "moveTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Copy a directory to a different location.
*
* @param parent
* {DirectoryEntry} the directory to which to copy the entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new Entry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
// success callback
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "copyTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Return a URL that can be passed across the bridge to identify this entry.
*/
Entry.prototype.toInternalURL = function() {
if (this.filesystem && this.filesystem.__format__) {
return this.filesystem.__format__(this.fullPath, this.nativeURL);
}
};
/**
* Return a URL that can be used to identify this entry.
* Use a URL that can be used to as the src attribute of a <video> or
* <audio> tag. If that is not possible, construct a cdvfile:// URL.
*/
Entry.prototype.toURL = function() {
if (this.nativeURL) {
return this.nativeURL;
}
// fullPath attribute may contain the full URL in the case that
// toInternalURL fails.
return this.toInternalURL() || "file://localhost" + this.fullPath;
};
/**
* Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
* cdvfile:// URL, and this method was necessary to obtain URLs usable by the
* webview.
* See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
* and CB-6300.
*/
Entry.prototype.toNativeURL = function() {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Returns a URI that can be used to identify this entry.
*
* @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
* @return uri
*/
Entry.prototype.toURI = function(mimeType) {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Remove a file or directory. It is an error to attempt to delete a
* directory that is not empty. It is an error to attempt to delete a
* root directory of a file system.
*
* @param successCallback {Function} called with no parameters
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.remove = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.remove', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "remove", [this.toInternalURL()]);
};
/**
* Look up the parent DirectoryEntry of this entry.
*
* @param successCallback {Function} called with the parent DirectoryEntry object
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.getParent = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getParent', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var DirectoryEntry = require('./DirectoryEntry');
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getParent", [this.toInternalURL()]);
};
module.exports = Entry;
});

View File

@ -0,0 +1,82 @@
cordova.define("cordova-plugin-file.File", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Constructor.
* name {DOMString} name of the file, without path information
* fullPath {DOMString} the full path of the file, including the name
* type {DOMString} mime type
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
var File = function(name, localURL, type, lastModifiedDate, size){
this.name = name || '';
this.localURL = localURL || null;
this.type = type || null;
this.lastModified = lastModifiedDate || null;
// For backwards compatibility, store the timestamp in lastModifiedDate as well
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
// These store the absolute start and end for slicing the file.
this.start = 0;
this.end = this.size;
};
/**
* Returns a "slice" of the file. Since Cordova Files don't contain the actual
* content, this really returns a File with adjusted start and end.
* Slices of slices are supported.
* start {Number} The index at which to start the slice (inclusive).
* end {Number} The index at which to end the slice (exclusive).
*/
File.prototype.slice = function(start, end) {
var size = this.end - this.start;
var newStart = 0;
var newEnd = size;
if (arguments.length) {
if (start < 0) {
newStart = Math.max(size + start, 0);
} else {
newStart = Math.min(size, start);
}
}
if (arguments.length >= 2) {
if (end < 0) {
newEnd = Math.max(size + end, 0);
} else {
newEnd = Math.min(end, size);
}
}
var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
newFile.start = this.start + newStart;
newFile.end = this.start + newEnd;
return newFile;
};
module.exports = File;
});

View File

@ -0,0 +1,96 @@
cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileWriter = require('./FileWriter'),
File = require('./File'),
FileError = require('./FileError');
/**
* An interface representing a file on the file system.
*
* {boolean} isFile always true (readonly)
* {boolean} isDirectory always false (readonly)
* {DOMString} name of the file, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the file (readonly)
* {FileSystem} filesystem on which the file resides (readonly)
*/
var FileEntry = function(name, fullPath, fileSystem, nativeURL) {
// remove trailing slash if it is present
if (fullPath && /\/$/.test(fullPath)) {
fullPath = fullPath.substring(0, fullPath.length - 1);
}
if (nativeURL && /\/$/.test(nativeURL)) {
nativeURL = nativeURL.substring(0, nativeURL.length - 1);
}
FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
};
utils.extend(FileEntry, Entry);
/**
* Creates a new FileWriter associated with the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new FileWriter
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.createWriter = function(successCallback, errorCallback) {
this.file(function(filePointer) {
var writer = new FileWriter(filePointer);
if (writer.localURL === null || writer.localURL === "") {
if (errorCallback) {
errorCallback(new FileError(FileError.INVALID_STATE_ERR));
}
} else {
if (successCallback) {
successCallback(writer);
}
}
}, errorCallback);
};
/**
* Returns a File that represents the current state of the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new File object
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.file = function(successCallback, errorCallback) {
var localURL = this.toInternalURL();
var win = successCallback && function(f) {
var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
successCallback(file);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFileMetadata", [localURL]);
};
module.exports = FileEntry;
});

View File

@ -0,0 +1,49 @@
cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileError
*/
function FileError(error) {
this.code = error || null;
}
// File error codes
// Found in DOMException
FileError.NOT_FOUND_ERR = 1;
FileError.SECURITY_ERR = 2;
FileError.ABORT_ERR = 3;
// Added by File API specification
FileError.NOT_READABLE_ERR = 4;
FileError.ENCODING_ERR = 5;
FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
FileError.INVALID_STATE_ERR = 7;
FileError.SYNTAX_ERR = 8;
FileError.INVALID_MODIFICATION_ERR = 9;
FileError.QUOTA_EXCEEDED_ERR = 10;
FileError.TYPE_MISMATCH_ERR = 11;
FileError.PATH_EXISTS_ERR = 12;
module.exports = FileError;
});

View File

@ -0,0 +1,292 @@
cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper'),
utils = require('cordova/utils'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent'),
origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
/**
* This class reads the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To read from the SD card, the file name is "sdcard/my_file.txt"
* @constructor
*/
var FileReader = function() {
this._readyState = 0;
this._error = null;
this._result = null;
this._progress = null;
this._localURL = '';
this._realReader = origFileReader ? new origFileReader() : {};
};
/**
* Defines the maximum size to read at a time via the native API. The default value is a compromise between
* minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
* (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
* OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
*/
FileReader.READ_CHUNK_SIZE = 256*1024;
// States
FileReader.EMPTY = 0;
FileReader.LOADING = 1;
FileReader.DONE = 2;
utils.defineGetter(FileReader.prototype, 'readyState', function() {
return this._localURL ? this._readyState : this._realReader.readyState;
});
utils.defineGetter(FileReader.prototype, 'error', function() {
return this._localURL ? this._error: this._realReader.error;
});
utils.defineGetter(FileReader.prototype, 'result', function() {
return this._localURL ? this._result: this._realReader.result;
});
function defineEvent(eventName) {
utils.defineGetterSetter(FileReader.prototype, eventName, function() {
return this._realReader[eventName] || null;
}, function(value) {
this._realReader[eventName] = value;
});
}
defineEvent('onloadstart'); // When the read starts.
defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
defineEvent('onload'); // When the read has successfully completed.
defineEvent('onerror'); // When the read has failed (see errors).
defineEvent('onloadend'); // When the request has completed (either in success or failure).
defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method.
function initRead(reader, file) {
// Already loading something
if (reader.readyState == FileReader.LOADING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
reader._result = null;
reader._error = null;
reader._progress = 0;
reader._readyState = FileReader.LOADING;
if (typeof file.localURL == 'string') {
reader._localURL = file.localURL;
} else {
reader._localURL = '';
return true;
}
if (reader.onloadstart) {
reader.onloadstart(new ProgressEvent("loadstart", {target:reader}));
}
}
/**
* Callback used by the following read* functions to handle incremental or final success.
* Must be bound to the FileReader's this along with all but the last parameter,
* e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
* @param readType The name of the read function to call.
* @param encoding Text encoding, or null if this is not a text type read.
* @param offset Starting offset of the read.
* @param totalSize Total number of bytes or chars to read.
* @param accumulate A function that takes the callback result and accumulates it in this._result.
* @param r Callback result returned by the last read exec() call, or null to begin reading.
*/
function readSuccessCallback(readType, encoding, offset, totalSize, accumulate, r) {
if (this._readyState === FileReader.DONE) {
return;
}
if (typeof r !== "undefined") {
accumulate(r);
this._progress = Math.min(this._progress + FileReader.READ_CHUNK_SIZE, totalSize);
if (typeof this.onprogress === "function") {
this.onprogress(new ProgressEvent("progress", {loaded:this._progress, total:totalSize}));
}
}
if (typeof r === "undefined" || this._progress < totalSize) {
var execArgs = [
this._localURL,
offset + this._progress,
offset + this._progress + Math.min(totalSize - this._progress, FileReader.READ_CHUNK_SIZE)];
if (encoding) {
execArgs.splice(1, 0, encoding);
}
exec(
readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
readFailureCallback.bind(this),
"File", readType, execArgs);
} else {
this._readyState = FileReader.DONE;
if (typeof this.onload === "function") {
this.onload(new ProgressEvent("load", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
}
/**
* Callback used by the following read* functions to handle errors.
* Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
*/
function readFailureCallback(e) {
if (this._readyState === FileReader.DONE) {
return;
}
this._readyState = FileReader.DONE;
this._result = null;
this._error = new FileError(e);
if (typeof this.onerror === "function") {
this.onerror(new ProgressEvent("error", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
/**
* Abort reading file.
*/
FileReader.prototype.abort = function() {
if (origFileReader && !this._localURL) {
return this._realReader.abort();
}
this._result = null;
if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) {
return;
}
this._readyState = FileReader.DONE;
// If abort callback
if (typeof this.onabort === 'function') {
this.onabort(new ProgressEvent('abort', {target:this}));
}
// If load end callback
if (typeof this.onloadend === 'function') {
this.onloadend(new ProgressEvent('loadend', {target:this}));
}
};
/**
* Read text file.
*
* @param file {File} File object containing file properties
* @param encoding [Optional] (see http://www.iana.org/assignments/character-sets)
*/
FileReader.prototype.readAsText = function(file, encoding) {
if (initRead(this, file)) {
return this._realReader.readAsText(file, encoding);
}
// Default encoding is UTF-8
var enc = encoding ? encoding : "UTF-8";
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsText", enc, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a base64 encoded data url.
* A data url is of the form:
* data:[<mediatype>][;base64],<data>
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsDataURL = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsDataURL(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsDataURL", null, file.start, totalSize, function(r) {
var commaIndex = r.indexOf(',');
if (this._progress === 0) {
this._result = r;
} else {
this._result += r.substring(commaIndex + 1);
}
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsBinaryString = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsBinaryString(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsBinaryString", null, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsArrayBuffer = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsArrayBuffer(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsArrayBuffer", null, file.start, totalSize, function(r) {
var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
resultArray.set(new Uint8Array(r), this._progress);
this._result = resultArray.buffer;
}.bind(this));
};
module.exports = FileReader;
});

View File

@ -0,0 +1,58 @@
cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var DirectoryEntry = require('./DirectoryEntry');
/**
* An interface representing a file system
*
* @constructor
* {DOMString} name the unique name of the file system (readonly)
* {DirectoryEntry} root directory of the file system (readonly)
*/
var FileSystem = function(name, root) {
this.name = name;
if (root) {
this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
} else {
this.root = new DirectoryEntry(this.name, '/', this);
}
};
FileSystem.prototype.__format__ = function(fullPath, nativeUrl) {
return fullPath;
};
FileSystem.prototype.toJSON = function() {
return "<FileSystem: " + this.name + ">";
};
// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
FileSystem.encodeURIPath = function(path) {
// Because # is a valid filename character, it must be encoded to prevent part of the
// path from being parsed as a URI fragment.
return encodeURI(path).replace(/#/g, '%23');
};
module.exports = FileSystem;
});

View File

@ -0,0 +1,44 @@
cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Options to customize the HTTP request used to upload files.
* @constructor
* @param fileKey {String} Name of file request parameter.
* @param fileName {String} Filename to be used by the server. Defaults to image.jpg.
* @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg.
* @param params {Object} Object with key: value params to send to the server.
* @param headers {Object} Keys are header names, values are header values. Multiple
* headers of the same name are not supported.
*/
var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
this.headers = headers || null;
this.httpMethod = httpMethod || null;
};
module.exports = FileUploadOptions;
});

View File

@ -0,0 +1,32 @@
cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileUploadResult
* @constructor
*/
module.exports = function FileUploadResult(size, code, content) {
this.bytesSent = size;
this.responseCode = code;
this.response = content;
};
});

View File

@ -0,0 +1,327 @@
cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent');
/**
* This class writes to the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To write to the SD card, the file name is "sdcard/my_file.txt"
*
* @constructor
* @param file {File} File object containing file properties
* @param append if true write to the end of the file, otherwise overwrite the file
*/
var FileWriter = function(file) {
this.fileName = "";
this.length = 0;
if (file) {
this.localURL = file.localURL || file;
this.length = file.size || 0;
}
// default is to write at the beginning of the file
this.position = 0;
this.readyState = 0; // EMPTY
this.result = null;
// Error
this.error = null;
// Event handlers
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
// States
FileWriter.INIT = 0;
FileWriter.WRITING = 1;
FileWriter.DONE = 2;
/**
* Abort writing file.
*/
FileWriter.prototype.abort = function() {
// check for invalid state
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// set error
this.error = new FileError(FileError.ABORT_ERR);
this.readyState = FileWriter.DONE;
// If abort callback
if (typeof this.onabort === "function") {
this.onabort(new ProgressEvent("abort", {"target":this}));
}
// If write end callback
if (typeof this.onwriteend === "function") {
this.onwriteend(new ProgressEvent("writeend", {"target":this}));
}
};
/**
* Writes data to the file
*
* @param data text or blob to be written
* @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
*/
FileWriter.prototype.write = function(data, isPendingBlobReadResult) {
var that=this;
var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
var isProxySupportBlobNatively = (cordova.platformId === "windows8" || cordova.platformId === "windows");
var isBinary;
// Check to see if the incoming data is a blob
if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
var fileReader = new FileReader();
fileReader.onload = function() {
// Call this method again, with the arraybuffer as argument
FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
};
fileReader.onerror = function () {
// DONE state
that.readyState = FileWriter.DONE;
// Save error
that.error = this.error;
// If onerror callback
if (typeof that.onerror === "function") {
that.onerror(new ProgressEvent("error", {"target":that}));
}
// If onwriteend callback
if (typeof that.onwriteend === "function") {
that.onwriteend(new ProgressEvent("writeend", {"target":that}));
}
};
// WRITING state
this.readyState = FileWriter.WRITING;
if (supportsBinary) {
fileReader.readAsArrayBuffer(data);
} else {
fileReader.readAsText(data);
}
return;
}
// Mark data type for safer transport over the binary bridge
isBinary = supportsBinary && (data instanceof ArrayBuffer);
if (isBinary && cordova.platformId === "windowsphone") {
// create a plain array, using the keys from the Uint8Array view so that we can serialize it
data = Array.apply(null, new Uint8Array(data));
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":me}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// position always increases by bytes written because file would be extended
me.position += r;
// The length of the file is now where we are done writing.
me.length = me.position;
// DONE state
me.readyState = FileWriter.DONE;
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "write", [this.localURL, data, this.position, isBinary]);
};
/**
* Moves the file pointer to the location specified.
*
* If the offset is a negative number the position of the file
* pointer is rewound. If the offset is greater than the file
* size the position is set to the end of the file.
*
* @param offset is the location to move the file pointer to.
*/
FileWriter.prototype.seek = function(offset) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
if (!offset && offset !== 0) {
return;
}
// See back from end of file.
if (offset < 0) {
this.position = Math.max(offset + this.length, 0);
}
// Offset is bigger than file size so set position
// to the end of the file.
else if (offset > this.length) {
this.position = this.length;
}
// Offset is between 0 and file size so set the position
// to start writing.
else {
this.position = offset;
}
};
/**
* Truncates the file to the size specified.
*
* @param size to chop the file at.
*/
FileWriter.prototype.truncate = function(size) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":this}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Update the length of the file
me.length = r;
me.position = Math.min(me.position, r);
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "truncate", [this.localURL, size]);
};
module.exports = FileWriter;
});

View File

@ -0,0 +1,39 @@
cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Supplies arguments to methods that lookup or create files and directories.
*
* @param create
* {boolean} file or directory if it doesn't exist
* @param exclusive
* {boolean} used with create; if true the command will fail if
* target path exists
*/
function Flags(create, exclusive) {
this.create = create || false;
this.exclusive = exclusive || false;
}
module.exports = Flags;
});

View File

@ -0,0 +1,26 @@
cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
exports.TEMPORARY = 0;
exports.PERSISTENT = 1;
});

View File

@ -0,0 +1,43 @@
cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Information about the state of the file or directory
*
* {Date} modificationTime (readonly)
*/
var Metadata = function(metadata) {
if (typeof metadata == "object") {
this.modificationTime = new Date(metadata.modificationTime);
this.size = metadata.size || 0;
} else if (typeof metadata == "undefined") {
this.modificationTime = null;
this.size = 0;
} else {
/* Backwards compatiblity with platforms that only return a timestamp */
this.modificationTime = new Date(metadata);
}
};
module.exports = Metadata;
});

View File

@ -0,0 +1,70 @@
cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
// Feature test: See if we can instantiate a native ProgressEvent;
// if so, use that approach,
// otherwise fill-in with our own implementation.
//
// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
var ProgressEvent = (function() {
/*
var createEvent = function(data) {
var event = document.createEvent('Events');
event.initEvent('ProgressEvent', false, false);
if (data) {
for (var i in data) {
if (data.hasOwnProperty(i)) {
event[i] = data[i];
}
}
if (data.target) {
// TODO: cannot call <some_custom_object>.dispatchEvent
// need to first figure out how to implement EventTarget
}
}
return event;
};
try {
var ev = createEvent({type:"abort",target:document});
return function ProgressEvent(type, data) {
data.type = type;
return createEvent(data);
};
} catch(e){
*/
return function ProgressEvent(type, dict) {
this.type = type;
this.bubbles = false;
this.cancelBubble = false;
this.cancelable = false;
this.lengthComputable = false;
this.loaded = dict && dict.loaded ? dict.loaded : 0;
this.total = dict && dict.total ? dict.total : 0;
this.target = dict && dict.target ? dict.target : null;
};
//}
})();
module.exports = ProgressEvent;
});

View File

@ -0,0 +1,52 @@
cordova.define("cordova-plugin-file.androidFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
FILESYSTEM_PROTOCOL = "cdvfile";
module.exports = {
__format__: function(fullPath, nativeUrl) {
var path;
var contentUrlMatch = /^content:\/\//.exec(nativeUrl);
if (contentUrlMatch) {
// When available, use the path from a native content URL, which was already encoded by Android.
// This is necessary because JavaScript's encodeURI() does not encode as many characters as
// Android, which can result in permission exceptions when the encoding of a content URI
// doesn't match the string for which permission was originally granted.
path = nativeUrl.substring(contentUrlMatch[0].length - 1);
} else {
path = FileSystem.encodeURIPath(fullPath);
if (!/^\//.test(path)) {
path = '/' + path;
}
var m = /\?.*/.exec(nativeUrl);
if (m) {
path += m[0];
}
}
return FILESYSTEM_PROTOCOL + '://localhost/' + this.name + path;
}
};
});

View File

@ -0,0 +1,29 @@
cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
module.exports = function () {
// window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
// possibly a good flag to indicate that we're running in Chrome
return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
};
});

View File

@ -0,0 +1,66 @@
cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var channel = require('cordova/channel');
exports.file = {
// Read-only directory where the application is installed.
applicationDirectory: null,
// Root of app's private writable storage
applicationStorageDirectory: null,
// Where to put app-specific data files.
dataDirectory: null,
// Cached files that should survive app restarts.
// Apps should not rely on the OS to delete files in here.
cacheDirectory: null,
// Android: the application space on external storage.
externalApplicationStorageDirectory: null,
// Android: Where to put app-specific data files on external storage.
externalDataDirectory: null,
// Android: the application cache on external storage.
externalCacheDirectory: null,
// Android: the external storage (SD card) root.
externalRootDirectory: null,
// iOS: Temp directory that the OS can clear at will.
tempDirectory: null,
// iOS: Holds app-specific files that should be synced (e.g. to iCloud).
syncedDataDirectory: null,
// iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
documentsDirectory: null,
// BlackBerry10: Files globally available to all apps
sharedDirectory: null
};
channel.waitForInitialization('onFileSystemPathsReady');
channel.onCordovaReady.subscribe(function() {
function after(paths) {
for (var k in paths) {
exports.file[k] = paths[k];
}
channel.initializationComplete('onFileSystemPathsReady');
}
exec(after, null, 'File', 'requestAllPaths', []);
});
});

View File

@ -0,0 +1,48 @@
cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Map of fsName -> FileSystem.
var fsMap = null;
var FileSystem = require('./FileSystem');
var exec = require('cordova/exec');
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
require('./fileSystems').getFs = function(name, callback) {
function success(response) {
fsMap = {};
for (var i = 0; i < response.length; ++i) {
var fsRoot = response[i];
var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
fsMap[fs.name] = fs;
}
callback(fsMap[name]);
}
if (fsMap) {
callback(fsMap[name]);
} else {
exec(success, null, "File", "requestAllFileSystems", []);
}
};
});

View File

@ -0,0 +1,28 @@
cordova.define("cordova-plugin-file.fileSystems", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
module.exports.getFs = function(name, callback) {
callback(null);
};
});

View File

@ -0,0 +1,85 @@
cordova.define("cordova-plugin-file.requestFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
(function() {
//For browser platform: not all browsers use this file.
function checkBrowser() {
if (cordova.platformId === "browser" && require('./isChrome')()) {
module.exports = window.requestFileSystem || window.webkitRequestFileSystem;
return true;
}
return false;
}
if (checkBrowser()) {
return;
}
var argscheck = require('cordova/argscheck'),
FileError = require('./FileError'),
FileSystem = require('./FileSystem'),
exec = require('cordova/exec');
var fileSystems = require('./fileSystems');
/**
* Request a file system in which to store application data.
* @param type local file system type
* @param size indicates how much storage space, in bytes, the application expects to need
* @param successCallback invoked with a FileSystem object
* @param errorCallback invoked if error occurs retrieving file system
*/
var requestFileSystem = function(type, size, successCallback, errorCallback) {
argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
var fail = function(code) {
if (errorCallback) {
errorCallback(new FileError(code));
}
};
if (type < 0) {
fail(FileError.SYNTAX_ERR);
} else {
// if successful, return a FileSystem object
var success = function(file_system) {
if (file_system) {
if (successCallback) {
fileSystems.getFs(file_system.name, function(fs) {
// This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
if (!fs) {
fs = new FileSystem(file_system.name, file_system.root);
}
successCallback(fs);
});
}
}
else {
// no FileSystem object returned
fail(FileError.NOT_FOUND_ERR);
}
};
exec(success, fail, "File", "requestFileSystem", [type, size]);
}
};
module.exports = requestFileSystem;
})();
});

View File

@ -0,0 +1,95 @@
cordova.define("cordova-plugin-file.resolveLocalFileSystemURI", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
(function() {
//For browser platform: not all browsers use overrided `resolveLocalFileSystemURL`.
function checkBrowser() {
if (cordova.platformId === "browser" && require('./isChrome')()) {
module.exports.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL;
return true;
}
return false;
}
if (checkBrowser()) {
return;
}
var argscheck = require('cordova/argscheck'),
DirectoryEntry = require('./DirectoryEntry'),
FileEntry = require('./FileEntry'),
FileError = require('./FileError'),
exec = require('cordova/exec');
var fileSystems = require('./fileSystems');
/**
* Look up file system Entry referred to by local URI.
* @param {DOMString} uri URI referring to a local file or directory
* @param successCallback invoked with Entry object corresponding to URI
* @param errorCallback invoked if error occurs retrieving file system entry
*/
module.exports.resolveLocalFileSystemURL = module.exports.resolveLocalFileSystemURL || function(uri, successCallback, errorCallback) {
argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments);
// error callback
var fail = function(error) {
if (errorCallback) {
errorCallback(new FileError(error));
}
};
// sanity check for 'not:valid:filename' or '/not:valid:filename'
// file.spec.12 window.resolveLocalFileSystemURI should error (ENCODING_ERR) when resolving invalid URI with leading /.
if(!uri || uri.split(":").length > 2) {
setTimeout( function() {
fail(FileError.ENCODING_ERR);
},0);
return;
}
// if successful, return either a file or directory entry
var success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var fsName = entry.filesystemName || (entry.filesystem && entry.filesystem.name) || (entry.filesystem == window.PERSISTENT ? 'persistent' : 'temporary');
fileSystems.getFs(fsName, function(fs) {
// This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
if (!fs) {
fs = new FileSystem(fsName, {name:"", fullPath:"/"});
}
var result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath, fs, entry.nativeURL) : new FileEntry(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
});
}
}
else {
// no Entry object returned
fail(FileError.NOT_FOUND_ERR);
}
};
exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]);
};
module.exports.resolveLocalFileSystemURI = function() {
console.log("resolveLocalFileSystemURI is deprecated. Please call resolveLocalFileSystemURL instead.");
module.exports.resolveLocalFileSystemURL.apply(this, arguments);
};
})();
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,286 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.Log;
import org.apache.cordova.CordovaResourceApi;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.HashMap;
import java.util.Map;
public class AssetFilesystem extends Filesystem {
private final AssetManager assetManager;
// A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
// See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
private static Object listCacheLock = new Object();
private static boolean listCacheFromFile;
private static Map<String, String[]> listCache;
private static Map<String, Long> lengthCache;
private void lazyInitCaches() {
synchronized (listCacheLock) {
if (listCache == null) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
listCache = (Map<String, String[]>) ois.readObject();
lengthCache = (Map<String, Long>) ois.readObject();
listCacheFromFile = true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
// Asset manifest won't exist if the gradle hook isn't set up correctly.
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
}
}
}
if (listCache == null) {
Log.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
listCache = new HashMap<String, String[]>();
}
}
}
}
private String[] listAssets(String assetPath) throws IOException {
if (assetPath.startsWith("/")) {
assetPath = assetPath.substring(1);
}
if (assetPath.endsWith("/")) {
assetPath = assetPath.substring(0, assetPath.length() - 1);
}
lazyInitCaches();
String[] ret = listCache.get(assetPath);
if (ret == null) {
if (listCacheFromFile) {
ret = new String[0];
} else {
ret = assetManager.list(assetPath);
listCache.put(assetPath, ret);
}
}
return ret;
}
private long getAssetSize(String assetPath) throws FileNotFoundException {
if (assetPath.startsWith("/")) {
assetPath = assetPath.substring(1);
}
lazyInitCaches();
if (lengthCache != null) {
Long ret = lengthCache.get(assetPath);
if (ret == null) {
throw new FileNotFoundException("Asset not found: " + assetPath);
}
return ret;
}
CordovaResourceApi.OpenForReadResult offr = null;
try {
offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
long length = offr.length;
if (length < 0) {
// available() doesn't always yield the file size, but for assets it does.
length = offr.inputStream.available();
}
return length;
} catch (IOException e) {
throw new FileNotFoundException("File not found: " + assetPath);
} finally {
if (offr != null) {
try {
offr.inputStream.close();
} catch (IOException e) {
}
}
}
}
public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
this.assetManager = assetManager;
}
@Override
public Uri toNativeUri(LocalFilesystemURL inputURL) {
return nativeUriForFullPath(inputURL.path);
}
@Override
public LocalFilesystemURL toLocalUri(Uri inputURL) {
if (!"file".equals(inputURL.getScheme())) {
return null;
}
File f = new File(inputURL.getPath());
// Removes and duplicate /s (e.g. file:///a//b/c)
Uri resolvedUri = Uri.fromFile(f);
String rootUriNoTrailingSlash = rootUri.getEncodedPath();
rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
return null;
}
String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
// Strip leading slash
if (!subPath.isEmpty()) {
subPath = subPath.substring(1);
}
Uri.Builder b = new Uri.Builder()
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
.authority("localhost")
.path(name);
if (!subPath.isEmpty()) {
b.appendEncodedPath(subPath);
}
if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
// Add trailing / for directories.
b.appendEncodedPath("");
}
return LocalFilesystemURL.parse(b.build());
}
private boolean isDirectory(String assetPath) {
try {
return listAssets(assetPath).length != 0;
} catch (IOException e) {
return false;
}
}
@Override
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
String pathNoSlashes = inputURL.path.substring(1);
if (pathNoSlashes.endsWith("/")) {
pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
}
String[] files;
try {
files = listAssets(pathNoSlashes);
} catch (IOException e) {
throw new FileNotFoundException();
}
LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
for (int i = 0; i < files.length; ++i) {
entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
}
return entries;
}
@Override
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
String path, JSONObject options, boolean directory)
throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
if (options != null && options.optBoolean("create")) {
throw new UnsupportedOperationException("Assets are read-only");
}
// Check whether the supplied path is absolute or relative
if (directory && !path.endsWith("/")) {
path += "/";
}
LocalFilesystemURL requestedURL;
if (path.startsWith("/")) {
requestedURL = localUrlforFullPath(normalizePath(path));
} else {
requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
}
// Throws a FileNotFoundException if it doesn't exist.
getFileMetadataForLocalURL(requestedURL);
boolean isDir = isDirectory(requestedURL.path);
if (directory && !isDir) {
throw new TypeMismatchException("path doesn't exist or is file");
} else if (!directory && isDir) {
throw new TypeMismatchException("path doesn't exist or is directory");
}
// Return the directory
return makeEntryForURL(requestedURL);
}
@Override
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
JSONObject metadata = new JSONObject();
long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
try {
metadata.put("size", size);
metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
metadata.put("name", new File(inputURL.path).getName());
metadata.put("fullPath", inputURL.path);
metadata.put("lastModifiedDate", 0);
} catch (JSONException e) {
return null;
}
return metadata;
}
@Override
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
return false;
}
@Override
long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
throw new NoModificationAllowedException("Assets are read-only");
}
@Override
long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
throw new NoModificationAllowedException("Assets are read-only");
}
@Override
String filesystemPathForURL(LocalFilesystemURL url) {
return null;
}
@Override
LocalFilesystemURL URLforFilesystemPath(String path) {
return null;
}
@Override
boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
throw new NoModificationAllowedException("Assets are read-only");
}
@Override
boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
throw new NoModificationAllowedException("Assets are read-only");
}
}

View File

@ -0,0 +1,216 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.cordova.CordovaResourceApi;
import org.json.JSONException;
import org.json.JSONObject;
public class ContentFilesystem extends Filesystem {
private final Context context;
public ContentFilesystem(Context context, CordovaResourceApi resourceApi) {
super(Uri.parse("content://"), "content", resourceApi);
this.context = context;
}
@Override
public Uri toNativeUri(LocalFilesystemURL inputURL) {
String authorityAndPath = inputURL.uri.getEncodedPath().substring(this.name.length() + 2);
if (authorityAndPath.length() < 2) {
return null;
}
String ret = "content://" + authorityAndPath;
String query = inputURL.uri.getEncodedQuery();
if (query != null) {
ret += '?' + query;
}
String frag = inputURL.uri.getEncodedFragment();
if (frag != null) {
ret += '#' + frag;
}
return Uri.parse(ret);
}
@Override
public LocalFilesystemURL toLocalUri(Uri inputURL) {
if (!"content".equals(inputURL.getScheme())) {
return null;
}
String subPath = inputURL.getEncodedPath();
if (subPath.length() > 0) {
subPath = subPath.substring(1);
}
Uri.Builder b = new Uri.Builder()
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
.authority("localhost")
.path(name)
.appendPath(inputURL.getAuthority());
if (subPath.length() > 0) {
b.appendEncodedPath(subPath);
}
Uri localUri = b.encodedQuery(inputURL.getEncodedQuery())
.encodedFragment(inputURL.getEncodedFragment())
.build();
return LocalFilesystemURL.parse(localUri);
}
@Override
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
}
@Override
public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
throws NoModificationAllowedException {
Uri contentUri = toNativeUri(inputURL);
try {
context.getContentResolver().delete(contentUri, null, null);
} catch (UnsupportedOperationException t) {
// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
// The ContentResolver applies only when the file was registered in the
// first case, which is generally only the case with images.
throw new NoModificationAllowedException("Deleting not supported for content uri: " + contentUri);
}
return true;
}
@Override
public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
throws NoModificationAllowedException {
throw new NoModificationAllowedException("Cannot remove content url");
}
@Override
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
throw new UnsupportedOperationException("readEntriesAtLocalURL() not supported for content:. Use resolveLocalFileSystemURL instead.");
}
@Override
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
long size = -1;
long lastModified = 0;
Uri nativeUri = toNativeUri(inputURL);
String mimeType = resourceApi.getMimeType(nativeUri);
Cursor cursor = openCursorForURL(nativeUri);
try {
if (cursor != null && cursor.moveToFirst()) {
size = resourceSizeForCursor(cursor);
Long modified = lastModifiedDateForCursor(cursor);
if (modified != null)
lastModified = modified.longValue();
} else {
// Some content providers don't support cursors at all!
CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(nativeUri);
size = offr.length;
}
} catch (IOException e) {
throw new FileNotFoundException();
} finally {
if (cursor != null)
cursor.close();
}
JSONObject metadata = new JSONObject();
try {
metadata.put("size", size);
metadata.put("type", mimeType);
metadata.put("name", name);
metadata.put("fullPath", inputURL.path);
metadata.put("lastModifiedDate", lastModified);
} catch (JSONException e) {
return null;
}
return metadata;
}
@Override
public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
int offset, boolean isBinary) throws NoModificationAllowedException {
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
}
@Override
public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
throws NoModificationAllowedException {
throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
}
protected Cursor openCursorForURL(Uri nativeUri) {
ContentResolver contentResolver = context.getContentResolver();
try {
return contentResolver.query(nativeUri, null, null, null, null);
} catch (UnsupportedOperationException e) {
return null;
}
}
private Long resourceSizeForCursor(Cursor cursor) {
int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
if (columnIndex != -1) {
String sizeStr = cursor.getString(columnIndex);
if (sizeStr != null) {
return Long.parseLong(sizeStr);
}
}
return null;
}
protected Long lastModifiedDateForCursor(Cursor cursor) {
int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
if (columnIndex == -1) {
columnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
}
if (columnIndex != -1) {
String dateStr = cursor.getString(columnIndex);
if (dateStr != null) {
return Long.parseLong(dateStr);
}
}
return null;
}
@Override
public String filesystemPathForURL(LocalFilesystemURL url) {
File f = resourceApi.mapUriToFile(toNativeUri(url));
return f == null ? null : f.getAbsolutePath();
}
@Override
public LocalFilesystemURL URLforFilesystemPath(String path) {
// Returns null as we don't support reverse mapping back to content:// URLs
return null;
}
@Override
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
return true;
}
}

View File

@ -0,0 +1,134 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.os.Environment;
import android.os.StatFs;
import java.io.File;
/**
* This class provides file directory utilities.
* All file operations are performed on the SD card.
*
* It is used by the FileUtils class.
*/
public class DirectoryManager {
@SuppressWarnings("unused")
private static final String LOG_TAG = "DirectoryManager";
/**
* Determine if a file or directory exists.
* @param name The name of the file to check.
* @return T=exists, F=not found
*/
public static boolean testFileExists(String name) {
boolean status;
// If SD card exists
if ((testSaveLocationExists()) && (!name.equals(""))) {
File path = Environment.getExternalStorageDirectory();
File newPath = constructFilePaths(path.toString(), name);
status = newPath.exists();
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Get the free space in external storage
*
* @return Size in KB or -1 if not available
*/
public static long getFreeExternalStorageSpace() {
String status = Environment.getExternalStorageState();
long freeSpaceInBytes = 0;
// Check if external storage exists
if (status.equals(Environment.MEDIA_MOUNTED)) {
freeSpaceInBytes = getFreeSpaceInBytes(Environment.getExternalStorageDirectory().getPath());
} else {
// If no external storage then return -1
return -1;
}
return freeSpaceInBytes / 1024;
}
/**
* Given a path return the number of free bytes in the filesystem containing the path.
*
* @param path to the file system
* @return free space in bytes
*/
public static long getFreeSpaceInBytes(String path) {
try {
StatFs stat = new StatFs(path);
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return availableBlocks * blockSize;
} catch (IllegalArgumentException e) {
// The path was invalid. Just return 0 free bytes.
return 0;
}
}
/**
* Determine if SD card exists.
*
* @return T=exists, F=not found
*/
public static boolean testSaveLocationExists() {
String sDCardStatus = Environment.getExternalStorageState();
boolean status;
// If SD card is mounted
if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
status = true;
}
// If no SD card
else {
status = false;
}
return status;
}
/**
* Create a new file object from two file paths.
*
* @param file1 Base file path
* @param file2 Remaining file path
* @return File object
*/
private static File constructFilePaths (String file1, String file2) {
File newPath;
if (file2.startsWith(file1)) {
newPath = new File(file2);
}
else {
newPath = new File(file1 + "/" + file2);
}
return newPath;
}
}

View File

@ -0,0 +1,29 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
@SuppressWarnings("serial")
public class EncodingException extends Exception {
public EncodingException(String message) {
super(message);
}
}

View File

@ -0,0 +1,29 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
@SuppressWarnings("serial")
public class FileExistsException extends Exception {
public FileExistsException(String msg) {
super(msg);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,331 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.net.Uri;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.cordova.CordovaResourceApi;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public abstract class Filesystem {
protected final Uri rootUri;
protected final CordovaResourceApi resourceApi;
public final String name;
private JSONObject rootEntry;
public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
this.rootUri = rootUri;
this.name = name;
this.resourceApi = resourceApi;
}
public interface ReadFileCallback {
public void handleData(InputStream inputStream, String contentType) throws IOException;
}
public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
try {
String path = inputURL.path;
int end = path.endsWith("/") ? 1 : 0;
String[] parts = path.substring(0, path.length() - end).split("/+");
String fileName = parts[parts.length - 1];
JSONObject entry = new JSONObject();
entry.put("isFile", !inputURL.isDirectory);
entry.put("isDirectory", inputURL.isDirectory);
entry.put("name", fileName);
entry.put("fullPath", path);
// The file system can't be specified, as it would lead to an infinite loop,
// but the filesystem name can be.
entry.put("filesystemName", inputURL.fsName);
// Backwards compatibility
entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
String nativeUrlStr = nativeURL.toString();
if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
nativeUrlStr += "/";
}
entry.put("nativeURL", nativeUrlStr);
return entry;
} catch (JSONException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
Uri nativeUri = toNativeUri(inputURL);
return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
}
public JSONObject makeEntryForNativeUri(Uri nativeUri) {
LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
}
public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
return makeEntryForURL(inputURL);
}
public JSONObject makeEntryForFile(File file) {
return makeEntryForNativeUri(Uri.fromFile(file));
}
abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
LocalFilesystemURL[] children = listChildren(inputURL);
JSONArray entries = new JSONArray();
if (children != null) {
for (LocalFilesystemURL url : children) {
entries.put(makeEntryForURL(url));
}
}
return entries;
}
abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
public Uri getRootUri() {
return rootUri;
}
public boolean exists(LocalFilesystemURL inputURL) {
try {
getFileMetadataForLocalURL(inputURL);
} catch (FileNotFoundException e) {
return false;
}
return true;
}
public Uri nativeUriForFullPath(String fullPath) {
Uri ret = null;
if (fullPath != null) {
String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
if (encodedPath.startsWith("/")) {
encodedPath = encodedPath.substring(1);
}
ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
}
return ret;
}
public LocalFilesystemURL localUrlforFullPath(String fullPath) {
Uri nativeUri = nativeUriForFullPath(fullPath);
if (nativeUri != null) {
return toLocalUri(nativeUri);
}
return null;
}
/**
* Removes multiple repeated //s, and collapses processes ../s.
*/
protected static String normalizePath(String rawPath) {
// If this is an absolute path, trim the leading "/" and replace it later
boolean isAbsolutePath = rawPath.startsWith("/");
if (isAbsolutePath) {
rawPath = rawPath.replaceFirst("/+", "");
}
ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
for (int index = 0; index < components.size(); ++index) {
if (components.get(index).equals("..")) {
components.remove(index);
if (index > 0) {
components.remove(index-1);
--index;
}
}
}
StringBuilder normalizedPath = new StringBuilder();
for(String component: components) {
normalizedPath.append("/");
normalizedPath.append(component);
}
if (isAbsolutePath) {
return normalizedPath.toString();
} else {
return normalizedPath.toString().substring(1);
}
}
/**
* Gets the free space in bytes available on this filesystem.
* Subclasses may override this method to return nonzero free space.
*/
public long getFreeSpaceInBytes() {
return 0;
}
public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
public JSONObject getRootEntry() {
if (rootEntry == null) {
rootEntry = makeEntryForNativeUri(rootUri);
}
return rootEntry;
}
public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
Uri parentUri = inputURL.uri;
String parentPath = new File(inputURL.uri.getPath()).getParent();
if (!"/".equals(parentPath)) {
parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
}
return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
}
protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
// I know this looks weird but it is to work around a JSON bug.
if ("null".equals(newName) || "".equals(newName)) {
newName = srcURL.uri.getLastPathSegment();;
}
String newDest = destURL.uri.toString();
if (newDest.endsWith("/")) {
newDest = newDest + newName;
} else {
newDest = newDest + "/" + newName;
}
if (isDirectory) {
newDest += '/';
}
return LocalFilesystemURL.parse(newDest);
}
/* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
* the destination URL on this filesystem, optionally with a new filename.
* If move is true, then this method should either perform an atomic move operation
* or remove the source file when finished.
*/
public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
// First, check to see that we can do it
if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
throw new NoModificationAllowedException("Cannot move file at source URL");
}
final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
Uri srcNativeUri = srcFs.toNativeUri(srcURL);
CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
OutputStream os = null;
try {
os = getOutputStreamForURL(destination);
} catch (IOException e) {
ofrr.inputStream.close();
throw e;
}
// Closes streams.
resourceApi.copyResource(ofrr, os);
if (move) {
srcFs.removeFileAtLocalURL(srcURL);
}
return getEntryForLocalURL(destination);
}
public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
return resourceApi.openOutputStream(toNativeUri(inputURL));
}
public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
ReadFileCallback readFileCallback) throws IOException {
CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
if (end < 0) {
end = ofrr.length;
}
long numBytesToRead = end - start;
try {
if (start > 0) {
ofrr.inputStream.skip(start);
}
InputStream inputStream = ofrr.inputStream;
if (end < ofrr.length) {
inputStream = new LimitedInputStream(inputStream, numBytesToRead);
}
readFileCallback.handleData(inputStream, ofrr.mimeType);
} finally {
ofrr.inputStream.close();
}
}
abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
boolean isBinary) throws NoModificationAllowedException, IOException;
abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
throws IOException, NoModificationAllowedException;
// This method should return null if filesystem urls cannot be mapped to paths
abstract String filesystemPathForURL(LocalFilesystemURL url);
abstract LocalFilesystemURL URLforFilesystemPath(String path);
abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
protected class LimitedInputStream extends FilterInputStream {
long numBytesToRead;
public LimitedInputStream(InputStream in, long numBytesToRead) {
super(in);
this.numBytesToRead = numBytesToRead;
}
@Override
public int read() throws IOException {
if (numBytesToRead <= 0) {
return -1;
}
numBytesToRead--;
return in.read();
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
if (numBytesToRead <= 0) {
return -1;
}
int bytesToRead = byteCount;
if (byteCount > numBytesToRead) {
bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
}
int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
numBytesToRead -= numBytesRead;
return numBytesRead;
}
}
}

View File

@ -0,0 +1,30 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
@SuppressWarnings("serial")
public class InvalidModificationException extends Exception {
public InvalidModificationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,510 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import org.apache.cordova.CordovaResourceApi;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Build;
import android.os.Environment;
import android.util.Base64;
import android.net.Uri;
import android.content.Context;
import android.content.Intent;
public class LocalFilesystem extends Filesystem {
private final Context context;
public LocalFilesystem(String name, Context context, CordovaResourceApi resourceApi, File fsRoot) {
super(Uri.fromFile(fsRoot).buildUpon().appendEncodedPath("").build(), name, resourceApi);
this.context = context;
}
public String filesystemPathForFullPath(String fullPath) {
return new File(rootUri.getPath(), fullPath).toString();
}
@Override
public String filesystemPathForURL(LocalFilesystemURL url) {
return filesystemPathForFullPath(url.path);
}
private String fullPathForFilesystemPath(String absolutePath) {
if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
return absolutePath.substring(rootUri.getPath().length() - 1);
}
return null;
}
@Override
public Uri toNativeUri(LocalFilesystemURL inputURL) {
return nativeUriForFullPath(inputURL.path);
}
@Override
public LocalFilesystemURL toLocalUri(Uri inputURL) {
if (!"file".equals(inputURL.getScheme())) {
return null;
}
File f = new File(inputURL.getPath());
// Removes and duplicate /s (e.g. file:///a//b/c)
Uri resolvedUri = Uri.fromFile(f);
String rootUriNoTrailingSlash = rootUri.getEncodedPath();
rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
return null;
}
String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
// Strip leading slash
if (!subPath.isEmpty()) {
subPath = subPath.substring(1);
}
Uri.Builder b = new Uri.Builder()
.scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
.authority("localhost")
.path(name);
if (!subPath.isEmpty()) {
b.appendEncodedPath(subPath);
}
if (f.isDirectory()) {
// Add trailing / for directories.
b.appendEncodedPath("");
}
return LocalFilesystemURL.parse(b.build());
}
@Override
public LocalFilesystemURL URLforFilesystemPath(String path) {
return localUrlforFullPath(fullPathForFilesystemPath(path));
}
@Override
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
boolean create = false;
boolean exclusive = false;
if (options != null) {
create = options.optBoolean("create");
if (create) {
exclusive = options.optBoolean("exclusive");
}
}
// Check for a ":" character in the file to line up with BB and iOS
if (path.contains(":")) {
throw new EncodingException("This path has an invalid \":\" in it.");
}
LocalFilesystemURL requestedURL;
// Check whether the supplied path is absolute or relative
if (directory && !path.endsWith("/")) {
path += "/";
}
if (path.startsWith("/")) {
requestedURL = localUrlforFullPath(normalizePath(path));
} else {
requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
}
File fp = new File(this.filesystemPathForURL(requestedURL));
if (create) {
if (exclusive && fp.exists()) {
throw new FileExistsException("create/exclusive fails");
}
if (directory) {
fp.mkdir();
} else {
fp.createNewFile();
}
if (!fp.exists()) {
throw new FileExistsException("create fails");
}
}
else {
if (!fp.exists()) {
throw new FileNotFoundException("path does not exist");
}
if (directory) {
if (fp.isFile()) {
throw new TypeMismatchException("path doesn't exist or is file");
}
} else {
if (fp.isDirectory()) {
throw new TypeMismatchException("path doesn't exist or is directory");
}
}
}
// Return the directory
return makeEntryForURL(requestedURL);
}
@Override
public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
File fp = new File(filesystemPathForURL(inputURL));
// You can't delete a directory that is not empty
if (fp.isDirectory() && fp.list().length > 0) {
throw new InvalidModificationException("You can't delete a directory that is not empty.");
}
return fp.delete();
}
@Override
public boolean exists(LocalFilesystemURL inputURL) {
File fp = new File(filesystemPathForURL(inputURL));
return fp.exists();
}
@Override
public long getFreeSpaceInBytes() {
return DirectoryManager.getFreeSpaceInBytes(rootUri.getPath());
}
@Override
public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
File directory = new File(filesystemPathForURL(inputURL));
return removeDirRecursively(directory);
}
protected boolean removeDirRecursively(File directory) throws FileExistsException {
if (directory.isDirectory()) {
for (File file : directory.listFiles()) {
removeDirRecursively(file);
}
}
if (!directory.delete()) {
throw new FileExistsException("could not delete: " + directory.getName());
} else {
return true;
}
}
@Override
public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
File fp = new File(filesystemPathForURL(inputURL));
if (!fp.exists()) {
// The directory we are listing doesn't exist so we should fail.
throw new FileNotFoundException();
}
File[] files = fp.listFiles();
if (files == null) {
// inputURL is a directory
return null;
}
LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
for (int i = 0; i < files.length; i++) {
entries[i] = URLforFilesystemPath(files[i].getPath());
}
return entries;
}
@Override
public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
File file = new File(filesystemPathForURL(inputURL));
if (!file.exists()) {
throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
}
JSONObject metadata = new JSONObject();
try {
// Ensure that directories report a size of 0
metadata.put("size", file.isDirectory() ? 0 : file.length());
metadata.put("type", resourceApi.getMimeType(Uri.fromFile(file)));
metadata.put("name", file.getName());
metadata.put("fullPath", inputURL.path);
metadata.put("lastModifiedDate", file.lastModified());
} catch (JSONException e) {
return null;
}
return metadata;
}
private void copyFile(Filesystem srcFs, LocalFilesystemURL srcURL, File destFile, boolean move) throws IOException, InvalidModificationException, NoModificationAllowedException {
if (move) {
String realSrcPath = srcFs.filesystemPathForURL(srcURL);
if (realSrcPath != null) {
File srcFile = new File(realSrcPath);
if (srcFile.renameTo(destFile)) {
return;
}
// Trying to rename the file failed. Possibly because we moved across file system on the device.
}
}
CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(srcFs.toNativeUri(srcURL));
copyResource(offr, new FileOutputStream(destFile));
if (move) {
srcFs.removeFileAtLocalURL(srcURL);
}
}
private void copyDirectory(Filesystem srcFs, LocalFilesystemURL srcURL, File dstDir, boolean move) throws IOException, NoModificationAllowedException, InvalidModificationException, FileExistsException {
if (move) {
String realSrcPath = srcFs.filesystemPathForURL(srcURL);
if (realSrcPath != null) {
File srcDir = new File(realSrcPath);
// If the destination directory already exists and is empty then delete it. This is according to spec.
if (dstDir.exists()) {
if (dstDir.list().length > 0) {
throw new InvalidModificationException("directory is not empty");
}
dstDir.delete();
}
// Try to rename the directory
if (srcDir.renameTo(dstDir)) {
return;
}
// Trying to rename the file failed. Possibly because we moved across file system on the device.
}
}
if (dstDir.exists()) {
if (dstDir.list().length > 0) {
throw new InvalidModificationException("directory is not empty");
}
} else {
if (!dstDir.mkdir()) {
// If we can't create the directory then fail
throw new NoModificationAllowedException("Couldn't create the destination directory");
}
}
LocalFilesystemURL[] children = srcFs.listChildren(srcURL);
for (LocalFilesystemURL childLocalUrl : children) {
File target = new File(dstDir, new File(childLocalUrl.path).getName());
if (childLocalUrl.isDirectory) {
copyDirectory(srcFs, childLocalUrl, target, false);
} else {
copyFile(srcFs, childLocalUrl, target, false);
}
}
if (move) {
srcFs.recursiveRemoveFileAtLocalURL(srcURL);
}
}
@Override
public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
// Check to see if the destination directory exists
String newParent = this.filesystemPathForURL(destURL);
File destinationDir = new File(newParent);
if (!destinationDir.exists()) {
// The destination does not exist so we should fail.
throw new FileNotFoundException("The source does not exist");
}
// Figure out where we should be copying to
final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
Uri dstNativeUri = toNativeUri(destinationURL);
Uri srcNativeUri = srcFs.toNativeUri(srcURL);
// Check to see if source and destination are the same file
if (dstNativeUri.equals(srcNativeUri)) {
throw new InvalidModificationException("Can't copy onto itself");
}
if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
throw new InvalidModificationException("Source URL is read-only (cannot move)");
}
File destFile = new File(dstNativeUri.getPath());
if (destFile.exists()) {
if (!srcURL.isDirectory && destFile.isDirectory()) {
throw new InvalidModificationException("Can't copy/move a file to an existing directory");
} else if (srcURL.isDirectory && destFile.isFile()) {
throw new InvalidModificationException("Can't copy/move a directory to an existing file");
}
}
if (srcURL.isDirectory) {
// E.g. Copy /sdcard/myDir to /sdcard/myDir/backup
if (dstNativeUri.toString().startsWith(srcNativeUri.toString() + '/')) {
throw new InvalidModificationException("Can't copy directory into itself");
}
copyDirectory(srcFs, srcURL, destFile, move);
} else {
copyFile(srcFs, srcURL, destFile, move);
}
return makeEntryForURL(destinationURL);
}
@Override
public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
boolean append = false;
if (offset > 0) {
this.truncateFileAtURL(inputURL, offset);
append = true;
}
byte[] rawData;
if (isBinary) {
rawData = Base64.decode(data, Base64.DEFAULT);
} else {
rawData = data.getBytes();
}
ByteArrayInputStream in = new ByteArrayInputStream(rawData);
try
{
byte buff[] = new byte[rawData.length];
String absolutePath = filesystemPathForURL(inputURL);
FileOutputStream out = new FileOutputStream(absolutePath, append);
try {
in.read(buff, 0, buff.length);
out.write(buff, 0, rawData.length);
out.flush();
} finally {
// Always close the output
out.close();
}
if (isPublicDirectory(absolutePath)) {
broadcastNewFile(Uri.fromFile(new File(absolutePath)));
}
}
catch (NullPointerException e)
{
// This is a bug in the Android implementation of the Java Stack
NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
throw realException;
}
return rawData.length;
}
private boolean isPublicDirectory(String absolutePath) {
// TODO: should expose a way to scan app's private files (maybe via a flag).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Lollipop has a bug where SD cards are null.
for (File f : context.getExternalMediaDirs()) {
if(f != null && absolutePath.startsWith(f.getAbsolutePath())) {
return true;
}
}
}
String extPath = Environment.getExternalStorageDirectory().getAbsolutePath();
return absolutePath.startsWith(extPath);
}
/**
* Send broadcast of new file so files appear over MTP
*/
private void broadcastNewFile(Uri nativeUri) {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, nativeUri);
context.sendBroadcast(intent);
}
@Override
public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
File file = new File(filesystemPathForURL(inputURL));
if (!file.exists()) {
throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
}
RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
try {
if (raf.length() >= size) {
FileChannel channel = raf.getChannel();
channel.truncate(size);
return size;
}
return raf.length();
} finally {
raf.close();
}
}
@Override
public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
String path = filesystemPathForURL(inputURL);
File file = new File(path);
return file.exists();
}
// This is a copy & paste from CordovaResource API that is required since CordovaResourceApi
// has a bug pre-4.0.0.
// TODO: Once cordova-android@4.0.0 is released, delete this copy and make the plugin depend on
// 4.0.0 with an engine tag.
private static void copyResource(CordovaResourceApi.OpenForReadResult input, OutputStream outputStream) throws IOException {
try {
InputStream inputStream = input.inputStream;
if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
long offset = 0;
long length = input.length;
if (input.assetFd != null) {
offset = input.assetFd.getStartOffset();
}
// transferFrom()'s 2nd arg is a relative position. Need to set the absolute
// position first.
inChannel.position(offset);
outChannel.transferFrom(inChannel, 0, length);
} else {
final int BUFFER_SIZE = 8192;
byte[] buffer = new byte[BUFFER_SIZE];
for (;;) {
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
if (bytesRead <= 0) {
break;
}
outputStream.write(buffer, 0, bytesRead);
}
}
} finally {
input.inputStream.close();
if (outputStream != null) {
outputStream.close();
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.net.Uri;
public class LocalFilesystemURL {
public static final String FILESYSTEM_PROTOCOL = "cdvfile";
public final Uri uri;
public final String fsName;
public final String path;
public final boolean isDirectory;
private LocalFilesystemURL(Uri uri, String fsName, String fsPath, boolean isDirectory) {
this.uri = uri;
this.fsName = fsName;
this.path = fsPath;
this.isDirectory = isDirectory;
}
public static LocalFilesystemURL parse(Uri uri) {
if (!FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
return null;
}
String path = uri.getPath();
if (path.length() < 1) {
return null;
}
int firstSlashIdx = path.indexOf('/', 1);
if (firstSlashIdx < 0) {
return null;
}
String fsName = path.substring(1, firstSlashIdx);
path = path.substring(firstSlashIdx);
boolean isDirectory = path.charAt(path.length() - 1) == '/';
return new LocalFilesystemURL(uri, fsName, path, isDirectory);
}
public static LocalFilesystemURL parse(String uri) {
return parse(Uri.parse(uri));
}
public String toString() {
return uri.toString();
}
}

View File

@ -0,0 +1,29 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
@SuppressWarnings("serial")
public class NoModificationAllowedException extends Exception {
public NoModificationAllowedException(String message) {
super(message);
}
}

View File

@ -0,0 +1,94 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
import android.util.SparseArray;
import org.apache.cordova.CallbackContext;
/**
* Holds pending runtime permission requests
*/
class PendingRequests {
private int currentReqId = 0;
private SparseArray<Request> requests = new SparseArray<Request>();
/**
* Creates a request and adds it to the array of pending requests. Each created request gets a
* unique result code for use with requestPermission()
* @param rawArgs The raw arguments passed to the plugin
* @param action The action this request corresponds to (get file, etc.)
* @param callbackContext The CallbackContext for this plugin call
* @return The request code that can be used to retrieve the Request object
*/
public synchronized int createRequest(String rawArgs, int action, CallbackContext callbackContext) {
Request req = new Request(rawArgs, action, callbackContext);
requests.put(req.requestCode, req);
return req.requestCode;
}
/**
* Gets the request corresponding to this request code and removes it from the pending requests
* @param requestCode The request code for the desired request
* @return The request corresponding to the given request code or null if such a
* request is not found
*/
public synchronized Request getAndRemove(int requestCode) {
Request result = requests.get(requestCode);
requests.remove(requestCode);
return result;
}
/**
* Holds the options and CallbackContext for a call made to the plugin.
*/
public class Request {
// Unique int used to identify this request in any Android permission callback
private int requestCode;
// Action to be performed after permission request result
private int action;
// Raw arguments passed to plugin
private String rawArgs;
// The callback context for this plugin request
private CallbackContext callbackContext;
private Request(String rawArgs, int action, CallbackContext callbackContext) {
this.rawArgs = rawArgs;
this.action = action;
this.callbackContext = callbackContext;
this.requestCode = currentReqId ++;
}
public int getAction() {
return this.action;
}
public String getRawArgs() {
return rawArgs;
}
public CallbackContext getCallbackContext() {
return callbackContext;
}
}
}

View File

@ -0,0 +1,30 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.file;
@SuppressWarnings("serial")
public class TypeMismatchException extends Exception {
public TypeMismatchException(String message) {
super(message);
}
}

View File

@ -0,0 +1,30 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "CDVFile.h"
extern NSString* const kCDVAssetsLibraryPrefix;
extern NSString* const kCDVAssetsLibraryScheme;
@interface CDVAssetLibraryFilesystem : NSObject<CDVFileSystem> {
}
- (id) initWithName:(NSString *)name;
@end

View File

@ -0,0 +1,253 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "CDVFile.h"
#import "CDVAssetLibraryFilesystem.h"
#import <Cordova/CDV.h>
#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <MobileCoreServices/MobileCoreServices.h>
NSString* const kCDVAssetsLibraryPrefix = @"assets-library://";
NSString* const kCDVAssetsLibraryScheme = @"assets-library";
@implementation CDVAssetLibraryFilesystem
@synthesize name=_name, urlTransformer;
/*
The CDVAssetLibraryFilesystem works with resources which are identified
by iOS as
asset-library://<path>
and represents them internally as URLs of the form
cdvfile://localhost/assets-library/<path>
*/
- (NSURL *)assetLibraryURLForLocalURL:(CDVFilesystemURL *)url
{
if ([url.url.scheme isEqualToString:kCDVFilesystemURLPrefix]) {
NSString *path = [[url.url absoluteString] substringFromIndex:[@"cdvfile://localhost/assets-library" length]];
return [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@", path]];
}
return url.url;
}
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
{
NSDictionary* entry = [self makeEntryForLocalURL:url];
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
}
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
return [self makeEntryForPath:url.fullPath isDirectory:NO];
}
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
{
NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
NSString* lastPart = [fullPath lastPathComponent];
if (isDir && ![fullPath hasSuffix:@"/"]) {
fullPath = [fullPath stringByAppendingString:@"/"];
}
[dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
[dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
[dirEntry setObject:fullPath forKey:@"fullPath"];
[dirEntry setObject:lastPart forKey:@"name"];
[dirEntry setObject:self.name forKey: @"filesystemName"];
NSURL* nativeURL = [NSURL URLWithString:[NSString stringWithFormat:@"assets-library:/%@",fullPath]];
if (self.urlTransformer) {
nativeURL = self.urlTransformer(nativeURL);
}
dirEntry[@"nativeURL"] = [nativeURL absoluteString];
return dirEntry;
}
/* helper function to get the mimeType from the file extension
* IN:
* NSString* fullPath - filename (may include path)
* OUT:
* NSString* the mime type as type/subtype. nil if not able to determine
*/
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
{
NSString* mimeType = nil;
if (fullPath) {
CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
if (typeId) {
mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
if (!mimeType) {
// special case for m4a
if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
mimeType = @"audio/mp4";
} else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
mimeType = @"audio/wav";
} else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
mimeType = @"text/css";
}
}
CFRelease(typeId);
}
}
return mimeType;
}
- (id)initWithName:(NSString *)name
{
if (self) {
self.name = name;
}
return self;
}
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
{
// return unsupported result for assets-library URLs
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."];
}
- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
{
// we don't (yet?) support getting the parent of an asset
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR];
}
- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
{
// setMetadata doesn't make sense for asset library files
return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
}
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
{
// return error for assets-library URLs
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
}
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
{
// return error for assets-library URLs
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."];
}
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
{
// return unsupported result for assets-library URLs
return [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."];
}
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
{
// assets-library files can't be truncated
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
}
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
{
// text can't be written into assets-library files
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR];
}
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
{
// Copying to an assets library file is not doable, since we can't write it.
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
callback(result);
}
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
{
NSString *path = nil;
if ([[url.url scheme] isEqualToString:kCDVAssetsLibraryScheme]) {
path = [url.url path];
} else {
path = url.fullPath;
}
if ([path hasSuffix:@"/"]) {
path = [path substringToIndex:([path length]-1)];
}
return path;
}
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
{
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
if (asset) {
// We have the asset! Get the data and send it off.
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
NSUInteger size = (end > start) ? (end - start) : [assetRepresentation size];
Byte* buffer = (Byte*)malloc(size);
NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:start length:size error:nil];
NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES];
NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType);
callback(data, MIMEType, NO_ERROR);
} else {
callback(nil, nil, NOT_FOUND_ERR);
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
// Retrieving the asset failed for some reason. Send the appropriate error.
NSLog(@"Error: %@", error);
callback(nil, nil, SECURITY_ERR);
};
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
}
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
{
// In this case, we need to use an asynchronous method to retrieve the file.
// Because of this, we can't just assign to `result` and send it at the end of the method.
// Instead, we return after calling the asynchronous method and send `result` in each of the blocks.
ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset* asset) {
if (asset) {
// We have the asset! Populate the dictionary and send it off.
NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation];
[fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"];
[fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
NSString* filename = [assetRepresentation filename];
[fileInfo setObject:filename forKey:@"name"];
[fileInfo setObject:[CDVAssetLibraryFilesystem getMimeTypeFromPath:filename] forKey:@"type"];
NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate];
NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000];
[fileInfo setObject:msDate forKey:@"lastModifiedDate"];
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]);
} else {
// We couldn't find the asset. Send the appropriate error.
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]);
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError* error) {
// Retrieving the asset failed for some reason. Send the appropriate error.
callback([CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]);
};
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:[self assetLibraryURLForLocalURL:localURL] resultBlock:resultBlock failureBlock:failureBlock];
return;
}
@end

View File

@ -0,0 +1,157 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import <Foundation/Foundation.h>
#import <Cordova/CDVPlugin.h>
NSString* const kCDVAssetsLibraryPrefix;
NSString* const kCDVFilesystemURLPrefix;
enum CDVFileError {
NO_ERROR = 0,
NOT_FOUND_ERR = 1,
SECURITY_ERR = 2,
ABORT_ERR = 3,
NOT_READABLE_ERR = 4,
ENCODING_ERR = 5,
NO_MODIFICATION_ALLOWED_ERR = 6,
INVALID_STATE_ERR = 7,
SYNTAX_ERR = 8,
INVALID_MODIFICATION_ERR = 9,
QUOTA_EXCEEDED_ERR = 10,
TYPE_MISMATCH_ERR = 11,
PATH_EXISTS_ERR = 12
};
typedef int CDVFileError;
@interface CDVFilesystemURL : NSObject {
NSURL *_url;
NSString *_fileSystemName;
NSString *_fullPath;
}
- (id) initWithString:(NSString*)strURL;
- (id) initWithURL:(NSURL*)URL;
+ (CDVFilesystemURL *)fileSystemURLWithString:(NSString *)strURL;
+ (CDVFilesystemURL *)fileSystemURLWithURL:(NSURL *)URL;
- (NSString *)absoluteURL;
@property (atomic) NSURL *url;
@property (atomic) NSString *fileSystemName;
@property (atomic) NSString *fullPath;
@end
@interface CDVFilesystemURLProtocol : NSURLProtocol
@end
@protocol CDVFileSystem
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url;
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options;
- (CDVPluginResult *)getParentForURL:(CDVFilesystemURL *)localURI;
- (CDVPluginResult *)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options;
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI;
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI;
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI;
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos;
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend;
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback;
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback;
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback;
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url;
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir;
@property (nonatomic,strong) NSString *name;
@property (nonatomic, copy) NSURL*(^urlTransformer)(NSURL*);
@optional
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)localURI;
- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path;
@end
@interface CDVFile : CDVPlugin {
NSString* rootDocsPath;
NSString* appDocsPath;
NSString* appLibraryPath;
NSString* appTempPath;
NSMutableArray* fileSystems_;
BOOL userHasAllowed;
}
- (NSNumber*)checkFreeDiskSpace:(NSString*)appPath;
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath fileSystemName:(NSString *)fsName isDirectory:(BOOL)isDir;
- (NSDictionary *)makeEntryForURL:(NSURL *)URL;
- (CDVFilesystemURL *)fileSystemURLforLocalPath:(NSString *)localPath;
- (NSObject<CDVFileSystem> *)filesystemForURL:(CDVFilesystemURL *)localURL;
/* Native Registration API */
- (void)registerFilesystem:(NSObject<CDVFileSystem> *)fs;
- (NSObject<CDVFileSystem> *)fileSystemByName:(NSString *)fsName;
/* Exec API */
- (void)requestFileSystem:(CDVInvokedUrlCommand*)command;
- (void)resolveLocalFileSystemURI:(CDVInvokedUrlCommand*)command;
- (void)getDirectory:(CDVInvokedUrlCommand*)command;
- (void)getFile:(CDVInvokedUrlCommand*)command;
- (void)getParent:(CDVInvokedUrlCommand*)command;
- (void)removeRecursively:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand*)command;
- (void)copyTo:(CDVInvokedUrlCommand*)command;
- (void)moveTo:(CDVInvokedUrlCommand*)command;
- (void)getFileMetadata:(CDVInvokedUrlCommand*)command;
- (void)readEntries:(CDVInvokedUrlCommand*)command;
- (void)readAsText:(CDVInvokedUrlCommand*)command;
- (void)readAsDataURL:(CDVInvokedUrlCommand*)command;
- (void)readAsArrayBuffer:(CDVInvokedUrlCommand*)command;
- (void)write:(CDVInvokedUrlCommand*)command;
- (void)testFileExists:(CDVInvokedUrlCommand*)command;
- (void)testDirectoryExists:(CDVInvokedUrlCommand*)command;
- (void)getFreeDiskSpace:(CDVInvokedUrlCommand*)command;
- (void)truncate:(CDVInvokedUrlCommand*)command;
- (void)doCopyMove:(CDVInvokedUrlCommand*)command isCopy:(BOOL)bCopy;
/* Compatibilty with older File API */
- (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
- (NSDictionary *)getDirectoryEntry:(NSString *)target isDirectory:(BOOL)bDirRequest;
/* Conversion between filesystem paths and URLs */
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)URL;
/* Internal methods for testing */
- (void)_getLocalFilesystemPath:(CDVInvokedUrlCommand*)command;
@property (nonatomic, strong) NSString* rootDocsPath;
@property (nonatomic, strong) NSString* appDocsPath;
@property (nonatomic, strong) NSString* appLibraryPath;
@property (nonatomic, strong) NSString* appTempPath;
@property (nonatomic, strong) NSString* persistentPath;
@property (nonatomic, strong) NSString* temporaryPath;
@property (nonatomic, strong) NSMutableArray* fileSystems;
@property BOOL userHasAllowed;
@end
#define kW3FileTemporary @"temporary"
#define kW3FilePersistent @"persistent"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "CDVFile.h"
@interface CDVLocalFilesystem : NSObject<CDVFileSystem> {
NSString *_name;
NSString *_fsRoot;
}
- (id) initWithName:(NSString *)name root:(NSString *)fsRoot;
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath;
@property (nonatomic,strong) NSString *fsRoot;
@end

View File

@ -0,0 +1,734 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "CDVFile.h"
#import "CDVLocalFilesystem.h"
#import <Cordova/CDV.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <sys/xattr.h>
@implementation CDVLocalFilesystem
@synthesize name=_name, fsRoot=_fsRoot, urlTransformer;
- (id) initWithName:(NSString *)name root:(NSString *)fsRoot
{
if (self) {
self.name = name;
self.fsRoot = fsRoot;
}
return self;
}
/*
* IN
* NSString localURI
* OUT
* CDVPluginResult result containing a file or directoryEntry for the localURI, or an error if the
* URI represents a non-existent path, or is unrecognized or otherwise malformed.
*/
- (CDVPluginResult *)entryForLocalURI:(CDVFilesystemURL *)url
{
CDVPluginResult* result = nil;
NSDictionary* entry = [self makeEntryForLocalURL:url];
if (entry) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:entry];
} else {
// return NOT_FOUND_ERR
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
}
return result;
}
- (NSDictionary *)makeEntryForLocalURL:(CDVFilesystemURL *)url {
NSString *path = [self filesystemPathForURL:url];
NSFileManager* fileMgr = [[NSFileManager alloc] init];
BOOL isDir = NO;
// see if exists and is file or dir
BOOL bExists = [fileMgr fileExistsAtPath:path isDirectory:&isDir];
if (bExists) {
return [self makeEntryForPath:url.fullPath isDirectory:isDir];
} else {
return nil;
}
}
- (NSDictionary*)makeEntryForPath:(NSString*)fullPath isDirectory:(BOOL)isDir
{
NSMutableDictionary* dirEntry = [NSMutableDictionary dictionaryWithCapacity:5];
NSString* lastPart = [[self stripQueryParametersFromPath:fullPath] lastPathComponent];
if (isDir && ![fullPath hasSuffix:@"/"]) {
fullPath = [fullPath stringByAppendingString:@"/"];
}
[dirEntry setObject:[NSNumber numberWithBool:!isDir] forKey:@"isFile"];
[dirEntry setObject:[NSNumber numberWithBool:isDir] forKey:@"isDirectory"];
[dirEntry setObject:fullPath forKey:@"fullPath"];
[dirEntry setObject:lastPart forKey:@"name"];
[dirEntry setObject:self.name forKey: @"filesystemName"];
NSURL* nativeURL = [NSURL fileURLWithPath:[self filesystemPathForFullPath:fullPath]];
if (self.urlTransformer) {
nativeURL = self.urlTransformer(nativeURL);
}
dirEntry[@"nativeURL"] = [nativeURL absoluteString];
return dirEntry;
}
- (NSString *)stripQueryParametersFromPath:(NSString *)fullPath
{
NSRange questionMark = [fullPath rangeOfString:@"?"];
if (questionMark.location != NSNotFound) {
return [fullPath substringWithRange:NSMakeRange(0,questionMark.location)];
}
return fullPath;
}
- (NSString *)filesystemPathForFullPath:(NSString *)fullPath
{
NSString *path = nil;
NSString *strippedFullPath = [self stripQueryParametersFromPath:fullPath];
path = [NSString stringWithFormat:@"%@%@", self.fsRoot, strippedFullPath];
if ([path length] > 1 && [path hasSuffix:@"/"]) {
path = [path substringToIndex:([path length]-1)];
}
return path;
}
/*
* IN
* NSString localURI
* OUT
* NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
* The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
* or if the URL is malformed.
* The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
*/
- (NSString *)filesystemPathForURL:(CDVFilesystemURL *)url
{
return [self filesystemPathForFullPath:url.fullPath];
}
- (CDVFilesystemURL *)URLforFullPath:(NSString *)fullPath
{
if (fullPath) {
NSString* escapedPath = [fullPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([fullPath hasPrefix:@"/"]) {
return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
}
return [CDVFilesystemURL fileSystemURLWithString:[NSString stringWithFormat:@"%@://localhost/%@/%@", kCDVFilesystemURLPrefix, self.name, escapedPath]];
}
return nil;
}
- (CDVFilesystemURL *)URLforFilesystemPath:(NSString *)path
{
return [self URLforFullPath:[self fullPathForFileSystemPath:path]];
}
- (NSString *)normalizePath:(NSString *)rawPath
{
// If this is an absolute path, the first path component will be '/'. Skip it if that's the case
BOOL isAbsolutePath = [rawPath hasPrefix:@"/"];
if (isAbsolutePath) {
rawPath = [rawPath substringFromIndex:1];
}
NSMutableArray *components = [NSMutableArray arrayWithArray:[rawPath pathComponents]];
for (int index = 0; index < [components count]; ++index) {
if ([[components objectAtIndex:index] isEqualToString:@".."]) {
[components removeObjectAtIndex:index];
if (index > 0) {
[components removeObjectAtIndex:index-1];
--index;
}
}
}
if (isAbsolutePath) {
return [NSString stringWithFormat:@"/%@", [components componentsJoinedByString:@"/"]];
} else {
return [components componentsJoinedByString:@"/"];
}
}
- (BOOL)valueForKeyIsNumber:(NSDictionary*)dict key:(NSString*)key
{
BOOL bNumber = NO;
NSObject* value = dict[key];
if (value) {
bNumber = [value isKindOfClass:[NSNumber class]];
}
return bNumber;
}
- (CDVPluginResult *)getFileForURL:(CDVFilesystemURL *)baseURI requestedPath:(NSString *)requestedPath options:(NSDictionary *)options
{
CDVPluginResult* result = nil;
BOOL bDirRequest = NO;
BOOL create = NO;
BOOL exclusive = NO;
int errorCode = 0; // !!! risky - no error code currently defined for 0
if ([self valueForKeyIsNumber:options key:@"create"]) {
create = [(NSNumber*)[options valueForKey:@"create"] boolValue];
}
if ([self valueForKeyIsNumber:options key:@"exclusive"]) {
exclusive = [(NSNumber*)[options valueForKey:@"exclusive"] boolValue];
}
if ([self valueForKeyIsNumber:options key:@"getDir"]) {
// this will not exist for calls directly to getFile but will have been set by getDirectory before calling this method
bDirRequest = [(NSNumber*)[options valueForKey:@"getDir"] boolValue];
}
// see if the requested path has invalid characters - should we be checking for more than just ":"?
if ([requestedPath rangeOfString:@":"].location != NSNotFound) {
errorCode = ENCODING_ERR;
} else {
// Build new fullPath for the requested resource.
// We concatenate the two paths together, and then scan the resulting string to remove
// parent ("..") references. Any parent references at the beginning of the string are
// silently removed.
NSString *combinedPath = [baseURI.fullPath stringByAppendingPathComponent:requestedPath];
combinedPath = [self normalizePath:combinedPath];
CDVFilesystemURL* requestedURL = [self URLforFullPath:combinedPath];
NSFileManager* fileMgr = [[NSFileManager alloc] init];
BOOL bIsDir;
BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:requestedURL] isDirectory:&bIsDir];
if (bExists && (create == NO) && (bIsDir == !bDirRequest)) {
// path exists and is not of requested type - return TYPE_MISMATCH_ERR
errorCode = TYPE_MISMATCH_ERR;
} else if (!bExists && (create == NO)) {
// path does not exist and create is false - return NOT_FOUND_ERR
errorCode = NOT_FOUND_ERR;
} else if (bExists && (create == YES) && (exclusive == YES)) {
// file/dir already exists and exclusive and create are both true - return PATH_EXISTS_ERR
errorCode = PATH_EXISTS_ERR;
} else {
// if bExists and create == YES - just return data
// if bExists and create == NO - just return data
// if !bExists and create == YES - create and return data
BOOL bSuccess = YES;
NSError __autoreleasing* pError = nil;
if (!bExists && (create == YES)) {
if (bDirRequest) {
// create the dir
bSuccess = [fileMgr createDirectoryAtPath:[self filesystemPathForURL:requestedURL] withIntermediateDirectories:NO attributes:nil error:&pError];
} else {
// create the empty file
bSuccess = [fileMgr createFileAtPath:[self filesystemPathForURL:requestedURL] contents:nil attributes:nil];
}
}
if (!bSuccess) {
errorCode = ABORT_ERR;
if (pError) {
NSLog(@"error creating directory: %@", [pError localizedDescription]);
}
} else {
// NSLog(@"newly created file/dir (%@) exists: %d", reqFullPath, [fileMgr fileExistsAtPath:reqFullPath]);
// file existed or was created
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:requestedURL.fullPath isDirectory:bDirRequest]];
}
} // are all possible conditions met?
}
if (errorCode > 0) {
// create error callback
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
}
return result;
}
- (CDVPluginResult*)getParentForURL:(CDVFilesystemURL *)localURI
{
CDVPluginResult* result = nil;
CDVFilesystemURL *newURI = nil;
if ([localURI.fullPath isEqualToString:@""]) {
// return self
newURI = localURI;
} else {
newURI = [CDVFilesystemURL fileSystemURLWithURL:[localURI.url URLByDeletingLastPathComponent]]; /* TODO: UGLY - FIX */
}
NSFileManager* fileMgr = [[NSFileManager alloc] init];
BOOL bIsDir;
BOOL bExists = [fileMgr fileExistsAtPath:[self filesystemPathForURL:newURI] isDirectory:&bIsDir];
if (bExists) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self makeEntryForPath:newURI.fullPath isDirectory:bIsDir]];
} else {
// invalid path or file does not exist
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
}
return result;
}
- (CDVPluginResult*)setMetadataForURL:(CDVFilesystemURL *)localURI withObject:(NSDictionary *)options
{
BOOL ok = NO;
NSString* filePath = [self filesystemPathForURL:localURI];
// we only care about this iCloud key for now.
// set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute)
NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup";
id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey];
if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) {
if (IsAtLeastiOSVersion(@"5.1")) {
NSURL* url = [NSURL fileURLWithPath:filePath];
NSError* __autoreleasing error = nil;
ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error];
} else { // below 5.1 (deprecated - only really supported in 5.01)
u_int8_t value = [iCloudBackupExtendedAttributeValue intValue];
if (value == 0) { // remove the attribute (allow backup, the default)
ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0);
} else { // set the attribute (skip backup)
ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0);
}
}
}
if (ok) {
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else {
return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
}
}
/* remove the file or directory (recursively)
* IN:
* NSString* fullPath - the full path to the file or directory to be removed
* NSString* callbackId
* called from remove and removeRecursively - check all pubic api specific error conditions (dir not empty, etc) before calling
*/
- (CDVPluginResult*)doRemove:(NSString*)fullPath
{
CDVPluginResult* result = nil;
BOOL bSuccess = NO;
NSError* __autoreleasing pError = nil;
NSFileManager* fileMgr = [[NSFileManager alloc] init];
@try {
bSuccess = [fileMgr removeItemAtPath:fullPath error:&pError];
if (bSuccess) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else {
// see if we can give a useful error
CDVFileError errorCode = ABORT_ERR;
NSLog(@"error removing filesystem entry at %@: %@", fullPath, [pError localizedDescription]);
if ([pError code] == NSFileNoSuchFileError) {
errorCode = NOT_FOUND_ERR;
} else if ([pError code] == NSFileWriteNoPermissionError) {
errorCode = NO_MODIFICATION_ALLOWED_ERR;
}
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
}
} @catch(NSException* e) { // NSInvalidArgumentException if path is . or ..
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:SYNTAX_ERR];
}
return result;
}
- (CDVPluginResult *)removeFileAtURL:(CDVFilesystemURL *)localURI
{
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
NSFileManager* fileMgr = [[NSFileManager alloc] init];
BOOL bIsDir = NO;
BOOL bExists = [fileMgr fileExistsAtPath:fileSystemPath isDirectory:&bIsDir];
if (!bExists) {
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
}
if (bIsDir && ([[fileMgr contentsOfDirectoryAtPath:fileSystemPath error:nil] count] != 0)) {
// dir is not empty
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:INVALID_MODIFICATION_ERR];
}
return [self doRemove:fileSystemPath];
}
- (CDVPluginResult *)recursiveRemoveFileAtURL:(CDVFilesystemURL *)localURI
{
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
return [self doRemove:fileSystemPath];
}
/*
* IN
* NSString localURI
* OUT
* NSString full local filesystem path for the represented file or directory, or nil if no such path is possible
* The file or directory does not necessarily have to exist. nil is returned if the filesystem type is not recognized,
* or if the URL is malformed.
* The incoming URI should be properly escaped (no raw spaces, etc. URI percent-encoding is expected).
*/
- (NSString *)fullPathForFileSystemPath:(NSString *)fsPath
{
if ([fsPath hasPrefix:self.fsRoot]) {
return [fsPath substringFromIndex:[self.fsRoot length]];
}
return nil;
}
- (CDVPluginResult *)readEntriesAtURL:(CDVFilesystemURL *)localURI
{
NSFileManager* fileMgr = [[NSFileManager alloc] init];
NSError* __autoreleasing error = nil;
NSString *fileSystemPath = [self filesystemPathForURL:localURI];
NSArray* contents = [fileMgr contentsOfDirectoryAtPath:fileSystemPath error:&error];
if (contents) {
NSMutableArray* entries = [NSMutableArray arrayWithCapacity:1];
if ([contents count] > 0) {
// create an Entry (as JSON) for each file/dir
for (NSString* name in contents) {
// see if is dir or file
NSString* entryPath = [fileSystemPath stringByAppendingPathComponent:name];
BOOL bIsDir = NO;
[fileMgr fileExistsAtPath:entryPath isDirectory:&bIsDir];
NSDictionary* entryDict = [self makeEntryForPath:[self fullPathForFileSystemPath:entryPath] isDirectory:bIsDir];
[entries addObject:entryDict];
}
}
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:entries];
} else {
// assume not found but could check error for more specific error conditions
return [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR];
}
}
- (unsigned long long)truncateFile:(NSString*)filePath atPosition:(unsigned long long)pos
{
unsigned long long newPos = 0UL;
NSFileHandle* file = [NSFileHandle fileHandleForWritingAtPath:filePath];
if (file) {
[file truncateFileAtOffset:(unsigned long long)pos];
newPos = [file offsetInFile];
[file synchronizeFile];
[file closeFile];
}
return newPos;
}
- (CDVPluginResult *)truncateFileAtURL:(CDVFilesystemURL *)localURI atPosition:(unsigned long long)pos
{
unsigned long long newPos = [self truncateFile:[self filesystemPathForURL:localURI] atPosition:pos];
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)newPos];
}
- (CDVPluginResult *)writeToFileAtURL:(CDVFilesystemURL *)localURL withData:(NSData*)encData append:(BOOL)shouldAppend
{
NSString *filePath = [self filesystemPathForURL:localURL];
CDVPluginResult* result = nil;
CDVFileError errCode = INVALID_MODIFICATION_ERR;
int bytesWritten = 0;
if (filePath) {
NSOutputStream* fileStream = [NSOutputStream outputStreamToFileAtPath:filePath append:shouldAppend];
if (fileStream) {
NSUInteger len = [encData length];
if (len == 0) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:(double)len];
} else {
[fileStream open];
bytesWritten = (int)[fileStream write:[encData bytes] maxLength:len];
[fileStream close];
if (bytesWritten > 0) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:bytesWritten];
// } else {
// can probably get more detailed error info via [fileStream streamError]
// errCode already set to INVALID_MODIFICATION_ERR;
// bytesWritten = 0; // may be set to -1 on error
}
}
} // else fileStream not created return INVALID_MODIFICATION_ERR
} else {
// invalid filePath
errCode = NOT_FOUND_ERR;
}
if (!result) {
// was an error
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errCode];
}
return result;
}
/**
* Helper function to check to see if the user attempted to copy an entry into its parent without changing its name,
* or attempted to copy a directory into a directory that it contains directly or indirectly.
*
* IN:
* NSString* srcDir
* NSString* destinationDir
* OUT:
* YES copy/ move is allows
* NO move is onto itself
*/
- (BOOL)canCopyMoveSrc:(NSString*)src ToDestination:(NSString*)dest
{
// This weird test is to determine if we are copying or moving a directory into itself.
// Copy /Documents/myDir to /Documents/myDir-backup is okay but
// Copy /Documents/myDir to /Documents/myDir/backup not okay
BOOL copyOK = YES;
NSRange range = [dest rangeOfString:src];
if (range.location != NSNotFound) {
NSRange testRange = {range.length - 1, ([dest length] - range.length)};
NSRange resultRange = [dest rangeOfString:@"/" options:0 range:testRange];
if (resultRange.location != NSNotFound) {
copyOK = NO;
}
}
return copyOK;
}
- (void)copyFileToURL:(CDVFilesystemURL *)destURL withName:(NSString *)newName fromFileSystem:(NSObject<CDVFileSystem> *)srcFs atURL:(CDVFilesystemURL *)srcURL copy:(BOOL)bCopy callback:(void (^)(CDVPluginResult *))callback
{
NSFileManager *fileMgr = [[NSFileManager alloc] init];
NSString *destRootPath = [self filesystemPathForURL:destURL];
BOOL bDestIsDir = NO;
BOOL bDestExists = [fileMgr fileExistsAtPath:destRootPath isDirectory:&bDestIsDir];
NSString *newFileSystemPath = [destRootPath stringByAppendingPathComponent:newName];
NSString *newFullPath = [self fullPathForFileSystemPath:newFileSystemPath];
BOOL bNewIsDir = NO;
BOOL bNewExists = [fileMgr fileExistsAtPath:newFileSystemPath isDirectory:&bNewIsDir];
CDVPluginResult *result = nil;
int errCode = 0;
if (!bDestExists) {
// the destination root does not exist
errCode = NOT_FOUND_ERR;
}
else if ([srcFs isKindOfClass:[CDVLocalFilesystem class]]) {
/* Same FS, we can shortcut with NSFileManager operations */
NSString *srcFullPath = [srcFs filesystemPathForURL:srcURL];
BOOL bSrcIsDir = NO;
BOOL bSrcExists = [fileMgr fileExistsAtPath:srcFullPath isDirectory:&bSrcIsDir];
if (!bSrcExists) {
// the source does not exist
errCode = NOT_FOUND_ERR;
} else if ([newFileSystemPath isEqualToString:srcFullPath]) {
// source and destination can not be the same
errCode = INVALID_MODIFICATION_ERR;
} else if (bSrcIsDir && (bNewExists && !bNewIsDir)) {
// can't copy/move dir to file
errCode = INVALID_MODIFICATION_ERR;
} else { // no errors yet
NSError* __autoreleasing error = nil;
BOOL bSuccess = NO;
if (bCopy) {
if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
// can't copy dir into self
errCode = INVALID_MODIFICATION_ERR;
} else if (bNewExists) {
// the full destination should NOT already exist if a copy
errCode = PATH_EXISTS_ERR;
} else {
bSuccess = [fileMgr copyItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
}
} else { // move
// iOS requires that destination must not exist before calling moveTo
// is W3C INVALID_MODIFICATION_ERR error if destination dir exists and has contents
//
if (!bSrcIsDir && (bNewExists && bNewIsDir)) {
// can't move a file to directory
errCode = INVALID_MODIFICATION_ERR;
} else if (bSrcIsDir && ![self canCopyMoveSrc:srcFullPath ToDestination:newFileSystemPath]) {
// can't move a dir into itself
errCode = INVALID_MODIFICATION_ERR;
} else if (bNewExists) {
if (bNewIsDir && ([[fileMgr contentsOfDirectoryAtPath:newFileSystemPath error:NULL] count] != 0)) {
// can't move dir to a dir that is not empty
errCode = INVALID_MODIFICATION_ERR;
newFileSystemPath = nil; // so we won't try to move
} else {
// remove destination so can perform the moveItemAtPath
bSuccess = [fileMgr removeItemAtPath:newFileSystemPath error:NULL];
if (!bSuccess) {
errCode = INVALID_MODIFICATION_ERR; // is this the correct error?
newFileSystemPath = nil;
}
}
} else if (bNewIsDir && [newFileSystemPath hasPrefix:srcFullPath]) {
// can't move a directory inside itself or to any child at any depth;
errCode = INVALID_MODIFICATION_ERR;
newFileSystemPath = nil;
}
if (newFileSystemPath != nil) {
bSuccess = [fileMgr moveItemAtPath:srcFullPath toPath:newFileSystemPath error:&error];
}
}
if (bSuccess) {
// should verify it is there and of the correct type???
NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:bSrcIsDir];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
} else {
if (error) {
if (([error code] == NSFileReadUnknownError) || ([error code] == NSFileReadTooLargeError)) {
errCode = NOT_READABLE_ERR;
} else if ([error code] == NSFileWriteOutOfSpaceError) {
errCode = QUOTA_EXCEEDED_ERR;
} else if ([error code] == NSFileWriteNoPermissionError) {
errCode = NO_MODIFICATION_ALLOWED_ERR;
}
}
}
}
} else {
// Need to copy the hard way
[srcFs readFileAtURL:srcURL start:0 end:-1 callback:^(NSData* data, NSString* mimeType, CDVFileError errorCode) {
CDVPluginResult* result = nil;
if (data != nil) {
BOOL bSuccess = [data writeToFile:newFileSystemPath atomically:YES];
if (bSuccess) {
// should verify it is there and of the correct type???
NSDictionary* newEntry = [self makeEntryForPath:newFullPath isDirectory:NO];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newEntry];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:ABORT_ERR];
}
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errorCode];
}
callback(result);
}];
return; // Async IO; return without callback.
}
if (result == nil) {
if (!errCode) {
errCode = INVALID_MODIFICATION_ERR; // Catch-all default
}
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:errCode];
}
callback(result);
}
/* helper function to get the mimeType from the file extension
* IN:
* NSString* fullPath - filename (may include path)
* OUT:
* NSString* the mime type as type/subtype. nil if not able to determine
*/
+ (NSString*)getMimeTypeFromPath:(NSString*)fullPath
{
NSString* mimeType = nil;
if (fullPath) {
CFStringRef typeId = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[fullPath pathExtension], NULL);
if (typeId) {
mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(typeId, kUTTagClassMIMEType);
if (!mimeType) {
// special case for m4a
if ([(__bridge NSString*)typeId rangeOfString : @"m4a-audio"].location != NSNotFound) {
mimeType = @"audio/mp4";
} else if ([[fullPath pathExtension] rangeOfString:@"wav"].location != NSNotFound) {
mimeType = @"audio/wav";
} else if ([[fullPath pathExtension] rangeOfString:@"css"].location != NSNotFound) {
mimeType = @"text/css";
}
}
CFRelease(typeId);
}
}
return mimeType;
}
- (void)readFileAtURL:(CDVFilesystemURL *)localURL start:(NSInteger)start end:(NSInteger)end callback:(void (^)(NSData*, NSString* mimeType, CDVFileError))callback
{
NSString *path = [self filesystemPathForURL:localURL];
NSString* mimeType = [CDVLocalFilesystem getMimeTypeFromPath:path];
if (mimeType == nil) {
mimeType = @"*/*";
}
NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
if (start > 0) {
[file seekToFileOffset:start];
}
NSData* readData;
if (end < 0) {
readData = [file readDataToEndOfFile];
} else {
readData = [file readDataOfLength:(end - start)];
}
[file closeFile];
callback(readData, mimeType, readData != nil ? NO_ERROR : NOT_FOUND_ERR);
}
- (void)getFileMetadataForURL:(CDVFilesystemURL *)localURL callback:(void (^)(CDVPluginResult *))callback
{
NSString *path = [self filesystemPathForURL:localURL];
CDVPluginResult *result;
NSFileManager* fileMgr = [[NSFileManager alloc] init];
NSError* __autoreleasing error = nil;
NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:path error:&error];
if (fileAttrs) {
// create dictionary of file info
NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5];
[fileInfo setObject:localURL.fullPath forKey:@"fullPath"];
[fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping
[fileInfo setObject:[path lastPathComponent] forKey:@"name"];
// Ensure that directories (and other non-regular files) report size of 0
unsigned long long size = ([fileAttrs fileType] == NSFileTypeRegular ? [fileAttrs fileSize] : 0);
[fileInfo setObject:[NSNumber numberWithUnsignedLongLong:size] forKey:@"size"];
NSDate* modDate = [fileAttrs fileModificationDate];
if (modDate) {
[fileInfo setObject:[NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000] forKey:@"lastModifiedDate"];
}
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo];
} else {
// didn't get fileAttribs
CDVFileError errorCode = ABORT_ERR;
NSLog(@"error getting metadata: %@", [error localizedDescription]);
if ([error code] == NSFileNoSuchFileError || [error code] == NSFileReadNoSuchFileError) {
errorCode = NOT_FOUND_ERR;
}
// log [NSNumber numberWithDouble: theMessage] objCtype to see what it returns
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:errorCode];
}
callback(result);
}
@end

View File

@ -0,0 +1,120 @@
cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileError = require('./FileError'),
DirectoryReader = require('./DirectoryReader');
/**
* An interface representing a directory on the file system.
*
* {boolean} isFile always false (readonly)
* {boolean} isDirectory always true (readonly)
* {DOMString} name of the directory, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the directory (readonly)
* {FileSystem} filesystem on which the directory resides (readonly)
*/
var DirectoryEntry = function(name, fullPath, fileSystem, nativeURL) {
// add trailing slash if it is missing
if ((fullPath) && !/\/$/.test(fullPath)) {
fullPath += "/";
}
// add trailing slash if it is missing
if (nativeURL && !/\/$/.test(nativeURL)) {
nativeURL += "/";
}
DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
};
utils.extend(DirectoryEntry, Entry);
/**
* Creates a new DirectoryReader to read entries from this directory
*/
DirectoryEntry.prototype.createReader = function() {
return new DirectoryReader(this.toInternalURL());
};
/**
* Creates or looks up a directory
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
* @param {Flags} options to create or exclusively create the directory
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getDirectory", [this.toInternalURL(), path, options]);
};
/**
* Deletes a directory and all of it's contents
*
* @param {Function} successCallback is called with no parameters
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "removeRecursively", [this.toInternalURL()]);
};
/**
* Creates or looks up a file
*
* @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
* @param {Flags} options to create or exclusively create the file
* @param {Function} successCallback is called with the new entry
* @param {Function} errorCallback is called with a FileError
*/
DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var FileEntry = require('./FileEntry');
var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFile", [this.toInternalURL(), path, options]);
};
module.exports = DirectoryEntry;
});

View File

@ -0,0 +1,76 @@
cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError') ;
/**
* An interface that lists the files and directories in a directory.
*/
function DirectoryReader(localURL) {
this.localURL = localURL || null;
this.hasReadEntries = false;
}
/**
* Returns a list of entries from a directory.
*
* @param {Function} successCallback is called with a list of entries
* @param {Function} errorCallback is called with a FileError
*/
DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
// If we've already read and passed on this directory's entries, return an empty list.
if (this.hasReadEntries) {
successCallback([]);
return;
}
var reader = this;
var win = typeof successCallback !== 'function' ? null : function(result) {
var retVal = [];
for (var i=0; i<result.length; i++) {
var entry = null;
if (result[i].isDirectory) {
entry = new (require('./DirectoryEntry'))();
}
else if (result[i].isFile) {
entry = new (require('./FileEntry'))();
}
entry.isDirectory = result[i].isDirectory;
entry.isFile = result[i].isFile;
entry.name = result[i].name;
entry.fullPath = result[i].fullPath;
entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
entry.nativeURL = result[i].nativeURL;
retVal.push(entry);
}
reader.hasReadEntries = true;
successCallback(retVal);
};
var fail = typeof errorCallback !== 'function' ? null : function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "readEntries", [this.localURL]);
};
module.exports = DirectoryReader;
});

View File

@ -0,0 +1,265 @@
cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var argscheck = require('cordova/argscheck'),
exec = require('cordova/exec'),
FileError = require('./FileError'),
Metadata = require('./Metadata');
/**
* Represents a file or directory on the local file system.
*
* @param isFile
* {boolean} true if Entry is a file (readonly)
* @param isDirectory
* {boolean} true if Entry is a directory (readonly)
* @param name
* {DOMString} name of the file or directory, excluding the path
* leading to it (readonly)
* @param fullPath
* {DOMString} the absolute full path to the file or directory
* (readonly)
* @param fileSystem
* {FileSystem} the filesystem on which this entry resides
* (readonly)
* @param nativeURL
* {DOMString} an alternate URL which can be used by native
* webview controls, for example media players.
* (optional, readonly)
*/
function Entry(isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
this.isFile = !!isFile;
this.isDirectory = !!isDirectory;
this.name = name || '';
this.fullPath = fullPath || '';
this.filesystem = fileSystem || null;
this.nativeURL = nativeURL || null;
}
/**
* Look up the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
*/
Entry.prototype.getMetadata = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
var success = successCallback && function(entryMetadata) {
var metadata = new Metadata({
size: entryMetadata.size,
modificationTime: entryMetadata.lastModifiedDate
});
successCallback(metadata);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(success, fail, "File", "getFileMetadata", [this.toInternalURL()]);
};
/**
* Set the metadata of the entry.
*
* @param successCallback
* {Function} is called with a Metadata object
* @param errorCallback
* {Function} is called with a FileError
* @param metadataObject
* {Object} keys and values to set
*/
Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) {
argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
exec(successCallback, errorCallback, "File", "setMetadata", [this.toInternalURL(), metadataObject]);
};
/**
* Move a file or directory to a new location.
*
* @param parent
* {DirectoryEntry} the directory to which to move this entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new DirectoryEntry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "moveTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Copy a directory to a different location.
*
* @param parent
* {DirectoryEntry} the directory to which to copy the entry
* @param newName
* {DOMString} new name of the entry, defaults to the current name
* @param successCallback
* {Function} called with the new Entry object
* @param errorCallback
* {Function} called with a FileError
*/
Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
var srcURL = this.toInternalURL(),
// entry name
name = newName || this.name,
// success callback
success = function(entry) {
if (entry) {
if (successCallback) {
// create appropriate Entry object
var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
var fs = newFSName ? new FileSystem(newFSName, { name: "", fullPath: "/" }) : new FileSystem(parent.filesystem.name, { name: "", fullPath: "/" });
var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
successCallback(result);
}
}
else {
// no Entry object returned
if (fail) {
fail(FileError.NOT_FOUND_ERR);
}
}
};
// copy
exec(success, fail, "File", "copyTo", [srcURL, parent.toInternalURL(), name]);
};
/**
* Return a URL that can be passed across the bridge to identify this entry.
*/
Entry.prototype.toInternalURL = function() {
if (this.filesystem && this.filesystem.__format__) {
return this.filesystem.__format__(this.fullPath, this.nativeURL);
}
};
/**
* Return a URL that can be used to identify this entry.
* Use a URL that can be used to as the src attribute of a <video> or
* <audio> tag. If that is not possible, construct a cdvfile:// URL.
*/
Entry.prototype.toURL = function() {
if (this.nativeURL) {
return this.nativeURL;
}
// fullPath attribute may contain the full URL in the case that
// toInternalURL fails.
return this.toInternalURL() || "file://localhost" + this.fullPath;
};
/**
* Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
* cdvfile:// URL, and this method was necessary to obtain URLs usable by the
* webview.
* See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
* and CB-6300.
*/
Entry.prototype.toNativeURL = function() {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Returns a URI that can be used to identify this entry.
*
* @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
* @return uri
*/
Entry.prototype.toURI = function(mimeType) {
console.log("DEPRECATED: Update your code to use 'toURL'");
return this.toURL();
};
/**
* Remove a file or directory. It is an error to attempt to delete a
* directory that is not empty. It is an error to attempt to delete a
* root directory of a file system.
*
* @param successCallback {Function} called with no parameters
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.remove = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.remove', arguments);
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(successCallback, fail, "File", "remove", [this.toInternalURL()]);
};
/**
* Look up the parent DirectoryEntry of this entry.
*
* @param successCallback {Function} called with the parent DirectoryEntry object
* @param errorCallback {Function} called with a FileError
*/
Entry.prototype.getParent = function(successCallback, errorCallback) {
argscheck.checkArgs('FF', 'Entry.getParent', arguments);
var fs = this.filesystem;
var win = successCallback && function(result) {
var DirectoryEntry = require('./DirectoryEntry');
var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
successCallback(entry);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getParent", [this.toInternalURL()]);
};
module.exports = Entry;
});

View File

@ -0,0 +1,82 @@
cordova.define("cordova-plugin-file.File", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Constructor.
* name {DOMString} name of the file, without path information
* fullPath {DOMString} the full path of the file, including the name
* type {DOMString} mime type
* lastModifiedDate {Date} last modified date
* size {Number} size of the file in bytes
*/
var File = function(name, localURL, type, lastModifiedDate, size){
this.name = name || '';
this.localURL = localURL || null;
this.type = type || null;
this.lastModified = lastModifiedDate || null;
// For backwards compatibility, store the timestamp in lastModifiedDate as well
this.lastModifiedDate = lastModifiedDate || null;
this.size = size || 0;
// These store the absolute start and end for slicing the file.
this.start = 0;
this.end = this.size;
};
/**
* Returns a "slice" of the file. Since Cordova Files don't contain the actual
* content, this really returns a File with adjusted start and end.
* Slices of slices are supported.
* start {Number} The index at which to start the slice (inclusive).
* end {Number} The index at which to end the slice (exclusive).
*/
File.prototype.slice = function(start, end) {
var size = this.end - this.start;
var newStart = 0;
var newEnd = size;
if (arguments.length) {
if (start < 0) {
newStart = Math.max(size + start, 0);
} else {
newStart = Math.min(size, start);
}
}
if (arguments.length >= 2) {
if (end < 0) {
newEnd = Math.max(size + end, 0);
} else {
newEnd = Math.min(end, size);
}
}
var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
newFile.start = this.start + newStart;
newFile.end = this.start + newEnd;
return newFile;
};
module.exports = File;
});

View File

@ -0,0 +1,96 @@
cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var utils = require('cordova/utils'),
exec = require('cordova/exec'),
Entry = require('./Entry'),
FileWriter = require('./FileWriter'),
File = require('./File'),
FileError = require('./FileError');
/**
* An interface representing a file on the file system.
*
* {boolean} isFile always true (readonly)
* {boolean} isDirectory always false (readonly)
* {DOMString} name of the file, excluding the path leading to it (readonly)
* {DOMString} fullPath the absolute full path to the file (readonly)
* {FileSystem} filesystem on which the file resides (readonly)
*/
var FileEntry = function(name, fullPath, fileSystem, nativeURL) {
// remove trailing slash if it is present
if (fullPath && /\/$/.test(fullPath)) {
fullPath = fullPath.substring(0, fullPath.length - 1);
}
if (nativeURL && /\/$/.test(nativeURL)) {
nativeURL = nativeURL.substring(0, nativeURL.length - 1);
}
FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
};
utils.extend(FileEntry, Entry);
/**
* Creates a new FileWriter associated with the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new FileWriter
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.createWriter = function(successCallback, errorCallback) {
this.file(function(filePointer) {
var writer = new FileWriter(filePointer);
if (writer.localURL === null || writer.localURL === "") {
if (errorCallback) {
errorCallback(new FileError(FileError.INVALID_STATE_ERR));
}
} else {
if (successCallback) {
successCallback(writer);
}
}
}, errorCallback);
};
/**
* Returns a File that represents the current state of the file that this FileEntry represents.
*
* @param {Function} successCallback is called with the new File object
* @param {Function} errorCallback is called with a FileError
*/
FileEntry.prototype.file = function(successCallback, errorCallback) {
var localURL = this.toInternalURL();
var win = successCallback && function(f) {
var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
successCallback(file);
};
var fail = errorCallback && function(code) {
errorCallback(new FileError(code));
};
exec(win, fail, "File", "getFileMetadata", [localURL]);
};
module.exports = FileEntry;
});

View File

@ -0,0 +1,49 @@
cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileError
*/
function FileError(error) {
this.code = error || null;
}
// File error codes
// Found in DOMException
FileError.NOT_FOUND_ERR = 1;
FileError.SECURITY_ERR = 2;
FileError.ABORT_ERR = 3;
// Added by File API specification
FileError.NOT_READABLE_ERR = 4;
FileError.ENCODING_ERR = 5;
FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
FileError.INVALID_STATE_ERR = 7;
FileError.SYNTAX_ERR = 8;
FileError.INVALID_MODIFICATION_ERR = 9;
FileError.QUOTA_EXCEEDED_ERR = 10;
FileError.TYPE_MISMATCH_ERR = 11;
FileError.PATH_EXISTS_ERR = 12;
module.exports = FileError;
});

View File

@ -0,0 +1,292 @@
cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
modulemapper = require('cordova/modulemapper'),
utils = require('cordova/utils'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent'),
origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
/**
* This class reads the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To read from the SD card, the file name is "sdcard/my_file.txt"
* @constructor
*/
var FileReader = function() {
this._readyState = 0;
this._error = null;
this._result = null;
this._progress = null;
this._localURL = '';
this._realReader = origFileReader ? new origFileReader() : {};
};
/**
* Defines the maximum size to read at a time via the native API. The default value is a compromise between
* minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
* (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
* OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
*/
FileReader.READ_CHUNK_SIZE = 256*1024;
// States
FileReader.EMPTY = 0;
FileReader.LOADING = 1;
FileReader.DONE = 2;
utils.defineGetter(FileReader.prototype, 'readyState', function() {
return this._localURL ? this._readyState : this._realReader.readyState;
});
utils.defineGetter(FileReader.prototype, 'error', function() {
return this._localURL ? this._error: this._realReader.error;
});
utils.defineGetter(FileReader.prototype, 'result', function() {
return this._localURL ? this._result: this._realReader.result;
});
function defineEvent(eventName) {
utils.defineGetterSetter(FileReader.prototype, eventName, function() {
return this._realReader[eventName] || null;
}, function(value) {
this._realReader[eventName] = value;
});
}
defineEvent('onloadstart'); // When the read starts.
defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
defineEvent('onload'); // When the read has successfully completed.
defineEvent('onerror'); // When the read has failed (see errors).
defineEvent('onloadend'); // When the request has completed (either in success or failure).
defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method.
function initRead(reader, file) {
// Already loading something
if (reader.readyState == FileReader.LOADING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
reader._result = null;
reader._error = null;
reader._progress = 0;
reader._readyState = FileReader.LOADING;
if (typeof file.localURL == 'string') {
reader._localURL = file.localURL;
} else {
reader._localURL = '';
return true;
}
if (reader.onloadstart) {
reader.onloadstart(new ProgressEvent("loadstart", {target:reader}));
}
}
/**
* Callback used by the following read* functions to handle incremental or final success.
* Must be bound to the FileReader's this along with all but the last parameter,
* e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
* @param readType The name of the read function to call.
* @param encoding Text encoding, or null if this is not a text type read.
* @param offset Starting offset of the read.
* @param totalSize Total number of bytes or chars to read.
* @param accumulate A function that takes the callback result and accumulates it in this._result.
* @param r Callback result returned by the last read exec() call, or null to begin reading.
*/
function readSuccessCallback(readType, encoding, offset, totalSize, accumulate, r) {
if (this._readyState === FileReader.DONE) {
return;
}
if (typeof r !== "undefined") {
accumulate(r);
this._progress = Math.min(this._progress + FileReader.READ_CHUNK_SIZE, totalSize);
if (typeof this.onprogress === "function") {
this.onprogress(new ProgressEvent("progress", {loaded:this._progress, total:totalSize}));
}
}
if (typeof r === "undefined" || this._progress < totalSize) {
var execArgs = [
this._localURL,
offset + this._progress,
offset + this._progress + Math.min(totalSize - this._progress, FileReader.READ_CHUNK_SIZE)];
if (encoding) {
execArgs.splice(1, 0, encoding);
}
exec(
readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
readFailureCallback.bind(this),
"File", readType, execArgs);
} else {
this._readyState = FileReader.DONE;
if (typeof this.onload === "function") {
this.onload(new ProgressEvent("load", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
}
/**
* Callback used by the following read* functions to handle errors.
* Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
*/
function readFailureCallback(e) {
if (this._readyState === FileReader.DONE) {
return;
}
this._readyState = FileReader.DONE;
this._result = null;
this._error = new FileError(e);
if (typeof this.onerror === "function") {
this.onerror(new ProgressEvent("error", {target:this}));
}
if (typeof this.onloadend === "function") {
this.onloadend(new ProgressEvent("loadend", {target:this}));
}
}
/**
* Abort reading file.
*/
FileReader.prototype.abort = function() {
if (origFileReader && !this._localURL) {
return this._realReader.abort();
}
this._result = null;
if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) {
return;
}
this._readyState = FileReader.DONE;
// If abort callback
if (typeof this.onabort === 'function') {
this.onabort(new ProgressEvent('abort', {target:this}));
}
// If load end callback
if (typeof this.onloadend === 'function') {
this.onloadend(new ProgressEvent('loadend', {target:this}));
}
};
/**
* Read text file.
*
* @param file {File} File object containing file properties
* @param encoding [Optional] (see http://www.iana.org/assignments/character-sets)
*/
FileReader.prototype.readAsText = function(file, encoding) {
if (initRead(this, file)) {
return this._realReader.readAsText(file, encoding);
}
// Default encoding is UTF-8
var enc = encoding ? encoding : "UTF-8";
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsText", enc, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a base64 encoded data url.
* A data url is of the form:
* data:[<mediatype>][;base64],<data>
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsDataURL = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsDataURL(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsDataURL", null, file.start, totalSize, function(r) {
var commaIndex = r.indexOf(',');
if (this._progress === 0) {
this._result = r;
} else {
this._result += r.substring(commaIndex + 1);
}
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsBinaryString = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsBinaryString(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsBinaryString", null, file.start, totalSize, function(r) {
if (this._progress === 0) {
this._result = "";
}
this._result += r;
}.bind(this));
};
/**
* Read file and return data as a binary data.
*
* @param file {File} File object containing file properties
*/
FileReader.prototype.readAsArrayBuffer = function(file) {
if (initRead(this, file)) {
return this._realReader.readAsArrayBuffer(file);
}
var totalSize = file.end - file.start;
readSuccessCallback.bind(this)("readAsArrayBuffer", null, file.start, totalSize, function(r) {
var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
resultArray.set(new Uint8Array(r), this._progress);
this._result = resultArray.buffer;
}.bind(this));
};
module.exports = FileReader;
});

View File

@ -0,0 +1,58 @@
cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var DirectoryEntry = require('./DirectoryEntry');
/**
* An interface representing a file system
*
* @constructor
* {DOMString} name the unique name of the file system (readonly)
* {DirectoryEntry} root directory of the file system (readonly)
*/
var FileSystem = function(name, root) {
this.name = name;
if (root) {
this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
} else {
this.root = new DirectoryEntry(this.name, '/', this);
}
};
FileSystem.prototype.__format__ = function(fullPath, nativeUrl) {
return fullPath;
};
FileSystem.prototype.toJSON = function() {
return "<FileSystem: " + this.name + ">";
};
// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
FileSystem.encodeURIPath = function(path) {
// Because # is a valid filename character, it must be encoded to prevent part of the
// path from being parsed as a URI fragment.
return encodeURI(path).replace(/#/g, '%23');
};
module.exports = FileSystem;
});

View File

@ -0,0 +1,44 @@
cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Options to customize the HTTP request used to upload files.
* @constructor
* @param fileKey {String} Name of file request parameter.
* @param fileName {String} Filename to be used by the server. Defaults to image.jpg.
* @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg.
* @param params {Object} Object with key: value params to send to the server.
* @param headers {Object} Keys are header names, values are header values. Multiple
* headers of the same name are not supported.
*/
var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) {
this.fileKey = fileKey || null;
this.fileName = fileName || null;
this.mimeType = mimeType || null;
this.params = params || null;
this.headers = headers || null;
this.httpMethod = httpMethod || null;
};
module.exports = FileUploadOptions;
});

View File

@ -0,0 +1,32 @@
cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* FileUploadResult
* @constructor
*/
module.exports = function FileUploadResult(size, code, content) {
this.bytesSent = size;
this.responseCode = code;
this.response = content;
};
});

View File

@ -0,0 +1,327 @@
cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec'),
FileError = require('./FileError'),
ProgressEvent = require('./ProgressEvent');
/**
* This class writes to the mobile device file system.
*
* For Android:
* The root directory is the root of the file system.
* To write to the SD card, the file name is "sdcard/my_file.txt"
*
* @constructor
* @param file {File} File object containing file properties
* @param append if true write to the end of the file, otherwise overwrite the file
*/
var FileWriter = function(file) {
this.fileName = "";
this.length = 0;
if (file) {
this.localURL = file.localURL || file;
this.length = file.size || 0;
}
// default is to write at the beginning of the file
this.position = 0;
this.readyState = 0; // EMPTY
this.result = null;
// Error
this.error = null;
// Event handlers
this.onwritestart = null; // When writing starts
this.onprogress = null; // While writing the file, and reporting partial file data
this.onwrite = null; // When the write has successfully completed.
this.onwriteend = null; // When the request has completed (either in success or failure).
this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method.
this.onerror = null; // When the write has failed (see errors).
};
// States
FileWriter.INIT = 0;
FileWriter.WRITING = 1;
FileWriter.DONE = 2;
/**
* Abort writing file.
*/
FileWriter.prototype.abort = function() {
// check for invalid state
if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// set error
this.error = new FileError(FileError.ABORT_ERR);
this.readyState = FileWriter.DONE;
// If abort callback
if (typeof this.onabort === "function") {
this.onabort(new ProgressEvent("abort", {"target":this}));
}
// If write end callback
if (typeof this.onwriteend === "function") {
this.onwriteend(new ProgressEvent("writeend", {"target":this}));
}
};
/**
* Writes data to the file
*
* @param data text or blob to be written
* @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
*/
FileWriter.prototype.write = function(data, isPendingBlobReadResult) {
var that=this;
var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
var isProxySupportBlobNatively = (cordova.platformId === "windows8" || cordova.platformId === "windows");
var isBinary;
// Check to see if the incoming data is a blob
if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
var fileReader = new FileReader();
fileReader.onload = function() {
// Call this method again, with the arraybuffer as argument
FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
};
fileReader.onerror = function () {
// DONE state
that.readyState = FileWriter.DONE;
// Save error
that.error = this.error;
// If onerror callback
if (typeof that.onerror === "function") {
that.onerror(new ProgressEvent("error", {"target":that}));
}
// If onwriteend callback
if (typeof that.onwriteend === "function") {
that.onwriteend(new ProgressEvent("writeend", {"target":that}));
}
};
// WRITING state
this.readyState = FileWriter.WRITING;
if (supportsBinary) {
fileReader.readAsArrayBuffer(data);
} else {
fileReader.readAsText(data);
}
return;
}
// Mark data type for safer transport over the binary bridge
isBinary = supportsBinary && (data instanceof ArrayBuffer);
if (isBinary && cordova.platformId === "windowsphone") {
// create a plain array, using the keys from the Uint8Array view so that we can serialize it
data = Array.apply(null, new Uint8Array(data));
}
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":me}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// position always increases by bytes written because file would be extended
me.position += r;
// The length of the file is now where we are done writing.
me.length = me.position;
// DONE state
me.readyState = FileWriter.DONE;
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "write", [this.localURL, data, this.position, isBinary]);
};
/**
* Moves the file pointer to the location specified.
*
* If the offset is a negative number the position of the file
* pointer is rewound. If the offset is greater than the file
* size the position is set to the end of the file.
*
* @param offset is the location to move the file pointer to.
*/
FileWriter.prototype.seek = function(offset) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
if (!offset && offset !== 0) {
return;
}
// See back from end of file.
if (offset < 0) {
this.position = Math.max(offset + this.length, 0);
}
// Offset is bigger than file size so set position
// to the end of the file.
else if (offset > this.length) {
this.position = this.length;
}
// Offset is between 0 and file size so set the position
// to start writing.
else {
this.position = offset;
}
};
/**
* Truncates the file to the size specified.
*
* @param size to chop the file at.
*/
FileWriter.prototype.truncate = function(size) {
// Throw an exception if we are already writing a file
if (this.readyState === FileWriter.WRITING) {
throw new FileError(FileError.INVALID_STATE_ERR);
}
// WRITING state
this.readyState = FileWriter.WRITING;
var me = this;
// If onwritestart callback
if (typeof me.onwritestart === "function") {
me.onwritestart(new ProgressEvent("writestart", {"target":this}));
}
// Write file
exec(
// Success callback
function(r) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Update the length of the file
me.length = r;
me.position = Math.min(me.position, r);
// If onwrite callback
if (typeof me.onwrite === "function") {
me.onwrite(new ProgressEvent("write", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
},
// Error callback
function(e) {
// If DONE (cancelled), then don't do anything
if (me.readyState === FileWriter.DONE) {
return;
}
// DONE state
me.readyState = FileWriter.DONE;
// Save error
me.error = new FileError(e);
// If onerror callback
if (typeof me.onerror === "function") {
me.onerror(new ProgressEvent("error", {"target":me}));
}
// If onwriteend callback
if (typeof me.onwriteend === "function") {
me.onwriteend(new ProgressEvent("writeend", {"target":me}));
}
}, "File", "truncate", [this.localURL, size]);
};
module.exports = FileWriter;
});

View File

@ -0,0 +1,39 @@
cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Supplies arguments to methods that lookup or create files and directories.
*
* @param create
* {boolean} file or directory if it doesn't exist
* @param exclusive
* {boolean} used with create; if true the command will fail if
* target path exists
*/
function Flags(create, exclusive) {
this.create = create || false;
this.exclusive = exclusive || false;
}
module.exports = Flags;
});

View File

@ -0,0 +1,26 @@
cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
exports.TEMPORARY = 0;
exports.PERSISTENT = 1;
});

View File

@ -0,0 +1,43 @@
cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
/**
* Information about the state of the file or directory
*
* {Date} modificationTime (readonly)
*/
var Metadata = function(metadata) {
if (typeof metadata == "object") {
this.modificationTime = new Date(metadata.modificationTime);
this.size = metadata.size || 0;
} else if (typeof metadata == "undefined") {
this.modificationTime = null;
this.size = 0;
} else {
/* Backwards compatiblity with platforms that only return a timestamp */
this.modificationTime = new Date(metadata);
}
};
module.exports = Metadata;
});

View File

@ -0,0 +1,70 @@
cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
// Feature test: See if we can instantiate a native ProgressEvent;
// if so, use that approach,
// otherwise fill-in with our own implementation.
//
// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
var ProgressEvent = (function() {
/*
var createEvent = function(data) {
var event = document.createEvent('Events');
event.initEvent('ProgressEvent', false, false);
if (data) {
for (var i in data) {
if (data.hasOwnProperty(i)) {
event[i] = data[i];
}
}
if (data.target) {
// TODO: cannot call <some_custom_object>.dispatchEvent
// need to first figure out how to implement EventTarget
}
}
return event;
};
try {
var ev = createEvent({type:"abort",target:document});
return function ProgressEvent(type, data) {
data.type = type;
return createEvent(data);
};
} catch(e){
*/
return function ProgressEvent(type, dict) {
this.type = type;
this.bubbles = false;
this.cancelBubble = false;
this.cancelable = false;
this.lengthComputable = false;
this.loaded = dict && dict.loaded ? dict.loaded : 0;
this.total = dict && dict.total ? dict.total : 0;
this.target = dict && dict.target ? dict.target : null;
};
//}
})();
module.exports = ProgressEvent;
});

View File

@ -0,0 +1,29 @@
cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
module.exports = function () {
// window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
// possibly a good flag to indicate that we're running in Chrome
return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
};
});

View File

@ -0,0 +1,66 @@
cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
var exec = require('cordova/exec');
var channel = require('cordova/channel');
exports.file = {
// Read-only directory where the application is installed.
applicationDirectory: null,
// Root of app's private writable storage
applicationStorageDirectory: null,
// Where to put app-specific data files.
dataDirectory: null,
// Cached files that should survive app restarts.
// Apps should not rely on the OS to delete files in here.
cacheDirectory: null,
// Android: the application space on external storage.
externalApplicationStorageDirectory: null,
// Android: Where to put app-specific data files on external storage.
externalDataDirectory: null,
// Android: the application cache on external storage.
externalCacheDirectory: null,
// Android: the external storage (SD card) root.
externalRootDirectory: null,
// iOS: Temp directory that the OS can clear at will.
tempDirectory: null,
// iOS: Holds app-specific files that should be synced (e.g. to iCloud).
syncedDataDirectory: null,
// iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
documentsDirectory: null,
// BlackBerry10: Files globally available to all apps
sharedDirectory: null
};
channel.waitForInitialization('onFileSystemPathsReady');
channel.onCordovaReady.subscribe(function() {
function after(paths) {
for (var k in paths) {
exports.file[k] = paths[k];
}
channel.initializationComplete('onFileSystemPathsReady');
}
exec(after, null, 'File', 'requestAllPaths', []);
});
});

View File

@ -0,0 +1,48 @@
cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
// Map of fsName -> FileSystem.
var fsMap = null;
var FileSystem = require('./FileSystem');
var exec = require('cordova/exec');
// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
require('./fileSystems').getFs = function(name, callback) {
function success(response) {
fsMap = {};
for (var i = 0; i < response.length; ++i) {
var fsRoot = response[i];
var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
fsMap[fs.name] = fs;
}
callback(fsMap[name]);
}
if (fsMap) {
callback(fsMap[name]);
} else {
exec(success, null, "File", "requestAllFileSystems", []);
}
};
});

Some files were not shown because too many files have changed in this diff Show More