diff --git a/hw07_file_copying/.sync b/hw07_file_copying/.sync deleted file mode 100644 index e69de29..0000000 diff --git a/hw07_file_copying/copy.go b/hw07_file_copying/copy.go index 1ddf87b..4623309 100644 --- a/hw07_file_copying/copy.go +++ b/hw07_file_copying/copy.go @@ -2,6 +2,9 @@ package main import ( "errors" + "fmt" + "io" + "os" ) var ( @@ -10,6 +13,93 @@ var ( ) func Copy(fromPath, toPath string, offset, limit int64) error { - // Place your code here. + // fromFile open and check + fromFile, err := os.Open(fromPath) + if err != nil { + return fmt.Errorf("fromFileOpen: %w", err) + } + defer fromFile.Close() + + fromInfo, err := fromFile.Stat() + if err != nil { + return fmt.Errorf("fromFile: %w", err) + } + + if !fromInfo.Mode().IsRegular() { + return fmt.Errorf("fromFile: %w", ErrUnsupportedFile) + } + + fromSize := fromInfo.Size() + if offset >= fromSize { + return fmt.Errorf("fromFile: %w", ErrOffsetExceedsFileSize) + } + + // toFile open and check + toFile, err := os.Create(toPath) + if err != nil { + return fmt.Errorf("toFileCreate: %w", err) + } + defer toFile.Close() + + toInfo, err := toFile.Stat() + if err != nil { + return fmt.Errorf("toFile: %w", err) + } + + if !toInfo.Mode().IsRegular() { + return fmt.Errorf("toFile: %w", ErrUnsupportedFile) + } + + // prepare copy + copySize := fromSize - offset + if limit > 0 && limit < copySize { + copySize = limit + } + + if offset > 0 { + _, err = fromFile.Seek(offset, 0) + if err != nil { + return fmt.Errorf("fromFile: %w", err) + } + } + + err = copyRW(fromFile, toFile, copySize) + if err != nil { + return fmt.Errorf("copyRW: %w", err) + } + + return toFile.Sync() +} + +// значение по умолчанию кол-ва байт копируемых за одну итерацию. из io.ReadAll. +const defCopySize int64 = 512 + +func copyRW(from io.Reader, to io.Writer, size int64) error { + var ( + copySize = defCopySize // кол-во байт копируемых за одну итерацию. + copied int64 + ) + if size < copySize { + copySize = size + } + + // bar := pb.New64(size) + + for i := size; i > 0; i -= copySize { + if i < copySize { + copySize = i + } + + c, err := io.CopyN(to, from, copySize) + if err != nil { + return err + } + copied += c + fmt.Printf("\r%v%%", (100*copied)/size) + // bar.Add64(copySize) + } + fmt.Println() + // bar.Finish() + return nil } diff --git a/hw07_file_copying/copy_test.go b/hw07_file_copying/copy_test.go index e070942..28e41ee 100644 --- a/hw07_file_copying/copy_test.go +++ b/hw07_file_copying/copy_test.go @@ -1,7 +1,213 @@ package main -import "testing" +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + defOutName string = "./testdata/out_test_file.txt" + defInputName string = "./testdata/input.txt" +) + +func prepareTestFile() (string, int64, error) { + testFile, err := os.CreateTemp("./testdata/", "test_file_*.txt") + if err != nil { + log.Fatalf("prepareTestFile: CreateTemp: %s", err) + } + defer func() { + testFile.Close() + }() + + size, err := testFile.WriteString("Необходимо реализовать утилиту копирования файлов\n") // size = 95 + if err != nil { + log.Printf("prepareTestFile: %s", err) + return "", 0, err + } + + return testFile.Name(), int64(size), nil +} + +func TestCopyFromFile(t *testing.T) { + // исходный файл не корректный + t.Run("fromFile not regular file", func(t *testing.T) { + fromName := "/dev/urandom" + toName := defOutName + + tstErr := Copy(fromName, toName, 0, 0) + + require.EqualError(t, tstErr, "fromFile: "+ErrUnsupportedFile.Error(), "actual err - %v", tstErr) + }) + + // исходный файл дирректория + t.Run("fromFile is directory", func(t *testing.T) { + fromName := "./testdata/" + toName := defOutName + + tstErr := Copy(fromName, toName, 0, 0) + + require.EqualError(t, tstErr, "fromFile: "+ErrUnsupportedFile.Error(), "actual err - %v", tstErr) + }) + + // исходный файл не существует + t.Run("fromFile no such file", func(t *testing.T) { + fromName := "./testdata/not_exists_file.txt" + toName := defOutName + + tstErr := Copy(fromName, toName, 0, 0) + + require.EqualError(t, tstErr, + fmt.Sprintf("fromFileOpen: open %v: no such file or directory", fromName), + "actual err - %v", tstErr) + }) +} + +func TestCopyToFile(t *testing.T) { + // целевой файл не корректный + t.Run("toFile not regular file", func(t *testing.T) { + tstErr := Copy(defInputName, "/dev/urandom", 0, 0) + + require.EqualError(t, tstErr, "toFile: "+ErrUnsupportedFile.Error(), "actual err - %v", tstErr) + }) + + // целевой файл директория + t.Run("toFile is a directory", func(t *testing.T) { + toName := "./testdata/" + + tstErr := Copy(defInputName, toName, 0, 0) + + require.EqualError(t, tstErr, fmt.Sprintf("toFileCreate: open %v: is a directory", toName), "actual err - %v", tstErr) + }) +} func TestCopy(t *testing.T) { - // Place your code here. + testFileName, size, err := prepareTestFile() + if err != nil { + t.Error(err) + return + } + defer func() { + os.Remove(testFileName) + }() + + // исходный файл меньше offset + t.Run("fromFile size less offset", func(t *testing.T) { + tstErr := Copy(testFileName, defOutName, size, 0) + + require.EqualError(t, tstErr, "fromFile: offset exceeds file size", "actual err - %v", tstErr) + }) + + // без ошибок копируем в новый файл + t.Run("no error full copy new file", func(t *testing.T) { + tstErr := Copy(testFileName, defOutName, 0, 0) + require.NoError(t, tstErr) + + toFile, tstErr := os.Open(defOutName) + if tstErr != nil { + t.Error(tstErr) + return + } + + defer func() { + toFile.Close() + os.Remove(toFile.Name()) + }() + + toFileInfo, tstErr := toFile.Stat() + if tstErr != nil { + t.Error(tstErr) + return + } + + require.Equalf(t, size, toFileInfo.Size(), "file size must be %v", size) + + require.NotNil(t, toFile, "file must not be nil") + }) + + // без ошибок копируем файл целиком + t.Run("no error full file", func(t *testing.T) { + err := Copy(testFileName, defOutName, 0, 0) + + require.NoError(t, err) + require.FileExistsf(t, defOutName, "file must %v exists", defOutName) + + toFile, tstErr := os.Open(defOutName) + if tstErr != nil { + t.Error(tstErr) + return + } + + defer func() { + toFile.Close() + os.Remove(toFile.Name()) + }() + + toFileInfo, tstErr := toFile.Stat() + if tstErr != nil { + t.Error(tstErr) + return + } + + require.Equalf(t, size, toFileInfo.Size(), "file size must be %v", size) + }) + + // без ошибок копируем, но limit превышает размер + t.Run("no error size less limit", func(t *testing.T) { + err := Copy(testFileName, defOutName, 90, 30) + + require.NoError(t, err) + require.FileExistsf(t, defOutName, "file must %v exists", defOutName) + + toFile, tstErr := os.Open(defOutName) + if tstErr != nil { + t.Error(tstErr) + return + } + + defer func() { + toFile.Close() + os.Remove(toFile.Name()) + }() + + toFileInfo, tstErr := toFile.Stat() + if tstErr != nil { + t.Error(tstErr) + return + } + require.Equalf(t, int64(5), toFileInfo.Size(), "file size must be %v", 5) + }) + + // без ошибок копируем в существующий файл + t.Run("no error exists toFile", func(t *testing.T) { + var limit int64 = 10 + fromName := "./testdata/input.txt" + toName := testFileName + err := Copy(fromName, toName, 0, limit) + + require.NoError(t, err) + require.FileExistsf(t, toName, "file must %v exists", toName) + + toFile, tstErr := os.Open(toName) + if tstErr != nil { + t.Error(err) + return + } + + defer func() { + toFile.Close() + os.Remove(toFile.Name()) + }() + + toFileInfo, tstErr := toFile.Stat() + if tstErr != nil { + t.Error(err) + return + } + + require.Equalf(t, limit, toFileInfo.Size(), "file size must be %v", size) + }) } diff --git a/hw07_file_copying/go.mod b/hw07_file_copying/go.mod index 982e4b1..706acf0 100644 --- a/hw07_file_copying/go.mod +++ b/hw07_file_copying/go.mod @@ -1,3 +1,11 @@ -module github.com/fixme_my_friend/hw07_file_copying +module github.com/DimVlas/otus_hw/hw07_file_copying go 1.19 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/hw07_file_copying/go.sum b/hw07_file_copying/go.sum index e69de29..60ce688 100644 --- a/hw07_file_copying/go.sum +++ b/hw07_file_copying/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw07_file_copying/main.go b/hw07_file_copying/main.go index 515e844..6b65a85 100644 --- a/hw07_file_copying/main.go +++ b/hw07_file_copying/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" ) var ( @@ -18,5 +19,9 @@ func init() { func main() { flag.Parse() - // Place your code here. + + if err := Copy(from, to, offset, limit); err != nil { + fmt.Println(err) + return + } }