Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions debian/copyright
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,3 @@ License: GPL-3+
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

Files: usr/lib/linuxmint/mintUpdate/proxygsettings.py
Copyright: 2011-2012 Canonical Ltd.
2014 Erik Devriendt
License: GPL-3 with special exception
This program 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.
.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranties of
MERCHANTABILITY, SATISFACTORY QUALITY, 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 this program. If not, see <http://www.gnu.org/licenses/>.
.
In addition, as a special exception, the copyright holders give
permission to link the code of portions of this program with the
OpenSSL library under certain conditions as described in each
individual source file, and distribute linked combinations
including the two.
You must obey the GNU General Public License in all respects
for all of the code used other than OpenSSL. If you modify
file(s) with this exception, you may extend this exception to your
version of the file(s), but you are not obligated to do so. If you
do not wish to do so, delete this exception statement from your
version. If you delete this exception statement from all source
files in the program, then also delete it here.
2 changes: 1 addition & 1 deletion tests/test_kernel_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from datetime import datetime
from Classes import *
from mintUpdate import *
from checkAPT import *
from aptUpdater import *

# Test KernelVersion object
def test_kernel_version_series_comparison():
Expand Down
40 changes: 36 additions & 4 deletions usr/lib/linuxmint/mintUpdate/Classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@

import datetime
import gettext
import html
import json
import os
import subprocess
import sys
import time
import re
import threading

gettext.install("mintupdate", "/usr/share/locale")
Expand Down Expand Up @@ -71,6 +69,40 @@ def get_release_dates():
pass
return release_dates

class AutorefreshTimer(GLib.Source):
# A GSource that can be armed, disarmed, and re-armed without being
# recreated. Attach once at startup; thereafter call arm(seconds) to
# schedule the next dispatch and disarm() to cancel a pending one.
def __init__(self, callback):
super().__init__()
self._callback = callback

def prepare(self):
ready_time = self.get_ready_time()
if ready_time == -1:
return (False, -1)
now = GLib.get_monotonic_time()
if ready_time <= now:
return (True, 0)
timeout_ms = (ready_time - now + 999) // 1000
return (False, min(int(timeout_ms), 0x7FFFFFFF))

def check(self):
ready_time = self.get_ready_time()
return ready_time != -1 and ready_time <= GLib.get_monotonic_time()

def dispatch(self, callback, user_data):
self.set_ready_time(-1)
self._callback()
return GLib.SOURCE_CONTINUE

def arm(self, seconds):
self.set_ready_time(GLib.get_monotonic_time() + seconds * 1_000_000)

def disarm(self):
self.set_ready_time(-1)


class KernelVersion():

def __init__(self, version):
Expand Down Expand Up @@ -210,15 +242,15 @@ class UpdateTracker():

# Loads past updates from JSON file
def __init__(self, settings, logger):
os.system("mkdir -p %s" % CONFIG_PATH)
os.makedirs(CONFIG_PATH, exist_ok=True)
self.path = os.path.join(CONFIG_PATH, "updates.json")

self.test_mode = os.getenv("MINTUPDATE_TEST") == "tracker-max-age"

self.tracker_version = 1 # version of the data structure
self.settings = settings
self.tracked_updates = {}
self.refreshed_update_names = [] # updates which are seen in checkAPT
self.refreshed_update_names = [] # updates which are seen in aptUpdater
self.today = datetime.date.today().strftime("%Y.%m.%d")
self.max_days = 0 # oldest update (in number of days seen)
self.oldest_since_date = self.today # oldest update (according to since date)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@
import codecs
import fnmatch
import gettext
import json
import os
import queue
import re
import subprocess
import sys
import threading
import traceback
import html
import locale

import apt
from gi.repository import Gio
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, Gtk, GLib

from Classes import (CONFIGURED_KERNEL_TYPE, KERNEL_PKG_NAMES,
PRIORITY_UPDATES, SUPPORTED_KERNEL_TYPES, Alias, KernelVersion, Update)
import aptkit.simpleclient
import aptkit.enums

from multiprocess import Process, Queue

from Classes import (CONFIG_PATH, CONFIGURED_KERNEL_TYPE, KERNEL_PKG_NAMES,
PRIORITY_UPDATES, SUPPORTED_KERNEL_TYPES, Alias, KernelVersion, Update,
_idle)

gettext.install("mintupdate", "/usr/share/locale")

Expand All @@ -23,22 +35,202 @@
# packages which description is incorrect in Ubuntu (usually those which were replaced by snap dependencies)
NON_TRANSLATED_PKGS = ["firefox", "thunderbird"]

class APTCheck():
class AptUpdater():

def __init__(self):
def __init__(self, ui_window=None):
self.settings = Gio.Settings(schema_id="com.linuxmint.updates")
self.cache = apt.Cache()
self.ui_window = ui_window
self.cache = None
self.priority_updates_available = False
# During build (find_changes/add_update) self.updates is a dict keyed by
# source_name. fetch_updates() replaces it with the final list of Updates.
self.updates = {}
self.error = None
self.install_error = None
self.install_cancelled = False

def load_cache(self):
self.cache = apt.Cache()

def refresh(self, interactive=False):
# Refresh the on-disk APT cache. Blocks until done. Returns None on
# success, or a short string describing what went wrong (transaction
# non-success exit_state, daemon error, cancellation, sync exception,
# or non-zero exit from mint-refresh-cache) — caller logs it.

if interactive:
self._aptkit_done = threading.Event()
self._refresh_error = None
self._refresh_via_aptkit()
if not self._aptkit_done.wait(timeout=600):
self._refresh_error = "Timed out waiting for aptkit cache refresh"
return self._refresh_error
else:
return self._refresh_via_mint_refresh_cache()

@_idle
def _refresh_via_aptkit(self):
try:
aptkit_client = aptkit.simpleclient.SimpleAPTClient(self.ui_window)

def on_finished(transaction, exit_state):
if exit_state != aptkit.enums.EXIT_SUCCESS:
self._refresh_error = f"aptkit transaction finished with exit_state={exit_state}"
self._aptkit_done.set()

def on_error(error_code, error_details):
self._refresh_error = f"aptkit error code={error_code} details={error_details}"
self._aptkit_done.set()

def on_cancelled():
self._refresh_error = "aptkit transaction cancelled"
self._aptkit_done.set()

aptkit_client.set_progress_callback(None)
aptkit_client.set_finished_callback(on_finished)
aptkit_client.set_error_callback(on_error)
aptkit_client.set_cancelled_callback(on_cancelled)

aptkit_client.update_cache()
except Exception as e:
self._refresh_error = f"aptkit setup raised: {e}"
self._aptkit_done.set()

@staticmethod
def _refresh_via_mint_refresh_cache():
try:
result = subprocess.run(["sudo", "/usr/bin/mint-refresh-cache"])
if result.returncode != 0:
return f"mint-refresh-cache exited with code {result.returncode}"
return None
except Exception as e:
return f"mint-refresh-cache raised: {e}"

def install_packages(self, packages):
# Install the given list of package names via aptkit. Blocks until the
# transaction finishes, is cancelled, or errors. After return, callers
# should check self.install_cancelled and self.install_error.
self.install_error = None
self.install_cancelled = False
self._aptkit_done = threading.Event()
self._start_install(packages)
# Generous bound (4h) so legitimately long upgrades aren't aborted, but
# we never hang forever if aptkit dies without firing a callback.
if not self._aptkit_done.wait(timeout=4 * 60 * 60):
self.install_error = "Timed out waiting for aptkit install"

@_idle
def _start_install(self, packages):
try:
client = aptkit.simpleclient.SimpleAPTClient(self.ui_window)

def on_finished(transaction, exit_state):
if exit_state != aptkit.enums.EXIT_SUCCESS:
self.install_error = f"aptkit transaction finished with exit_state={exit_state}"
self._aptkit_done.set()

def on_error(error_code, error_details):
self.install_error = f"aptkit error code={error_code} details={error_details}"
self._aptkit_done.set()

def on_cancelled():
self.install_cancelled = True
self._aptkit_done.set()

client.set_finished_callback(on_finished)
client.set_error_callback(on_error)
client.set_cancelled_callback(on_cancelled)

client.install_packages(packages)
except Exception as e:
self.install_error = f"aptkit setup raised: {e}"
self._aptkit_done.set()

def fetch_updates(self):
# Compute the list of available updates. Blocks until done, or until
# the child process dies / times out — in which case self.error is set.
self.updates = []
self.error = None
result_queue = Queue()
process = Process(target=self._fetch_updates_in_process, args=(result_queue,))
process.start()
try:
self.error, self.updates = result_queue.get(timeout=60)
except queue.Empty:
self.error = "Timed out waiting for fetch_updates result"
if process.is_alive():
process.terminate()
process.join(timeout=5)
if self.error is None and process.exitcode not in (0, None):
self.error = f"fetch_updates child exited with code {process.exitcode}"

def _fetch_updates_in_process(self, queue):
try:
self.load_cache()
self.find_changes()
self.apply_l10n_descriptions()
self.load_aliases()
self.apply_aliases()
self.clean_descriptions()
queue.put([None, self.get_updates()])
except Exception as error:
print(sys.exc_info()[0])
print("Error in fetch_updates: %s" % error)
traceback.print_exc()
queue.put([str(error).replace("E:", "\n").strip(), []])

def fetch_test_updates(self, test_name):
print("SIMULATING TEST MODE:", test_name)
self.updates = {}
self.error = None

if test_name == "error":
self.error = "Testing - this is a simulated error."
elif test_name == "up-to-date":
pass
elif test_name == "self-update":
self.load_cache()
self._add_dummy_update("mintupdate", False)
elif test_name == "updates":
self.load_cache()
self._add_dummy_update("python3", False)
self._add_dummy_update("mint-meta-core", False)
self._add_dummy_update("linux-generic", True)
self._add_dummy_update("xreader", False)
elif test_name == "tracker-max-age":
self.load_cache()
self._add_dummy_update("dnsmasq", False)
self._add_dummy_update("linux-generic", True)

updates_json = {
"mint-meta-common": { "type": "package", "since": "2020.12.03", "days": 99 },
"linux-meta": { "type": "security", "since": "2020.12.03", "days": 99 }
}
root_json = {
"updates": updates_json,
"version": 1,
"checked": "2020.12.04",
"notified": "2020.12.03"
}
os.makedirs(CONFIG_PATH, exist_ok=True)
with open(os.path.join(CONFIG_PATH, "updates.json"), "w") as f:
json.dump(root_json, f)

# Match the post-fetch_updates contract: .updates is a list of Update objects.
self.updates = list(self.updates.values())

def _add_dummy_update(self, package_name, kernel_update):
pkg = self.cache[package_name]
self.add_update(pkg, kernel_update, "99.0.0")

def load_aliases(self):
self.aliases = {}
with open("/usr/lib/linuxmint/mintUpdate/aliases") as alias_file:
for line in alias_file:
if not line.startswith('#'):
splitted = line.split("#####")

Check failure on line 231 in usr/lib/linuxmint/mintUpdate/aptUpdater.py

View workflow job for this annotation

GitHub Actions / build / build (mint22, linuxmintd/mint22.3-amd64, Mint 22, true) / Mint 22

splitted ==> split
if len(splitted) == 4:

Check failure on line 232 in usr/lib/linuxmint/mintUpdate/aptUpdater.py

View workflow job for this annotation

GitHub Actions / build / build (mint22, linuxmintd/mint22.3-amd64, Mint 22, true) / Mint 22

splitted ==> split
(alias_packages, alias_name, alias_short_description, alias_description) = splitted

Check failure on line 233 in usr/lib/linuxmint/mintUpdate/aptUpdater.py

View workflow job for this annotation

GitHub Actions / build / build (mint22, linuxmintd/mint22.3-amd64, Mint 22, true) / Mint 22

splitted ==> split
alias_object = Alias(alias_name, alias_short_description, alias_description)
for alias_package in alias_packages.split(','):
alias_package = alias_package.strip()
Expand Down Expand Up @@ -347,14 +539,13 @@

if __name__ == "__main__":
try:
check = APTCheck()
check.find_changes()
check.apply_l10n_descriptions()
check.load_aliases()
check.apply_aliases()
check.clean_descriptions()
updates = check.get_updates()
for update in updates:
check = AptUpdater()
check.refresh()
check.fetch_updates()
if check.error is not None:
print("Error: %s" % check.error)
sys.exit(1)
for update in check.updates:
print(update.display_name, update.new_version, update.short_description)
except Exception as error:
print(error)
Expand Down
Loading
Loading