package main import ( "database/sql" "encoding/json" "fmt" "io" "log" "net/url" "os" "path" "path/filepath" "strings" _ "github.com/mattn/go-sqlite3" ) var Error *log.Logger func init() { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logFile, err := os.Create("main.log") if err != nil { log.Printf("Error creating log file: %v", err) os.Exit(1) } logger := io.MultiWriter(os.Stdout, logFile) log.SetOutput(logger) Error = log.New(io.MultiWriter(logFile, os.Stderr, os.Stdout), fmt.Sprintf("%sERROR:%s ", "\033[0;101m", "\033[0m"), log.Lmicroseconds|log.Lshortfile) } const projectsFile = `C:\Users\Administrator\Seafile\VSCode\Code\User\globalStorage\state.vscdb` const ( TABLE = "ItemTable" KEY = "history.recentlyOpenedPathsList" ) var scanFolders []string = []string{ `C:\Users\Administrator\Seafile\Projects-Clion\ClionProjects`, `C:\Users\Administrator\Seafile\Projects-Elixir\ElixirProjects`, `C:\Users\Administrator\Seafile\Projects-Go\GoProjects`, `C:\Users\Administrator\Seafile\Projects-Idea\IdeaProjects`, `C:\Users\Administrator\Seafile\Projects-Other\Projects`, `C:\Users\Administrator\Seafile\Projects-Pycharm\PycharmProjects`, `C:\Users\Administrator\Seafile\Projects-Rider\RiderProjects`, `C:\Users\Administrator\Seafile\Projects-Webstorm\WebstormProjects`, } func main() { for idx, folder := range scanFolders { scanFolders[idx] = path.Clean(folder) } // config, err := ReadDiskConfig() config, err := ReadDBConfig() if err != nil { Error.Fatalf("Error reading database config: %v", err) return } log.Printf("Loaded %d entries", len(config.Entries)) cleanConfig := CleanConfig(config) log.Printf("%d after cleaning config", len(cleanConfig)) cleaned := 0 for ipath := range cleanConfig { file, err := os.Open(ipath) if err != nil { if os.IsNotExist(err) { log.Printf("Discarding non existing directory %s", ipath) delete(cleanConfig, ipath) cleaned++ } else { // Maybe we fail to open it for some other reason... // Could be it still does exist Error.Printf("Error opening file %s: %v", ipath, err) continue } continue } file.Close() } log.Printf("Cleaned %d invalid entries, %d valid entries remain", cleaned, len(cleanConfig)) added := 0 for _, folder := range scanFolders { files, err := os.ReadDir(folder) if err != nil { log.Printf("Error reading folder %s: %v", folder, err) continue } for _, fileEntry := range files { if !fileEntry.IsDir() { continue } newEntry := Entry{ FolderURI: filepath.ToSlash(path.Clean(path.Join(folder, fileEntry.Name()))), } // Lowercase disk letters... Because it's what vscode would have wanted newEntry.FolderURI = strings.ToLower(newEntry.FolderURI[:1]) + newEntry.FolderURI[1:] _, exists := cleanConfig[newEntry.FolderURI] if exists { log.Printf("Folder %s already exists in config", newEntry.FolderURI) continue } cleanConfig[newEntry.FolderURI] = &newEntry added++ } } log.Printf("Added %d new entries from scan folders, now %d total", added, len(cleanConfig)) originalFormat := Config{ Entries: make([]Entry, 0, len(cleanConfig)), } for _, entry := range cleanConfig { originalFormat.Entries = append(originalFormat.Entries, *entry) } escapedConfig := EscapeConfig(originalFormat) err = WriteDBConfig(escapedConfig) if err != nil { Error.Printf("Failed to write config: %v", err) return } } func ReadDBConfig() (Config, error) { var res = Config{} conn, err := sql.Open("sqlite3", projectsFile) if err != nil { return res, err } row := conn.QueryRow(fmt.Sprintf("SELECT * FROM %s WHERE key = '%s'", TABLE, KEY)) var key string var value string err = row.Scan(&key, &value) if err != nil { return res, err } conn.Close() err = json.NewDecoder(strings.NewReader(value)).Decode(&res) if err != nil { return res, fmt.Errorf("error decoding JSON: %v", err) } return res, nil } func ReadDiskConfig() (Config, error) { var res = Config{} file, err := os.Open("dump.json") if err != nil { return res, fmt.Errorf("error opening file: %v", err) } err = json.NewDecoder(file).Decode(&res) if err != nil { return res, fmt.Errorf("error decoding JSON: %v", err) } return res, nil } func CleanConfig(config Config) map[string]*Entry { var res = make(map[string]*Entry) for _, entry := range config.Entries { entry.FileURI = UnescapePath(entry.FileURI) entry.FolderURI = UnescapePath(entry.FolderURI) relevantPath := entry.FileURI if relevantPath == "" { relevantPath = entry.FolderURI } res[relevantPath] = &entry } return res } func UnescapePath(input string) string { if input == "" { return input } input = strings.TrimPrefix(input, "file:///") // For some reason QueryUnescape does not handle : input = strings.ReplaceAll(input, "%3A", ":") decoded, err := url.QueryUnescape(input) if err != nil { Error.Printf("Error decoding URI %s: %v", input, err) return input } input = path.Clean(decoded) return input } func EscapeConfig(config Config) Config { for idx, entry := range config.Entries { config.Entries[idx].FolderURI = EscapePath(entry.FolderURI) config.Entries[idx].FileURI = EscapePath(entry.FileURI) } return config } func EscapePath(input string) string { if input == "" { return input } input = strings.TrimPrefix(input, "file:///") escaped := url.QueryEscape(input) escaped = strings.ReplaceAll(escaped, "%2F", "/") escaped = strings.ReplaceAll(escaped, "%5C", "/") escaped = strings.ReplaceAll(escaped, "+", "%20") escaped = "file:///" + escaped return escaped } func WriteDBConfig(config Config) error { parsedConfig, err := json.Marshal(config) if err != nil { return err } conn, err := sql.Open("sqlite3", projectsFile) if err != nil { return err } _, err = conn.Exec(fmt.Sprintf("UPDATE %s SET value = ? WHERE key = '%s'", TABLE, KEY), string(parsedConfig)) if err != nil { return err } return nil }