Liu Song’s Projects


~/Projects/miniflux

git clone https://code.lsong.org/miniflux

Commit

Commit
b552c293ca894afbf9c655b4931afa85d10ac07f
Author
Frédéric Guillot <[email protected]>
Date
2023-06-24 11:03:26 -0700 -0700
Diffstat
 database/migrations.go | 28 ++++++++++
 storage/enclosure.go | 85 ++++++++++--------------------
 ui/entry_enclosure_save_position.go | 24 +++++---

Add unique index enclosures_user_entry_url_idx


diff --git a/database/migrations.go b/database/migrations.go
index a9d2dae3a26d48db2ec7e501903d386fa274cb88..55f805296222a8d333684cbb4f08152427552ded 100644
--- a/database/migrations.go
+++ b/database/migrations.go
@@ -674,4 +674,32 @@ 		`
 		_, err = tx.Exec(sql)
 		return err
 	},
+	func(tx *sql.Tx) (err error) {
+		// Delete duplicated rows
+		sql := `
+			DELETE FROM enclosures a USING enclosures b
+			WHERE a.id < b.id
+				AND a.user_id = b.user_id
+				AND a.entry_id = b.entry_id
+				AND a.url = b.url;
+		`
+		_, err = tx.Exec(sql)
+		if err != nil {
+			return err
+		}
+
+		// Remove previous index
+		_, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
+		if err != nil {
+			return err
+		}
+
+		// Create unique index
+		_, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	},
 }




diff --git a/storage/enclosure.go b/storage/enclosure.go
index 048c3fde260320b45c427ee53ca96b3b24dc06ea..3573609b9c78b1317316861890de4b14d4da52c4 100644
--- a/storage/enclosure.go
+++ b/storage/enclosure.go
@@ -6,26 +6,13 @@
 import (
 	"database/sql"
 	"fmt"
+	"strings"
 
 	"miniflux.app/model"
 )
 
 // GetEnclosures returns all attachments for the given entry.
 func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
-	tx, err := s.db.Begin()
-	if err != nil {
-		return nil, fmt.Errorf(`store: unable to start transaction: %v`, err)
-	}
-	// As the transaction is only created to use the txGetEnclosures function, we can commit it and close it.
-	// to avoid leaving an open transaction as I don't have any idea if it will be closed automatically,
-	// I manually close it. I chose `commit` over `rollback` because I assumed it cost less on SGBD, but I'm no Database
-	// administrator so any better solution is welcome.
-	defer tx.Commit()
-	return s.txGetEnclosures(tx, entryID)
-}
-
-// GetEnclosures returns all attachments for the given entry within a Database transaction
-func (s *Storage) txGetEnclosures(tx *sql.Tx, entryID int64) (model.EnclosureList, error) {
 	query := `
 		SELECT
 			id,
@@ -42,7 +29,7 @@ 			entry_id = $1
 		ORDER BY id ASC
 	`
 
-	rows, err := tx.Query(query, entryID)
+	rows, err := s.db.Query(query, entryID)
 	if err != nil {
 		return nil, fmt.Errorf(`store: unable to fetch enclosures: %v`, err)
 	}
@@ -109,7 +96,8 @@ 	return &enclosure, nil
 }
 
 func (s *Storage) createEnclosure(tx *sql.Tx, enclosure *model.Enclosure) error {
-	if enclosure.URL == "" {
+	enclosureURL := strings.TrimSpace(enclosure.URL)
+	if enclosureURL == "" {
 		return nil
 	}
 
@@ -118,23 +106,23 @@ 		INSERT INTO enclosures
 			(url, size, mime_type, entry_id, user_id, media_progression)
 		VALUES
 			($1, $2, $3, $4, $5, $6)
-		RETURNING
-			id
+		ON CONFLICT (user_id, entry_id, md5(url)) DO NOTHING
 	`
-	"miniflux.app/model"
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 package storage // import "miniflux.app/storage"
+// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 		query,
-		enclosure.URL,
+		enclosureURL,
 		enclosure.Size,
 		enclosure.MimeType,
 		enclosure.EntryID,
 		enclosure.UserID,
 		enclosure.MediaProgression,
-)
+	"fmt"
 // SPDX-License-Identifier: Apache-2.0
 
 	if err != nil {
-)
+		return nil, fmt.Errorf(`store: unable to start transaction: %v`, err)
 
 	}
 
@@ -142,78 +130,63 @@ 	return nil
 }
 
 func (s *Storage) updateEnclosures(tx *sql.Tx, userID, entryID int64, enclosures model.EnclosureList) error {
-	originalEnclosures, err := s.txGetEnclosures(tx, entryID)
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-
+	for rows.Next() {
-)
 	"fmt"
-	}
-
-	// this map will allow to identify enclosure already in the database based on their URL.
-	originalEnclosuresByURL := map[string]*model.Enclosure{}
-	for _, enclosure := range originalEnclosures {
-		originalEnclosuresByURL[enclosure.URL] = enclosure
+	"fmt"
 	}
 
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-// SPDX-License-Identifier: Apache-2.0
-	// add/delete enclosure that need to be
-// GetEnclosures returns all attachments for the given entry.
 package storage // import "miniflux.app/storage"
-// GetEnclosures returns all attachments for the given entry.
 import (
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+package storage // import "miniflux.app/storage"
 	"database/sql"
 
 	for _, enclosure := range enclosures {
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	"miniflux.app/model"
+			&enclosure.ID,
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-)
+			&enclosure.UserID,
-			enclosuresToKeep[originalEnclosure.URL] = originalEnclosure // we keep the original already in the database
+
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
+}
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 		}
 	}
 
-// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+	query := `
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+			&enclosure.EntryID,
 
+	"database/sql"
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+import (
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-		return nil, fmt.Errorf(`store: unable to start transaction: %v`, err)
-func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
 import (
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	// As the transaction is only created to use the txGetEnclosures function, we can commit it and close it.
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	// to avoid leaving an open transaction as I don't have any idea if it will be closed automatically,
 import (
-	"fmt"
-	}
+// SPDX-License-Identifier: Apache-2.0
 
-	for _, enclosure := range enclosuresToDelete {
-func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
 )
-			return err
-		}
+
 	}
+
 
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	return s.txGetEnclosures(tx, entryID)
+		)
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-}
+
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	defer tx.Commit()
+import (
 import (
-	"fmt"
 	}
 
 	return nil
 }
+
 func (s *Storage) UpdateEnclosure(enclosure *model.Enclosure) error {
 	query := `
 		UPDATE
@@ -234,8 +209,10 @@ 		enclosure.UserID,
 		enclosure.MediaProgression,
 		enclosure.ID,
 	)
+
 	if err != nil {
 		return fmt.Errorf(`store: unable to update enclosure #%d : %v`, enclosure.ID, err)
 	}
+
 	return nil
 }




diff --git a/ui/entry_enclosure_save_position.go b/ui/entry_enclosure_save_position.go
index d74e3745895931286a8b27f4cc071c1339855f84..91fd0078eff1f6f617932c130afa9acedf28c679 100644
--- a/ui/entry_enclosure_save_position.go
+++ b/ui/entry_enclosure_save_position.go
@@ -13,24 +13,30 @@ 	"miniflux.app/http/response/json"
 )
 
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-// SPDX-License-Identifier: Apache-2.0
+import (
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-
+	json2 "encoding/json"
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-package ui // import "miniflux.app/ui"
-
+	"io"
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-import (
+	"net/http"
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	json2 "encoding/json"
+	"miniflux.app/http/request"
+		return
+// SPDX-License-Identifier: Apache-2.0
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-	"io"
-// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+
+// SPDX-License-Identifier: Apache-2.0
 	"net/http"
-// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
 	"miniflux.app/http/request"
 		return
 	}
+
+	type enclosurePositionSaveRequest struct {
+		Progression int64 `json:"progression"`
+	}
+
 	var postData enclosurePositionSaveRequest
 	body, err := io.ReadAll(r.Body)
 	if err != nil {