L.ui.view.extend({ title: L.tr('Package management'), opkg: { updateLists: L.rpc.declare({ object: 'luci2.opkg', method: 'update', expect: { '': { } } }), _allPackages: L.rpc.declare({ object: 'luci2.opkg', method: 'list', params: [ 'offset', 'limit', 'pattern' ], expect: { '': { } } }), _installedPackages: L.rpc.declare({ object: 'luci2.opkg', method: 'list_installed', params: [ 'offset', 'limit', 'pattern' ], expect: { '': { } } }), _findPackages: L.rpc.declare({ object: 'luci2.opkg', method: 'find', params: [ 'offset', 'limit', 'pattern' ], expect: { '': { } } }), _fetchPackages: function(action, offset, limit, pattern) { var packages = [ ]; return action(offset, limit, pattern).then(function(list) { if (!list.total || !list.packages) return { length: 0, total: 0 }; packages.push.apply(packages, list.packages); packages.total = list.total; if (limit <= 0) limit = list.total; if (packages.length >= limit) return packages; L.rpc.batch(); for (var i = offset + packages.length; i < limit; i += 100) action(i, (Math.min(i + 100, limit) % 100) || 100, pattern); return L.rpc.flush(); }).then(function(lists) { for (var i = 0; i < lists.length; i++) { if (!lists[i].total || !lists[i].packages) continue; packages.push.apply(packages, lists[i].packages); packages.total = lists[i].total; } return packages; }); }, listPackages: function(offset, limit, pattern) { return this._fetchPackages(this._allPackages, offset, limit, pattern); }, installedPackages: function(offset, limit, pattern) { return this._fetchPackages(this._installedPackages, offset, limit, pattern); }, findPackages: function(offset, limit, pattern) { return this._fetchPackages(this._findPackages, offset, limit, pattern); }, installPackage: L.rpc.declare({ object: 'luci2.opkg', method: 'install', params: [ 'package' ], expect: { '': { } } }), removePackage: L.rpc.declare({ object: 'luci2.opkg', method: 'remove', params: [ 'package' ], expect: { '': { } } }), getConfig: L.rpc.declare({ object: 'luci2.opkg', method: 'config_get', expect: { config: '' } }), setConfig: L.rpc.declare({ object: 'luci2.opkg', method: 'config_set', params: [ 'data' ] }), isInstalled: function(pkg) { return this._installedPackages(0, 1, pkg).then(function(list) { return (!isNaN(list.total) && list.total > 0); }); } }, updateDiskSpace: function() { return L.system.getDiskInfo().then(function(info) { $('#package_space').empty().append( new L.ui.progress({ value: info.root.used / 1024, max: info.root.total / 1024, format: '%d ' + L.tr('kB') + ' / %d ' + L.tr('kB') + ' ' + L.trc('Used disk space', 'used') + ' (%d%%)' }).render()); }); }, installRemovePackage: function(pkgname, installed) { var self = this; var dspname = pkgname.replace(/^.+\//, ''); var action = installed ? self.opkg.removePackage : self.opkg.installPackage; var title = (installed ? L.tr('Removing package "%s" …') : L.tr('Installing package "%s" …')).format(dspname); var confirm = (installed ? L.tr('Really remove package "%h" ?') : L.tr('Really install package "%h" ?')).format(dspname); L.ui.dialog(title, confirm, { style: 'confirm', confirm: function() { L.ui.dialog(title, L.tr('Waiting for package manager …'), { style: 'wait' }); action.call(self.opkg, pkgname).then(function(res) { self.fetchInstalledList().then(function() { return self.fetchPackageList(); }).then(function() { var output = [ ]; if (res.stdout) output.push($('
').text(res.stdout));

						if (res.stderr)
							output.push($('
').addClass('alert-message').text(res.stderr));

						output.push(res.code ? L.tr('Package manager failed with status %d.').format(res.code) : L.tr('Package manager finished successfully.'));

						L.ui.dialog(title, output, { style: 'close' });

						if (name)
							$('#package_url').val('');
					});
				});
			}
		});
	},

	fetchInstalledList: function()
	{
		var self = this;
		return self.opkg.installedPackages(0, 0, '*').then(function(list) {
			self.installedList = { };
			for (var i = 0; i < list.length; i++)
				self.installedList[list[i][0]] = true;
		});
	},

	fetchPackageList: function(offset, interactive)
	{
		if (interactive)
			L.ui.loading(true);

		if (typeof(offset) == 'undefined')
			offset = parseInt($('#package_filter').attr('offset')) || 0;

		var self = this;

		var pattern = $('#package_filter').val() || '';
		var action;

		if (pattern.length)
		{
			action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.findPackages;
			pattern = '*' + pattern + '*';

			$('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/remove.gif');
		}
		else
		{
			action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.listPackages;
			pattern = '*';

			$('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/find.gif');
		}

		$('#package_filter').attr('offset', offset);

		var install_disabled = $('#package_install').attr('disabled');

		return action.call(self.opkg, offset, 100, pattern).then(function(list) {
			var packageTable = new L.ui.table({
				placeholder: L.tr('No matching packages found.'),
				columns: [ {
					caption: L.trc('Package table header', 'Package'),
					key:     0
				}, {
					caption: L.trc('Package table header', 'Version'),
					key:     1,
					format:  function(v) {
						return (v.length > 15 ? v.substring(0, 14) + '…' : v);
					}
				}, {
					caption: L.trc('Package table header', 'Description'),
					key:     2
				}, {
					caption: L.trc('Package table header', 'Installation Status'),
					key:     0,
					width:   '120px',
					format: function(v, n) {
						var inst = self.installedList[list[n][0]];
						return L.ui.button(inst ? L.trc('Package state', 'Installed') : L.trc('Package state', 'Not installed'), inst ? 'success' : 'danger')
							.css('width', '100%')
							.attr('disabled', install_disabled)
							.attr('pkgname', list[n][0])
							.attr('installed', inst)
							.click(function() {
								self.installRemovePackage(this.getAttribute('pkgname'), this.getAttribute('installed') == 'true');
							});
					}
				} ]
			});

			packageTable.rows(list);
			packageTable.insertInto('#package_table');

			if (offset > 0)
				$('#package_prev')
					.attr('offset', offset - 100)
					.attr('disabled', false)
					.text('« %d - %d'.format(offset - 100 + 1, offset));
			else
				$('#package_prev')
					.attr('disabled', true)
					.text('« %d - %d'.format(1, Math.min(100, list.total)));

			if ((offset + 100) < list.total)
				$('#package_next')
					.attr('offset', offset + 100)
					.attr('disabled', false)
					.text('%d - %d »'.format(offset + 100 + 1, Math.min(offset + 200, list.total)));
			else
				$('#package_next')
					.attr('disabled', true)
					.text('%d - %d »'.format(list.total - (list.total % 100) + 1, list.total));

			if (interactive)
				L.ui.loading(false);
		}).then(self.updateDiskSpace);
	},

	execute: function()
	{
		var self = this;

		$('textarea, input.cbi-button-save').attr('disabled', !this.options.acls.software);
		$('#package_update, #package_url, #package_install').attr('disabled', !this.options.acls.software);

		return $.when(
			self.opkg.getConfig().then(function(config) {
				$('#config textarea')
					.attr('rows', (config.match(/\n/g) || [ ]).length + 1)
					.val(config);

				$('#config button')
					.click(function() {
						var data = ($('#config textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
						L.ui.loading(true);
						self.opkg.setConfig(data).then(function() {
							$('#config textarea')
								.attr('rows', (data.match(/\n/g) || [ ]).length + 1)
								.val(data);

							L.ui.loading(false);
						});
					});
			}),
			self.fetchInstalledList(),
			self.updateDiskSpace()
		).then(function() {
			$('#package_prev, #package_next').click(function(ev) {
				if (!this.getAttribute('disabled'))
				{
					self.fetchPackageList(parseInt(this.getAttribute('offset')), true);
					this.blur();
				}
			});

			$('#package_filter').next().click(function(ev) {
				$('#package_filter').val('');
				self.fetchPackageList(0, true);
			});

			$('#package_filter').keyup(function(ev) {
				if (ev.which != 13)
					return true;

				ev.preventDefault();
				self.fetchPackageList(0, true);
				return false;
			});

			$('#package_which').click(function(ev) {
				this.blur();
				self.fetchPackageList(0, true);
			});

			$('#package_url').keyup(function(ev) {
				if (ev.which != 13)
					return true;

				ev.preventDefault();

				if (this.value)
					self.installRemovePackage(this.value, false);
			});

			$('#package_install').click(function(ev) {
				var name = $('#package_url').val();
				if (name)
					self.installRemovePackage(name, false);
			});

			$('#package_update').click(function(ev) {
				L.ui.dialog(L.tr('Updating package lists'), L.tr('Waiting for package manager …'), { style: 'wait' });
				self.opkg.updateLists().then(function(res) {
					var output = [ ];

					if (res.stdout)
						output.push($('
').text(res.stdout));

					if (res.stderr)
						output.push($('
').addClass('alert-message').text(res.stderr));

					output.push(res.code ? L.tr('Package manager failed with status %d.').format(res.code) : L.tr('Package manager finished successfully.'));

					L.ui.dialog(L.tr('Updating package lists'), output, { style: 'close' });
				});
			});

			return self.fetchPackageList(0);
		});
	}
});