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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ if(WEBVIEW_BUILD)
if(WEBVIEW_ENABLE_PACKAGING)
add_subdirectory(packaging)
endif()

add_subdirectory(alloy)
endif()
41 changes: 41 additions & 0 deletions alloy/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.16)
project(alloy LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17) # Updated to C++17 for std::filesystem
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(ALLOY_SOURCES
main.cpp
runtime.cpp
cron_parser.cpp
subprocess.cpp
process_manager.cpp
shell_lexer.cpp
shell_parser.cpp
shell_builtins.cpp
shell_interpreter.cpp
shell_glob.cpp
)

if(APPLE)
list(APPEND ALLOY_SOURCES cron_manager_macos.mm)
elseif(WIN32)
list(APPEND ALLOY_SOURCES cron_manager_windows.cpp)
else()
list(APPEND ALLOY_SOURCES cron_manager_linux.cpp)
endif()

add_executable(alloy_bin ${ALLOY_SOURCES})
target_include_directories(alloy_bin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../core/include)
target_link_libraries(alloy_bin PRIVATE webview::core)

if(UNIX AND NOT APPLE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0 webkit2gtk-4.1)
target_include_directories(alloy_bin PRIVATE ${GTK3_INCLUDE_DIRS})
target_link_libraries(alloy_bin PRIVATE ${GTK3_LIBRARIES} dl util)
elseif(APPLE)
target_link_libraries(alloy_bin PRIVATE "-framework WebKit" dl)
elseif(WIN32)
target_link_libraries(alloy_bin PRIVATE advapi32 ole32 shell32 shlwapi user32 version)
endif()
13 changes: 13 additions & 0 deletions alloy/cron_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "cron_manager.hpp"

namespace alloy {

#ifdef _WIN32
// Included via cron_manager_windows.cpp in actual build
#elif defined(__APPLE__)
// Included via cron_manager_macos.mm in actual build
#else
// Included via cron_manager_linux.cpp in actual build
#endif

} // namespace alloy
16 changes: 16 additions & 0 deletions alloy/cron_manager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef ALLOY_CRON_MANAGER_HPP
#define ALLOY_CRON_MANAGER_HPP

#include <string>

namespace alloy {

class cron_manager {
public:
static void register_job(const std::string& path, const std::string& schedule, const std::string& title);
static void remove_job(const std::string& title);
};

} // namespace alloy

#endif // ALLOY_CRON_MANAGER_HPP
130 changes: 130 additions & 0 deletions alloy/cron_manager_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include "cron_manager.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <unistd.h>
#include <sys/wait.h>

namespace alloy {

static std::string run_command_with_args(const std::vector<std::string>& args, const std::string& input = "") {
int input_pipe[2];
int output_pipe[2];
if (pipe(input_pipe) == -1 || pipe(output_pipe) == -1) {
throw std::runtime_error("Failed to create pipes");
}

pid_t pid = fork();
if (pid == -1) {
throw std::runtime_error("Failed to fork");
}

if (pid == 0) { // Child
dup2(input_pipe[0], STDIN_FILENO);
dup2(output_pipe[1], STDOUT_FILENO);
close(input_pipe[0]);
close(input_pipe[1]);
close(output_pipe[0]);
close(output_pipe[1]);

std::vector<char*> argv;
for (const auto& arg : args) argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);

execvp(argv[0], argv.data());
exit(1);
}

// Parent
close(input_pipe[0]);
close(output_pipe[1]);

if (!input.empty()) {
write(input_pipe[1], input.c_str(), input.size());
}
close(input_pipe[1]);

std::stringstream output;
char buffer[4096];
ssize_t n;
while ((n = read(output_pipe[0], buffer, sizeof(buffer))) > 0) {
output.write(buffer, n);
}
close(output_pipe[0]);

int status;
waitpid(pid, &status, 0);
return output.str();
}

void cron_manager::register_job(const std::string& path, const std::string& schedule, const std::string& title) {
char current_path[4096];
if (readlink("/proc/self/exe", current_path, sizeof(current_path)) == -1) {
throw std::runtime_error("Failed to get current executable path");
}
std::string alloy_exe = std::string(current_path);

std::string current_crontab = run_command_with_args({"crontab", "-l"});
std::stringstream ss(current_crontab);
std::string line;
std::vector<std::string> lines;
std::string marker = "# Alloy-cron: " + title;
bool in_old_job = false;

while (std::getline(ss, line)) {
if (line == marker) {
in_old_job = true;
continue;
}
if (in_old_job) {
in_old_job = false;
continue;
}
lines.push_back(line);
}

std::stringstream new_crontab;
for (const auto& l : lines) {
new_crontab << l << "\n";
}

new_crontab << marker << "\n";
new_crontab << schedule << " '" << alloy_exe << "' run --cron-title='" << title << "' --cron-period='" << schedule << "' '" << path << "'\n";

run_command_with_args({"crontab", "-"}, new_crontab.str());
}

void cron_manager::remove_job(const std::string& title) {
std::string current_crontab = run_command_with_args({"crontab", "-l"});
std::stringstream ss(current_crontab);
std::string line;
std::vector<std::string> lines;
std::string marker = "# Alloy-cron: " + title;
bool in_old_job = false;
bool found = false;

while (std::getline(ss, line)) {
if (line == marker) {
in_old_job = true;
found = true;
continue;
}
if (in_old_job) {
in_old_job = false;
continue;
}
lines.push_back(line);
}

if (found) {
std::stringstream new_crontab;
for (const auto& l : lines) {
new_crontab << l << "\n";
}
run_command_with_args({"crontab", "-"}, new_crontab.str());
}
}

} // namespace alloy
118 changes: 118 additions & 0 deletions alloy/cron_manager_macos.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "cron_manager.hpp"
#include "cron_parser.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <unistd.h>
#include <sys/wait.h>
#include <pwd.h>
#include <mach-o/dyld.h>

namespace alloy {

void cron_manager::register_job(const std::string& path, const std::string& schedule, const std::string& title) {
auto expr = cron_parser::parse(schedule);

char current_path[4096];
uint32_t size = sizeof(current_path);
if (_NSGetExecutablePath(current_path, &size) != 0) {
throw std::runtime_error("Failed to get current executable path");
}
std::string alloy_exe = std::string(current_path);

struct passwd *pw = getpwuid(getuid());
std::string home_dir = pw->pw_dir;
std::string plist_path = home_dir + "/Library/LaunchAgents/Alloy.cron." + title + ".plist";

std::stringstream ss;
ss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
ss << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
ss << "<plist version=\"1.0\">\n";
ss << "<dict>\n";
ss << " <key>Label</key>\n";
ss << " <string>Alloy.cron." << title << "</string>\n";
ss << " <key>ProgramArguments</key>\n";
ss << " <array>\n";
ss << " <string>" << alloy_exe << "</string>\n";
ss << " <string>run</string>\n";
ss << " <string>--cron-title=" << title << "</string>\n";
ss << " <string>--cron-period=" << schedule << "</string>\n";
ss << " <string>" << path << "</string>\n";
ss << " </array>\n";
ss << " <key>StartCalendarInterval</key>\n";
ss << " <array>\n";

auto generate_intervals = [&](const std::set<int>& months, const std::set<int>& days, const std::set<int>& hours, const std::set<int>& minutes, const std::set<int>& weekdays) {
for (int m : months) {
for (int d : days) {
for (int h : hours) {
for (int mi : minutes) {
for (int w : weekdays) {
ss << " <dict>\n";
if (m != -1) ss << " <key>Month</key><integer>" << m << "</integer>\n";
if (d != -1) ss << " <key>Day</key><integer>" << d << "</integer>\n";
if (h != -1) ss << " <key>Hour</key><integer>" << h << "</integer>\n";
if (mi != -1) ss << " <key>Minute</key><integer>" << mi << "</integer>\n";
if (w != -1) ss << " <key>Weekday</key><integer>" << w << "</integer>\n";
ss << " </dict>\n";
}
}
}
}
}
};

std::set<int> m_set = expr.months.size() == 12 ? std::set<int>{-1} : expr.months;
std::set<int> d_set = expr.days_of_month.size() == 31 ? std::set<int>{-1} : expr.days_of_month;
std::set<int> h_set = expr.hours.size() == 24 ? std::set<int>{-1} : expr.hours;
std::set<int> mi_set = expr.minutes.size() == 60 ? std::set<int>{-1} : expr.minutes;
std::set<int> w_set = expr.days_of_week.size() == 7 ? std::set<int>{-1} : expr.days_of_week;

if (expr.dom_restricted && expr.dow_restricted) {
generate_intervals(m_set, d_set, h_set, mi_set, std::set<int>{-1});
generate_intervals(m_set, std::set<int>{-1}, h_set, mi_set, w_set);
} else if (expr.dom_restricted) {
generate_intervals(m_set, d_set, h_set, mi_set, std::set<int>{-1});
} else if (expr.dow_restricted) {
generate_intervals(m_set, std::set<int>{-1}, h_set, mi_set, w_set);
} else {
generate_intervals(m_set, std::set<int>{-1}, h_set, mi_set, std::set<int>{-1});
}

ss << " </array>\n";
ss << " <key>StandardOutPath</key>\n";
ss << " <string>/tmp/Alloy.cron." << title << ".stdout.log</string>\n";
ss << " <key>StandardErrorPath</key>\n";
ss << " <string>/tmp/Alloy.cron." << title << ".stderr.log</string>\n";
ss << "</dict>\n";
ss << "</plist>\n";

std::ofstream ofs(plist_path);
ofs << ss.str();
ofs.close();

pid_t pid = fork();
if (pid == 0) {
execlp("launchctl", "launchctl", "load", plist_path.c_str(), nullptr);
exit(1);
}
waitpid(pid, nullptr, 0);
}

void cron_manager::remove_job(const std::string& title) {
struct passwd *pw = getpwuid(getuid());
std::string home_dir = pw->pw_dir;
std::string plist_path = home_dir + "/Library/LaunchAgents/Alloy.cron." + title + ".plist";

pid_t pid = fork();
if (pid == 0) {
execlp("launchctl", "launchctl", "unload", plist_path.c_str(), nullptr);
exit(1);
}
waitpid(pid, nullptr, 0);
unlink(plist_path.c_str());
}

} // namespace alloy
Loading