Compare commits

...

8 Commits

31 changed files with 1075 additions and 55 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ instance/
*.class
*.db
.vscode/
sources.txt

View File

@ -1,37 +1,42 @@
import quick.Interface;
import squirrel.Database;
import squirrel.ModelManager;
import interfaces.Index;
import interfaces.Login;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import src.models.UserModel;
import src.models.squirrel.Database;
import src.models.squirrel.ModelManager;
import squirrel.LocalStorage;
public class Main {
public static void main(String[] args) throws SQLException {
public static void main(String[] args) {
ModelManager.initializeModels();
Database.migrate = conn -> src.Migration.run(conn);
Database.getConnection();
UserModel userModel = ModelManager.get(UserModel.class);
List<UserModel> users = userModel.where(java.util.Collections.emptyMap());
for (UserModel user : users) {
user.set("name", user.get("name") + " Updated");
user.save();
System.out.println(user);
}
Connection conn = Database.getConnection();
// Example: Run a simple SQL query
try (var stmt = conn.createStatement();
var rs = stmt.executeQuery("SELECT COUNT(*) AS user_count FROM users")) {
if (rs.next()) {
int userCount = rs.getInt("user_count");
System.out.println("Total users: " + userCount);
Migration.run(conn);
Interface app = new Interface(Login.class);
app.registerView(Index.class);
app.registerView(Login.class);
class Point {
public int x;
public int y;
public int z = 0;
public String name = "origin";
public Point(int x, int y) {
this.x = x;
this.y = y;
}
} catch (SQLException e) {
e.printStackTrace();
}
Database.close();
Point p = new Point(3, 4);
System.out.println("Point: (" + p.x + ", " + p.y + ", " + p.z + ", " + p.name + ")");
LocalStorage storage = new LocalStorage();
storage.save("point1", p);
Point loaded = storage.load("point1", Point.class);
System.out.println("Loaded Point: (" + loaded.x + ", " + loaded.y + ", " + loaded.z + ", " + loaded.name + ")");
storage.flush();
}
}

9
run.sh
View File

@ -8,16 +8,21 @@ if ! command -v java >/dev/null 2>&1; then
exit 1
fi
# build list of source files
find src -name "*.java" > sources.txt
echo "Main.java" >> sources.txt
if ! javac -d build -cp "./extern/sqlite-jdbc-3.50.3.0.jar" @sources.txt Main.java; then
# compile with all extern jars
if ! javac -d build -cp "extern/*" @sources.txt Main.java; then
echo "Error: javac failed to compile sources."
exit 1
fi
rm sources.txt
# create instance dir if not exists
if [ ! -d "instance" ]; then
mkdir instance
fi
java -cp "build:./extern/sqlite-jdbc-3.50.3.0.jar" Main
# run with all extern jars
java -cp "build:extern/*" Main

View File

@ -1,11 +1,10 @@
package src;
import java.sql.Connection;
import java.sql.Statement;
public class Migration {
public static void run(Connection conn) {
try (Statement stmt = conn.createStatement()) {
System.out.println("Running migrations...");
stmt.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -52,6 +51,15 @@ public class Migration {
)
""");
stmt.execute("""
CREATE TABLE IF NOT EXISTS storage (
key TEXT PRIMARY KEY,
type TEXT,
data TEXT NOT NULL
)
""");
System.out.println("Migrations completed.");
} catch (Exception e) {
e.printStackTrace();
}

44
src/interfaces/Index.java Normal file
View File

@ -0,0 +1,44 @@
package interfaces;
import quick.Action;
import quick.View;
import quick.Color;
public class Index extends View {
private boolean yes = false;
public Boolean init() {
return true;
}
public void draw() {
terminal.print(Color.ANSI_CYAN + "Index View" + Color.ANSI_RESET);
terminal.print("\n");
terminal.print("This is the index view. Use commands to navigate.\n");
terminal.print("\n");
terminal.print("Available commands:\n");
terminal.print(" - test: go test view\n");
terminal.print(" - confirm: confirm\n");
if (yes) {
terminal.print(Color.ANSI_GREEN + "You have confirmed yes!" + Color.ANSI_RESET + "\n");
} else {
terminal.print(Color.ANSI_YELLOW + "You have not confirmed yes." + Color.ANSI_RESET + "\n");
}
}
public Action onCommand(String command) {
if (command.equals("confirm")) {
yes = true;
}
if (command.equals("test")) {
return actions.redirect(Login.class);
}
return actions.nop();
}
public Boolean onSelection(String userInputRaw) {
return false;
}
}

114
src/interfaces/Login.java Normal file
View File

@ -0,0 +1,114 @@
package interfaces;
import quick.Action;
import quick.View;
import quick.Color;
public class Login extends View {
private String username = "";
private String password = "";
private boolean loginSuccess = false;
private int loginTries = 0;
private String editField = null;
private String error = "";
@Override
public Boolean init() {
username = "";
password = "";
loginSuccess = false;
loginTries = 0;
editField = null;
error = "";
viewPrompt = "> ";
return true;
}
@Override
public void draw() {
terminal.print(Color.ANSI_CYAN + "=== Login ===" + Color.ANSI_RESET + "\n");
terminal.print("Username: " + (username.isEmpty() ? "<not set>" : username) + "\n");
terminal.print("Password: " + (password.isEmpty() ? "<not set>" : "*".repeat(password.length())) + "\n");
if (loginSuccess) {
terminal.print(Color.ANSI_GREEN + "Login successful!" + Color.ANSI_RESET + "\n");
terminal.print("Press any key to continue..." + "\n");
viewPrompt = "> ";
return;
}
if (!error.isEmpty()) {
terminal.print(Color.ANSI_RED + error + Color.ANSI_RESET + "\n");
}
terminal.print("\nCommands:\n");
terminal.print(" edit username - enter username\n");
terminal.print(" edit password - enter password\n");
terminal.print(" login - attempt login\n");
terminal.print(" back - go back\n");
if (editField != null) {
viewPrompt = "Enter " + editField + ": ";
} else {
viewPrompt = "> ";
}
}
@Override
public Action onCommand(String command) {
String cmd = command.trim().toLowerCase();
if (cmd.isEmpty()) {
error = "Empty input. Type a command.";
return actions.nop();
}
if (cmd.equals("back")) {
return actions.redirect(Index.class);
}
if (cmd.equals("login")) {
if (username.equals("a") && password.equals("a")) {
loginSuccess = true;
error = "";
} else {
loginTries++;
error = "Login failed (" + loginTries + " attempts).";
}
return actions.nop();
}
if (cmd.startsWith("edit")) {
if (cmd.contains("username")) {
editField = "username";
error = "";
} else if (cmd.contains("password")) {
editField = "password";
error = "";
} else {
error = "Unknown field to edit. Use: edit username | edit password";
}
return actions.nop();
}
if (editField != null) {
if (editField.equals("username")) {
username = command.trim();
} else if (editField.equals("password")) {
password = command.trim();
}
editField = null;
error = "";
return actions.nop();
}
error = "Unknown command: " + command;
return actions.nop();
}
@Override
public Boolean onSelection(String userInputRaw) {
return false;
}
}

View File

@ -1,10 +1,10 @@
package src.models;
package models;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import src.models.squirrel.Model;
import src.models.squirrel.ModelManager;
import squirrel.Model;
import squirrel.ModelManager;
import java.security.SecureRandom;
import java.util.Base64;
@ -25,7 +25,7 @@ public class UserModel extends Model {
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256;
public static UserModel register(String name, String email, String password) {
public UserModel register(String name, String email, String password) {
if (password == null || password.trim().isEmpty()) {
throw new IllegalArgumentException("Password cannot be empty");
}
@ -46,7 +46,29 @@ public class UserModel extends Model {
return user;
}
public static String hashPassword(String password, byte[] salt) {
public UserModel authenticate(String email, String password) {
UserModel userModel = ModelManager.get(UserModel.class);
List<UserModel> users = userModel.where(Map.of("email", email));
if (users == null || users.isEmpty()) {
return null;
}
UserModel user = users.get(0);
String storedHash = (String) user.get("password_hash");
if (storedHash == null) {
return null;
}
byte[] salt = Base64.getDecoder().decode(storedHash);
if (!validatePassword(password, storedHash, salt)) {
return null;
}
return user;
}
public String hashPassword(String password, byte[] salt) {
try {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
@ -57,14 +79,14 @@ public class UserModel extends Model {
}
}
public static byte[] getSalt() {
public byte[] getSalt() {
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt;
}
public static boolean validatePassword(String password, String storedHash, byte[] salt) {
public boolean validatePassword(String password, String storedHash, byte[] salt) {
try {
String newHash = hashPassword(password, salt);
return Arrays.equals(Base64.getDecoder().decode(storedHash), Base64.getDecoder().decode(newHash));

6
src/quick/Action.java Normal file
View File

@ -0,0 +1,6 @@
package quick;
public abstract class Action {
protected Global global = Global.getInstance();
public abstract Boolean execute(Interface base);
}

22
src/quick/Color.java Normal file
View File

@ -0,0 +1,22 @@
package quick;
public class Color {
public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_BLACK = "\u001B[30m";
public static final String ANSI_RED = "\u001B[31m";
public static final String ANSI_GREEN = "\u001B[32m";
public static final String ANSI_YELLOW = "\u001B[33m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_PURPLE = "\u001B[35m";
public static final String ANSI_CYAN = "\u001B[36m";
public static final String ANSI_WHITE = "\u001B[37m";
public static final String ANSI_BLACK_BACKGROUND = "\u001B[40m";
public static final String ANSI_RED_BACKGROUND = "\u001B[41m";
public static final String ANSI_GREEN_BACKGROUND = "\u001B[42m";
public static final String ANSI_YELLOW_BACKGROUND = "\u001B[43m";
public static final String ANSI_BLUE_BACKGROUND = "\u001B[44m";
public static final String ANSI_PURPLE_BACKGROUND = "\u001B[45m";
public static final String ANSI_CYAN_BACKGROUND = "\u001B[46m";
public static final String ANSI_WHITE_BACKGROUND = "\u001B[47m";
}

18
src/quick/Global.java Normal file
View File

@ -0,0 +1,18 @@
package quick;
import java.util.ArrayList;
import java.util.List;
public class Global {
private static Global instance;
public List<Class<? extends View>> registeredViews = new ArrayList<>();
private Global() {}
public static synchronized Global getInstance() {
if (instance == null) {
instance = new Global();
}
return instance;
}
}

7
src/quick/Helper.java Normal file
View File

@ -0,0 +1,7 @@
package quick;
public class Helper {
public static void clearScreen() {
System.out.print("");
}
}

99
src/quick/Interface.java Normal file
View File

@ -0,0 +1,99 @@
package quick;
import java.util.Scanner;
import quick.exceptions.InvalidViewActionReturn;
import quick.guard.PrintDog;
import quick.intern.v_help;
import quick.intern.v_select;
public class Interface {
public final Scanner scanner = new Scanner(System.in);
private Global global = Global.getInstance();
private Boolean active = true;
private Class<? extends View> defaultView = v_select.class;
private View selectedView;
public int[] __terminalSize;
public int __renderCycleLines;
public Interface() {}
public Interface(Class<? extends View> defaultView) {
this.defaultView = defaultView;
}
public void registerView(Class<? extends View> viewClass) {
global.registeredViews.add(viewClass);
}
public Boolean selectView(View viewInstance) {
selectedView = viewInstance;
return viewInstance.init();
}
public Boolean selectView(Class<? extends View> viewClass) {
View new_view = View.instantiate(viewClass);
selectedView = new_view;
return new_view.init();
}
private Boolean cycle() {
selectedView.initBase(this);
// Always start with a clean break
System.out.print("\r\n");
System.out.flush();
this.__terminalSize = TerminalSize.getTerminalSize();
this.__renderCycleLines = 0;
Helper.clearScreen();
// Draw the view; it should update __renderCycleLines
selectedView.draw();
System.out.flush();
// Always leave at least 2 lines: one for prompt, one for safety
int overheadLines = 2;
int linesToFill = Math.max(0, this.__terminalSize[0] - __renderCycleLines - overheadLines);
for (int i = 0; i < linesToFill; i++) {
System.out.println();
}
String view_prompt = (selectedView.viewPrompt == null ? "" : selectedView.viewPrompt);
System.err.print(view_prompt);
System.err.flush();
String userInputRaw = scanner.nextLine();
System.out.println();
Action viewResponse = selectedView.onCommand(userInputRaw);
if (viewResponse == null) {
return true;
}
if (viewResponse instanceof Action) {
return viewResponse.execute(this);
}
throw new InvalidViewActionReturn();
}
private void mainLoop() {
selectView(defaultView);
while (active) {
active = cycle();
}
}
public void start() {
PrintDog.start(true);
View.scanner = scanner;
registerView(v_help.class);
registerView(v_select.class);
mainLoop();
}
}

View File

@ -0,0 +1,25 @@
package quick;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TerminalSize {
public static int[] getTerminalSize() {
try {
Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", "stty size < /dev/tty"});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
if (line != null) {
String[] parts = line.trim().split(" ");
int rows = Integer.parseInt(parts[0]);
int cols = Integer.parseInt(parts[1]);
return new int[]{rows, cols};
}
} catch (Exception e) {
System.err.println("[ERROR] Failed to get terminal size: " + e.getMessage());
}
return new int[]{69, 69}; // Soggy UwU fallback
}
}

53
src/quick/View.java Normal file
View File

@ -0,0 +1,53 @@
package quick;
import java.util.Scanner;
import quick.exceptions.DuplicateViewMatchException;
import java.util.ArrayList;
public abstract class View {
protected static Global global = Global.getInstance();
protected static ViewActionFactories actions = new ViewActionFactories();
protected ViewTerminalAcces terminal;
public static Scanner scanner;
public static Interface __base;
public String viewPrompt;
public String viewSignature;
public String helperText;
public abstract Boolean init();
public abstract void draw();
public abstract Action onCommand(String command);
public abstract Boolean onSelection(String userInputRaw);
public void initBase(Interface base) {
View.__base = base;
this.terminal = new ViewTerminalAcces(base);
}
public static View instantiate(Class<? extends View> viewClass) {
try {
return viewClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Could not instantiate view: " + viewClass.getName(), e);
}
}
public static View signatureParse(String userInputRaw) {
ArrayList<View> matched = new ArrayList<View>();
for (Class<? extends View> ViewClass : global.registeredViews) {
View viewInstance = View.instantiate(ViewClass);
if (viewInstance.onSelection(userInputRaw)) {
matched.add(viewInstance);
}
}
if (matched.size() > 1) {
throw new DuplicateViewMatchException(userInputRaw, matched.size());
}
return matched.isEmpty() ? null : matched.get(0);
}
}

View File

@ -0,0 +1,28 @@
package quick;
import quick.action.a_dd;
import quick.action.a_nop;
import quick.action.a_redirect;
import quick.action.a_stop;
public class ViewActionFactories {
public Action redirect(Class<? extends View> target) {
return new a_redirect(target);
}
public Action redirect(View target) {
return new a_redirect(target);
}
public Action stop() {
return new a_stop();
}
public Action nop() {
return new a_nop();
}
public Action dd(Object... data) {
return new a_dd(data);
}
}

View File

@ -0,0 +1,32 @@
package quick;
public class ViewTerminalAcces {
private Interface base;
public ViewTerminalAcces(Interface base) {
this.base = base;
}
public void print(String text) {
base.__renderCycleLines += 1;
System.out.print(text);
}
public int getWidth() {
return base.__terminalSize[1];
}
public int getHeight() {
return base.__terminalSize[0];
}
public int getCurrentRclCount() {
return base.__renderCycleLines;
}
public void fill(int size) {
for (int i = 0; i < size; i++) {
print("\n");
}
}
}

109
src/quick/action/a_dd.java Normal file
View File

@ -0,0 +1,109 @@
package quick.action;
import java.util.List;
import java.util.Map;
import java.util.Set;
import quick.Action;
import quick.Color;
import quick.Interface;
public class a_dd extends Action {
private final Object[] data;
public a_dd(Object... data) {
this.data = data;
}
@Override
public Boolean execute(Interface base) {
System.out.println(Color.ANSI_BLUE + "========== DUMP & DIE ==========" + Color.ANSI_RESET);
for (int i = 0; i < data.length; i++) {
Object item = data[i];
String type = (item == null) ? "null" : item.getClass().getSimpleName();
String typeColor = getTypeColor(type);
System.out.println(
Color.ANSI_WHITE + "[" + i + "] => " +
typeColor + "(" + type + ") " + formatValue(item) + Color.ANSI_RESET
);
}
System.out.println(Color.ANSI_BLUE + "============ END ============" + Color.ANSI_RESET);
return false;
}
private String formatValue(Object obj) {
if (obj == null) return Color.ANSI_RED + "null" + Color.ANSI_RESET;
if (obj instanceof Map) {
StringBuilder sb = new StringBuilder(Color.ANSI_PURPLE + "Map {\n");
((Map<?, ?>) obj).forEach((k, v) -> {
String type = (v == null) ? "null" : v.getClass().getSimpleName();
String color = getTypeColor(type);
sb.append(Color.ANSI_WHITE + " " + k + " => " + color + "(" + type + ") " + formatValue(v) + "\n");
});
sb.append(Color.ANSI_PURPLE + "}");
return sb.toString();
}
if (obj instanceof List) {
StringBuilder sb = new StringBuilder(Color.ANSI_BLUE + "List [\n");
int i = 0;
for (Object val : (List<?>) obj) {
String type = (val == null) ? "null" : val.getClass().getSimpleName();
String color = getTypeColor(type);
sb.append(Color.ANSI_WHITE + " " + i++ + " => " + color + "(" + type + ") " + formatValue(val) + "\n");
}
sb.append(Color.ANSI_BLUE + "]");
return sb.toString();
}
if (obj instanceof Set) {
StringBuilder sb = new StringBuilder(Color.ANSI_RED + "Set {\n");
for (Object val : (Set<?>) obj) {
String type = (val == null) ? "null" : val.getClass().getSimpleName();
String color = getTypeColor(type);
sb.append(Color.ANSI_WHITE + " => " + color + "(" + type + ") " + formatValue(val) + "\n");
}
sb.append(Color.ANSI_RED + "}");
return sb.toString();
}
if (obj.getClass().isArray()) {
StringBuilder sb = new StringBuilder(Color.ANSI_CYAN + "Array [\n");
int len = java.lang.reflect.Array.getLength(obj);
for (int i = 0; i < len; i++) {
Object val = java.lang.reflect.Array.get(obj, i);
String type = (val == null) ? "null" : val.getClass().getSimpleName();
String color = getTypeColor(type);
sb.append(Color.ANSI_WHITE + " " + i + " => " + color + "(" + type + ") " + formatValue(val) + "\n");
}
sb.append(Color.ANSI_CYAN + "]");
return sb.toString();
}
return getTypeColor(obj.getClass().getSimpleName()) + obj.toString();
}
private String getTypeColor(String type) {
if (type == null) return Color.ANSI_RED;
switch (type) {
case "String": return Color.ANSI_GREEN;
case "Integer":
case "Long":
case "Double":
case "Float":
case "Short":
case "Byte": return Color.ANSI_YELLOW;
case "Boolean": return Color.ANSI_CYAN;
case "Map": return Color.ANSI_PURPLE;
case "List": return Color.ANSI_BLUE;
case "Set": return Color.ANSI_RED;
case "Object[]": return Color.ANSI_WHITE;
default: return Color.ANSI_WHITE;
}
}
}

View File

@ -0,0 +1,10 @@
package quick.action;
import quick.Action;
import quick.Interface;
public class a_nop extends Action {
public Boolean execute(Interface base) {
return true;
};
}

View File

@ -0,0 +1,49 @@
package quick.action;
import quick.Action;
import quick.Interface;
import quick.View;
import quick.exceptions.InvalidRedirect;
public class a_redirect extends Action{
public Class<? extends View> targetClass;
public View targetInstance;
private Interface base;
public a_redirect(Class<? extends View> target) {
this.targetClass = target;
}
public a_redirect(View target) {
this.targetInstance = target;
}
public Boolean execute(Interface base) {
this.base = base;
if (targetClass != null) {
return redirectByClass();
}
if (targetInstance != null) {
return redirectByInstance();
}
throw new InvalidRedirect(null);
}
private Boolean redirectByClass() {
if (!global.registeredViews.contains(targetClass)) {
throw new InvalidRedirect(targetClass);
}
return base.selectView(targetClass);
}
private Boolean redirectByInstance() {
if (!global.registeredViews.contains(targetInstance.getClass())) {
throw new InvalidRedirect(targetInstance.getClass());
}
return base.selectView(targetInstance);
}
}

View File

@ -0,0 +1,10 @@
package quick.action;
import quick.Action;
import quick.Interface;
public class a_stop extends Action {
public Boolean execute(Interface base) {
return false;
};
}

View File

@ -0,0 +1,7 @@
package quick.exceptions;
public class DuplicateViewMatchException extends RuntimeException {
public DuplicateViewMatchException(String input, int count) {
super("Input \"" + input + "\" matched " + count + " views. View signatures must be unique for this input.");
}
}

View File

@ -0,0 +1,7 @@
package quick.exceptions;
public class IllegalTerminalPrintException extends RuntimeException {
public IllegalTerminalPrintException(String message) {
super(message);
}
}

View File

@ -0,0 +1,11 @@
package quick.exceptions;
import quick.View;
public class InvalidRedirect extends RuntimeException {
public InvalidRedirect(Class<? extends View> target) {
super(target == null
? "invalid use of null in redirect"
: "View" + target.getName() + " is not registered in base");
}
}

View File

@ -0,0 +1,7 @@
package quick.exceptions;
public class InvalidViewActionReturn extends RuntimeException {
public InvalidViewActionReturn() {
super("Please only return a Child of Action or null from a View");
}
}

View File

@ -0,0 +1,101 @@
package quick.guard;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
import java.util.Set;
import quick.Color;
import java.util.HashSet;
public class PrintDog {
private static final Set<String> trustedPackages = new HashSet<>();
private static boolean strict = true;
private static PrintStream originalOut;
private static PrintStream mirroredOut;
public static void start(boolean strictMode) {
strict = strictMode;
originalOut = System.out;
// Register your actual source packages only
trustedPackages.add("quick.");
trustedPackages.add("quick.action.");
trustedPackages.add("quick.exceptions.");
trustedPackages.add("quick.guard.");
trustedPackages.add("quick.intern.");
// Setup global uncaught exception handler
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
originalOut.println(Color.ANSI_RED + "🔥 UNCAUGHT EXCEPTION IN THREAD: " + thread.getName() + Color.ANSI_RESET);
throwable.printStackTrace(originalOut);
// Optional kill:
// System.exit(1);
});
// Setup mirror output stream
mirroredOut = new PrintStream(new MirrorOutputStream(originalOut), true);
System.setOut(mirroredOut);
}
private static class MirrorOutputStream extends OutputStream {
private final OutputStream original;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public MirrorOutputStream(OutputStream original) {
this.original = original;
}
@Override
public void write(int b) {
try {
original.write(b);
buffer.write(b);
if (b == '\n') {
flushBuffer();
}
} catch (Exception e) {
e.printStackTrace(originalOut);
System.exit(69);
}
}
private void flushBuffer() {
String content = buffer.toString();
buffer.reset();
Throwable t = new Throwable();
StackTraceElement[] stack = t.getStackTrace();
if (strict && !isFromTrustedCode(stack)) {
originalOut.println(
Color.ANSI_WHITE + Color.ANSI_RED_BACKGROUND +
"🐶 BARK! UNAUTHORIZED TERMINAL PRINT DETECTED!" +
Color.ANSI_RESET + "\n" +
Color.ANSI_YELLOW + "→ Use terminal.print() instead.\n" +
"→ Offending content:\n" +
Color.ANSI_WHITE + content.trim() + Color.ANSI_RESET
);
// 🚨 HARD CRASH
System.err.println(Color.ANSI_RED + "☠️ FATAL: System.out used in untrusted context!" + Color.ANSI_RESET);
System.exit(123);
}
}
private boolean isFromTrustedCode(StackTraceElement[] stack) {
for (StackTraceElement frame : stack) {
String cls = frame.getClassName();
for (String prefix : trustedPackages) {
if (cls.startsWith(prefix)) {
return true;
}
}
}
return false;
}
}
}

View File

@ -0,0 +1,30 @@
package quick.intern;
import quick.Action;
import quick.View;
public class v_help extends View {
@Override
public Boolean init() {
viewPrompt = "HELP";
viewSignature = "help";
helperText = "help view most likely not implemented 😂 u noob";
return true;
}
@Override
public void draw() {
return;
}
@Override
public Action onCommand(String command) {
return null;
}
@Override
public Boolean onSelection(String userInputRaw) {
return false;
}
}

View File

@ -0,0 +1,68 @@
package quick.intern;
import quick.Action;
import quick.Color;
import quick.View;
import quick.action.a_nop;
import quick.action.a_redirect;
public class v_select extends View {
private String viewList;
private String lastSelectionWrong;
@Override
public Boolean init() {
StringBuilder sb = new StringBuilder();
for (Class<? extends View> viewClass : global.registeredViews) {
if (viewClass == v_select.class) continue;
View viewInstance = View.instantiate(viewClass);
viewInstance.init();
if (viewInstance.viewSignature != null && !viewInstance.viewSignature.isEmpty()) {
sb.append(": " + viewInstance.viewSignature + " - " + viewInstance.helperText).append("\n");
} else {
sb.append(": " + viewInstance.helperText).append("\n");
}
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') {
sb.deleteCharAt(sb.length() - 1);
}
viewList = sb.toString();
return true;
}
@Override
public void draw() {
terminal.print("Select a action:\n");
for (String line : viewList.split("\n")) terminal.print(line+"\n");
terminal.print("\n");
if (lastSelectionWrong != null) {
terminal.print(Color.ANSI_RED + "Something went wrong with: " + lastSelectionWrong + "\"" + Color.ANSI_RESET + "\n");
terminal.print("\n");
lastSelectionWrong = null;
} else {
terminal.print("\n");
}
}
@Override
public Action onCommand(String command) {
if (command.isEmpty()) {
return new a_nop();
}
View selectedView = View.signatureParse(command);
if (selectedView == null) {
lastSelectionWrong = command;
return new a_nop();
}
return new a_redirect(selectedView);
}
@Override
public Boolean onSelection(String userInputRaw) {
return false;
}
}

View File

@ -1,21 +1,21 @@
package src.models.squirrel;
package squirrel;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Consumer;
public class Database {
private static Connection conn;
public static Consumer<Connection> migrate;
public static Connection getConnection() throws SQLException {
if (conn == null || conn.isClosed()) {
String url = "jdbc:sqlite:instance/test.db";
conn = DriverManager.getConnection(url);
if (migrate != null) {
migrate.accept(conn);
public static Connection getConnection() {
try {
if (conn == null || conn.isClosed()) {
String url = "jdbc:sqlite:instance/test.db";
conn = DriverManager.getConnection(url);
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("Failed to connect to database", e);
}
return conn;
}

View File

@ -0,0 +1,95 @@
package squirrel;
import java.sql.*;
import java.util.*;
public class LocalStorage {
private final Connection conn;
public LocalStorage() {
this.conn = Database.getConnection();
}
// Save: serialize constructor arguments
public <T> void save(String key, T value) {
try {
var ctor = value.getClass().getDeclaredConstructors()[0];
ctor.setAccessible(true);
var fields = value.getClass().getDeclaredFields();
List<String> pairs = new ArrayList<>();
for (var f : fields) {
f.setAccessible(true);
Object v = f.get(value);
pairs.add(f.getName() + "=" + (v == null ? "null" : v.toString()));
}
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO storage(key, data) VALUES(?, ?) " +
"ON CONFLICT(key) DO UPDATE SET data=excluded.data"
)) {
ps.setString(1, key);
ps.setString(2, String.join(";", pairs));
ps.executeUpdate();
}
} catch (Exception e) {
throw new RuntimeException("Save failed", e);
}
}
// Load: reconstruct using the constructor of clazz
@SuppressWarnings("unchecked")
public <T> T load(String key, Class<T> clazz) {
try (PreparedStatement ps = conn.prepareStatement(
"SELECT data FROM storage WHERE key=?"
)) {
ps.setString(1, key);
ResultSet rs = ps.executeQuery();
if (!rs.next()) return null;
String data = rs.getString("data");
Map<String, String> map = new LinkedHashMap<>();
for (String pair : data.split(";")) {
if (pair.isBlank()) continue;
int idx = pair.indexOf('=');
if (idx == -1) continue;
map.put(pair.substring(0, idx), pair.substring(idx + 1));
}
var ctor = clazz.getDeclaredConstructors()[0];
ctor.setAccessible(true);
Class<?>[] paramTypes = ctor.getParameterTypes();
Object[] converted = new Object[paramTypes.length];
int i = 0;
for (Class<?> pt : paramTypes) {
String raw = map.values().toArray(new String[0])[i];
if (pt == int.class || pt == Integer.class) {
converted[i] = Integer.parseInt(raw);
} else if (pt == long.class || pt == Long.class) {
converted[i] = Long.parseLong(raw);
} else if (pt == double.class || pt == Double.class) {
converted[i] = Double.parseDouble(raw);
} else if (pt == String.class) {
converted[i] = raw;
} else {
throw new IllegalArgumentException("Unsupported param type: " + pt);
}
i++;
}
return (T) ctor.newInstance(converted);
} catch (Exception e) {
throw new RuntimeException("Load failed", e);
}
}
public void flush() {
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DELETE FROM storage");
} catch (Exception e) {
throw new RuntimeException("Flush failed", e);
}
}
}

View File

@ -1,4 +1,4 @@
package src.models.squirrel;
package squirrel;
import java.sql.*;
import java.util.*;
@ -10,18 +10,45 @@ public abstract class Model {
protected Set<String> columns = new HashSet<>();
protected Map<String, Object> attributes = new LinkedHashMap<>();
protected boolean rowMode = false;
protected boolean rowMode = true;
// Only one classmode instance per subclass
private static final Map<Class<? extends Model>, Model> classModeInstances = new HashMap<>();
public Model() {
if (conn == null) {
conn = Database.getConnection();
}
// Always rowMode by default
this.rowMode = true;
}
// Call once per subclass to create the classmode instance
public static <T extends Model> T initializeClassMode(Class<T> clazz) {
synchronized (classModeInstances) {
if (classModeInstances.containsKey(clazz)) {
throw new IllegalStateException("Classmode instance already initialized for " + clazz.getSimpleName());
}
try {
conn = Database.getConnection();
} catch (SQLException e) {
throw new RuntimeException("Failed to get DB connection for model '" + getTableName() + "'", e);
T instance = clazz.getDeclaredConstructor().newInstance();
instance.rowMode = false;
classModeInstances.put(clazz, instance);
return instance;
} catch (Exception e) {
throw new RuntimeException("Failed to initialize classmode instance for " + clazz.getSimpleName(), e);
}
}
}
// Get the classmode instance for a subclass
public static <T extends Model> T getClassMode(Class<T> clazz) {
Model instance = classModeInstances.get(clazz);
if (instance == null) {
throw new IllegalStateException("Classmode instance not initialized for " + clazz.getSimpleName());
}
return clazz.cast(instance);
}
// -------------------------------
// Query (class/global mode only)
// -------------------------------

View File

@ -1,8 +1,8 @@
package src.models.squirrel;
package squirrel;
import java.util.*;
import src.models.UserModel;
import models.UserModel;
public class ModelManager {
private static final Map<Class<? extends Model>, Model> models = new HashMap<>();
@ -13,7 +13,7 @@ public class ModelManager {
);
for (Class<? extends Model> clazz : modelClasses) {
try {
Model instance = clazz.getDeclaredConstructor().newInstance();
Model instance = Model.initializeClassMode(clazz);
models.put(clazz, instance);
} catch (Exception e) {
e.printStackTrace();