add packaging changes and some cleanup

This commit is contained in:
GloriousEggroll 2025-04-13 14:38:00 -06:00
parent 9034ccc087
commit 537d04db82
9 changed files with 272 additions and 99841 deletions

45
Makefile Normal file
View file

@ -0,0 +1,45 @@
PYTHON_SITE_PACKAGES := $(shell python3 -c "import site; print(site.getsitepackages()[0].replace('lib64', 'lib'))")
TARGET_DIR := $(DESTDIR)$(PYTHON_SITE_PACKAGES)/flatpost
BIN_DIR := $(DESTDIR)/usr/bin
DESKTOP_DIR := $(DESTDIR)/usr/share/applications
DATA_DIR := $(DESTDIR)/usr/share/flatpost
ICON_DIR := $(DESTDIR)/usr/share/icons/hicolor/1024x1024/apps
LICENSE_DIR := $(DESTDIR)/usr/share/licenses/flatpost
.PHONY: all install clean
all: install
install:
@echo "Installing Python files to $(TARGET_DIR)"
mkdir -p $(TARGET_DIR)
install -m 644 src/fp_turbo.py $(TARGET_DIR)/fp_turbo.py
@echo "Main executable file to $(BIN_DIR)"
mkdir -p $(BIN_DIR)
install -m 755 src/flatpost.py $(BIN_DIR)/flatpost
@echo "Installing desktop file to $(DESKTOP_DIR)"
mkdir -p $(DESKTOP_DIR)
install -m 644 data/usr/share/applications/com.flatpost.flatpostapp.desktop $(DESKTOP_DIR)/com.flatpost.flatpostapp.desktop
@echo "Installing data files to $(DATA_DIR)"
mkdir -p $(DATA_DIR)
install -m 644 data/usr/share/flatpost/collections_data.json $(DATA_DIR)/collections_data.json
@echo "Installing icon file to $(ICON_DIR)"
mkdir -p $(ICON_DIR)
install -m 644 data/usr/share/icons/hicolor/1024x1024/apps/com.flatpost.flatpostapp.png $(ICON_DIR)/com.flatpost.flatpostapp.png
@echo "Installing license file to $(LICENSE_DIR)"
mkdir -p $(LICENSE_DIR)
install -m 644 data/usr/share/licenses/flatpost/LICENSE $(LICENSE_DIR)/LICENSE
clean:
@echo "Cleaning up installed files"
rm -rf $(TARGET_DIR)
rm -f $(BIN_DIR)/flatpost
rm -f $(DESKTOP_DIR)/com.flatpost.flatpostapp.desktop
rm -f $(DATA_DIR)/collections_data.json
rm -f $(ICON_DIR)/com.flatpost.flatpostapp.png
rm -f $(LICENSE_DIR)/com.flatpost.flatpostapp.png

View file

@ -0,0 +1,6 @@
[Desktop Entry]
Name=Flatpost
Exec=python3 /usr/bin/flatpost
Icon=com.flatpost.flatpostapp
Type=Application
Categories=Utility;

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

71
packaging/flatpost.spec Normal file
View file

@ -0,0 +1,71 @@
Name: flatpost
Version: 1.0.0
Release: 1%{?dist}
License: BSD 2-Clause
Summary: Desktop environment agnostic Flathub software center.
URL: https://github.com/gloriouseggroll/flatpost
Source0: %{URL}/releases/download/1.0/flatpost.tar.gz
BuildArch: noarch
BuildRequires: python3-devel
BuildRequires: make
Provides: nobara-updater
# App Deps
Requires: python
Requires: python3
Requires: python3-gobject
Requires: python3-requests
Requires: python3-pillow
Requires: python3-svgwrite
Requires: python3-fonttools
Requires: python3-numpy
Requires: flatpak
Requires: glib2
Requires: gtk3
Requires: gtk4
Requires: xdg-utils
Provides: flatpost
%description
Desktop environment agnostic Flathub software center. Allows for browsing,
installation, removal, updating, and permission management of flatpak packages and repositories.
%prep
%autosetup -p1 -n flatpost
%build
make all DESTDIR=%{buildroot}
%post
#!/bin/bash
# Check if we already have the association
if [ ! -f /usr/bin/xdg-mime ]; then
# If xdg-mime is not available, skip this step
exit 0
fi
# Set the default application for .rpm files
xdg-mime default /usr/share/applications/com.flatpost.flatpostapp.desktop application/vnd.flatpak.ref
xdg-mime default /usr/share/applications/com.flatpost.flatpostapp.desktop application/vnd.flatpak.repo
update-mime-database /usr/share/mime
%files
%{python3_sitelib}/flatpost/
%{_bindir}/flatpost
%{_datadir}/applications/com.flatpost.flatpostapp.desktop
%{_datadir}/flatpost/collections_data.json
%{_datadir}/icons/hicolor/1024x1024/apps/com.flatpost.flatpostapp.png
%license %{_datadir}/licenses/flatpost/LICENSE
%clean
rm -rf %{buildroot}
%changelog
* Fri Jun 28 2024 Your Name <you@example.com> - 1.0-1
- Initial package

View file

@ -7,8 +7,8 @@ gi.require_version("GLib", "2.0")
gi.require_version("Flatpak", "1.0")
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gtk, Gio, Gdk, GLib, GdkPixbuf
import fp_turbo
from fp_turbo import AppStreamComponentKind as AppKind
import flatpost.fp_turbo as fp_turbo
from flatpost.fp_turbo import AppStreamComponentKind as AppKind
import json
import threading
import subprocess
@ -21,11 +21,38 @@ from datetime import datetime
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Flatpost")
# Step 1: Verify file exists and is accessible
icon_path = "/usr/share/icons/hicolor/1024x1024/apps/com.flatpost.flatpostapp.png"
if not os.path.exists(icon_path):
print("ERROR: Icon file not found!")
return
# Step 2: Test loading individual pixbufs
try:
# Try loading smallest size first
self.pixbuf16 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 16, 16, True)
# Now load full set of sizes
self.pixbuf24 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 24, 24, True)
self.pixbuf32 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 32, 32, True)
self.pixbuf48 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 48, 48, True)
self.pixbuf64 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 64, 64, True)
self.pixbuf128 = GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_path, 128, 128, True)
Gtk.Window.set_default_icon(self.pixbuf48)
# Set the icon list
self.set_icon_list([self.pixbuf16, self.pixbuf24, self.pixbuf32, self.pixbuf48, self.pixbuf64, self.pixbuf128])
except Exception as e:
print(f"ERROR loading icon: {str(e)}")
# Store search results as an instance variable
self.all_apps = []
self.current_component_type = None
self.category_results = [] # Initialize empty list
self.subcategory_results = [] # Initialize empty list
self.subcategory_buttons = {}
self.collection_results = [] # Initialize empty list
self.installed_results = [] # Initialize empty list
@ -419,6 +446,8 @@ class MainWindow(Gtk.Window):
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 600
)
self.refresh_data()
# Create main layout
self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@ -430,9 +459,6 @@ class MainWindow(Gtk.Window):
# Create panels
self.create_panels()
self.refresh_data()
#self.refresh_local()
# Select Trending by default
self.select_default_category()
@ -615,7 +641,12 @@ class MainWindow(Gtk.Window):
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
main_box.set_border_width(12)
# Icon
image = Gtk.Image.new_from_pixbuf(self.pixbuf64)
# Version label
name_label = Gtk.Label(label="Flatpost")
name_label.get_style_context().add_class("permissions-header-label")
version_label = Gtk.Label(label="Version 1.0.0")
copyright_label = Gtk.Label(label=f"Copyright © 2025-{datetime.now().year} Thomas Crider")
program_label = Gtk.Label(label="This program comes with absolutely no warranty.")
@ -644,6 +675,8 @@ class MainWindow(Gtk.Window):
# Add all widgets
content_area.add(main_box)
main_box.pack_start(name_label, False, False, 0)
main_box.pack_start(image, False, False, 0)
main_box.pack_start(version_label, False, False, 0)
main_box.pack_start(copyright_label, False, False, 0)
main_box.pack_start(program_label, False, False, 0)
@ -750,9 +783,8 @@ class MainWindow(Gtk.Window):
# Define thread target function
def retrieve_metadata():
try:
category_results, collection_results, subcategory_results, installed_results, updates_results, all_apps = searcher.retrieve_metadata(self.system_mode)
category_results, collection_results, installed_results, updates_results, all_apps = searcher.retrieve_metadata(self.system_mode)
self.category_results = category_results
self.category_results = subcategory_results
self.collection_results = collection_results
self.installed_results = installed_results
self.updates_results = updates_results
@ -1021,6 +1053,9 @@ class MainWindow(Gtk.Window):
label.set_markup(markup)
break
if self.updates_results == []:
self.updates_available_bar.set_visible(False)
self.current_page = category
self.current_group = group
self.update_category_header(category)
@ -1393,7 +1428,7 @@ class MainWindow(Gtk.Window):
button = Gtk.Button(label=label)
button.get_style_context().add_class("app-button")
if condition is not None:
if not condition(app):
# if not condition(app):
return None
button.connect("clicked", callback, app)
return button
@ -1427,9 +1462,20 @@ class MainWindow(Gtk.Window):
if apps:
apps.sort(key=lambda app: self.get_app_priority(app.get_details()['kind']))
# Define paths
app_data_dir = Path.home() / ".local" / "share" / "flatpost"
system_data_dir = Path("/usr/share/flatpost")
# Ensure local directory exists
app_data_dir.mkdir(parents=True, exist_ok=True)
# Define file paths
json_path = app_data_dir / "collections_data.json"
subcategories_path = app_data_dir / "subcategories_data.json"
# Load collections data
try:
with open("collections_data.json", 'r', encoding='utf-8') as f:
with open(json_path, 'r', encoding='utf-8') as f:
collections_data = json.load(f)
# Find the specific category in collections data
@ -1457,12 +1503,12 @@ class MainWindow(Gtk.Window):
])
except (IOError, json.JSONDecodeError) as e:
print(f"Error reading collections data: {str(e)}")
apps.extend([
app for app in self.collection_results
if category in app.get_details()['categories']
])
if 'repositories' in category:
# Clear existing content
for child in self.right_container.get_children():
@ -1748,14 +1794,25 @@ class MainWindow(Gtk.Window):
buttons_box.set_halign(Gtk.Align.END)
buttons_box.set_valign(Gtk.Align.CENTER) # Center vertically
self._add_action_button(
buttons_box,
status['is_installed'],
app,
self.on_remove_clicked if status['is_installed'] else self.on_install_clicked,
"list-remove-symbolic" if status['is_installed'] else "list-add-symbolic",
"remove" if status['is_installed'] else "install"
)
# Add install/remove buttons separately
if status['is_installed']:
self._add_action_button(
buttons_box,
True,
app,
self.on_remove_clicked,
"list-remove-symbolic",
"remove"
)
else:
self._add_action_button(
buttons_box,
True,
app,
self.on_install_clicked,
"list-add-symbolic",
"install"
)
if status['is_installed']:
self._add_action_button(
@ -1939,7 +1996,6 @@ class MainWindow(Gtk.Window):
selected_repo = None
if button:
selected_repo = self.repo_combo.get_active_text()
print(selected_repo)
def installation_thread():
GLib.idle_add(self.show_waiting_dialog)

View file

@ -45,6 +45,7 @@ import argparse
import requests
from urllib.parse import quote_plus, urlparse
import tempfile
import shutil
import os
import sys
import json
@ -416,7 +417,7 @@ class AppstreamSearcher:
inst.modify_remote(remote, None)
inst.update_appstream_full_sync(remote.get_name(), None, None, True)
appstream_file = Path(remote.get_appstream_dir().get_path() + "/appstream.xml.gz")
if not appstream_file.exists() and check_internet():
if not appstream_file.exists():
try:
if remote.get_name() == "flathub" or remote.get_name() == "flathub-beta":
remote.set_gpg_verify(True)
@ -611,11 +612,14 @@ class AppstreamSearcher:
def save_collections_data(self, filename='collections_data.json'):
"""Save all collected collections data to a JSON file."""
app_data_dir = Path.home() / ".local" / "share" / "flatpost"
app_data_dir.mkdir(parents=True, exist_ok=True)
json_path = app_data_dir / filename
if not hasattr(self, 'collections_db') or not self.collections_db:
return
try:
with open(filename, 'w', encoding='utf-8') as f:
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(self.collections_db, f, indent=2, ensure_ascii=False)
except IOError as e:
print(f"Error saving collections data: {str(e)}")
@ -668,41 +672,9 @@ class AppstreamSearcher:
print(f"Error fetching apps: {str(e)}")
return None
def save_subcategories_data(self, filename='subcategories_data.json'):
"""Save all collected subcategories data to a JSON file."""
if not hasattr(self, 'subcategories_results') or not self.subcategories_results:
return
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.subcategories_results, f, indent=2, ensure_ascii=False)
except IOError as e:
print(f"Error saving subcategories data: {str(e)}")
def update_subcategories_data(self):
"""Fetch and store data for all subcategories."""
if not hasattr(self, 'subcategories_results'):
self.subcategories_results = []
# Process each category and its subcategories
for category, subcategories in self.subcategory_groups.items():
for subcategory, title in subcategories.items():
api_data = self.fetch_flathub_subcategory_apps(category, subcategory)
if api_data:
self.subcategories_results.append({
'category': category,
'subcategory': subcategory,
'data': api_data
})
# Save the collected data
self.save_subcategories_data()
def refresh_local(self, system=False):
# make sure to reset these to empty before refreshing.
self.installed_results = [] # Initialize empty list
self.updates_results = [] # Initialize empty list
self._initialize_metadata()
total_categories = sum(len(categories) for categories in self.category_groups.values())
current_category = 0
@ -712,19 +684,7 @@ class AppstreamSearcher:
for group_name, categories in self.category_groups.items():
# Process categories one at a time to keep GUI responsive
for category, title in categories.items():
if "installed" in category:
installed_apps = searcher.get_installed_apps(system)
for app_id, repo_name, repo_type in installed_apps:
if repo_name:
search_result = searcher.search_flatpak(app_id, repo_name)
self.installed_results.extend(search_result)
elif "updates" in category:
updates = searcher.check_updates(system)
for app_id, repo_name, repo_type in updates:
if repo_name:
search_result = searcher.search_flatpak(app_id, repo_name)
self.updates_results.extend(search_result)
self._process_system_category(searcher, category, system)
# Update progress bar
self.refresh_progress = (current_category / total_categories) * 100
# make sure to reset these to empty before refreshing.
@ -747,31 +707,62 @@ class AppstreamSearcher:
"""Initialize empty lists for metadata storage."""
self.category_results = []
self.collection_results = []
self.subcategories_results = []
self.installed_results = []
self.updates_results = []
self.all_apps = []
def _handle_offline_mode(self):
"""Handle metadata retrieval when offline."""
json_path = "collections_data.json"
total_categories = sum(len(categories) for categories in self.category_groups.values())
current_category = 0
# Search for each app in local repositories
searcher = get_reposearcher()
search_result = []
for group_name, categories in self.category_groups.items():
# Process categories one at a time to keep GUI responsive
for category, title in categories.items():
self._process_system_category(searcher, category)
# Define paths
app_data_dir = Path.home() / ".local" / "share" / "flatpost"
system_data_dir = Path("/usr/share/flatpost")
# Ensure local directory exists
app_data_dir.mkdir(parents=True, exist_ok=True)
# Define file paths
json_path = app_data_dir / "collections_data.json"
# Helper function to copy file if it doesn't exist locally
def copy_if_missing(source_path, dest_path):
try:
shutil.copy(str(source_path), str(dest_path))
logger.info(f"Copied {dest_path.name} to user directory")
except IOError as e:
logger.error(f"Failed to copy {dest_path.name}: {str(e)}")
return False
return True
# Try to copy collections_data.json if needed
if not json_path.exists() and not copy_if_missing(
system_data_dir / "collections_data.json",
json_path
):
logger.error("Could not load or copy collections_data.json")
return None, [], [], [], []
try:
with open(json_path, 'r', encoding='utf-8') as f:
collections_data = json.load(f)
return self._process_offline_data(collections_data)
processed_data = self._process_offline_data(collections_data)
return processed_data
except (IOError, json.JSONDecodeError) as e:
logger.error(f"Error loading offline data: {str(e)}")
return None, [], [], [], []
# Also load subcategories data
subcategories_path = "subcategories_data.json"
try:
with open(subcategories_path, 'r', encoding='utf-8') as f:
subcategories_data = json.load(f)
self.subcategories_results = subcategories_data
except (IOError, json.JSONDecodeError) as e:
logger.error(f"Error loading subcategories data: {str(e)}")
self.subcategories_results = []
def _process_offline_data(self, collections_data):
"""Process cached collections data when offline."""
@ -798,9 +789,6 @@ class AppstreamSearcher:
current_category += 1
self.save_collections_data()
if self._should_refresh():
self.update_subcategories_data()
return self._get_current_results()
def _process_category(self, searcher, category, current_category, total_categories):
@ -809,7 +797,9 @@ class AppstreamSearcher:
if self._should_refresh():
self._refresh_category_data(searcher, category)
json_path = "collections_data.json"
app_data_dir = Path.home() / ".local" / "share" / "flatpost"
app_data_dir.mkdir(parents=True, exist_ok=True)
json_path = app_data_dir / "collections_data.json"
try:
with open(json_path, 'r', encoding='utf-8') as f:
collections_data = json.load(f)
@ -830,7 +820,9 @@ class AppstreamSearcher:
def _should_refresh(self):
"""Check if category data needs refresh."""
json_path = "collections_data.json"
app_data_dir = Path.home() / ".local" / "share" / "flatpost"
app_data_dir.mkdir(parents=True, exist_ok=True)
json_path = app_data_dir / "collections_data.json"
try:
mod_time = os.path.getmtime(json_path)
return (time.time() - mod_time) > 168 * 3600
@ -856,24 +848,21 @@ class AppstreamSearcher:
def _process_system_category(self, searcher, category, system=False):
"""Process system-related categories."""
if "installed" in category:
installed_apps = searcher.get_installed_apps(system)
for app_id, repo_name, repo_type in installed_apps:
if repo_name:
search_result = searcher.search_flatpak(app_id, repo_name)
self.installed_results.extend(search_result)
elif "updates" in category:
updates = searcher.check_updates(system)
for app_id, repo_name, repo_type in updates:
if repo_name:
search_result = searcher.search_flatpak(app_id, repo_name)
self.updates_results.extend(search_result)
installed_apps = get_installation(system).list_installed_refs()
for app in installed_apps:
search_result = searcher.search_flatpak(app.get_name(), app.get_origin())
self.installed_results.extend(search_result)
elif "updates" in category and check_internet():
updates = get_installation(system).list_installed_refs_for_update()
for app in updates:
search_result = searcher.search_flatpak(app.get_name(), app.get_origin())
self.updates_results.extend(search_result)
def _get_current_results(self):
"""Return current metadata results."""
return (
self.category_results,
self.collection_results,
self.subcategories_results,
self.installed_results,
self.updates_results,
self.all_apps
@ -2846,13 +2835,14 @@ def handle_search(args, searcher):
for i, screenshot in enumerate(details['screenshots'], 1):
print(f"\nScreenshot #{i}:")
image = screenshot_details(screenshot)
# Get image properties using the correct methods
print("\nImage Properties:")
print(f"URL: {image.get_url()}")
print(f"Width: {image.get_width()}")
print(f"Height: {image.get_height()}")
print(f"Scale: {image.get_scale()}")
print(f"Locale: {image.get_locale()}")
if image:
# Get image properties using the correct methods
print("\nImage Properties:")
print(f"URL: {image.get_url()}")
print(f"Width: {image.get_width()}")
print(f"Height: {image.get_height()}")
print(f"Scale: {image.get_scale()}")
print(f"Locale: {image.get_locale()}")
print("-" * 50)

File diff suppressed because one or more lines are too long