reddit: fix image copy for existing should go to temp dir first
This commit is contained in:
parent
c5c8058376
commit
65bb5732ca
|
@ -5,10 +5,12 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -185,21 +187,32 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
|
||||||
return api.saveImageToFSAndDatabase(ctx, tmpImageFile, subreddit, post, devices)
|
return api.saveImageToFSAndDatabase(ctx, tmpImageFile, subreddit, post, devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stat interface {
|
||||||
|
Stat() (fs.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) saveImageToFSAndDatabase(ctx context.Context, image io.ReadCloser, subreddit *models.Subreddit, post reddit.Post, devices models.DeviceSlice) (err error) {
|
func (api *API) saveImageToFSAndDatabase(ctx context.Context, image io.ReadCloser, subreddit *models.Subreddit, post reddit.Post, devices models.DeviceSlice) (err error) {
|
||||||
ctx, span := tracer.Start(ctx, "*API.saveImageToFSAndDatabase")
|
ctx, span := tracer.Start(ctx, "*API.saveImageToFSAndDatabase")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
defer image.Close()
|
defer image.Close()
|
||||||
|
|
||||||
w, close, err := api.createDeviceImageWriters(post, devices)
|
w, close, err := api.createDeviceImageWriters(post, devices)
|
||||||
|
defer close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.Wrapw(err, "failed to create image files")
|
return errs.Wrapw(err, "failed to create image files")
|
||||||
}
|
}
|
||||||
log.New(ctx).Debug("saving image files", "post_id", post.GetID(), "post_url", post.GetImageURL(), "devices", devices)
|
log.New(ctx).Debug("saving image files", "post_id", post.GetID(), "post_url", post.GetImageURL(), "devices", devices)
|
||||||
defer close()
|
size, err := io.Copy(w, image)
|
||||||
_, err = io.Copy(w, image)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.Wrapw(err, "failed to save image files")
|
return errs.Wrapw(err, "failed to save image files")
|
||||||
}
|
}
|
||||||
|
if size == 0 {
|
||||||
|
if s, ok := image.(stat); ok {
|
||||||
|
if fi, err := s.Stat(); err == nil {
|
||||||
|
size = fi.Size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var many []*models.ImageSetter
|
var many []*models.ImageSetter
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -209,10 +222,6 @@ func (api *API) saveImageToFSAndDatabase(ctx context.Context, image io.ReadClose
|
||||||
nsfw = 1
|
nsfw = 1
|
||||||
}
|
}
|
||||||
width, height := post.GetImageSize()
|
width, height := post.GetImageSize()
|
||||||
var size int64
|
|
||||||
if fi, err := os.Stat(post.GetImageTargetPath(api.config, device)); err == nil {
|
|
||||||
size = fi.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
many = append(many, &models.ImageSetter{
|
many = append(many, &models.ImageSetter{
|
||||||
Subreddit: omit.From(subreddit.Name),
|
Subreddit: omit.From(subreddit.Name),
|
||||||
|
@ -300,14 +309,14 @@ func (api *API) isImageEntryExists(ctx context.Context, post reddit.Post, device
|
||||||
ctx, span := tracer.Start(ctx, "*API.IsImageExists")
|
ctx, span := tracer.Start(ctx, "*API.IsImageExists")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
_, errQuery := models.Images.Query(ctx, api.db,
|
exist, errQuery := models.Images.Query(ctx, api.db,
|
||||||
models.SelectWhere.Images.Device.EQ(device.Slug),
|
models.SelectWhere.Images.Device.EQ(device.Slug),
|
||||||
models.SelectWhere.Images.PostName.EQ(post.GetName()),
|
models.SelectWhere.Images.PostName.EQ(post.GetName()),
|
||||||
).One()
|
).Exists()
|
||||||
|
|
||||||
_, errStat := os.Stat(post.GetImageTargetPath(api.config, device))
|
_, errStat := os.Stat(post.GetImageTargetPath(api.config, device))
|
||||||
|
|
||||||
return errQuery == nil && errStat == nil
|
return exist && errQuery == nil && errStat == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findImageFileForDevice finds if any of the image file exists for given devices.
|
// findImageFileForDevice finds if any of the image file exists for given devices.
|
||||||
|
@ -317,16 +326,39 @@ func (api *API) isImageEntryExists(ctx context.Context, post reddit.Post, device
|
||||||
// Return nil if no image file exists for the devices.
|
// Return nil if no image file exists for the devices.
|
||||||
//
|
//
|
||||||
// Ensure to close the file after use.
|
// Ensure to close the file after use.
|
||||||
func (api *API) findImageFileForDevices(ctx context.Context, post reddit.Post, devices models.DeviceSlice) (file *os.File) {
|
func (api *API) findImageFileForDevices(ctx context.Context, post reddit.Post, devices models.DeviceSlice) (oldImageFile *os.File) {
|
||||||
for _, device := range devices {
|
for _, device := range devices {
|
||||||
_, err := os.Stat(post.GetImageTargetPath(api.config, device))
|
stat, err := os.Stat(post.GetImageTargetPath(api.config, device))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
file, err = os.Open(post.GetImageTargetPath(api.config, device))
|
oldImageFile, err = os.Open(post.GetImageTargetPath(api.config, device))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to open image file", "filename", post.GetImageTargetPath(api.config, device))
|
log.New(ctx).Err(err).Error("failed to open image file", "filename", post.GetImageTargetPath(api.config, device))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return file
|
defer oldImageFile.Close()
|
||||||
|
|
||||||
|
tempFilename := filepath.Join(os.TempDir(), "redmage", stat.Name())
|
||||||
|
|
||||||
|
tempFileWrite, err := os.Create(tempFilename)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to create temp file", "filename", post.GetImageTargetPath(api.config, device))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer tempFileWrite.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(tempFileWrite, oldImageFile)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to copy image file", "filename", post.GetImageTargetPath(api.config, device))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rf, err := os.Open(tempFilename)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to open temp file", "filename", tempFileWrite.Name())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return rf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ POST http://localhost:8080/api/v1/subreddits HTTP/1.1
|
||||||
Host: localhost:8080
|
Host: localhost:8080
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "wallpapers",
|
"name": "wallpaper",
|
||||||
"enable_schedule": 1,
|
"enable_schedule": 1,
|
||||||
"schedule": "@daily",
|
"schedule": "@daily",
|
||||||
"countback": 300
|
"countback": 300
|
||||||
|
|
|
@ -3,5 +3,5 @@ Host: localhost:8080
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"subreddit": "wallpapers"
|
"subreddit": "wallpaper"
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,7 @@ templ HomeContent(c *views.Context, data Data) {
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
>
|
>
|
||||||
<h1 class="mb-4">
|
<h1 class="mb-4">
|
||||||
Recently Added -
|
Recently Added
|
||||||
{ strconv.FormatInt(data.TotalImages, 10) } Images
|
|
||||||
</h1>
|
</h1>
|
||||||
@recentRangeInput(c)
|
@recentRangeInput(c)
|
||||||
@nsfwToggle(c, data)
|
@nsfwToggle(c, data)
|
||||||
|
@ -48,6 +47,10 @@ templ HomeContent(c *views.Context, data Data) {
|
||||||
<div id="recently-added-images">
|
<div id="recently-added-images">
|
||||||
if data.TotalImages == 0 {
|
if data.TotalImages == 0 {
|
||||||
<h2 class="mt-4">There are no recently added images in the current time range.</h2>
|
<h2 class="mt-4">There are no recently added images in the current time range.</h2>
|
||||||
|
} else {
|
||||||
|
<h2 class="mt-4">
|
||||||
|
Added Images: { strconv.FormatInt(data.TotalImages, 10) }
|
||||||
|
</h2>
|
||||||
}
|
}
|
||||||
for _, recently := range data.RecentlyAddedImages {
|
for _, recently := range data.RecentlyAddedImages {
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package homeview
|
package homeview
|
||||||
|
|
||||||
import "github.com/tigorlazuardi/redmage/models"
|
import "github.com/tigorlazuardi/redmage/models"
|
||||||
import "github.com/tigorlazuardi/redmage/api/reddit"
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
import "strconv"
|
||||||
|
import "github.com/alecthomas/units"
|
||||||
|
|
||||||
type ImageCardOption uint
|
type ImageCardOption uint
|
||||||
|
|
||||||
|
@ -19,14 +20,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
|
templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
|
||||||
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl w-[256px] min-w-[256px] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all">
|
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl min-w-[256px] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all">
|
||||||
<figure>
|
<figure>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
|
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="object-contain w-[256px] h-[256px]"
|
class="object-contain max-w-[256px] max-h-[256px]"
|
||||||
src={ fmt.Sprintf("/img/%s", data.ThumbnailRelativePath) }
|
src={ fmt.Sprintf("/img/%s", data.ThumbnailRelativePath) }
|
||||||
alt={ data.PostTitle }
|
alt={ data.PostTitle }
|
||||||
/>
|
/>
|
||||||
|
@ -40,18 +41,17 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
|
||||||
>{ truncateTitle(data.PostTitle) }</a>
|
>{ truncateTitle(data.PostTitle) }</a>
|
||||||
}
|
}
|
||||||
<a class="text-primary underline" href={ templ.URL(data.PostAuthorURL) }>{ data.PostAuthor }</a>
|
<a class="text-primary underline" href={ templ.URL(data.PostAuthorURL) }>{ data.PostAuthor }</a>
|
||||||
<div>
|
<div class="flex-1"></div>
|
||||||
@utils.RelativeTimeNode(fmt.Sprintf("relative-time-%s", data.PostName), data.CreatedAt)
|
<div class="flex w-full justify-end">
|
||||||
|
@utils.RelativeTimeNode(fmt.Sprintf("relative-time-%s", data.PostName), data.CreatedAt, "text-sm")
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions justify-between">
|
<div class="grid grid-cols-2 gap-x-4">
|
||||||
if data.R.Subreddit != nil {
|
<p class="text-xs">Width</p>
|
||||||
if !opts.Has(HideSubreddit) {
|
<p class="text-xs text-end">{ strconv.Itoa(int(data.ImageWidth)) } px</p>
|
||||||
<a
|
<p class="text-xs">Height</p>
|
||||||
class="badge badge-outline badge-primary"
|
<p class="text-xs text-end">{ strconv.Itoa(int(data.ImageHeight)) } px</p>
|
||||||
href={ templ.URL(fmt.Sprintf("https://reddit.com/%s/%s", reddit.SubredditType(data.R.Subreddit.Subtype), data.R.Subreddit.Name)) }
|
<p class="text-xs">Size</p>
|
||||||
>{ data.R.Subreddit.Name } </a>
|
<p class="text-xs text-end">{ units.MetricBytes(data.ImageSize).Round(1).String() }</p>
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue