/*
* This file is part of Adblock Plus ,
* Copyright (C) 2006-2017 eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see .
*/
"use strict";
let {IO: LegacyIO} = require("legacyIO");
let {Utils} = require("utils");
let webextension = require("webextension");
let messageID = 0;
let messageCallbacks = new Map();
webextension.then(port =>
{
port.onMessage.addListener(message =>
{
let {id} = message;
let callbacks = messageCallbacks.get(id);
if (callbacks)
{
messageCallbacks.delete(id);
if (message.success)
callbacks.resolve(message.result);
else
callbacks.reject(message.result);
}
});
});
function callWebExt(method, ...args)
{
return webextension.then(port =>
{
return new Promise((resolve, reject) =>
{
let id = ++messageID;
messageCallbacks.set(id, {resolve, reject});
port.postMessage({id, method, args});
});
});
}
function callLegacy(method, ...args)
{
return new Promise((resolve, reject) =>
{
LegacyIO[method](...args, (error, result) =>
{
if (error)
reject(error);
else
resolve(result);
});
});
}
function legacyFile(fileName)
{
let file = LegacyIO.resolveFilePath("adblockplus");
file.append(fileName);
return file;
}
function ensureDirExists(file)
{
if (!file.exists())
{
ensureDirExists(file.parent);
file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
}
}
let fallback = {
readFromFile(fileName, listener)
{
let wrapper = {
process(line)
{
if (line !== null)
listener(line);
}
};
return callLegacy("readFromFile", legacyFile(fileName), wrapper);
},
writeToFile(fileName, data)
{
let file = legacyFile(fileName);
ensureDirExists(file.parent);
return callLegacy("writeToFile", file, data);
},
copyFile(fromFile, toFile)
{
return callLegacy("copyFile", legacyFile(fromFile), legacyFile(toFile));
},
renameFile(fromFile, newName)
{
return callLegacy("renameFile", legacyFile(fromFile), newName);
},
removeFile(fileName)
{
return callLegacy("removeFile", legacyFile(fileName));
},
statFile(fileName)
{
return callLegacy("statFile", legacyFile(fileName));
}
};
exports.IO =
{
/**
* @callback TextSink
* @param {string} line
*/
/**
* Reads text lines from a file.
* @param {string} fileName
* Name of the file to be read
* @param {TextSink} listener
* Function that will be called for each line in the file
* @return {Promise}
* Promise to be resolved or rejected once the operation is completed
*/
readFromFile(fileName, listener)
{
return callWebExt("readFromFile", fileName).then(contents =>
{
return new Promise((resolve, reject) =>
{
let lineIndex = 0;
function processBatch()
{
while (lineIndex < contents.length)
{
listener(contents[lineIndex++]);
if (lineIndex % 1000 == 0)
{
Utils.runAsync(processBatch);
return;
}
}
resolve();
}
processBatch();
});
});
},
/**
* Writes text lines to a file.
* @param {string} fileName
* Name of the file to be written
* @param {Iterable.} data
* An array-like or iterable object containing the lines (without line
* endings)
* @return {Promise}
* Promise to be resolved or rejected once the operation is completed
*/
writeToFile(fileName, data)
{
return callWebExt("writeToFile", fileName, Array.from(data));
},
/**
* Copies a file.
* @param {string} fromFile
* Name of the file to be copied
* @param {string} toFile
* Name of the file to be written, will be overwritten if exists
* @return {Promise}
* Promise to be resolved or rejected once the operation is completed
*/
copyFile(fromFile, toFile)
{
return callWebExt("copyFile", fromFile, toFile);
},
/**
* Renames a file.
* @param {string} fromFile
* Name of the file to be renamed
* @param {string} newName
* New file name, will be overwritten if exists
* @return {Promise}
* Promise to be resolved or rejected once the operation is completed
*/
renameFile(fromFile, newName)
{
return callWebExt("renameFile", fromFile, newName);
},
/**
* Removes a file.
* @param {string} fileName
* Name of the file to be removed
* @return {Promise}
* Promise to be resolved or rejected once the operation is completed
*/
removeFile(fileName)
{
return callWebExt("removeFile", fileName);
},
/**
* @typedef StatData
* @type {object}
* @property {boolean} exists
* true if the file exists
* @property {number} lastModified
* file modification time in milliseconds
*/
/**
* Retrieves file metadata.
* @param {string} fileName
* Name of the file to be looked up
* @return {Promise.}
* Promise to be resolved with file metadata once the operation is
* completed
*/
statFile(fileName)
{
return callWebExt("statFile", fileName);
}
};
let {application} = require("info");
if (application != "firefox" && application != "fennec2")
{
// Currently, only Firefox has a working WebExtensions implementation, other
// applications should just use the fallback.
exports.IO = fallback;
}
else
{
// Add fallbacks to IO methods - fall back to legacy I/O if file wasn't found.
for (let name of Object.getOwnPropertyNames(exports.IO))
{
// No fallback for writeToFile method, new data should always be stored to
// new storage only.
if (name == "writeToFile")
continue;
let method = exports.IO[name];
let fallbackMethod = fallback[name];
exports.IO[name] = (...args) =>
{
return method(...args).catch(error =>
{
if (error == "NoSuchFile")
return fallbackMethod(...args);
throw error;
});
};
}
}