Skip to content

Commit

Permalink
first push
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie Root committed Oct 31, 2017
1 parent 78a9915 commit 5a248af
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 1 deletion.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
# torrmleaf
Clean Leaf files from torrented directories.
Clean Leaf files from torrented directories.
When downloading torrents over already populated directories we are prone end with leftover files that ccan grow to quite a bunch over the time (Damn Mame and Touhou torrents!).
This utility will list the files from a directory that aren't on a torrent file and prompt you to move them out.
* Usage:
Specifying the torrent file and his already downloaded directory:
`torrmleaf <.torrent file> <downladed torrent dir>`
You can also use the the torrent file only if you are the same dir of the default torrent dir:
`torrmleaf <.torrent file>`
* Example:
```
# torrmleaf_freebsd-amd64 MAME\ 0.191\ Software\ List\ CHDs\ \(merged\).torrent
Reading 'MAME 0.191 Software List CHDs (merged).torrent'.
Name: MAME 0.191 Software List CHDs (merged)
Creation Date: 2017-10-25 11:28:18 +0200 CEST
Created By: qBittorrent v3.3.16
Has 7960 files.
Example: 'psx/gol/game of life, the (usa).chd'
Checking directory 'MAME 0.191 Software List CHDs (merged)'
Has 7960 files.
```
173 changes: 173 additions & 0 deletions torrmleaf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package main

import (
"github.com/jackpal/bencode-go"
"fmt"
"os"
"io/ioutil"
"strings"
"path/filepath"
"time"
)

// Nested structs with the metadata we need
type FileDict struct {
Path []string "path"
}

type InfoDict struct {
Name string "name"
Files []FileDict "files"
}

type MetaInfo struct {
Info InfoDict "info"
CreationDate int64 "creation date"
Comment string "comment"
CreatedBy string "created by"
}

// Comodity panic
func check(e error) {
if e != nil {
fmt.Println("ERROR:", e)
os.Exit(1)
}
}

// Check for empty dirs
func IsEmptyDir(name string) (bool, error) {
entries, err := ioutil.ReadDir(name)
if err != nil {
return false, err
}
return len(entries) == 0, nil
}

// Recursively delete orphaned dirs
func recRmOrphanedDir(name string) (error) {
if empty, _ := IsEmptyDir(name); empty {
fmt.Printf("Deleting empty dir '%s'\n", name)
e := os.Remove(name)
if e != nil {
return e
}
previous := filepath.Dir(name)
return recRmOrphanedDir(previous)
}
return nil
}

func main() {
// Init vars
progName := filepath.Base(os.Args[0])
torrent, searchDir, backupDir := "", "", "_BACKUP"
args := os.Args[1:]
switch len(args) {
case 1:
torrent = args[0]
case 2:
torrent, searchDir = args[0], args[1]
default:
fmt.Printf("Usage: %s <.torrent file> <Directory to Check>\n", progName)
os.Exit(1)
}

// Check exntension.
if fileExt := filepath.Ext(torrent); fileExt != ".torrent" {
fmt.Printf("'%s' Is not a .torrent file!\n", torrent)
os.Exit(1)
}

// Open file now.s
fmt.Printf("Reading '%s'.\n", torrent)
f, e := os.Open(torrent)
check(e)
defer f.Close()

// Unmarshal the file into our struct
mi := MetaInfo{}
e = bencode.Unmarshal(f, &mi)
check(e)

// Print some info
fmt.Printf("\tName: %s \n", mi.Info.Name)
if mi.Info.Name != mi.Comment {
fmt.Printf("\tComment: %s \n", mi.Comment)
}
fmt.Printf("\tCreation Date: %s \n", time.Unix(mi.CreationDate, 0))
fmt.Printf("\tCreated By: %s\n", mi.CreatedBy)

// Get all the files with their path
torrentFileMap := map[string]bool{}
for _, file := range mi.Info.Files {
filePath := strings.Join(file.Path, "/")
torrentFileMap[filePath] = true
}

// Print one of the files to help figure layout if needed
fmt.Printf("\tHas %d files.\n", len(torrentFileMap))
for k, _ := range torrentFileMap {
fmt.Printf("\tExample: '%s'\n", k)
break
}

// Use the torrent Name as a Dir if it was not specified
if searchDir == "" {
searchDir = mi.Info.Name
}

// Change the working dir
e = os.Chdir(searchDir)
check(e)
fmt.Printf("Checking directory '%s'\n", searchDir)

// Get all the files with their path on our working dir
localFileMap := map[string]bool{}
e = filepath.Walk(".", func(localFile string, f os.FileInfo, e error) error {
// Skip dirs and everything under our backup dir
if !f.IsDir() && !strings.HasPrefix(localFile, backupDir) {
// Replace each file separator with a slash to be OS consitent
localFile = filepath.ToSlash(localFile)
localFileMap[localFile] = true
}
return nil
})
check(e)
fmt.Printf("\tHas %d files.\n", len(localFileMap))

// Get the Files we have locally that aren't on the torrent file
unwantedFiles := []string{}
for localFile, _ := range localFileMap {
if _, ok := torrentFileMap[localFile]; !ok {
unwantedFiles = append(unwantedFiles, localFile)
}
}

if len(unwantedFiles) > 0 {
// Show the unwanted files
fmt.Printf("\tGot %d unwanted Files.\n", len(unwantedFiles))
fmt.Printf("\tExample: '%s'\n", unwantedFiles[0])
// Ask user for confirmation
fmt.Printf("\nEnter YES to move them to '%s': ", backupDir)
res := ""
_, e := fmt.Scanln(&res)
check(e)
if res != "YES" {
os.Exit(0)
}
// Backup the files
for _, file := range unwantedFiles {
newFile := backupDir + "/" + file
newBase := filepath.Dir(newFile)
os.MkdirAll(newBase, os.ModePerm);
fmt.Printf("Moving '%s'\n", file)
e := os.Rename(file, newFile)
check(e)
// Clean orphaned directories
oldBase := filepath.Dir(file)
e = recRmOrphanedDir(oldBase)
check(e)
}
}
}

0 comments on commit 5a248af

Please sign in to comment.