mirror of
https://github.com/go-shiori/shiori.git
synced 2024-09-20 06:56:10 +08:00
fix: Ensure bookmark files are correctly downloaded before deleting current ones (#683)
* generate ebook get dstPath * Archive and ebook can recover if download faild * recover thumb if download faild * thumb image create just if image processing is sucssesful * create epub in tmp if it sucssesful copy to destination * archive file create in tmp if it successful move to destination * move to destination as function * update ebook download api and remove .epub from file name * report faild item to user * not show dialog if error not happen * update thumbnail based on last status of bookmark fix #524 * better warning massage Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com> * tmpFile without .epub * MoveToDestination change to MoveFileToDestination * return .epub * log if downloadBookImage return error * fix bug remove imgPath just if download last image be unsuccessful * update old unit test * add processing.go unit test * small massage for report failded item to the user * add some more unit test and samplefile * use sample image in unit test * use local sample file and unit test not need internet connection anymore * update error to user and log that too * add more comment * update comment * change variable name parentDir to dstDir * more simpler error handling * remove unneeded defer * remvoe unneeded epubWriter.Close() * more readable unit test in processing * more readable unit test for ebooks * delete all defer os.RemoveAll from unit tests * Better comment Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com> * Better Error output Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com> * fix err.String() method --------- Co-authored-by: Felipe Martin <812088+fmartingr@users.noreply.github.com>
This commit is contained in:
parent
8b015a3850
commit
f4817cb9c3
|
@ -16,7 +16,10 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateEbook(req ProcessRequest) (book model.Bookmark, err error) {
|
// GenerateEbook receives a `ProcessRequest` and generates an ebook file in the destination path specified.
|
||||||
|
// The destination path `dstPath` should include file name with ".epub" extension
|
||||||
|
// The bookmark model will be used to update the UI based on whether this function is successful or not.
|
||||||
|
func GenerateEbook(req ProcessRequest, dstPath string) (book model.Bookmark, err error) {
|
||||||
// variable for store generated html code
|
// variable for store generated html code
|
||||||
var html string
|
var html string
|
||||||
|
|
||||||
|
@ -27,6 +30,7 @@ func GenerateEbook(req ProcessRequest) (book model.Bookmark, err error) {
|
||||||
return book, errors.New("bookmark ID is not valid")
|
return book, errors.New("bookmark ID is not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get current state of bookmark
|
||||||
// cheak archive and thumb
|
// cheak archive and thumb
|
||||||
strID := strconv.Itoa(book.ID)
|
strID := strconv.Itoa(book.ID)
|
||||||
|
|
||||||
|
@ -40,35 +44,23 @@ func GenerateEbook(req ProcessRequest) (book model.Bookmark, err error) {
|
||||||
if _, err := os.Stat(archivePath); err == nil {
|
if _, err := os.Stat(archivePath); err == nil {
|
||||||
book.HasArchive = true
|
book.HasArchive = true
|
||||||
}
|
}
|
||||||
ebookfile := fp.Join(req.DataDir, "ebook", fmt.Sprintf("%d.epub", book.ID))
|
|
||||||
// if epub exist finish prosess else continue
|
// this function create ebook from reader mode of bookmark so
|
||||||
if _, err := os.Stat(ebookfile); err == nil {
|
// we can't create ebook from PDF so we return error here if bookmark is a pdf
|
||||||
book.HasEbook = true
|
|
||||||
return book, nil
|
|
||||||
}
|
|
||||||
contentType := req.ContentType
|
contentType := req.ContentType
|
||||||
if strings.Contains(contentType, "application/pdf") {
|
if strings.Contains(contentType, "application/pdf") {
|
||||||
return book, errors.New("can't create ebook for pdf")
|
return book, errors.New("can't create ebook for pdf")
|
||||||
}
|
}
|
||||||
|
|
||||||
ebookDir := fp.Join(req.DataDir, "ebook")
|
// create temporary epub file
|
||||||
// check if directory not exsist create that
|
tmpFile, err := os.CreateTemp("", "ebook")
|
||||||
if _, err := os.Stat(ebookDir); os.IsNotExist(err) {
|
|
||||||
err := os.MkdirAll(ebookDir, model.DataDirPerm)
|
|
||||||
if err != nil {
|
|
||||||
return book, errors.Wrap(err, "can't create ebook directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create epub file
|
|
||||||
epubFile, err := os.Create(ebookfile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return book, errors.Wrap(err, "can't create ebook")
|
return book, errors.Wrap(err, "can't create temporary EPUB file")
|
||||||
}
|
}
|
||||||
defer epubFile.Close()
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
// Create zip archive
|
// Create zip archive
|
||||||
epubWriter := zip.NewWriter(epubFile)
|
epubWriter := zip.NewWriter(tmpFile)
|
||||||
defer epubWriter.Close()
|
|
||||||
|
|
||||||
// Create the mimetype file
|
// Create the mimetype file
|
||||||
mimetypeWriter, err := epubWriter.Create("mimetype")
|
mimetypeWriter, err := epubWriter.Create("mimetype")
|
||||||
|
@ -223,6 +215,27 @@ img {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return book, errors.Wrap(err, "can't write into content.html")
|
return book, errors.Wrap(err, "can't write into content.html")
|
||||||
}
|
}
|
||||||
|
// close epub and tmpFile
|
||||||
|
err = epubWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return book, errors.Wrap(err, "failed to close EPUB writer")
|
||||||
|
}
|
||||||
|
err = tmpFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return book, errors.Wrap(err, "failed to close temporary EPUB file")
|
||||||
|
}
|
||||||
|
// open temporary file again
|
||||||
|
tmpFile, err = os.Open(tmpFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
return book, errors.Wrap(err, "can't open temporary EPUB file")
|
||||||
|
}
|
||||||
|
defer tmpFile.Close()
|
||||||
|
// if everitings go well we start move ebook to dstPath
|
||||||
|
err = MoveFileToDestination(dstPath, tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return book, errors.Wrap(err, "failed move ebook to destination")
|
||||||
|
}
|
||||||
|
|
||||||
book.HasEbook = true
|
book.HasEbook = true
|
||||||
return book, nil
|
return book, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package core_test
|
package core_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
fp "path/filepath"
|
fp "path/filepath"
|
||||||
|
@ -12,208 +11,165 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateEbook_ValidBookmarkID_ReturnsBookmarkWithHasEbookTrue(t *testing.T) {
|
func TestGenerateEbook(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
t.Run("Successful ebook generate", func(t *testing.T) {
|
||||||
|
t.Run("valid bookmarkId that return HasEbook true", func(t *testing.T) {
|
||||||
|
// test cae
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstDir := t.TempDir()
|
||||||
|
|
||||||
defer os.RemoveAll(tempDir)
|
mockRequest := core.ProcessRequest{
|
||||||
|
Bookmark: model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
Title: "Example Bookmark",
|
||||||
|
HTML: "<html><body>Example HTML</body></html>",
|
||||||
|
HasEbook: false,
|
||||||
|
},
|
||||||
|
DataDir: dstDir,
|
||||||
|
ContentType: "text/html",
|
||||||
|
}
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
bookmark, err := core.GenerateEbook(mockRequest, fp.Join(tempDir, "1"))
|
||||||
Bookmark: model.Bookmark{
|
|
||||||
ID: 1,
|
|
||||||
Title: "Example Bookmark",
|
|
||||||
HTML: "<html><body>Example HTML</body></html>",
|
|
||||||
HasEbook: false,
|
|
||||||
},
|
|
||||||
DataDir: tempDir,
|
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
assert.True(t, bookmark.HasEbook)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("ebook generate with valid BookmarkID EbookExist ImagePathExist ReturnWithHasEbookTrue", func(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstDir := t.TempDir()
|
||||||
|
|
||||||
assert.True(t, bookmark.HasEbook)
|
mockRequest := core.ProcessRequest{
|
||||||
assert.NoError(t, err)
|
Bookmark: model.Bookmark{
|
||||||
}
|
ID: 1,
|
||||||
|
HasEbook: false,
|
||||||
|
},
|
||||||
|
DataDir: dstDir,
|
||||||
|
ContentType: "text/html",
|
||||||
|
}
|
||||||
|
// Create the image directory
|
||||||
|
imageDir := fp.Join(mockRequest.DataDir, "thumb")
|
||||||
|
err := os.MkdirAll(imageDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Create the image file
|
||||||
|
imagePath := fp.Join(mockRequest.DataDir, "thumb", fmt.Sprintf("%d", mockRequest.Bookmark.ID))
|
||||||
|
file, err := os.Create(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
func TestGenerateEbook_InvalidBookmarkID_ReturnsError(t *testing.T) {
|
bookmark, err := core.GenerateEbook(mockRequest, fp.Join(tempDir, "1"))
|
||||||
tempDir := t.TempDir()
|
expectedimagePath := "/bookmark/1/thumb"
|
||||||
defer os.RemoveAll(tempDir)
|
if expectedimagePath != bookmark.ImageURL {
|
||||||
mockRequest := core.ProcessRequest{
|
t.Errorf("Expected imageURL %s, but got %s", bookmark.ImageURL, expectedimagePath)
|
||||||
Bookmark: model.Bookmark{
|
}
|
||||||
ID: 0,
|
assert.True(t, bookmark.HasEbook)
|
||||||
HasEbook: false,
|
assert.NoError(t, err)
|
||||||
},
|
})
|
||||||
DataDir: tempDir,
|
t.Run("generate ebook valid BookmarkID EbookExist Returnh HasArchive True", func(t *testing.T) {
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
tempDir := t.TempDir()
|
||||||
|
dstDir := t.TempDir()
|
||||||
|
|
||||||
assert.Equal(t, model.Bookmark{
|
mockRequest := core.ProcessRequest{
|
||||||
ID: 0,
|
Bookmark: model.Bookmark{
|
||||||
HasEbook: false,
|
ID: 1,
|
||||||
}, bookmark)
|
HasEbook: false,
|
||||||
assert.Error(t, err)
|
},
|
||||||
}
|
DataDir: dstDir,
|
||||||
|
ContentType: "text/html",
|
||||||
|
}
|
||||||
|
// Create the archive directory
|
||||||
|
archiveDir := fp.Join(mockRequest.DataDir, "archive")
|
||||||
|
err := os.MkdirAll(archiveDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Create the archive file
|
||||||
|
archivePath := fp.Join(mockRequest.DataDir, "archive", fmt.Sprintf("%d", mockRequest.Bookmark.ID))
|
||||||
|
file, err := os.Create(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
func TestGenerateEbook_ValidBookmarkID_EbookExist_EbookExist_ReturnWithHasEbookTrue(t *testing.T) {
|
bookmark, err := core.GenerateEbook(mockRequest, fp.Join(tempDir, "1"))
|
||||||
tempDir := t.TempDir()
|
assert.True(t, bookmark.HasArchive)
|
||||||
defer os.RemoveAll(tempDir)
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("specific ebook generate case", func(t *testing.T) {
|
||||||
|
t.Run("unvalid bookmarkId that return Error", func(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
mockRequest := core.ProcessRequest{
|
||||||
|
Bookmark: model.Bookmark{
|
||||||
|
ID: 0,
|
||||||
|
HasEbook: false,
|
||||||
|
},
|
||||||
|
DataDir: tempDir,
|
||||||
|
ContentType: "text/html",
|
||||||
|
}
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
bookmark, err := core.GenerateEbook(mockRequest, tempDir)
|
||||||
Bookmark: model.Bookmark{
|
|
||||||
ID: 1,
|
|
||||||
HasEbook: false,
|
|
||||||
},
|
|
||||||
DataDir: tempDir,
|
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
// Create the ebook directory
|
|
||||||
ebookDir := fp.Join(mockRequest.DataDir, "ebook")
|
|
||||||
err := os.MkdirAll(ebookDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create the ebook file
|
|
||||||
ebookfile := fp.Join(mockRequest.DataDir, "ebook", fmt.Sprintf("%d.epub", mockRequest.Bookmark.ID))
|
|
||||||
file, err := os.Create(ebookfile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
assert.Equal(t, model.Bookmark{
|
||||||
|
ID: 0,
|
||||||
|
HasEbook: false,
|
||||||
|
}, bookmark)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("ebook exist return HasEbook true", func(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstDir := t.TempDir()
|
||||||
|
|
||||||
assert.True(t, bookmark.HasEbook)
|
mockRequest := core.ProcessRequest{
|
||||||
assert.NoError(t, err)
|
Bookmark: model.Bookmark{
|
||||||
}
|
ID: 1,
|
||||||
|
HasEbook: false,
|
||||||
|
},
|
||||||
|
DataDir: dstDir,
|
||||||
|
ContentType: "text/html",
|
||||||
|
}
|
||||||
|
// Create the ebook directory
|
||||||
|
ebookDir := fp.Join(mockRequest.DataDir, "ebook")
|
||||||
|
err := os.MkdirAll(ebookDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Create the ebook file
|
||||||
|
ebookfile := fp.Join(mockRequest.DataDir, "ebook", fmt.Sprintf("%d.epub", mockRequest.Bookmark.ID))
|
||||||
|
file, err := os.Create(ebookfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
func TestGenerateEbook_ValidBookmarkID_EbookExist_ImagePathExist_ReturnWithHasEbookTrue(t *testing.T) {
|
bookmark, err := core.GenerateEbook(mockRequest, fp.Join(tempDir, "1"))
|
||||||
tempDir := t.TempDir()
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
assert.True(t, bookmark.HasEbook)
|
||||||
Bookmark: model.Bookmark{
|
assert.NoError(t, err)
|
||||||
ID: 1,
|
})
|
||||||
HasEbook: false,
|
t.Run("generate ebook valid BookmarkID RetuenError for PDF file", func(t *testing.T) {
|
||||||
},
|
tempDir := t.TempDir()
|
||||||
DataDir: tempDir,
|
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
// Create the image directory
|
|
||||||
imageDir := fp.Join(mockRequest.DataDir, "thumb")
|
|
||||||
err := os.MkdirAll(imageDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create the image file
|
|
||||||
imagePath := fp.Join(mockRequest.DataDir, "thumb", fmt.Sprintf("%d", mockRequest.Bookmark.ID))
|
|
||||||
file, err := os.Create(imagePath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
mockRequest := core.ProcessRequest{
|
||||||
expectedimagePath := "/bookmark/1/thumb"
|
Bookmark: model.Bookmark{
|
||||||
if expectedimagePath != bookmark.ImageURL {
|
ID: 1,
|
||||||
t.Errorf("Expected imageURL %s, but got %s", bookmark.ImageURL, expectedimagePath)
|
HasEbook: false,
|
||||||
}
|
},
|
||||||
assert.True(t, bookmark.HasEbook)
|
DataDir: tempDir,
|
||||||
assert.NoError(t, err)
|
ContentType: "application/pdf",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateEbook_ValidBookmarkID_EbookExist_ReturnWithHasArchiveTrue(t *testing.T) {
|
bookmark, err := core.GenerateEbook(mockRequest, tempDir)
|
||||||
tempDir := t.TempDir()
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
assert.False(t, bookmark.HasEbook)
|
||||||
Bookmark: model.Bookmark{
|
assert.Error(t, err)
|
||||||
ID: 1,
|
assert.Contains(t, err.Error(), "can't create ebook for pdf")
|
||||||
HasEbook: false,
|
})
|
||||||
},
|
})
|
||||||
DataDir: tempDir,
|
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
// Create the archive directory
|
|
||||||
archiveDir := fp.Join(mockRequest.DataDir, "archive")
|
|
||||||
err := os.MkdirAll(archiveDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create the archive file
|
|
||||||
archivePath := fp.Join(mockRequest.DataDir, "archive", fmt.Sprintf("%d", mockRequest.Bookmark.ID))
|
|
||||||
file, err := os.Create(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
|
||||||
assert.True(t, bookmark.HasArchive)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateEbook_ValidBookmarkID_RetuenError_PDF(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
|
||||||
Bookmark: model.Bookmark{
|
|
||||||
ID: 1,
|
|
||||||
HasEbook: false,
|
|
||||||
},
|
|
||||||
DataDir: tempDir,
|
|
||||||
ContentType: "application/pdf",
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
|
||||||
|
|
||||||
assert.False(t, bookmark.HasEbook)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "can't create ebook for pdf")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateEbook_CreateEbookDirectoryNotWritable(t *testing.T) {
|
|
||||||
// Create a temporary directory to use as the parent directory
|
|
||||||
parentDir := t.TempDir()
|
|
||||||
|
|
||||||
// Create a child directory with read-only permissions
|
|
||||||
ebookDir := fp.Join(parentDir, "ebook")
|
|
||||||
err := os.Mkdir(ebookDir, 0444)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not create ebook directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mockRequest := core.ProcessRequest{
|
|
||||||
Bookmark: model.Bookmark{
|
|
||||||
ID: 1,
|
|
||||||
HasEbook: false,
|
|
||||||
},
|
|
||||||
DataDir: ebookDir,
|
|
||||||
ContentType: "text/html",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call GenerateEbook to create the ebook directory
|
|
||||||
bookmark, err := core.GenerateEbook(mockRequest)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("GenerateEbook succeeded even though MkdirAll should have failed")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, os.ErrPermission) {
|
|
||||||
t.Fatalf("unexpected error: expected os.ErrPermission, got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the ebook directory still exists and has read-only permissions
|
|
||||||
info, err := os.Stat(ebookDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not retrieve ebook directory info: %s", err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
t.Errorf("ebook directory is not a directory")
|
|
||||||
}
|
|
||||||
if info.Mode().Perm() != 0444 {
|
|
||||||
t.Errorf("ebook directory has incorrect permissions: expected 0444, got %o", info.Mode().Perm())
|
|
||||||
}
|
|
||||||
assert.False(t, bookmark.HasEbook)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add more unit tests for other scenarios that missing specialy
|
// Add more unit tests for other scenarios that missing specialy
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
fp "path/filepath"
|
fp "path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -37,6 +37,8 @@ type ProcessRequest struct {
|
||||||
LogArchival bool
|
LogArchival bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrNoSupportedImageType = errors.New("unsupported image type")
|
||||||
|
|
||||||
// ProcessBookmark process the bookmark and archive it if needed.
|
// ProcessBookmark process the bookmark and archive it if needed.
|
||||||
// Return three values, is error fatal, and error value.
|
// Return three values, is error fatal, and error value.
|
||||||
func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool, err error) {
|
func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool, err error) {
|
||||||
|
@ -66,13 +68,15 @@ func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is HTML, parse for readable content
|
// If this is HTML, parse for readable content
|
||||||
|
strID := strconv.Itoa(book.ID)
|
||||||
|
imgPath := fp.Join(req.DataDir, "thumb", strID)
|
||||||
var imageURLs []string
|
var imageURLs []string
|
||||||
if strings.Contains(contentType, "text/html") {
|
if strings.Contains(contentType, "text/html") {
|
||||||
isReadable := readability.Check(readabilityCheckInput)
|
isReadable := readability.Check(readabilityCheckInput)
|
||||||
|
|
||||||
nurl, err := url.Parse(book.URL)
|
nurl, err := url.Parse(book.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return book, true, fmt.Errorf("Failed to parse url: %v", err)
|
return book, true, fmt.Errorf("failed to parse url: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
article, err := readability.FromReader(readabilityInput, nurl)
|
article, err := readability.FromReader(readabilityInput, nurl)
|
||||||
|
@ -101,6 +105,8 @@ func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool,
|
||||||
// Get image URL
|
// Get image URL
|
||||||
if article.Image != "" {
|
if article.Image != "" {
|
||||||
imageURLs = append(imageURLs, article.Image)
|
imageURLs = append(imageURLs, article.Image)
|
||||||
|
} else {
|
||||||
|
os.Remove(imgPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if article.Favicon != "" {
|
if article.Favicon != "" {
|
||||||
|
@ -115,26 +121,32 @@ func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save article image to local disk
|
// Save article image to local disk
|
||||||
strID := strconv.Itoa(book.ID)
|
for i, imageURL := range imageURLs {
|
||||||
imgPath := fp.Join(req.DataDir, "thumb", strID)
|
err = DownloadBookImage(imageURL, imgPath)
|
||||||
|
if err != nil && errors.Is(err, ErrNoSupportedImageType) {
|
||||||
for _, imageURL := range imageURLs {
|
log.Printf("%s: %s", err, imageURL)
|
||||||
err = downloadBookImage(imageURL, imgPath)
|
if i == len(imageURLs)-1 {
|
||||||
|
os.Remove(imgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("File download not successful for image URL: %s", imageURL)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
book.ImageURL = path.Join("/", "bookmark", strID, "thumb")
|
book.ImageURL = fp.Join("/", "bookmark", strID, "thumb")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If needed, create ebook as well
|
// If needed, create ebook as well
|
||||||
if book.CreateEbook {
|
if book.CreateEbook {
|
||||||
ebookPath := fp.Join(req.DataDir, "ebook", fmt.Sprintf("%d.epub", book.ID))
|
ebookPath := fp.Join(req.DataDir, "ebook", strID+".epub")
|
||||||
os.Remove(ebookPath)
|
|
||||||
|
|
||||||
if strings.Contains(contentType, "application/pdf") {
|
if strings.Contains(contentType, "application/pdf") {
|
||||||
return book, false, errors.Wrap(err, "can't create ebook from pdf")
|
return book, false, errors.Wrap(err, "can't create ebook from pdf")
|
||||||
} else {
|
} else {
|
||||||
_, err = GenerateEbook(req)
|
_, err = GenerateEbook(req, ebookPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return book, true, errors.Wrap(err, "failed to create ebook")
|
return book, true, errors.Wrap(err, "failed to create ebook")
|
||||||
}
|
}
|
||||||
|
@ -144,8 +156,11 @@ func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool,
|
||||||
|
|
||||||
// If needed, create offline archive as well
|
// If needed, create offline archive as well
|
||||||
if book.CreateArchive {
|
if book.CreateArchive {
|
||||||
archivePath := fp.Join(req.DataDir, "archive", fmt.Sprintf("%d", book.ID))
|
tmpFile, err := os.CreateTemp("", "archive")
|
||||||
os.Remove(archivePath)
|
if err != nil {
|
||||||
|
return book, false, fmt.Errorf("failed to create temp archive: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
archivalRequest := warc.ArchivalRequest{
|
archivalRequest := warc.ArchivalRequest{
|
||||||
URL: book.URL,
|
URL: book.URL,
|
||||||
|
@ -155,18 +170,27 @@ func ProcessBookmark(req ProcessRequest) (book model.Bookmark, isFatalErr bool,
|
||||||
LogEnabled: req.LogArchival,
|
LogEnabled: req.LogArchival,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = warc.NewArchive(archivalRequest, archivePath)
|
err = warc.NewArchive(archivalRequest, tmpFile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
return book, false, fmt.Errorf("failed to create archive: %v", err)
|
return book, false, fmt.Errorf("failed to create archive: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare destination file.
|
||||||
|
dstPath := fp.Join(req.DataDir, "archive", fmt.Sprintf("%d", book.ID))
|
||||||
|
|
||||||
|
err = MoveFileToDestination(dstPath, tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return book, false, fmt.Errorf("failed move archive to destination `: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
book.HasArchive = true
|
book.HasArchive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return book, false, nil
|
return book, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadBookImage(url, dstPath string) error {
|
func DownloadBookImage(url, dstPath string) error {
|
||||||
// Fetch data from URL
|
// Fetch data from URL
|
||||||
resp, err := httpClient.Get(url)
|
resp, err := httpClient.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,22 +204,16 @@ func downloadBookImage(url, dstPath string) error {
|
||||||
!strings.Contains(cp, "image/pjpeg") &&
|
!strings.Contains(cp, "image/pjpeg") &&
|
||||||
!strings.Contains(cp, "image/jpg") &&
|
!strings.Contains(cp, "image/jpg") &&
|
||||||
!strings.Contains(cp, "image/png") {
|
!strings.Contains(cp, "image/png") {
|
||||||
|
return ErrNoSupportedImageType
|
||||||
return fmt.Errorf("%s is not a supported image", url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, the download has finished successfully.
|
// At this point, the download has finished successfully.
|
||||||
// Prepare destination file.
|
// Create tmpFile
|
||||||
err = os.MkdirAll(fp.Dir(dstPath), model.DataDirPerm)
|
tmpFile, err := os.CreateTemp("", "image")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create image dir: %v", err)
|
return fmt.Errorf("failed to create temporary image file: %v", err)
|
||||||
}
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
dstFile, err := os.Create(dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create image file: %v", err)
|
|
||||||
}
|
|
||||||
defer dstFile.Close()
|
|
||||||
|
|
||||||
// Parse image and process it.
|
// Parse image and process it.
|
||||||
// If image is smaller than 600x400 or its ratio is less than 4:3, resize.
|
// If image is smaller than 600x400 or its ratio is less than 4:3, resize.
|
||||||
|
@ -211,7 +229,7 @@ func downloadBookImage(url, dstPath string) error {
|
||||||
imgRatio := float64(imgWidth) / float64(imgHeight)
|
imgRatio := float64(imgWidth) / float64(imgHeight)
|
||||||
|
|
||||||
if imgWidth >= 600 && imgHeight >= 400 && imgRatio > 1.3 {
|
if imgWidth >= 600 && imgHeight >= 400 && imgRatio > 1.3 {
|
||||||
err = jpeg.Encode(dstFile, img, nil)
|
err = jpeg.Encode(tmpFile, img, nil)
|
||||||
} else {
|
} else {
|
||||||
// Create background
|
// Create background
|
||||||
bg := image.NewNRGBA(imgRect)
|
bg := image.NewNRGBA(imgRect)
|
||||||
|
@ -236,12 +254,44 @@ func downloadBookImage(url, dstPath string) error {
|
||||||
draw.Draw(bg, bgRect, fg, fgPosition, draw.Over)
|
draw.Draw(bg, bgRect, fg, fgPosition, draw.Over)
|
||||||
|
|
||||||
// Save to file
|
// Save to file
|
||||||
err = jpeg.Encode(dstFile, bg, nil)
|
err = jpeg.Encode(tmpFile, bg, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save image %s: %v", url, err)
|
return fmt.Errorf("failed to save image %s: %v", url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = MoveFileToDestination(dstPath, tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dstPath requires the filename
|
||||||
|
func MoveFileToDestination(dstPath string, tmpFile *os.File) error {
|
||||||
|
// Prepare destination file.
|
||||||
|
err := os.MkdirAll(fp.Dir(dstPath), model.DataDirPerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstFile, err := os.Create(dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create destination file: %v", err)
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
// Copy temporary file to destination
|
||||||
|
_, err = tmpFile.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to rewind temporary file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(dstFile, tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to copy file to the destination")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
349
internal/core/processing_test.go
Normal file
349
internal/core/processing_test.go
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
package core_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
fp "path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-shiori/shiori/internal/core"
|
||||||
|
"github.com/go-shiori/shiori/internal/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMoveFileToDestination(t *testing.T) {
|
||||||
|
t.Run("create fails", func(t *testing.T) {
|
||||||
|
t.Run("directory create fails", func(t *testing.T) {
|
||||||
|
// test if create dir fails
|
||||||
|
tmpFile, err := os.CreateTemp("", "image")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
err = core.MoveFileToDestination("/destination/test", tmpFile)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "failed to create destination dir")
|
||||||
|
})
|
||||||
|
t.Run("file create fails", func(t *testing.T) {
|
||||||
|
// if create file failed
|
||||||
|
tmpFile, err := os.CreateTemp("", "image")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
// Create a destination directory
|
||||||
|
dstDir := t.TempDir()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(dstDir)
|
||||||
|
|
||||||
|
// Set destination path to an invalid file name to force os.Create to fail
|
||||||
|
dstPath := fp.Join(dstDir, "\000invalid\000")
|
||||||
|
|
||||||
|
err = core.MoveFileToDestination(dstPath, tmpFile)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "failed to create destination file")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func TestDownloadBookImage(t *testing.T) {
|
||||||
|
t.Run("Download Images", func(t *testing.T) {
|
||||||
|
t.Run("fails", func(t *testing.T) {
|
||||||
|
// images is too small with unsupported format with a valid URL
|
||||||
|
imageURL := "https://github.com/go-shiori/shiori/blob/master/internal/view/assets/res/apple-touch-icon-152x152.png"
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstPath := fp.Join(tempDir, "1")
|
||||||
|
defer os.Remove(dstPath)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := core.DownloadBookImage(imageURL, dstPath)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.EqualError(t, err, "unsupported image type")
|
||||||
|
assert.NoFileExists(t, dstPath)
|
||||||
|
})
|
||||||
|
t.Run("sucssesful downlosd image", func(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
imageURL := "https://raw.githubusercontent.com/go-shiori/shiori/master/docs/readme/cover.png"
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstPath := fp.Join(tempDir, "1")
|
||||||
|
defer os.Remove(dstPath)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := core.DownloadBookImage(imageURL, dstPath)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.FileExists(t, dstPath)
|
||||||
|
})
|
||||||
|
t.Run("sucssesful downlosd medium size image", func(t *testing.T) {
|
||||||
|
// create a file server handler for the 'testdata' directory
|
||||||
|
fs := http.FileServer(http.Dir("../../testdata/"))
|
||||||
|
|
||||||
|
// start a test server with the file server handler
|
||||||
|
server := httptest.NewServer(fs)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
imageURL := server.URL + "/medium_image.png"
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dstPath := fp.Join(tempDir, "1")
|
||||||
|
defer os.Remove(dstPath)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := core.DownloadBookImage(imageURL, dstPath)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.FileExists(t, dstPath)
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessBookmark(t *testing.T) {
|
||||||
|
t.Run("ProcessRequest with sucssesful result", func(t *testing.T) {
|
||||||
|
t.Run("Normal without image", func(t *testing.T) {
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "Example",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString("<html><head></head><body><p>This is an example article</p></body></html>")
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
expected, _, _ := core.ProcessBookmark(request)
|
||||||
|
|
||||||
|
if expected.ID != bookmark.ID {
|
||||||
|
t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID)
|
||||||
|
}
|
||||||
|
if expected.URL != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected URL: got %v, want %v", expected.URL, bookmark.URL)
|
||||||
|
}
|
||||||
|
if expected.Title != bookmark.Title {
|
||||||
|
t.Errorf("Unexpected Title: got %v, want %v", expected.Title, bookmark.Title)
|
||||||
|
}
|
||||||
|
if expected.Excerpt != bookmark.Excerpt {
|
||||||
|
t.Errorf("Unexpected Excerpt: got %v, want %v", expected.Excerpt, bookmark.Excerpt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Normal with multipleimage", func(t *testing.T) {
|
||||||
|
|
||||||
|
html := `html<html>
|
||||||
|
<head>
|
||||||
|
<meta property="og:image" content="http://example.com/image1.jpg">
|
||||||
|
<meta property="og:image" content="http://example.com/image2.jpg">
|
||||||
|
<link rel="icon" type="image/png" href="http://example.com/favicon.png">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This is an example article</p>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "Example",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString(html)
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
expected, _, _ := core.ProcessBookmark(request)
|
||||||
|
|
||||||
|
if expected.ID != bookmark.ID {
|
||||||
|
t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID)
|
||||||
|
}
|
||||||
|
if expected.URL != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected URL: got %v, want %v", expected.URL, bookmark.URL)
|
||||||
|
}
|
||||||
|
if expected.Title != bookmark.Title {
|
||||||
|
t.Errorf("Unexpected Title: got %v, want %v", expected.Title, bookmark.Title)
|
||||||
|
}
|
||||||
|
if expected.Excerpt != bookmark.Excerpt {
|
||||||
|
t.Errorf("Unexpected Excerpt: got %v, want %v", expected.Excerpt, bookmark.Excerpt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ProcessRequest sucssesful with multipleimage included favicon and Thumbnail ", func(t *testing.T) {
|
||||||
|
// create a file server handler for the 'testdata' directory
|
||||||
|
fs := http.FileServer(http.Dir("../../testdata/"))
|
||||||
|
|
||||||
|
// start a test server with the file server handler
|
||||||
|
server := httptest.NewServer(fs)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
html := `html<html>
|
||||||
|
<head>
|
||||||
|
<meta property="og:image" content="http://example.com/image1.jpg">
|
||||||
|
<meta property="og:image" content="` + server.URL + `/big_image.png">
|
||||||
|
<link rel="icon" type="image/svg" href="` + server.URL + `/favicon.svg">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This is an example article</p>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "Example",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString(html)
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
expected, _, _ := core.ProcessBookmark(request)
|
||||||
|
|
||||||
|
if expected.ID != bookmark.ID {
|
||||||
|
t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID)
|
||||||
|
}
|
||||||
|
if expected.URL != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected URL: got %v, want %v", expected.URL, bookmark.URL)
|
||||||
|
}
|
||||||
|
if expected.Title != bookmark.Title {
|
||||||
|
t.Errorf("Unexpected Title: got %v, want %v", expected.Title, bookmark.Title)
|
||||||
|
}
|
||||||
|
if expected.Excerpt != bookmark.Excerpt {
|
||||||
|
t.Errorf("Unexpected Excerpt: got %v, want %v", expected.Excerpt, bookmark.Excerpt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ProcessRequest sucssesful with empty title ", func(t *testing.T) {
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString("<html><head></head><body><p>This is an example article</p></body></html>")
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
expected, _, _ := core.ProcessBookmark(request)
|
||||||
|
|
||||||
|
if expected.ID != bookmark.ID {
|
||||||
|
t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID)
|
||||||
|
}
|
||||||
|
if expected.URL != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected URL: got %v, want %v", expected.URL, bookmark.URL)
|
||||||
|
}
|
||||||
|
if expected.Title != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected Title: got %v, want %v", expected.Title, bookmark.Title)
|
||||||
|
}
|
||||||
|
if expected.Excerpt != bookmark.Excerpt {
|
||||||
|
t.Errorf("Unexpected Excerpt: got %v, want %v", expected.Excerpt, bookmark.Excerpt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("ProcessRequest sucssesful with empty Excerpt", func(t *testing.T) {
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString("<html><head></head><body><p>This is an example article</p></body></html>")
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: false,
|
||||||
|
}
|
||||||
|
expected, _, _ := core.ProcessBookmark(request)
|
||||||
|
|
||||||
|
if expected.ID != bookmark.ID {
|
||||||
|
t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID)
|
||||||
|
}
|
||||||
|
if expected.URL != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected URL: got %v, want %v", expected.URL, bookmark.URL)
|
||||||
|
}
|
||||||
|
if expected.Title != bookmark.URL {
|
||||||
|
t.Errorf("Unexpected Title: got %v, want %v", expected.Title, bookmark.Title)
|
||||||
|
}
|
||||||
|
if expected.Excerpt != bookmark.Excerpt {
|
||||||
|
t.Errorf("Unexpected Excerpt: got %v, want %v", expected.Excerpt, bookmark.Excerpt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("Specific case", func(t *testing.T) {
|
||||||
|
t.Run("ProcessRequest with ID zero", func(t *testing.T) {
|
||||||
|
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 0,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "Example",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString("<html><head></head><body><p>This is an example article</p></body></html>")
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "text/html",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
_, isFatal, err := core.ProcessBookmark(request)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "bookmark ID is not valid")
|
||||||
|
assert.True(t, isFatal)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ProcessRequest that content type not zero", func(t *testing.T) {
|
||||||
|
|
||||||
|
bookmark := model.Bookmark{
|
||||||
|
ID: 1,
|
||||||
|
URL: "https://example.com",
|
||||||
|
Title: "Example",
|
||||||
|
Excerpt: "This is an example article",
|
||||||
|
CreateEbook: true,
|
||||||
|
CreateArchive: true,
|
||||||
|
}
|
||||||
|
content := bytes.NewBufferString("<html><head></head><body><p>This is an example article</p></body></html>")
|
||||||
|
request := core.ProcessRequest{
|
||||||
|
Bookmark: bookmark,
|
||||||
|
Content: content,
|
||||||
|
ContentType: "application/pdf",
|
||||||
|
DataDir: "/tmp",
|
||||||
|
KeepTitle: true,
|
||||||
|
KeepExcerpt: true,
|
||||||
|
}
|
||||||
|
_, _, err := core.ProcessBookmark(request)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -703,14 +703,35 @@ export default {
|
||||||
this.dialog.loading = false;
|
this.dialog.loading = false;
|
||||||
this.dialog.visible = false;
|
this.dialog.visible = false;
|
||||||
|
|
||||||
json.forEach(book => {
|
let faildedUpdateArchives = [];
|
||||||
var item = items.find(el => el.id === book.id);
|
let faildedCreateEbook = [];
|
||||||
this.bookmarks.splice(item.index, 1, book);
|
json.forEach(book => {
|
||||||
});
|
var item = items.find(el => el.id === book.id);
|
||||||
}).catch(err => {
|
this.bookmarks.splice(item.index, 1, book);
|
||||||
this.selection = [];
|
|
||||||
this.editMode = false;
|
if (data.createArchive && !book.hasArchive){
|
||||||
this.dialog.loading = false;
|
faildedUpdateArchives.push(book.id);
|
||||||
|
console.error("can't update archive for bookmark id", book.id)
|
||||||
|
}
|
||||||
|
if (data.createEbook && !book.hasEbook){
|
||||||
|
faildedCreateEbook.push(book.id);
|
||||||
|
console.error("can't update ebook for bookmark id:", book.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(faildedCreateEbook.length > 0 || faildedUpdateArchives.length > 0){
|
||||||
|
this.showDialog({
|
||||||
|
title: `Bookmarks Id that Update Action Faild`,
|
||||||
|
content: `Not all bookmarks could have their contents updated, but no files were overwritten.`,
|
||||||
|
mainText: "OK",
|
||||||
|
mainClick: () => {
|
||||||
|
this.dialog.visible = false;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
this.selection = [];
|
||||||
|
this.editMode = false;
|
||||||
|
this.dialog.loading = false;
|
||||||
|
|
||||||
this.getErrorMessage(err).then(msg => {
|
this.getErrorMessage(err).then(msg => {
|
||||||
this.showErrorDialog(msg);
|
this.showErrorDialog(msg);
|
||||||
|
|
|
@ -434,12 +434,32 @@ func (h *Handler) ApiDownloadEbook(w http.ResponseWriter, r *http.Request, ps ht
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
}
|
}
|
||||||
|
|
||||||
book, err = core.GenerateEbook(request)
|
// if file exist book return avilable file
|
||||||
content.Close()
|
strID := strconv.Itoa(book.ID)
|
||||||
|
ebookPath := fp.Join(request.DataDir, "ebook", strID+".epub")
|
||||||
|
_, err = os.Stat(ebookPath)
|
||||||
|
if err == nil {
|
||||||
|
// file already exists, return the existing file
|
||||||
|
imagePath := fp.Join(request.DataDir, "thumb", fmt.Sprintf("%d", book.ID))
|
||||||
|
archivePath := fp.Join(request.DataDir, "archive", fmt.Sprintf("%d", book.ID))
|
||||||
|
|
||||||
if err != nil {
|
if _, err := os.Stat(imagePath); err == nil {
|
||||||
chProblem <- book.ID
|
book.ImageURL = fp.Join("/", "bookmark", strID, "thumb")
|
||||||
return
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(archivePath); err == nil {
|
||||||
|
book.HasArchive = true
|
||||||
|
}
|
||||||
|
book.HasEbook = true
|
||||||
|
} else {
|
||||||
|
// generate ebook file
|
||||||
|
book, err = core.GenerateEbook(request, ebookPath)
|
||||||
|
content.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
chProblem <- book.ID
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update list of bookmarks
|
// Update list of bookmarks
|
||||||
|
|
|
@ -48,7 +48,7 @@ func (h *Handler) ServeBookmarkContent(w http.ResponseWriter, r *http.Request, p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it has archive.
|
// Check if it has ebook.
|
||||||
ebookPath := fp.Join(h.DataDir, "ebook", strID+".epub")
|
ebookPath := fp.Join(h.DataDir, "ebook", strID+".epub")
|
||||||
if fileExists(ebookPath) {
|
if fileExists(ebookPath) {
|
||||||
bookmark.HasEbook = true
|
bookmark.HasEbook = true
|
||||||
|
|
BIN
testdata/big_image.png
vendored
Normal file
BIN
testdata/big_image.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
testdata/favicon.png
vendored
Normal file
BIN
testdata/favicon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 534 B |
6
testdata/favicon.svg
vendored
Normal file
6
testdata/favicon.svg
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg width="32" height="32">
|
||||||
|
<circle cx="16" cy="16" r="16"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 223 B |
BIN
testdata/medium_image.png
vendored
Normal file
BIN
testdata/medium_image.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in a new issue