LKFCoder

Форк
0
/
LKFCoder.go 
192 строки · 5.2 Кб
1
// Command-line utility for encoding/decoding LKF files.
2
package main
3

4
import (
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

19
const UsageString = `Использование: %v [опции] <команда> [путь...]
20

21
Опции:
22
 -v    Включает подробный вывод журнала работы программы.
23
 -w=<число>    Задаёт число горутин-воркеров.
24
  По умолчанию число горутин равно числу доступных в системе логических процессоров.
25

26
Команда:
27
 decode    Декодирование lkf-файлов в mp3
28
 encode    Кодирование mp3-файлов в lkf
29

30
Путь: Один или более путей к обрабатываемым файлам или каталогам.
31
 Требуемые файлы определяются по расширению имени файла. *.lkf при декодировании и *.mp3 при кодировании.
32
 Если в качестве пути указан каталог, то поиск нужных файлов будет выполнен рекурсивно во всех вложенных подкаталогах.
33
 Если ни один путь не указан, то для поиска файлов будет использоваться текущий рабочий каталог.
34
`
35

36
type CryptorFunc func(*lkf.Cryptor, []byte) int
37

38
func FileCryptor(path string, cryptor CryptorFunc) error {
39
	f, err := os.OpenFile(path, os.O_RDWR, 0)
40
	if err != nil {
41
		return err
42
	}
43

44
	buf := make([]byte, lkf.BlockSize*1024) // 512 Kb
45
	c := new(lkf.Cryptor)
46
	var off int64
47

48
	for err == nil {
49
		var n int
50
		n, err = io.ReadFull(f, buf)
51
		if err != nil {
52
			if err != io.ErrUnexpectedEOF {
53
				// Fatal error when reading from file or end of file without data
54
				// Processing must be aborted immediately
55
				break
56
			}
57
			// End of file, but there is read data
58
			// We try to process them, and then break with io.EOF
59
			err = 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
64
		if np := cryptor(c, buf[:n]); np != 0 {
65
			if _, wErr := f.WriteAt(buf[:np], off); wErr != nil {
66
				// Fatal error when writing to file
67
				// Processing must be aborted immediately
68
				err = wErr
69
				break
70
			}
71
			off += int64(np)
72
		}
73
	}
74

75
	if err != io.EOF {
76
		// Fatal error occurred while processing the file. Close the file and return this error
77
		f.Close()
78
		return err
79
	}
80

81
	// Just the end of the file with io.EOF. Closing it
82
	return f.Close()
83
}
84

85
func worker(pathCH <-chan string, wg *sync.WaitGroup, logger *log.Logger, targetExt string, cryptor CryptorFunc) {
86
	defer wg.Done()
87
	for path := range pathCH {
88
		targetPath := strings.TrimSuffix(path, filepath.Ext(path)) + targetExt
89
		tmpPath := targetPath + ".tmp"
90
		if err := os.Rename(path, tmpPath); err != nil {
91
			logger.Printf("worker: %v\n", err)
92
			continue
93
		}
94

95
		if err := FileCryptor(tmpPath, cryptor); err != nil {
96
			logger.Printf("worker: %v\n", err)
97
			continue
98
		}
99

100
		if err := os.Rename(tmpPath, targetPath); err != nil {
101
			logger.Printf("worker: %v\n", err)
102
			continue
103
		}
104
	}
105
}
106

107
func main() {
108
	var fileCounter int
109
	var srcExt, targetExt string
110
	var cryptor CryptorFunc
111
	wg := new(sync.WaitGroup)
112
	pathCH := make(chan string)
113
	logger := log.New(os.Stdout, "", 0)
114

115
	var verbosityFlag bool
116
	var numWorkersFlag int
117
	flag.BoolVar(&verbosityFlag, "v", false, "")
118
	flag.IntVar(&numWorkersFlag, "w", runtime.NumCPU(), "")
119
	flag.Usage = func() {
120
		logger.Printf(UsageString, os.Args[0])
121
	}
122
	flag.Parse()
123

124
	if numWorkersFlag <= 0 {
125
		logger.Fatalf("No available workers\n")
126
	}
127

128
	cmd := flag.Arg(0)
129
	switch cmd {
130
	case "decode":
131
		cryptor = func(c *lkf.Cryptor, data []byte) int { return c.Decrypt(data, data) }
132
		srcExt = ".lkf"
133
		targetExt = ".mp3"
134
	case "encode":
135
		cryptor = func(c *lkf.Cryptor, data []byte) int { return c.Encrypt(data, data) }
136
		srcExt = ".mp3"
137
		targetExt = ".lkf"
138
	default:
139
		logger.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!
144
	paths := flag.Args()[1:]
145

146
	if len(paths) == 0 {
147
		wd, err := os.Getwd()
148
		if err != nil {
149
			logger.Fatalf("Unable to get current working directory: %v\n", err)
150
		}
151
		paths = append(paths, wd)
152
	}
153

154
	for n := 0; n < numWorkersFlag; n++ {
155
		wg.Add(1)
156
		go worker(pathCH, wg, logger, targetExt, cryptor)
157
	}
158

159
	walker := func(path string, d fs.DirEntry, err error) error {
160
		if err != nil || d.IsDir() {
161
			return err
162
		}
163
		if strings.ToLower(filepath.Ext(path)) == srcExt {
164
			fileCounter++
165
			pathCH <- path
166
		}
167
		return nil
168
	}
169

170
	start := time.Now()
171
	if verbosityFlag {
172
		logger.Printf("Start processing with %d workers\n", numWorkersFlag)
173
	}
174

175
	for _, path := range paths {
176
		if verbosityFlag {
177
			logger.Printf("Walking by path: %v\n", path)
178
		}
179
		if err := filepath.WalkDir(path, walker); err != nil {
180
			logger.Printf("Filewalker: %v\n", err)
181
			break
182
		}
183
	}
184

185
	close(pathCH)
186
	wg.Wait()
187

188
	finish := time.Since(start)
189
	if verbosityFlag {
190
		logger.Printf("Processed %d *%s files in %v\n", fileCounter, srcExt, finish)
191
	}
192
}
193

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.