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
104 changes: 104 additions & 0 deletions algo/topo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package algo

import "fmt"

type Value interface{}

type Graph map[Value]*Node

type Node struct {
Value Value
graph Graph
Dependents,
Dependencies,
Optionals map[*Node]struct{}
}


func (n *Node) String() string {
return fmt.Sprintf("%+v", n.Value)
}

func (n *Node) AddDependencies(keys ...Value) {
for _, key := range keys {
dep := n.graph.AddNode(key)
n.Dependencies[dep] = struct{}{}
dep.Dependents[n] = struct{}{}
}
}

func (n *Node) AddOptionals(keys ...Value) {
for _, key := range keys {
dep := n.graph.AddNode(key)
n.Optionals[dep] = struct{}{}
}
}

func (n *Node) IsRoot() bool {
return len(n.Dependents) == 0
}

func (n *Node) IsLeaf() bool {
return len(n.Dependencies) == 0
}


func MakeGraph() Graph {
return make(Graph)
}

func (g Graph) AddNode(key Value) *Node {
if g[key] == nil {
g[key] = &Node{
Value: key,
graph: g,
Dependents: make(map[*Node]struct{}),
Dependencies: make(map[*Node]struct{}),
Optionals: make(map[*Node]struct{}),
}
}
return g[key]
}

func (g Graph) RemoveNode(key Value) {
if g[key] == nil {
return
}
n := g[key]
for _, d := range g {
delete(d.Dependencies, n)
delete(d.Dependents, n)
delete(d.Optionals, n)
}
delete(g, key)
}

func (g Graph) Sorted() []*Node {
sorted := make([]*Node, 0, len(g))
degree := make(map[*Node]int)

var next []*Node
for _, n := range g {
if n.IsRoot() {
next = append(next, n)
} else {
degree[n] = len(n.Dependents)
}
}

for len(next) > 0 {
n := next[0]
next = next[1:]

sorted = append(sorted, n)

for d := range n.Dependencies {
degree[d]--
if degree[d] == 0 {
next = append(next, d)
}
}
}

return sorted
}
104 changes: 94 additions & 10 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ import (
"compress/bzip2"
"database/sql"
"fmt"
"log"
"os"
"path/filepath"

"regexp"
"strconv"
"strings"

"golang.org/x/text/language"
"golang.org/x/text/message"

_ "github.com/mattn/go-sqlite3"

"mcdex/algo"
)

type Database struct {
Expand All @@ -38,6 +42,14 @@ type Database struct {
version string
}

type DepType int

const (
Required = 1
Optional = 2
Embedded = 3
)

func OpenDatabase() (*Database, error) {
db := new(Database)

Expand Down Expand Up @@ -231,8 +243,8 @@ func (db *Database) getLatestFileTstamp() (int, error) {

func (db *Database) getLatestModFile(modID int, mcvsn string) (*ModFile, error) {
// First, look up the modid for the given name
var name, desc string
err := db.sqlDb.QueryRow("select name, description from projects where type = 0 and projectid = ?", modID).Scan(&name, &desc)
var name, slug, desc string
err := db.sqlDb.QueryRow("select name, slug, description from projects where type = 0 and projectid = ?", modID).Scan(&name, &slug, &desc)
switch {
case err == sql.ErrNoRows:
return nil, fmt.Errorf("No mod found %d", modID)
Expand All @@ -251,7 +263,7 @@ func (db *Database) getLatestModFile(modID int, mcvsn string) (*ModFile, error)
return nil, err
}

return &ModFile{fileID: fileID, modID: modID, modName: name, modDesc: desc}, nil
return &ModFile{fileID: fileID, modID: modID, modName: name, slug: slug, modDesc: desc}, nil
}

func (db *Database) findProjectBySlug(slug string, ptype int) (int, error) {
Expand Down Expand Up @@ -285,7 +297,7 @@ func (db *Database) findModByName(name string) (int, error) {
func (db *Database) findModFile(modID, fileID int, mcversion string) (*ModFile, error) {
// Try to match the file ID
if fileID > 0 {
err := db.sqlDb.QueryRow("select fileid from files where projectid = ? and fileid = ? and version = ?", modID, fileID, mcversion).Scan(&fileID)
err := db.sqlDb.QueryRow("select projectid from files where fileid = ? and version = ?", fileID, mcversion).Scan(&modID)
if err != nil {
return nil, fmt.Errorf("No matching file ID for %s version", mcversion)
}
Expand All @@ -298,13 +310,13 @@ func (db *Database) findModFile(modID, fileID int, mcversion string) (*ModFile,
}

// We matched some file; pull the name and description for the mod
var name, desc string
err := db.sqlDb.QueryRow("select slug, description from projects where projectid = ?", modID).Scan(&name, &desc)
var name, slug, desc string
err := db.sqlDb.QueryRow("select name, slug, description from projects where projectid = ?", modID).Scan(&name, &slug, &desc)
if err != nil {
return nil, fmt.Errorf("Failed to retrieve name, description for mod %d: %+v", modID, err)
}

return &ModFile{fileID: fileID, modID: modID, modName: name, modDesc: desc}, nil
return &ModFile{fileID: fileID, modID: modID, modName: name, slug: slug, modDesc: desc}, nil
}

func (db *Database) getDeps(fileID int) ([]int, error) {
Expand Down Expand Up @@ -351,6 +363,78 @@ func (db *Database) getLatestPackURL(slug string) (string, error) {
}

// Construct a URL using the slug and file ID
return fmt.Sprintf("https://minecraft.curseforge.com/projects/%d/files/%d/download", pid, fileID), nil;
return fmt.Sprintf("https://minecraft.curseforge.com/projects/%d/files/%d/download", pid, fileID), nil
}

func (db *Database) buildDepGraph(m *ModPack) (algo.Graph, error) {

var fileIds map[int]*ManifestFileEntry
var fileIdsString strings.Builder
g := algo.MakeGraph()

// Load all files from manifest
{
nameQuery, err := db.sqlDb.Prepare("SELECT DISTINCT name FROM files f, projects p WHERE f.fileid = ? AND f.projectid = ? AND p.projectid = f.projectid")
if err != nil {
return nil, err
}
defer nameQuery.Close()

files, _ := m.manifest.S("files").Children()
fileIds = make(map[int]*ManifestFileEntry, len(files))
for i, file := range files {
record := ManifestFileEntry{idx: i}

record.projId = int(file.S("projectID").Data().(float64))
record.fileId = int(file.S("fileID").Data().(float64))

}
if fid, filename := m.modCache.GetLastModFile(record.projId); fid > 0 {
record.file = filename
}

err = nameQuery.QueryRow(record.fileId, record.projId).Scan(&record.name)
switch {
case err == sql.ErrNoRows:
log.Printf("No mod found in database with project id %d and file id %d - File: %q\n\tDependency resolution may be incomplete!", record.projId, record.fileId, record.file)
case err != nil:
return nil, err
}

fileIds[record.fileId] = &record
g.AddNode(&record)

fileIdsString.WriteString(strconv.Itoa(record.fileId))
fileIdsString.WriteByte(',')
}
// Simple hack to deal with trailing comma or empty
fileIdsString.WriteByte('0')
}

// Load dependencies and add to graph
{
rows, err := db.sqlDb.Query(fmt.Sprintf("SELECT DISTINCT d.fileid, f.fileid, level FROM deps d, files f WHERE level <> 3 AND f.projectid = d.projectid AND d.fileid IN (%[1]s) AND f.fileid IN (%[1]s)", fileIdsString.String()))
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var fileid, depid int
var depType DepType
if err = rows.Scan(&fileid, &depid, &depType); err != nil {
return nil, err
}

node := g[fileIds[fileid]]
dep := fileIds[depid]
switch depType {
case Required:
node.AddDependencies(dep)
case Optional:
node.AddOptionals(dep)
}
}
}

return g, nil
}
Loading