// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); const Config = @import("Config.zig"); const Snapshot = @import("browser/js/Snapshot.zig"); const Platform = @import("browser/js/Platform.zig"); const Telemetry = @import("telemetry/telemetry.zig").Telemetry; const Network = @import("network/Network.zig"); const Watchdog = @import("Watchdog.zig"); pub const ArenaPool = @import("ArenaPool.zig"); const log = lp.log; const Allocator = std.mem.Allocator; const App = @This(); network: Network, config: *const Config, platform: Platform, snapshot: Snapshot, telemetry: Telemetry, watchdog: Watchdog, allocator: Allocator, arena_pool: ArenaPool, app_dir_path: ?[]const u8, pub fn init(allocator: Allocator, config: *const Config) !*App { const platform = try Platform.init(.{ .v8_flags = config.v8Flags(), .locale = config.locale(), .timezone = config.timezone(), }); errdefer platform.deinit(); const snapshot = try Snapshot.load(); errdefer snapshot.deinit(); const app = try allocator.create(App); errdefer allocator.destroy(app); app.* = .{ .config = config, .allocator = allocator, .platform = platform, .snapshot = snapshot, .network = undefined, .app_dir_path = undefined, .telemetry = undefined, .arena_pool = undefined, .watchdog = .init(config.watchdogMs()), }; try app.watchdog.start(); errdefer app.watchdog.deinit(); app.network = try Network.init(app); errdefer app.network.deinit(); app.app_dir_path = getAndMakeAppDir(allocator); app.telemetry = try Telemetry.init(app); errdefer app.telemetry.deinit(allocator); app.arena_pool = ArenaPool.init(allocator, .{}); errdefer app.arena_pool.deinit(); return app; } pub fn deinit(self: *App) void { const allocator = self.allocator; // All browsers are gone by now, so the entry list is empty; this just // stops the checker thread. self.watchdog.deinit(); if (self.app_dir_path) |app_dir_path| { allocator.free(app_dir_path); self.app_dir_path = null; } self.telemetry.deinit(allocator); self.network.deinit(); self.snapshot.deinit(); self.platform.deinit(); self.arena_pool.deinit(); allocator.destroy(self); } fn getAndMakeAppDir(allocator: Allocator) ?[]const u8 { if (lp.IS_TEST) { return allocator.dupe(u8, "/tmp") catch unreachable; } const app_dir_path = getAppDataDir(allocator, "lightpanda") catch |err| { log.warn(.app, "get data dir", .{ .err = err }); return null; }; std.Io.Dir.cwd().createDirPath(lp.io, app_dir_path) catch |err| switch (err) { else => { allocator.free(app_dir_path); log.warn(.app, "create data dir", .{ .err = err, .path = app_dir_path }); return null; }, }; return app_dir_path; } pub fn getAppDataDir(allocator: Allocator, appname: []const u8) ![]const u8 { switch (@import("builtin").os.tag) { .macos, .ios => { const home = std.c.getenv("HOME") orelse return error.AppDataDirUnavailable; return std.fs.path.join(allocator, &.{ std.mem.span(home), "Library", "Application Support", appname }); }, else => { if (std.c.getenv("XDG_DATA_HOME")) |xdg| { const x = std.mem.span(xdg); if (x.len > 0) { return std.fs.path.join(allocator, &.{ x, appname }); } } const home = std.c.getenv("HOME") orelse return error.AppDataDirUnavailable; return std.fs.path.join(allocator, &.{ std.mem.span(home), ".local", "share", appname }); }, } }