Skip to content

Instantly share code, notes, and snippets.

@yajd
Last active August 29, 2015 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yajd/9791029 to your computer and use it in GitHub Desktop.
Save yajd/9791029 to your computer and use it in GitHub Desktop.
var me = Services.wm.getMostRecentWindow(null);
Cu.import('resource://gre/modules/osfile.jsm');
var pathProfilesIni = OS.Path.join(OS.Constants.Path.userApplicationDataDir, 'profiles.ini');
me.alert(pathProfilesIni);
//apparently theres for profiles made in default prof dir:
//localDir = C:\Users\ali57233\AppData\Local\Mozilla\Firefox\Profiles\czbm1ps9.rnd1
//rootDir = C:\Users\ali57233\AppData\Roaming\Mozilla\Firefox\Profiles\czbm1ps9.rnd1
//for custom prof dir localDir and rootDir are same
var ini = {};
function readIni() {
let decoder = new TextDecoder(); // This decoder can be reused for several reads
let promise = OS.File.read(pathProfilesIni); // Read the complete file as an array
promise = promise.then(
function onSuccess(ArrayBuffer) {
var readStr = decoder.decode(ArrayBuffer); // Convert this array to a text
console.log(readStr);
ini = {};
var patt = /\[(.*?)(\d*?)\](?:\s+?([\S]+)=([\S]+))(?:\s+?([\S]+)=([\S]+))?(?:\s+?([\S]+)=([\S]+))?(?:\s+?([\S]+)=([\S]+))?(?:\s+?([\S]+)=([\S]+))?/mg;
var blocks = [];
var match;
while (match = patt.exec(readStr)) {
console.log('MAAAAAAAAAAATCH', match);
var group = match[1];
ini[group] = {};
if (group == 'Profile') {
ini[group]['num'] = match[2];
}
ini[group].props = {};
for (var i = 3; i < match.length; i = i + 2) {
var prop = match[i];
if (prop === undefined) {
break;
}
var propVal = match[i + 1]
ini[group].props[prop] = propVal;
}
if (group == 'Profile') {
//Object.defineProperty(ini, ini[group].props.Name, Object.getOwnPropertyDescriptor(ini[group], group));
ini[ini[group].props.Name] = ini[group];
delete ini[group];
}
}
console.log('successfully read ini = ', ini);
updateProfToolkit();
return ini;
},
function onReject() {
console.error('Read ini failed');
}
);
return promise;
}
var tps = Cc['@mozilla.org/toolkit/profile-service;1'].createInstance(Ci.nsIToolkitProfileService); //toolkitProfileService
//XPCOMUtils.defineLazyGetter(myServices, 'tps', function(){ return Cc['@mozilla.org/toolkit/profile-service;1'].createInstance(Ci.nsIToolkitProfileService) });
function initProfToolkit() {
profToolkit = {};
profToolkit.localPathDefault = OS.Path.dirname(OS.Constants.Path.localProfileDir); //will work as long as at least one profile is in the default profile folder //i havent tested when only custom profile
profToolkit.rootPathDefault = OS.Path.dirname(OS.Constants.Path.profileDir);
profToolkit.selectedProfile = {};
profToolkit.selectedProfile.name = tps.selectedProfile.name;
}
function updateProfToolkit() {
if (!profToolkit.selectedProfile) {
initProfToolkit();
}
var profileCount = 0;
var profileNames = [];
profToolkit.profiles = {};
for (var p in ini) {
if ('num' in p) {
profileCount++;
profileNames.push(p);
}
}
profToolkit.profileCount = profileCount;
profToolkit.profileNames = profileNames;
}
function writeIni() {
var writeStr = [];
var profileI = -1;
for (var p in ini) {
if ('num' in ini[p]) {
//is profile
profileI++; //because we init profileI at -1
var group = 'Profile' + profileI;
if (ini[p].num != profileI) {
console.log('profile I of profile changed from ' + ini[p].num + ' to ' + profileI + ' the object from ini read is =', ini[p]);
}
} else {
var group = p;
}
writeStr.push('[' + group + ']');
for (var p2 in ini[p].props) {
writeStr.push(p2 + '=' + ini[p].props[p2]);
}
writeStr.push('');
}
writeStr[writeStr.length - 1] = '\n'; //we want double new line at end of file
let encoder = new TextEncoder(); // This encoder can be reused for several writes
let BufferArray = encoder.encode(writeStr.join('\n')); // Convert the text to an array
let promise = OS.File.writeAtomic(pathProfilesIni, BufferArray, // Write the array atomically to "file.txt", using as temporary
{
tmpPath: pathProfilesIni + '.profilist.tmp'
}); // buffer "file.txt.tmp".
promise.then(
function() {},
function() {
console.error('writeIni failed');
}
);
return promise;
}
/*start - salt generator from http://mxr.mozilla.org/mozilla-aurora/source/toolkit/profile/content/createProfileWizard.js?raw=1*/
var kSaltTable = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
];
var kSaltString = '';
for (var i = 0; i < 8; ++i) {
kSaltString += kSaltTable[Math.floor(Math.random() * kSaltTable.length)];
}
function saltName(aName) {
return kSaltString + '.' + aName;
}
/*end - salt generator*/
function createProfile(refreshIni, profName) {
//refreshIni is 0,1 or programmatically 2
if (refreshIni == 1) {
var promise = readIni();
promise.then(
function() {
createProfile(2, profName);
},
function() {
console.error('Failed to refresh ini object from file during renameProfile');
}
);
} else {
if (profName in ini) {
Services.prompt.alert(null, self.name + ' - ' + 'EXCEPTION', 'Cannot create profile with name "' + newName + '" because this name is already taken by another profile.');
return;
}
//create folder in root dir (in it, one file "times.json" with contents:
/*
{
"created": 1395861287491
}
*/
//todo: im curious i should ask at ask.m.o how come when profile is custom driectory, a folder in local path is not created, of course the one to the custom path will be root. b ut why not make a local folder? like is done for IsRelative createds?
//create folder in local dir if root dir is different (this one is empty)
//add to profiles ini
//check if profile exists first
var numProfiles = Object.keys(ini) - 1;
var dirName = saltName(profName);
ini[profName] = {
num: numProfiles,
props: {
Name: profName,
IsRelative: 1,
Path: 'Profiles/' + dirName
}
}
var rootPathDefaultDirName = OS.File.join(profToolkit.rootPathDefault, dirName);
var localPathDefaultDirName = OS.File.join(profToolkit.localPathDefault, dirName);
var promise = OS.File.makeDir(rootPathDefaultDirName);
promise.then(
function onSuc() {
console.log('successfully created root dir for profile ' + profName + ' the path is = ', rootPathDefaultDirName);
let encoder = new TextEncoder();
let BufferArray = encoder.encode('{\n"created": ' + new Date().getTime() + '}\n');
let promise3 = OS.File.writeAtomic(OS.Path.join(rootPathDefaultDirName, 'times.json'), BufferArray,
{
tmpPath: OS.Path.join(rootPathDefaultDirName, 'times.json') + '.profilist.tmp'
}
);
promise3.then(
function() {
console.log('succesfully created times.json for profName of ' + profName + ' path is = ', OS.Path.join(rootPathDefaultDirName, 'times.json'));
},
function() {
console.error('FAILED creating times.json for profName of ' + profName + ' failed times.json path is = ', OS.Path.join(rootPathDefaultDirName, 'times.json'));
}
);
return promise3;
},
function onRej() {
console.error('FAILED to create root dir for profile ' + profName + ' the path is = ', rootPathDefaultDirName);
}
);
if (profToolkit.rootPathDefault != profToolkit.localPathDefault) {
var promise2 = OS.File.makeDir(localPathDefaultDirName);
promise2.then(
function onSuc() {
console.log('successfully created local dir for profile ' + profName + ' the path is = ', localPathDefaultDirName);
},
function onRej() {
console.error('FAILED to create local dir for profile ' + profName + ' the path is = ', localPathDefaultDirName);
}
);
}
//see here: http://mxr.mozilla.org/mozilla-aurora/source/toolkit/profile/content/createProfileWizard.js
/*
29 var dirService = C["@mozilla.org/file/directory_service;1"].getService(I.nsIProperties);
30 gDefaultProfileParent = dirService.get("DefProfRt", I.nsIFile);
73 var defaultProfileDir = gDefaultProfileParent.clone();
74 defaultProfileDir.append(saltName(document.getElementById("profileName").value));
75 gProfileRoot = defaultProfileDir;
*/
//see here for internal: http://mxr.mozilla.org/mozilla-aurora/source/toolkit/profile/content/profileSelection.js#139
//actually see here for internal: http://mxr.mozilla.org/mozilla-central/source/toolkit/profile/nsToolkitProfileService.cpp#699
}
}
function renameProfile(refreshIni, profName, newName) {
//refreshIni is 0,1 or programmatically 2
if (refreshIni == 1) {
var promise = readIni();
promise.then(
function() {
renameProfile(2, profName, newName);
},
function() {
console.error('Failed to refresh ini object from file during renameProfile');
}
);
} else {
//check if name is taken
if (profName in ini == false) {
Services.prompt.alert(null, self.name + ' - ' + 'EXCEPTION', 'Cannot find this profile name, "' + profName + '" so cannot delete it.');
return;
}
if (newName in ini) {
Services.prompt.alert(null, self.name + ' - ' + 'EXCEPTION', 'Cannot rename to "' + newName + '" because this name is already taken by another profile.');
return;
}
ini[profName].props.Name = newName;
var promise = writeIni();
promise.then(
function onSuc() {
console.log('successfully edited name of ' + profName + ' to ' + newName + ' in Profiles.ini');
},
function onRej() {
console.error('FAILED to edit name of ' + profName + ' to ' + newName + ' in Profiles.ini');
}
);
}
}
function deleteProfile(refreshIni, profName) {
//refreshIni is 0,1 or programmatically 2
if (refreshIni == 1) {
var promise = readIni();
promise.then(
function() {
deleteProfile(2, profName);
},
function() {
console.error('Failed to refresh ini object from file on deleteProfile');
}
);
} else {
//before deleting check if its default profile
if (profName in ini == false) {
Services.prompt.alert(null, self.name + ' - ' + 'EXCEPTION', 'Cannot find this profile name, "' + profName + '" so cannot delete it.');
return;
}
//if (Object.keys(ini).length == 2) {
//Services.prompt.alert(null, self.name + ' - ' + 'EXCEPTION', 'Cannot delete this profile as it is the last profile remaining.');
//return;
//}
//todo: figure out how to check if the profile is running, if it is dont delete but msg its open
if (ini[profName].props.IsRelative == '1') {
var PathRootDir = OS.Path.join(profToolkit.rootPathDefault, profToolkit.profiles[profName].rootDirName;
var PathLocalDir = OS.Path.join(profToolkit.localPathDefault, profToolkit.profiles[profName].localDirName;
var promise = OS.File.remove(PathRootDir);
promise.then(
function() {
console.log('successfully removed PathRootDir for profName of ' + profName, 'PathRootDir=', PathRootDir);
},
function() {
console.warn('FAILED to remove PathRootDir for profName of ' + profName, 'PathRootDir=', PathRootDir);
}
);
var promise2 = OS.File.remove(PathLocalDir);
promise2.then(
function() {
console.info('successfully removed PathLocalDir for profName of ' + profName, 'PathLocalDir=', PathLocalDir);
},
function() {
console.warn('FAILED to remove PathLocalDir for profName of ' + profName, 'PathLocalDir=', PathLocalDir);
}
);
} else {
var Path = ini[profName].props.Path;
var promise = OS.File.remove(Path);
promise.then(
function() {
console.log('successfully removed Path for profName of ' + profName, 'Path=', Path);
},
function() {
console.warn('FAILED to remove Path for profName of ' + profName, 'Path=', Path);
}
);
}
delete ini[profName];
var promise0 = writeIni();
promise0.then(
function() {
console.log('successfully edited out profName of ' + profName + ' from Profiles.ini');
},
function() {
console.error('FAILED to edit out profName of ' + profName + ' from Profiles.ini');
}
);
}
}
var promise = readIni();
promise.then(function onSuc() {
me.alert('read done will now write')
var promise2 = writeIni();
promise2.then(function onSuc() {
me.alert('write done')
})
})
@yajd
Copy link
Author

yajd commented Mar 26, 2014

might have to ignore profiles with name that start with webapp

http://mxr.mozilla.org/mozilla-aurora/source/mobile/android/base/GeckoProfile.java#568
556
557 if (!isDefaultSet && !mName.startsWith("webapp")) {
558 // only set as default if this is the first non-webapp
559 // profile we're creating
560 profileSection.setProperty("Default", 1);
561 }

@yajd
Copy link
Author

yajd commented Mar 26, 2014

README

Rev2

  • Added notes in comment for createProfile

Rev3

  • Got rid of the vars of onSuc and onRej, now just doing it in the function arguments, looks cleaner and simpler then before
  • All promises now have a .then with success and reject functions now

Rev4

  • Trying to associate and populate with profToolkit (found a bunch of bugs with the old way of doing things thats currently in bootstrap.js of Profilist, like if loaded in custom profile but do OS.Constants.Path.profileDir it returns path to the root profile dir but the basename is that of the first profile folder in root (im not sure if first or default)

Rev5

  • Initial but untested createProfile, deleteProfile, and renameProfile finished
  • prefToolkit more complete I don't know if it's ready for port into bootstrap yet, I actually don't think it is
  • todo: Should probably set TextEncoder and TextDecoder objects to lazy loader

Rev6

  • Rather than recreate profToolkit exactly as it is in in bootsrap I have decided to change how bootstrap relies on profToolkit now that I have ini
  • Made some updates

@yajd
Copy link
Author

yajd commented Mar 27, 2014

Notes on StartWithLastProfile (SWLP)
  • On profile start of any profile FROM ProfileManager with option "Use the selected profile without asking at startup" is unchecked, the starting profile always gets set to default, if don't start from ProfileManager and when SWLP is 0 or 1
  • I suspect if SWLP is 0, on start of exe it will open ProfileManager and if SWLP is 1 on start of exe it will open last profile marked as Default

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment