~aleteoryx/tclircc

9af66920a9b8e82c34a8301e4d134a3bbeb0eb41 — aleteoryx a month ago dd09632
core database initialization
7 files changed, 155 insertions(+), 33 deletions(-)

M cap.tcl
M db/main.tcl
A irc/main.tcl
M main.tcl
A migrate_core.tcl
A plugins.tcl
M threads.tcl
M cap.tcl => cap.tcl +3 -3
@@ 9,9 9,9 @@

# handling for CAP-related things
namespace eval ::cap {
  variable log [logger::init tclircc::cap]
  variable logp [logger::init tclircc::cap::parser]
  variable logh [logger::init tclircc::cap::dispatch]
  variable log [logger::init irc::cap]
  variable logp [logger::init irc::cap::parser]
  variable logh [logger::init irc::cap::dispatch]
  proc value_dict str {
    set ret {}
    foreach pair [split $str ","] {

M db/main.tcl => db/main.tcl +24 -0
@@ 3,3 3,27 @@ package require logger

set log [logger::init tclircc::db]

switch -- [set tcl_platform(platform)] {
  "windows" {
    set data_dir "[set env(APPDATA)][file separator]tclircc"
  }
  "unix" {
    if [info exists env(XDG_CONFIG_HOME)] {
      set data_dir "[set env(XDG_CONFIG_HOME)][file separator]tclircc"
    } elseif [info exists env(HOME)] {
      set data_dir "[set env(HOME)][file separator].config[file separator]tclircc"
    } else {
      return -code error {Missing $HOME or $XDG_CONFIG_HOME, can't store config!}
    }
  }
  default {
    return -code error {Unknown platform, can't store config.}
  }
}

file mkdir $data_dir

proc path_to_core {} {
  global data_dir
  return "$data_dir[file separator]core.db"
}

A irc/main.tcl => irc/main.tcl +5 -0
@@ 0,0 1,5 @@
package require logger

source "$path[file separator]irc.tcl"
source "$path[file separator]cap.tcl"


M main.tcl => main.tcl +33 -25
@@ 6,40 6,48 @@ set version v0.0.1
package require Thread
package require logger
set log [logger::init tclircc::main]
package require sqlite3

${log}::info "tclircc $version <https://amehut.dev/~aleteoryx/tclircc>"
${log}::info "running from $path"

source threads.tcl

${log}::debug "starting db thread..."
set db_thread [thread::create -preserved]
threads::manage $db_thread tclircc::db
thread::send $db_thread [list variable path $path version $version]
if {[thread::send $db_thread [list source "$path[file separator]db[file separator]main.tcl"] result] == 1} {
  ${log}::critical "couldn't start db thread: $result"
  exit -1
}
${log}::debug "started db thread."

${log}::debug "starting ui thread..."
set ui_thread [thread::create -preserved]
threads::manage $ui_thread tclircc::ui
thread::send $ui_thread [list variable path $path version $version]
if {[thread::send $ui_thread [list source "$path[file separator]ui[file separator]main.tcl"] result] == 1} {
  ${log}::critical "couldn't start ui thread: $result"
  exit -1
}
${log}::debug "started ui thread."
proc start_thread {name} {
  global path
  global version
  global log

  ${log}::debug "starting $name thread..."

  set thread [thread::create -preserved]
  threads::manage $thread tclircc::$name
  thread::send $thread [list variable path $path version $version]

  if {[thread::send $thread [list source "$path[file separator]$name[file separator]main.tcl"] result] == 1} {
    ${log}::critical "couldn't start $name thread: $result"
    exit -1
  }

update
foreach key [array names threads::ns] {
  ${log}::debug "thread \"$key\" has id [set threads::ns($key)]"
  ${log}::debug "started $name thread."
}

${log}::debug "opening main window..."
thread::send $ui_thread {mk_toplevel name; return $name} mainwin
${log}::debug "main window opened: $mainwin"
start_thread db

thread::send [t::ns tclircc::db] {path_to_core} core_db_path
sqlite3 core_db $core_db_path -create true -fullmutex true
source migrate_core.tcl

source plugins.tcl

start_thread irc
start_thread ui

${log}::debug "opening initial window..."
thread::send [t::ns tclircc::ui] {mk_toplevel name; return $name} initial
${log}::debug "initial window opened: $initial"

threads::debug

${log}::info "entering event loop"
vwait nil

A migrate_core.tcl => migrate_core.tcl +48 -0
@@ 0,0 1,48 @@
# TODO: make this more robust maybe

interp create migrator

migrator eval {
  lappend migrations 0 {init plugins} {
-- init plugins
CREATE TABLE plugins (slug TEXT PRIMARY KEY,
                      version INTEGER,
                      hashes TEXT,
                      trusted BOOL,
                      priority INTEGER);
  }
}

migrator alias db core_db
migrator eval {
  package require logger
  package require Tk
  wm withdraw .

  set log [logger::init tclircc::db::migrate]

  ${log}::debug "starting migrations"
  if ![db eval {
    SELECT count(*) FROM sqlite_master WHERE name="_migrations"}] {
    ${log}::info "initializing core db!"
    db eval {CREATE TABLE _migrations (id INTEGER PRIMARY KEY, date TEXT)}
  }

  foreach {id name contents} $migrations {
    # TODO: debug interpolation?
    if ![db eval "SELECT count(*) FROM _migrations WHERE id=$id"] {
      ${log}::info "performing migration $id: $name"
      db eval BEGIN
      set migrationdate [clock seconds]
      db eval $contents
      db eval "INSERT INTO _migrations (id, date) VALUES ($id, $migrationdate)"
      set last $id
      db eval COMMIT
    } elseif [info exists last] {
      ${log}::warn "completed migrations exist after incomplete migration $last, core database may be corrupted!"
    }
  }

  ${log}::info "core db migrated!"
}
interp delete migrator

A plugins.tcl => plugins.tcl +16 -0
@@ 0,0 1,16 @@
package require sha256

# plugin manifest format:
# tcl script that must return the following

namespace eval plugins {
  variable plugins

  proc load {dir} {
    set mf_fd [open "$dir[file separator]manifest.tcl"]
    set manifest [read $mf_fd]
    close $mf_fd

    
  }
}

M threads.tcl => threads.tcl +26 -5
@@ 2,8 2,6 @@ namespace eval threads {
  variable threads [list main [thread::id]]
  variable log [logger::init tclircc::threads]

  variable ns

  proc manage {tid name} {
    variable threads
    variable log


@@ 11,12 9,16 @@ namespace eval threads {
    ${log}::debug "managing thread $tid as \"$name\""

    thread::send -head $tid {
      namespace eval threads {
      namespace eval t {
        variable ns
        proc update {} {
          variable ns
          thread::send -head [set ns(main)] threads::update
        }
        proc ns {name} {
          variable ns
          set ns($name)
        }
      }
    }



@@ 35,11 37,30 @@ namespace eval threads {
      if ![thread::exists $tid] {dict unset $threads $name}
    }
    dict for {name tid} $threads {
      set payload {array unset threads::ns; }
      set payload {array unset t::ns; }
      dict for {n t} $threads {
        append payload {set threads::ns(} $n {) } $t {; }
        append payload {set t::ns(} $n {) } $t {; }
      }
      thread::send -head $tid $payload
    }
    ::update
  }
  proc debug {} {
    variable log
    update
    foreach key [array names threads::ns] {
      ${log}::debug "thread \"$key\" has id [set threads::ns($key)]"
    }
  }
}
namespace eval t {
  variable ns
  proc update {} {
    variable ns
    thread::send -head [set ns(main)] threads::update
  }
  proc ns {name} {
    variable ns
    set ns($name)
  }
}