mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
287 lines
8.1 KiB
JavaScript
287 lines
8.1 KiB
JavaScript
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = ["TelemetryFile"];
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
let imports = {};
|
|
Cu.import("resource://gre/modules/Services.jsm", imports);
|
|
Cu.import("resource://gre/modules/Deprecated.jsm", imports);
|
|
Cu.import("resource://gre/modules/NetUtil.jsm", imports);
|
|
|
|
let {Services, Deprecated, NetUtil} = imports;
|
|
|
|
// Constants from prio.h for nsIFileOutputStream.init
|
|
const PR_WRONLY = 0x2;
|
|
const PR_CREATE_FILE = 0x8;
|
|
const PR_TRUNCATE = 0x20;
|
|
const PR_EXCL = 0x80;
|
|
const RW_OWNER = parseInt("0600", 8);
|
|
const RWX_OWNER = parseInt("0700", 8);
|
|
|
|
// Delete ping files that have been lying around for longer than this.
|
|
const MAX_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week
|
|
|
|
// The number of outstanding saved pings that we have issued loading
|
|
// requests for.
|
|
let pingsLoaded = 0;
|
|
|
|
// The number of those requests that have actually completed.
|
|
let pingLoadsCompleted = 0;
|
|
|
|
// If |true|, send notifications "telemetry-test-save-complete"
|
|
// and "telemetry-test-load-complete" once save/load is complete.
|
|
let shouldNotifyUponSave = false;
|
|
|
|
// Data that has neither been saved nor sent by ping
|
|
let pendingPings = [];
|
|
|
|
this.TelemetryFile = {
|
|
|
|
/**
|
|
* Save a single ping to a file.
|
|
*
|
|
* @param {object} ping The content of the ping to save.
|
|
* @param {nsIFile} file The destination file.
|
|
* @param {bool} sync If |true|, write synchronously. Deprecated.
|
|
* This argument should be |false|.
|
|
* @param {bool} overwrite If |true|, the file will be overwritten
|
|
* if it exists.
|
|
*/
|
|
savePingToFile: function(ping, file, sync, overwrite) {
|
|
let pingString = JSON.stringify(ping);
|
|
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
converter.charset = "UTF-8";
|
|
|
|
let ostream = Cc["@mozilla.org/network/file-output-stream;1"]
|
|
.createInstance(Ci.nsIFileOutputStream);
|
|
let initFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
|
|
if (!overwrite) {
|
|
initFlags |= PR_EXCL;
|
|
}
|
|
try {
|
|
ostream.init(file, initFlags, RW_OWNER, 0);
|
|
} catch (e) {
|
|
// Probably due to PR_EXCL.
|
|
return;
|
|
}
|
|
|
|
if (sync) {
|
|
let utf8String = converter.ConvertFromUnicode(pingString);
|
|
utf8String += converter.Finish();
|
|
let success = false;
|
|
try {
|
|
let amount = ostream.write(utf8String, utf8String.length);
|
|
success = amount == utf8String.length;
|
|
} catch (e) {
|
|
}
|
|
finishTelemetrySave(success, ostream);
|
|
} else {
|
|
let istream = converter.convertToInputStream(pingString);
|
|
let self = this;
|
|
NetUtil.asyncCopy(istream, ostream,
|
|
function(result) {
|
|
finishTelemetrySave(Components.isSuccessCode(result),
|
|
ostream);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Save a ping to its file, synchronously.
|
|
*
|
|
* @param {object} ping The content of the ping to save.
|
|
* @param {bool} overwrite If |true|, the file will be overwritten
|
|
* if it exists.
|
|
*/
|
|
savePing: function(ping, overwrite) {
|
|
this.savePingToFile(ping,
|
|
getSaveFileForPing(ping), true, overwrite);
|
|
},
|
|
|
|
/**
|
|
* Save all pending pings, synchronously.
|
|
*
|
|
* @param {object} sessionPing The additional session ping.
|
|
*/
|
|
savePendingPings: function(sessionPing) {
|
|
this.savePing(sessionPing, true);
|
|
pendingPings.forEach(function sppcb(e, i, a) {
|
|
this.savePing(e, false);
|
|
}, this);
|
|
pendingPings = [];
|
|
},
|
|
|
|
/**
|
|
* Remove the file for a ping
|
|
*
|
|
* @param {object} ping The ping.
|
|
*/
|
|
cleanupPingFile: function(ping) {
|
|
// FIXME: We shouldn't create the directory just to remove the file.
|
|
let file = getSaveFileForPing(ping);
|
|
try {
|
|
file.remove(true); // FIXME: Should be |false|, isn't it?
|
|
} catch(e) {
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load all saved pings.
|
|
*
|
|
* Once loaded, the saved pings can be accessed (destructively only)
|
|
* through |popPendingPings|.
|
|
*
|
|
* @param {bool} sync If |true|, loading takes place synchronously.
|
|
* @param {function*} onLoad A function called upon loading of each
|
|
* ping. It is passed |true| in case of success, |false| in case of
|
|
* format error.
|
|
*/
|
|
loadSavedPings: function(sync, onLoad = null) {
|
|
let directory = ensurePingDirectory();
|
|
let entries = directory.directoryEntries
|
|
.QueryInterface(Ci.nsIDirectoryEnumerator);
|
|
pingsLoaded = 0;
|
|
pingLoadsCompleted = 0;
|
|
try {
|
|
while (entries.hasMoreElements()) {
|
|
this.loadHistograms(entries.nextFile, sync, onLoad);
|
|
}
|
|
} finally {
|
|
entries.close();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load the histograms from a file.
|
|
*
|
|
* Once loaded, the saved pings can be accessed (destructively only)
|
|
* through |popPendingPings|.
|
|
*
|
|
* @param {nsIFile} file The file to load.
|
|
* @param {bool} sync If |true|, loading takes place synchronously.
|
|
* @param {function*} onLoad A function called upon loading of the
|
|
* ping. It is passed |true| in case of success, |false| in case of
|
|
* format error.
|
|
*/
|
|
loadHistograms: function loadHistograms(file, sync, onLoad = null) {
|
|
let now = new Date();
|
|
if (now - file.lastModifiedTime > MAX_PING_FILE_AGE) {
|
|
// We haven't had much luck in sending this file; delete it.
|
|
file.remove(true);
|
|
return;
|
|
}
|
|
|
|
pingsLoaded++;
|
|
if (sync) {
|
|
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Ci.nsIFileInputStream);
|
|
stream.init(file, -1, -1, 0);
|
|
addToPendingPings(file, stream, onLoad);
|
|
} else {
|
|
let channel = NetUtil.newChannel(file);
|
|
channel.contentType = "application/json";
|
|
|
|
NetUtil.asyncFetch(channel, (function(stream, result) {
|
|
if (!Components.isSuccessCode(result)) {
|
|
return;
|
|
}
|
|
addToPendingPings(file, stream, onLoad);
|
|
}).bind(this));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The number of pings loaded since the beginning of time.
|
|
*/
|
|
get pingsLoaded() {
|
|
return pingsLoaded;
|
|
},
|
|
|
|
/**
|
|
* Iterate destructively through the pending pings.
|
|
*
|
|
* @return {iterator}
|
|
*/
|
|
popPendingPings: function(reason) {
|
|
while (pendingPings.length > 0) {
|
|
let data = pendingPings.pop();
|
|
// Send persisted pings to the test URL too.
|
|
if (reason == "test-ping") {
|
|
data.reason = reason;
|
|
}
|
|
yield data;
|
|
}
|
|
},
|
|
|
|
set shouldNotifyUponSave(value) {
|
|
shouldNotifyUponSave = value;
|
|
},
|
|
|
|
testLoadHistograms: function(file, sync, onLoad) {
|
|
pingsLoaded = 0;
|
|
pingLoadsCompleted = 0;
|
|
this.loadHistograms(file, sync, onLoad);
|
|
}
|
|
};
|
|
|
|
///// Utility functions
|
|
|
|
function getSaveFileForPing(ping) {
|
|
let file = ensurePingDirectory();
|
|
file.append(ping.slug);
|
|
return file;
|
|
};
|
|
|
|
function ensurePingDirectory() {
|
|
let directory = Services.dirsvc.get("ProfD", Ci.nsILocalFile).clone();
|
|
directory.append("saved-telemetry-pings");
|
|
try {
|
|
directory.create(Ci.nsIFile.DIRECTORY_TYPE, RWX_OWNER);
|
|
} catch (e) {
|
|
// Already exists, just ignore this.
|
|
}
|
|
return directory;
|
|
};
|
|
|
|
function addToPendingPings(file, stream, onLoad) {
|
|
let success = false;
|
|
|
|
try {
|
|
let string = NetUtil.readInputStreamToString(stream, stream.available(),
|
|
{ charset: "UTF-8" });
|
|
stream.close();
|
|
let ping = JSON.parse(string);
|
|
// The ping's payload used to be stringified JSON. Deal with that.
|
|
if (typeof(ping.payload) == "string") {
|
|
ping.payload = JSON.parse(ping.payload);
|
|
}
|
|
pingLoadsCompleted++;
|
|
pendingPings.push(ping);
|
|
if (shouldNotifyUponSave &&
|
|
pingLoadsCompleted == pingsLoaded) {
|
|
Services.obs.notifyObservers(null, "telemetry-test-load-complete", null);
|
|
}
|
|
success = true;
|
|
} catch (e) {
|
|
// An error reading the file, or an error parsing the contents.
|
|
stream.close(); // close is idempotent.
|
|
file.remove(true); // FIXME: Should be false, isn't it?
|
|
}
|
|
if (onLoad) {
|
|
onLoad(success);
|
|
}
|
|
};
|
|
|
|
function finishTelemetrySave(ok, stream) {
|
|
stream.close();
|
|
if (shouldNotifyUponSave && ok) {
|
|
Services.obs.notifyObservers(null, "telemetry-test-save-complete", null);
|
|
}
|
|
};
|