529 lines
22 KiB
JavaScript
529 lines
22 KiB
JavaScript
var repoindex = (function() {
|
|
|
|
// a list of all possible connection states
|
|
repoindex.ConnectionStatus = {
|
|
Disconnected: "Disconnected",
|
|
Connecting: "Connecting",
|
|
Connected: "Connected"
|
|
};
|
|
|
|
// a list of all possible request/response types
|
|
repoindex.RequestType = {
|
|
BasicRepoInfo: "basicrepoinfo",
|
|
BasicPackageInfo: "basicpkginfo",
|
|
FullPackageInfo: "fullpkginfo",
|
|
GroupInfo: "groupinfo",
|
|
UpgradeLookup: "upgradelookup",
|
|
Suggestions: "suggestions",
|
|
Postponed: "postponed"
|
|
};
|
|
|
|
repoindex.isLoopback = function(domain) {
|
|
return domain === "localhost" || domain === "127.0.0.1" || domain === "::1";
|
|
};
|
|
|
|
// responsible for the connection to the server; holds all repo/package information returned by the server
|
|
var Client = function(url) {
|
|
// basic initialization
|
|
this.url = url;
|
|
this.socket = undefined;
|
|
this.hasBasicRepoInfo = false;
|
|
this.hasGroupInfo = false;
|
|
this.scheduledRequests = [];
|
|
this.sentRequests = [];
|
|
this.repos = {};
|
|
|
|
// define functions control the connections
|
|
this.resetReconnectTries = function(tries) {
|
|
if(tries !== undefined && tries >= 0) {
|
|
this.remainingReconnectTries = this.reconnectTries = tries;
|
|
} else {
|
|
this.remainingReconnectTries = this.reconnectTries;
|
|
}
|
|
};
|
|
this.resetReconnectTries(2);
|
|
|
|
this.reconnect = function() {
|
|
if(this.remainingReconnectTries > 0) {
|
|
--this.remainingReconnectTries;
|
|
var reconnectTimer = window.setInterval(function() {
|
|
window.clearInterval(reconnectTimer);
|
|
repoindex.client.init();
|
|
}, 5000);
|
|
}
|
|
};
|
|
|
|
this.init = function() {
|
|
repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Connecting);
|
|
if(typeof WebSocket != 'function' && typeof MozWebSocket == 'function') {
|
|
WebSocket = MozWebSocket;
|
|
}
|
|
if(this.socket && this.socket.readyState === 1) {
|
|
this.socket.onclose = function() {
|
|
this.client.init();
|
|
};
|
|
this.socket.close();
|
|
return;
|
|
}
|
|
this.socket = new WebSocket(this.url);
|
|
this.socket.client = this;
|
|
|
|
this.socket.onopen = function(event) {
|
|
repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Connected);
|
|
this.client.resetReconnectTries();
|
|
this.client.doScheduledRequests();
|
|
};
|
|
|
|
this.socket.onclose = function(event) {
|
|
repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Disconnected);
|
|
this.client.reconnect();
|
|
};
|
|
|
|
this.socket.onmessage = function(event) {
|
|
var msg = JSON.parse(event.data);
|
|
switch(msg.c) {
|
|
case "error":
|
|
repoindex.pageManager.addError("Server replied error: " + msg.msg);
|
|
break;
|
|
case "results":
|
|
this.client.parseResults(msg.id, msg.w, msg.v);
|
|
break;
|
|
}
|
|
};
|
|
|
|
this.socket.onerror = function(event) {
|
|
repoindex.pageManager.addError("Connecting to server failed.");
|
|
repoindex.pageManager.setConnectionStatus(repoindex.ConnectionStatus.Disconnected);
|
|
};
|
|
};
|
|
|
|
this.isOpen = function() {
|
|
return this.socket.readyState === 1;
|
|
};
|
|
|
|
this.stop = function() {
|
|
if(this.socket) {
|
|
this.socket.close();
|
|
}
|
|
};
|
|
|
|
// define functions to manage requests
|
|
this.requestById = function(id) {
|
|
if(id) {
|
|
var i;
|
|
// check scheduled requests as well as sent requests
|
|
for(i = 0; i < this.scheduledRequests.length; ++i) {
|
|
if(this.scheduledRequests[i].params.id === id) {
|
|
return this.scheduledRequests[i];
|
|
}
|
|
}
|
|
for(i = 0; i < this.sentRequests.length; ++i) {
|
|
if(this.sentRequests[i].params.id === id) {
|
|
return this.sentRequests[i];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.scheduleRequest = function(type, params, callback) {
|
|
// add request information to params
|
|
params.c = "query";
|
|
params.w = type;
|
|
// check whether the same kind of request has already been scheduled
|
|
var i = 0;
|
|
switch(type) {
|
|
case repoindex.RequestType.BasicRepoInfo:
|
|
case repoindex.RequestType.GroupInfo:
|
|
// -> upgrade existing reuquest in this case
|
|
for(; i < this.scheduledRequests.length; ++i) {
|
|
if(this.scheduledRequests[i].params.c === "query" && this.scheduledRequests[i].params.w === type) {
|
|
// there is already such a request
|
|
this.scheduledRequests[i].callbacks.push(callback); // add callback
|
|
params.id = this.scheduledRequests[i].params.id; // keep the old ID
|
|
this.scheduledRequests[i].params = params; // upgrade params
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// -> other request types can't be upgraded
|
|
i = this.scheduledRequests.length;
|
|
}
|
|
if(i >= this.scheduledRequests.length) {
|
|
// a new request is required
|
|
// -> add callback array
|
|
var callbacks = [];
|
|
if(callback) {
|
|
callbacks.push(callback);
|
|
}
|
|
// -> add unique request ID
|
|
while(this.requestById(params.id = repoindex.makeId()));
|
|
// -> schedule request
|
|
this.scheduledRequests.push({params: params, sent: false, callbacks: callbacks});
|
|
}
|
|
// try to send scheduled requests
|
|
this.doScheduledRequests();
|
|
};
|
|
|
|
this.doScheduledRequests = function() {
|
|
// can only send requests if connection is established
|
|
if(this.isOpen()) {
|
|
while(this.scheduledRequests.length > 0) {
|
|
var request = this.scheduledRequests.shift();
|
|
request.sent = true;
|
|
this.sentRequests.push(request);
|
|
this.socket.send(JSON.stringify(request.params));
|
|
}
|
|
}
|
|
};
|
|
|
|
this.parseResults = function(id, what, values) {
|
|
if(!what) {
|
|
repoindex.pageManager.addError("Server replied insufficiant results.");
|
|
} else if(!values) {
|
|
repoindex.pageManager.addError("Server replied \"" + what + "\" with insufficiant data.");
|
|
} else {
|
|
var request = this.requestById(id);
|
|
if(!id || (request && request.sent)) {
|
|
// use the received information
|
|
switch(what) {
|
|
case repoindex.RequestType.BasicRepoInfo:
|
|
this.useBasicRepoInfo(values);
|
|
break;
|
|
case repoindex.RequestType.BasicPackageInfo:
|
|
this.usePackageInfo(values);
|
|
break;
|
|
case repoindex.RequestType.FullPackageInfo:
|
|
this.usePackageInfo(values);
|
|
break;
|
|
case repoindex.RequestType.GroupInfo:
|
|
this.useGroupInfo(values);
|
|
break;
|
|
case repoindex.RequestType.UpgradeLookup:
|
|
// the info is used via the registred callbacks only
|
|
break;
|
|
case repoindex.RequestType.Suggestions:
|
|
this.useSuggestions(values);
|
|
break;
|
|
case repoindex.RequestType.Postponed:
|
|
// server is busy, but will send results later
|
|
pageManager.addError("The server is busy.");
|
|
// -> do not call the callbacks already
|
|
request = undefined;
|
|
break;
|
|
default:
|
|
pageManager.addError("Server replied unknown results: " + what);
|
|
return; // don't invoke callbacks when results are of unknown type
|
|
}
|
|
if(request) {
|
|
// also invoke callbacks registred for this request
|
|
for(var i = 0; i < request.callbacks.length; ++i) {
|
|
request.callbacks[i](values);
|
|
}
|
|
// TODO: remove request object from list of sent requests
|
|
}
|
|
} else {
|
|
repoindex.pageManager.addError("Server replied \"" + what + "\" with unknown ID.");
|
|
}
|
|
}
|
|
};
|
|
|
|
// define functions to perform several requests
|
|
this.requestBasicRepoInfo = function(callback) {
|
|
this.scheduleRequest(repoindex.RequestType.BasicRepoInfo, {updates: true}, callback);
|
|
};
|
|
|
|
this.requestBasicPackagesInfo = function(packageSelection, callback) {
|
|
this.scheduleRequest(repoindex.RequestType.BasicPackageInfo, {sel: packageSelection, updates: true}, callback);
|
|
};
|
|
|
|
this.requestFullPackagesInfo = function(packageSelection, callback) {
|
|
this.scheduleRequest(repoindex.RequestType.FullPackageInfo, {sel: packageSelection}, callback);
|
|
};
|
|
|
|
this.requestGroupInfo = function(callback) {
|
|
this.scheduleRequest(repoindex.RequestType.GroupInfo, {updates: true}, callback);
|
|
};
|
|
|
|
this.requestSuggestions = function(repoNames, searchTerm, callback) {
|
|
this.scheduleRequest(repoindex.RequestType.Suggestions, {repos: repoNames, term: searchTerm});
|
|
};
|
|
|
|
this.requestAurSuggestions = function(term) {
|
|
var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
|
|
xmlhttp.onreadystatechange = function() {
|
|
if(xmlhttp.readyState === 4 && xmlhttp.status === 200) {
|
|
repoindex.client.useAurSuggestions(JSON.parse(xmlhttp.responseText));
|
|
}
|
|
};
|
|
xmlhttp.open("GET", "http://aur.archlinux.org/rpc.php?type=suggest&arg=" + encodeURIComponent(term), true);
|
|
xmlhttp.send();
|
|
};
|
|
|
|
this.sendCmd = function(cmd) {
|
|
if(this.isOpen()) {
|
|
this.socket.send(JSON.stringify({c: "cmd", w: cmd}));
|
|
} else {
|
|
repoindex.pageManager.addError("Not connected to a server.");
|
|
}
|
|
};
|
|
|
|
this.stopServer = function() {
|
|
this.sendCmd("stop");
|
|
};
|
|
|
|
this.reinitAlpm = function() {
|
|
this.sendCmd("reinitalpm");
|
|
};
|
|
|
|
this.updateAlpm = function() {
|
|
this.sendCmd("updatealpm");
|
|
};
|
|
|
|
this.checkForUpgrades = function(dbName, syncdbNames, callback) {
|
|
var params = {
|
|
db: dbName
|
|
};
|
|
if(syncdbNames) {
|
|
params.syncdbs = syncdbNames;
|
|
}
|
|
this.scheduleRequest(repoindex.RequestType.UpgradeLookup, params, callback);
|
|
};
|
|
|
|
// define helper functions to access repo/package information
|
|
this.getPackageInfo = function(repoName, packageName) {
|
|
var repoInfo = this.repos[repoName];
|
|
if(!repoInfo) {
|
|
// the repo doesn't exists; might happen when receiving AUR suggestions directly
|
|
// -> just create a new, empty repo
|
|
this.repo[repoName] = (repoInfo = {name: repoName, packages: {}});
|
|
}
|
|
var packageInfo = repoInfo.packages[packageName];
|
|
if(!packageInfo) {
|
|
repoInfo.packages[packageName] = (packageInfo = {repo: repoName});
|
|
}
|
|
return packageInfo;
|
|
};
|
|
|
|
// define functions to use differend kinds of results
|
|
this.useBasicRepoInfo = function(value) {
|
|
if(!value) {
|
|
repoindex.pageManager.addError("Server replied insufficiant basic repo info.");
|
|
return false;
|
|
}
|
|
// upgrade repos and package list
|
|
this.repos = value;
|
|
var repoMgr = repoindex.pageManager.repoManager;
|
|
var pkgMgr = repoindex.pageManager.packageManager;
|
|
repoMgr.removeEntries();
|
|
pkgMgr.removeEntries();
|
|
var incomplete = false;
|
|
var reposInOrder = [];
|
|
for(var repoName in value) {
|
|
if(value.hasOwnProperty(repoName)) {
|
|
var info = value[repoName];
|
|
if(info.incomplete) {
|
|
incomplete = true;
|
|
} else {
|
|
reposInOrder.push({name: repoName, info: info});
|
|
}
|
|
}
|
|
}
|
|
reposInOrder.sort(function(lhs, rhs) {
|
|
if(lhs === rhs) {
|
|
return 1;
|
|
} else if(lhs.info.index !== undefined) {
|
|
if(rhs.info.index !== undefined) {
|
|
return lhs.info.index > rhs.info.index;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if(rhs.info.index !== undefined) {
|
|
return 1;
|
|
} else {
|
|
return lhs.name > rhs.name;
|
|
}
|
|
});
|
|
for(var i = 0; i < reposInOrder.length; ++i) {
|
|
var repoName = reposInOrder[i].name;
|
|
var repoInfo = reposInOrder[i].info;
|
|
var repoEntry = repoMgr.addEntry(repoName, repoInfo);
|
|
var packages = repoInfo.packages;
|
|
for(var packageName in packages) {
|
|
if(value.hasOwnProperty(repoName)) {
|
|
var packageInfo = packages[packageName];
|
|
pkgMgr.addEntry(repoEntry, packageName, packageInfo);
|
|
}
|
|
}
|
|
}
|
|
if(incomplete) {
|
|
repoindex.pageManager.addError("Server replied incomplete repository info: Server is busy.");
|
|
}
|
|
this.hasBasicRepoInfo = true;
|
|
pkgMgr.invalidate();
|
|
repoMgr.invalidate();
|
|
repoMgr.applyRepoStatusChange();
|
|
};
|
|
|
|
this.usePackageInfo = function(values) {
|
|
if(!Array.isArray(values)) {
|
|
repoindex.pageManager.addError("Server replied insufficiant package info.");
|
|
return false;
|
|
}
|
|
for(var i = 0; i < values.length; ++i) {
|
|
var value = values[i];
|
|
var repo = repoindex.client.repos[value.repo];
|
|
if(repo) {
|
|
var pkg = repo.packages[value.name];
|
|
if(pkg) {
|
|
if(value.basics) {
|
|
pkg.basics = value.basics;
|
|
}
|
|
if(value.details) {
|
|
pkg.details = value.details;
|
|
}
|
|
} else {
|
|
repoindex.pageManager.addError("Package info replied by server refers to unknown package.");
|
|
}
|
|
} else {
|
|
repoindex.pageManager.addError("Package info replied by server refers to unknown repository.");
|
|
}
|
|
}
|
|
if(value.error) {
|
|
switch(value.error) {
|
|
case "na":
|
|
if(value.repo) {
|
|
if(value.name) {
|
|
repoindex.pageManager.addError("Server replied error: The package " + value.name + " doesn't exist in the repository " + value.repo + ".");
|
|
} else {
|
|
repoindex.pageManager.addError("Server replied error: The repository " + value.repo + " doesn't exist.");
|
|
}
|
|
} else {
|
|
repoindex.pageManager.addError("Server replied error: The requested info is not available.");
|
|
}
|
|
break;
|
|
case "busy":
|
|
repoindex.pageManager.addError("Server replied error in package info: The server is busy.");
|
|
break;
|
|
default:
|
|
repoindex.pageManager.addError("Server replied unknown error code in package info: " + value.error);
|
|
}
|
|
}
|
|
// updating table rows or any other GUI elements is done via callbacks
|
|
};
|
|
|
|
this.useGroupInfo = function(values) {
|
|
var groupMgr = repoindex.pageManager.groupManager;
|
|
groupMgr.removeEntries();
|
|
var incomplete = false;
|
|
var groupEntries = groupMgr.entries;
|
|
for(var i1 = 0; i1 < values.length; ++i1) {
|
|
var info = values[i1];
|
|
if(info.incomplete) {
|
|
incomplete = true;
|
|
}
|
|
if(info.repo && info.groups) {
|
|
for(var i2 = 0; i2 < info.groups.length; ++i2) {
|
|
var group = info.groups[i2];
|
|
groupMgr.addEntry(info.repo, group.name, group.pkgs);
|
|
}
|
|
}
|
|
}
|
|
if(incomplete) {
|
|
repoindex.pageManager.addError("Server replied incomplete group info: Server is busy.");
|
|
}
|
|
this.hasGroupInfo = true;
|
|
groupMgr.useRequestedData();
|
|
};
|
|
|
|
this.useSuggestions = function(values) {
|
|
if(Array.isArray(values)) {
|
|
for(var i1 = 0; i1 < values.length; ++i1) {
|
|
var value = values[i1];
|
|
var repoName = value.repo;
|
|
var suggestions = value.res;
|
|
if(Array.isArray(suggestions)) {
|
|
if(suggestions.length > 0) {
|
|
var repoEntry = repoindex.pageManager.repoManager.entryByName(repoName);
|
|
if(repoEntry) {
|
|
var pkgMgr = repoindex.pageManager.packageManager;
|
|
for(var i2 = 0; i2 < suggestions.length; ++i2) {
|
|
var packageName = suggestions[i2];
|
|
var entryExists = false;
|
|
for(var i = 0, len = pkgMgr.entries.length; i < len; ++i) {
|
|
var pkgEntry = pkgMgr.entries[i];
|
|
if(pkgEntry.name === packageName && pkgEntry.repoEntry.name === repoName) {
|
|
entryExists = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!entryExists) {
|
|
var packageInfo = this.getPackageInfo(repoName, packageName);
|
|
pkgMgr.addEntry(repoEntry, packageName, packageInfo);
|
|
}
|
|
}
|
|
pkgMgr.invalidate();
|
|
} else {
|
|
repoindex.pageManager.addError("There is no repo entry for the suggestions replied by the server.");
|
|
}
|
|
var entries = pkgMgr.entries;
|
|
entries.sort(function(lhs, rhs) {
|
|
return lhs.name > rhs.name;
|
|
});
|
|
for(var i = 0; i < entries.length; ++i) {
|
|
entries[i].index = i;
|
|
}
|
|
}
|
|
} else {
|
|
// TODO: handle error
|
|
}
|
|
}
|
|
} else if(Array.isArray(values.errors)) {
|
|
for(var i = 0; i < values.errors.length; ++i) {
|
|
repoindex.pageManager.addError("Error replied by server in response of suggestion request: " + value.errors[i]);
|
|
}
|
|
} else {
|
|
repoindex.pageManager.addError("Server replied insufficiant suggestion reply.");
|
|
}
|
|
};
|
|
|
|
this.useAurSuggestions = function(suggestions) {
|
|
if(Array.isArray(suggestions)) {
|
|
if(suggestions.length > 0) {
|
|
var aurEntry = repoindex.pageManager.repoManager.entryByName("AUR");
|
|
if(aurEntry) {
|
|
var pkgMgr = repoindex.pageManager.packageManager;
|
|
for(var i = 0; i < suggestions.length; ++i) {
|
|
var packageName = suggestions[i];
|
|
if(!pkgMgr.entryByName(packageName)) {
|
|
var packageInfo = this.getPackageInfo("AUR", packageName);
|
|
pkgMgr.addEntry(aurEntry, packageName, packageInfo);
|
|
}
|
|
}
|
|
pkgMgr.invalidate();
|
|
} else {
|
|
// there is no AUR entry but we requested AUR suggestions
|
|
// -> shouldn't happen
|
|
}
|
|
}
|
|
} else {
|
|
// TODO: handle error
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
// create a global client used within the entire document
|
|
repoindex.client = new Client((window.location.protocol === "https:" ? "wss://" : "ws://") + (window.location.protocol === "file:" ? "localhost" : document.domain) + ":1234");
|
|
if(!repoindex.isLoopback(document.domain)) {
|
|
// hide stop button if server is not on loopback interface
|
|
document.getElementById("nav_server").style.display = "none";
|
|
}
|
|
if(window.location.protocol === "file:") {
|
|
document.getElementById("nav_connect").style.display = "none";
|
|
}
|
|
|
|
return repoindex;
|
|
|
|
})(repoindex || {});
|