commit 7d7b7d255ece059c8c2521839c3554a8abf76833 Author: Marcell Mars Date: Sun Jul 30 12:37:23 2023 +0000 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d024f60 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Marcell Mars + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b34c75 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# sneakernet + +a downloadable archive containing sections of the Memory of the World, an extensive archive of human knowledge which defies copyright in favour of the importance of an open and democratic access to the wealth of human knowledge. + +## usage + +1) plug the usb stick into the cable +2) transfer starts & light start to blink +3) wait until the light stops blinking +3a) press any key until 3) + +_the first file getting copied is packed with all the info you'll need to keep the transfer rolling once you're back home_ + +![](photo1610985806.jpeg) diff --git a/copystation.service b/copystation.service new file mode 100644 index 0000000..1685f88 --- /dev/null +++ b/copystation.service @@ -0,0 +1,20 @@ +[Unit] +Description=copystation + +[Service] +Type=simple +Restart=always +RestartSec=5s +WorkingDirectory=/home/pi/ +ExecStart=/home/pi/copystation +Restart=always +PermissionsStartOnly=true +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=sleepservice + +[Install] +WantedBy=multi-user.target + +# this goes to: /etc/systemd/system/copystation.service +# and chmod +x /home/pi/copystation (binary) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e7fcfc2 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module main + +go 1.15 + +replace github.com/marcellmars/lsblk => /home/m/devel/mygodev/lsblk +//replace github.com/marcellmars/lsblk => /home/pi/devel/lsblk + +require ( + github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb + github.com/marcellmars/lsblk v0.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..30f2020 --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb h1:WHSAxLz3P5t4DKukfJ5wu7+aMyVkuTNSbCiAjVS92sM= +github.com/gvalkov/golang-evdev v0.0.0-20191114124502-287e62b94bcb/go.mod h1:SAzVFKCRezozJTGavF3GX8MBUruETCqzivVLYiywouA= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= +github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/wesovilabs/koazee v0.0.5/go.mod h1:pYhJpCWJQGXU5aVVD+LxutvCKLDSK8I7g5htWvaZlvw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..bb0441a --- /dev/null +++ b/main.go @@ -0,0 +1,401 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "syscall" + "time" + + evdev "github.com/gvalkov/golang-evdev" + "github.com/marcellmars/lsblk" +) + +var ( + copyStatus = false + cancelCopy = false +) + +func check(e error) { + if e != nil { + fmt.Println(fmt.Errorf("FROM CHECK: %w", e)) + panic(e) + } + return +} + +func recoverFromError() { + if r := recover(); r != nil { + fmt.Println("MYERROR:", r) + panic(r) + } +} + +//_START: CopyToDisk + +type Files struct { + Files []PathTotalSize +} + +type PathTotalSize struct { + IsDir bool + Path string + FileSize int64 + FileSizeHumanReadable string + TotalSize int64 + TotalSizeHumanReadable string +} + +func byteCountSI(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%dB", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.3f%cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + +func copyToDisk(p string, waitingLoop chan string, cancelCopy chan bool) { + var totalSize int64 + var ps Files + var bufSize int64 + fmt.Println("COPY STARTED...") + s := time.Now() + // srcBaseDir := "/dev/shm/src/" + // srcBaseDir := "/home/m/CalibreLibraries/" + srcBaseDir := "/home/pi/" + srcDir := "MemoryOfTheWorld4Orsimanirana/" + // srcDir := "PirateCareCollection/" + destBaseDir := "/mnt/MOTW0/" + // destBaseDir := "/dev/shm/dest/" + // + ps.Files = append( + ps.Files, + PathTotalSize{ + IsDir: true, + Path: srcBaseDir + srcDir, + FileSize: 0, + FileSizeHumanReadable: "0Kb", + TotalSize: 0, + TotalSizeHumanReadable: "0Kb", + }) + + ps.Files = append( + ps.Files, + PathTotalSize{ + IsDir: false, + Path: srcBaseDir + srcDir + "_README_.txt", + FileSize: 4, + FileSizeHumanReadable: "4Kb", + TotalSize: 4, + TotalSizeHumanReadable: "4Kb", + }) + + ps.Files = append( + ps.Files, + PathTotalSize{ + IsDir: false, + Path: srcBaseDir + srcDir + "BROWSE_LIBRARY.html", + FileSize: 4, + FileSizeHumanReadable: "4Kb", + TotalSize: 4, + TotalSizeHumanReadable: "4Kb", + }) + + err := filepath.Walk(filepath.Join(srcBaseDir, srcDir+"static"), func(path string, info os.FileInfo, err error) error { + check(err) + totalSize = totalSize + info.Size() + ps.Files = append( + ps.Files, + PathTotalSize{ + IsDir: info.IsDir(), + Path: path, + FileSize: info.Size(), + FileSizeHumanReadable: byteCountSI(info.Size()), + TotalSize: totalSize, + TotalSizeHumanReadable: byteCountSI(totalSize), + }) + return nil + }) + check(err) + + err = filepath.Walk(filepath.Join(srcBaseDir, srcDir), func(path string, info os.FileInfo, err error) error { + check(err) + totalSize = totalSize + info.Size() + if path == srcBaseDir+srcDir+"static" || path == srcBaseDir+srcDir+"_README_.html" || path == srcBaseDir+srcDir+"BROWSE_LIBRARY.html" { + return nil + } + ps.Files = append( + ps.Files, + PathTotalSize{ + IsDir: info.IsDir(), + Path: path, + FileSize: info.Size(), + FileSizeHumanReadable: byteCountSI(info.Size()), + TotalSize: totalSize, + TotalSizeHumanReadable: byteCountSI(totalSize), + }) + return nil + }) + check(err) + + copyStatus = true + availableDisk := lsblk.USBMountedPartitionAvailableSpace("/mnt/MOTW0") + for _, file := range ps.Files { + setLedLight(p, true) + + // sf := time.Now() + destPath := filepath.Join(destBaseDir, strings.ReplaceAll(file.Path, srcBaseDir, "")) + if file.IsDir { + err := os.MkdirAll(destPath, os.ModePerm) + check(err) + continue + } + + if availableDisk > 0 && (file.TotalSize > availableDisk) { + fmt.Println("NOT ENOUGH OF DISK SPACE:", "Last file copied:", file.Path) + waitingLoop <- "finishing" + return + } + + // fmt.Printf("_%s_", file.FileSizeHumanReadable) + inFile, err := os.Open(file.Path) + check(err) + + outFile, err := os.Create(destPath) + check(err) + + reader := bufio.NewReader(inFile) + writer := bufio.NewWriter(outFile) + bufSize = 512 * 2 * 16 + buf := make([]byte, 0, bufSize) + + for { + n, err := io.ReadFull(reader, buf[:cap(buf)]) + buf = buf[:n] + + if err != nil { + if err == io.EOF { + break + } + + if err != io.ErrUnexpectedEOF { + check(err) + } + } + writer.Write(buf) + writer.Flush() + // outFile.Sync() + } + outFile.Sync() + outFile.Close() + inFile.Close() + select { + case cc := <-cancelCopy: + if cc { + fmt.Println("CANCEL VIA KEYBOARD:", "Last file copied:", file.Path) + setLedLight(p, true) + waitingLoop <- "finishing" + return + } + default: + } + setLedLight(p, false) + } + + setLedLight(p, true) + lastFile := ps.Files[len(ps.Files)-1] + fmt.Println(lastFile.Path, lastFile.TotalSizeHumanReadable) + e := time.Now() + fmt.Printf("TIME: %v\n", e.Sub(s)) + fmt.Println("BUFFER SIZE:", byteCountSI(bufSize)) + copyStatus = false + waitingLoop <- "finishing" +} + +//_END: CopyToDisk + +//_START: NumKey & LED + +func setLedLight(p string, state bool) { + d, err := os.Create(p) + check(err) + defer d.Close() + var led evdev.InputEvent + var bb bytes.Buffer + var time syscall.Timeval + err = syscall.Gettimeofday(&time) + check(err) + led.Time = time + led.Type = evdev.EV_LED + led.Code = evdev.LED_NUML + if state { + led.Value = 1 + } else { + led.Value = 0 + } + + binary.Write(&bb, binary.LittleEndian, led) + d.Write(bb.Bytes()) +} + +func grebIt(greb chan *evdev.InputEvent, device *evdev.InputDevice) { + for { + i, _ := device.ReadOne() + greb <- i + } +} + +//_END: NumKey & LED + +//_START: Initial unmounting +func umountUSBDisk() bool { + for range [10]int{} { + if lsblk.USBMountpointed("/mnt/MOTW0") { + fmt.Println("Initial pass unmount MOTW0.") + _, err := exec.Command("umount", "/mnt/MOTW0").Output() + check(err) + } else { + return true + } + time.Sleep(500 * time.Millisecond) + } + return false +} + +//_END: Initial unmounting + +//_START: Mounting USB disk +func mountUSBDisk(waitingLoop chan string) bool { + largestNotMounted := lsblk.USBNotMountedPartitionOfLargestSize() + if largestNotMounted.Name != "" { + + fmt.Println("mounting usb disk...", largestNotMounted.Name, largestNotMounted.Mountpoint, largestNotMounted.Fstype, largestNotMounted.Size.HumanReadable) + // _, err := exec.Command("mount", "-t", largestNotMounted.Fstype, largestNotMounted.Name, "/mnt/MOTW0").Output() + _, err := exec.Command("mount", largestNotMounted.Name, "/mnt/MOTW0").Output() + check(err) + fmt.Println("just mounted:", largestNotMounted.Name, largestNotMounted.Mountpoint, largestNotMounted.Fstype, largestNotMounted.Size.HumanReadable, "available space:", lsblk.USBMountedPartitionAvailableSpace("/mnt/MOTW0")) + fmt.Printf("^") + return true + } + + if lsblk.USBMountpointed("/mnt/MOTW0") { + return true + } + + return false +} + +//_END: Mounting USB disk + +func main() { + defer recoverFromError() + //_START: NumKey & LED + devices, _ := evdev.ListInputDevices() + myNumKeyboard := []string{} + for _, d := range devices { + // fmt.Println("name: ", d.Name, "phys: ", d.Phys, "file.name: ", d.File.Name(), "bustype: ", d.Bustype, "vendor: ", d.Vendor, "product: ", d.Product, "version: ", d.Version) + if d.Vendor == 1241 && d.Product == 4611 { + myNumKeyboard = append(myNumKeyboard, d.File.Name()) + // fmt.Println("myNumKeyboard:", myNumKeyboard) + // break + } + } + sort.Strings(myNumKeyboard) + if len(myNumKeyboard) > 0 { + fmt.Println("Good! Keyboard attached:", myNumKeyboard) + setLedLight(myNumKeyboard[0], false) + } else { + os.Exit(1) + } + + device, _ := evdev.Open(myNumKeyboard[0]) + // device.Grab() + + initialLoop := true + greb := make(chan *evdev.InputEvent, 1) + go grebIt(greb, device) + //_END: NumKey & LED + + //_START waitingLoop + waitingLoop := make(chan string, 1024*100) + waitingLoop <- "initial" + //_END copyLoop + + cancelCopy := make(chan bool, 10) + + // go copyToDisk(myNumKeyboard[0], waitingLoop, greb) + + for { + select { + case ev := <-greb: + if ev.Type == evdev.EV_KEY && ev.Value == 0 { + fmt.Println(ev.String()) + if copyStatus { + cancelCopy <- true + } + if initialLoop { + initialLoop = false + } + // } else if ev.Type == evdev.EV_LED { + // fmt.Println("LED:", ev.String()) + } + default: + } + + select { + case wl := <-waitingLoop: + if wl == "initial" { + fmt.Println("waitingLoop state:", wl) + if !umountUSBDisk() { + fmt.Println("Need a hard reset.") + os.Exit(1) + } else { + waitingLoop <- "mounting" + } + } else if wl == "mounting" { + if mountUSBDisk(waitingLoop) { + waitingLoop <- "copying" + } else { + waitingLoop <- "mounting" + } + } else if wl == "copying" { + fmt.Println("waitingLoop state:", wl) + go copyToDisk(myNumKeyboard[0], waitingLoop, cancelCopy) + } else if wl == "finishing" { + fmt.Println("waitingLoop state:", wl) + if !umountUSBDisk() { + fmt.Println("Need a hard reset.") + os.Exit(1) + } else { + waitingLoop <- "ejecting" + setLedLight(myNumKeyboard[0], false) + } + } else if wl == "ejecting" { + fmt.Println("waitingLoop state:", wl) + for { + if lsblk.NoUSBDisksAttached() { + waitingLoop <- "initial" + break + } else { + time.Sleep(500 * time.Millisecond) + } + } + } + default: + } + time.Sleep(10 * time.Millisecond) + } +} diff --git a/photo1610985806.jpeg b/photo1610985806.jpeg new file mode 100644 index 0000000..5b3a8dc Binary files /dev/null and b/photo1610985806.jpeg differ