LKFCoder
/
LKFCoder.go
192 строки · 5.2 Кб
1// Command-line utility for encoding/decoding LKF files.
2package main
3
4import (
5"flag"
6"io"
7"io/fs"
8"log"
9"os"
10"path/filepath"
11"runtime"
12"strings"
13"sync"
14"time"
15
16"gitverse.ru/kvark128/lkf"
17)
18
19const UsageString = `Использование: %v [опции] <команда> [путь...]
20
21Опции:
22-v Включает подробный вывод журнала работы программы.
23-w=<число> Задаёт число горутин-воркеров.
24По умолчанию число горутин равно числу доступных в системе логических процессоров.
25
26Команда:
27decode Декодирование lkf-файлов в mp3
28encode Кодирование mp3-файлов в lkf
29
30Путь: Один или более путей к обрабатываемым файлам или каталогам.
31Требуемые файлы определяются по расширению имени файла. *.lkf при декодировании и *.mp3 при кодировании.
32Если в качестве пути указан каталог, то поиск нужных файлов будет выполнен рекурсивно во всех вложенных подкаталогах.
33Если ни один путь не указан, то для поиска файлов будет использоваться текущий рабочий каталог.
34`
35
36type CryptorFunc func(*lkf.Cryptor, []byte) int
37
38func FileCryptor(path string, cryptor CryptorFunc) error {
39f, err := os.OpenFile(path, os.O_RDWR, 0)
40if err != nil {
41return err
42}
43
44buf := make([]byte, lkf.BlockSize*1024) // 512 Kb
45c := new(lkf.Cryptor)
46var off int64
47
48for err == nil {
49var n int
50n, err = io.ReadFull(f, buf)
51if err != nil {
52if err != io.ErrUnexpectedEOF {
53// Fatal error when reading from file or end of file without data
54// Processing must be aborted immediately
55break
56}
57// End of file, but there is read data
58// We try to process them, and then break with io.EOF
59err = io.EOF
60}
61
62// Encrypt or decrypt the read data
63// Processed data should be written back to the file instead of the previously read ones
64if np := cryptor(c, buf[:n]); np != 0 {
65if _, wErr := f.WriteAt(buf[:np], off); wErr != nil {
66// Fatal error when writing to file
67// Processing must be aborted immediately
68err = wErr
69break
70}
71off += int64(np)
72}
73}
74
75if err != io.EOF {
76// Fatal error occurred while processing the file. Close the file and return this error
77f.Close()
78return err
79}
80
81// Just the end of the file with io.EOF. Closing it
82return f.Close()
83}
84
85func worker(pathCH <-chan string, wg *sync.WaitGroup, logger *log.Logger, targetExt string, cryptor CryptorFunc) {
86defer wg.Done()
87for path := range pathCH {
88targetPath := strings.TrimSuffix(path, filepath.Ext(path)) + targetExt
89tmpPath := targetPath + ".tmp"
90if err := os.Rename(path, tmpPath); err != nil {
91logger.Printf("worker: %v\n", err)
92continue
93}
94
95if err := FileCryptor(tmpPath, cryptor); err != nil {
96logger.Printf("worker: %v\n", err)
97continue
98}
99
100if err := os.Rename(tmpPath, targetPath); err != nil {
101logger.Printf("worker: %v\n", err)
102continue
103}
104}
105}
106
107func main() {
108var fileCounter int
109var srcExt, targetExt string
110var cryptor CryptorFunc
111wg := new(sync.WaitGroup)
112pathCH := make(chan string)
113logger := log.New(os.Stdout, "", 0)
114
115var verbosityFlag bool
116var numWorkersFlag int
117flag.BoolVar(&verbosityFlag, "v", false, "")
118flag.IntVar(&numWorkersFlag, "w", runtime.NumCPU(), "")
119flag.Usage = func() {
120logger.Printf(UsageString, os.Args[0])
121}
122flag.Parse()
123
124if numWorkersFlag <= 0 {
125logger.Fatalf("No available workers\n")
126}
127
128cmd := flag.Arg(0)
129switch cmd {
130case "decode":
131cryptor = func(c *lkf.Cryptor, data []byte) int { return c.Decrypt(data, data) }
132srcExt = ".lkf"
133targetExt = ".mp3"
134case "encode":
135cryptor = func(c *lkf.Cryptor, data []byte) int { return c.Encrypt(data, data) }
136srcExt = ".mp3"
137targetExt = ".lkf"
138default:
139logger.Fatalf("Unsupported command specified\n")
140}
141
142// The first argument is the command. Paths are all arguments after the command.
143// Note that if the user did not specify a command, then the next line will cause a panic!
144paths := flag.Args()[1:]
145
146if len(paths) == 0 {
147wd, err := os.Getwd()
148if err != nil {
149logger.Fatalf("Unable to get current working directory: %v\n", err)
150}
151paths = append(paths, wd)
152}
153
154for n := 0; n < numWorkersFlag; n++ {
155wg.Add(1)
156go worker(pathCH, wg, logger, targetExt, cryptor)
157}
158
159walker := func(path string, d fs.DirEntry, err error) error {
160if err != nil || d.IsDir() {
161return err
162}
163if strings.ToLower(filepath.Ext(path)) == srcExt {
164fileCounter++
165pathCH <- path
166}
167return nil
168}
169
170start := time.Now()
171if verbosityFlag {
172logger.Printf("Start processing with %d workers\n", numWorkersFlag)
173}
174
175for _, path := range paths {
176if verbosityFlag {
177logger.Printf("Walking by path: %v\n", path)
178}
179if err := filepath.WalkDir(path, walker); err != nil {
180logger.Printf("Filewalker: %v\n", err)
181break
182}
183}
184
185close(pathCH)
186wg.Wait()
187
188finish := time.Since(start)
189if verbosityFlag {
190logger.Printf("Processed %d *%s files in %v\n", fileCounter, srcExt, finish)
191}
192}
193