diff --git a/crawler.go b/crawler.go index afa7f9e..8caa073 100644 --- a/crawler.go +++ b/crawler.go @@ -14,8 +14,7 @@ var visitedStore *VisitedStore // webCrawlerInit is called during init on program start func webCrawlerInit() { - // Initialize the store with, say, batchSize=50 - store, err := NewVisitedStore(filepath.Join(config.DriveCache.Path, "visited-urls.txt"), 50) + store, err := NewVisitedStore(filepath.Join(config.DriveCache.Path, "visited-urls.txt"), config.IndexBatchSize) if err != nil { printErr("Failed to initialize visited store: %v", err) } @@ -170,7 +169,7 @@ func crawlDomainsToFile(domains [][2]string, maxPages int) error { userAgent, _ := GetUserAgent("crawler-chrome") title, desc, keywords := fetchPageMetadataChrome(fullURL, userAgent) if title == "" || desc == "" { - printWarn("Skipping %s: unable to get title/desc data", fullURL) + printDebug("Skipping %s: unable to get title/desc data", fullURL) // Here is print for all domains that fail to be crawled continue } diff --git a/indexer.go b/indexer.go index 73ca9e3..c8cf6fe 100644 --- a/indexer.go +++ b/indexer.go @@ -9,7 +9,6 @@ import ( "strconv" "strings" "sync" - "time" "github.com/blevesearch/bleve/v2" "golang.org/x/net/publicsuffix" @@ -73,17 +72,17 @@ func indexDocImmediately(link, title, tags, desc, rank string) error { return nil } -// StartBatchIndexing spawns a goroutine that flushes the buffer every interval. -func StartBatchIndexing() { - go func() { - ticker := time.NewTicker(config.IndexRefreshInterval) - defer ticker.Stop() +// // StartBatchIndexing spawns a goroutine that flushes the buffer every interval. +// func StartBatchIndexing() { +// go func() { +// ticker := time.NewTicker(config.IndexRefreshInterval) +// defer ticker.Stop() - for range ticker.C { - flushDocBuffer() - } - }() -} +// for range ticker.C { +// flushDocBuffer() +// } +// }() +// } func flushDocBuffer() { docBufferMu.Lock() @@ -264,6 +263,11 @@ func IndexFile(filePath string) error { // SearchIndex performs a full-text search on the indexed data. func SearchIndex(queryStr string, page, pageSize int) ([]Document, error) { + // Check if the indexer is enabled + if !config.IndexerEnabled { + return nil, fmt.Errorf("indexer is disabled") + } + exactMatch := bleve.NewMatchQuery(queryStr) // Exact match fuzzyMatch := bleve.NewFuzzyQuery(queryStr) // Fuzzy match fuzzyMatch.Fuzziness = 2 diff --git a/init.go b/init.go index 666d93a..bf0d220 100644 --- a/init.go +++ b/init.go @@ -3,6 +3,7 @@ package main import ( "flag" "os" + "path/filepath" ) var config Config @@ -77,9 +78,18 @@ func main() { // Check if the cache directory exists when caching is enabled if config.DriveCacheEnabled { - if _, err := os.Stat(config.DriveCache.Path); os.IsNotExist(err) { - printErr("Error: Drive cache is enabled, but cache directory '%s' does not exist.\n", config.DriveCache.Path) - os.Exit(1) // Exit with a non-zero status to indicate an error + cacheDir := config.DriveCache.Path + imagesDir := filepath.Join(cacheDir, "images") + + // Check if the directory already exists + if _, err := os.Stat(imagesDir); os.IsNotExist(err) { + // Try to create the directory since it doesn't exist + if err := os.MkdirAll(imagesDir, os.ModePerm); err != nil { + printErr("Error: Failed to create cache or images directory '%s': %v", imagesDir, err) + os.Exit(1) // Exit with a non-zero status to indicate an error + } + // Print a warning if the directory had to be created + printWarn("Warning: Created missing directory '%s'.", imagesDir) } } @@ -109,7 +119,7 @@ func main() { err := InitIndex() if err != nil { - printErr("Failed to initialize index:", err) + printErr("Failed to initialize index: %v", err) } webCrawlerInit() diff --git a/main.go b/main.go index cc6b8c3..12c2381 100755 --- a/main.go +++ b/main.go @@ -221,6 +221,7 @@ func runServer() { http.HandleFunc("/save-settings", handleSaveSettings) http.HandleFunc("/image/", handleImageServe) http.HandleFunc("/image_status", handleImageStatus) + http.HandleFunc("/privacy", handlePrivacyPage) http.HandleFunc("/opensearch.xml", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/opensearchdescription+xml") http.ServeFile(w, r, "static/opensearch.xml") @@ -235,6 +236,7 @@ func runServer() { http.HandleFunc("/save-settings", handleWebsiteDisabled) http.HandleFunc("/image/", handleWebsiteDisabled) http.HandleFunc("/image_status", handleWebsiteDisabled) + http.HandleFunc("/privacy", handleWebsiteDisabled) http.HandleFunc("/opensearch.xml", handleWebsiteDisabled) printInfo("Website functionality disabled.") } @@ -252,3 +254,46 @@ func handleWebsiteDisabled(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) _, _ = w.Write([]byte("The website functionality is currently disabled.")) } + +func handlePrivacyPage(w http.ResponseWriter, r *http.Request) { + settings := loadUserSettings(w, r) + iconPathSVG, iconPathPNG := GetIconPath() + + // Define the data structure for the template + data := struct { + Theme string + IconPathSVG string + IconPathPNG string + IsThemeDark bool + CookieRows []CookieRow + CurrentLang string + Safe string + LanguageOptions []LanguageOption + }{ + Theme: settings.Theme, + IconPathSVG: iconPathSVG, + IconPathPNG: iconPathPNG, + IsThemeDark: settings.IsThemeDark, + CookieRows: generateCookieTable(r), + CurrentLang: settings.SiteLanguage, + Safe: settings.SafeSearch, + LanguageOptions: languageOptions, + } + + // Parse the template + tmpl, err := template.New("privacy.html").ParseFiles("templates/privacy.html") + if err != nil { + log.Printf("Error parsing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Set the response content type + w.Header().Set("Content-Type", "text/html; charset=utf-8") + + // Execute the template + if err := tmpl.Execute(w, data); err != nil { + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } +} diff --git a/static/css/style-imageviewer.css b/static/css/style-imageviewer.css index 4c0696f..ac6874a 100644 --- a/static/css/style-imageviewer.css +++ b/static/css/style-imageviewer.css @@ -60,13 +60,6 @@ gap: 5px; /* Add spacing between buttons */ } -.image-view-close .btn-nostyle { - background-color: inherit; - border: none; - padding: 0px; - cursor: pointer; -} - #viewer-close-button, #viewer-prev-button, #viewer-next-button { @@ -128,6 +121,7 @@ .full-size:hover, .proxy-size:hover { + transition: all 0.3s ease; text-decoration: underline; } @@ -136,15 +130,6 @@ visibility: visible; } -/* Button No Style */ -.btn-nostyle { - background-color: inherit; - border: none; - padding: 0px; - width: fit-content; - cursor: pointer; -} - /* Image Navigation Icons */ .image-close, .image-next, @@ -163,6 +148,7 @@ .image-close:hover, .image-next:hover, .image-before:hover { + transition: all 0.3s ease; background-color: var(--image-select); } diff --git a/static/css/style-menu.css b/static/css/style-menu.css index d85810b..95be6cf 100644 --- a/static/css/style-menu.css +++ b/static/css/style-menu.css @@ -1,3 +1,5 @@ +/* ------------------ Mini-Menu Styles ------------------ */ + .settings-search-div-search { right: 20px; top: 25px; @@ -140,4 +142,106 @@ margin-right: 0; border-radius: 0; } +} + +/* ------------------ About QGato Modal Styles ------------------ */ + +#aboutQGatoModal { + display: none; + position: fixed; + /* Center modal */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + /* Keep it on top */ + z-index: 999; + + /* Match mini-menu background style */ + background-color: var(--html-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + border: 1px solid var(--border); + border-radius: 12px; + + /* Spacing & sizing */ + padding: 32px; + max-width: 600px; /* Increased width */ + max-height: 80vh; /* Optional: restrict height to 80% of viewport */ + overflow-y: auto; /* Enable scrolling if content exceeds height */ + color: var(--font-fg); +} + +#aboutQGatoModal #close-button { + position: absolute; + top: 12px; + right: 12px; /* Moved close button to top-right */ +} + +#aboutQGatoModal .modal-content { + text-align: center; + margin-top: 20px; /* Adjusted spacing */ +} + +/* Logo */ +#aboutQGatoModal .modal-content img { + width: 100px; /* Increased logo size */ + margin-bottom: 16px; +} + +/* Headings, paragraphs, etc. */ +#aboutQGatoModal .modal-content h2 { + font-size: 2rem; /* Larger heading */ + margin: 8px 0; +} + +#aboutQGatoModal .modal-content p { + font-size: 1.1rem; /* Larger paragraph text */ + margin: 12px 0; +} + +/* Container for the Source Code / Privacy Policy buttons */ +#aboutQGatoModal .button-container { + margin-top: 16px; + display: flex; + justify-content: center; + gap: 16px; +} + +/* Match mini-menu button style as closely as possible */ +#aboutQGatoModal .button-container button { + background-color: var(--button); + color: var(--font-fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 12px 16px; /* Larger button padding */ + font-size: 1rem; /* Larger button text */ + cursor: pointer; + transition: border 0.3s ease, background-color 0.3s ease, color 0.3s ease; +} + +#aboutQGatoModal .button-container button:hover { + border: 1px solid var(--font-fg); +} + +/* Close Button Style */ +.cloase-btn { + font-size: 1.5rem; /* Larger close button */ + color: var(--search-button); + border-radius: 50%; + padding: 8px; +} + +.cloase-btn:hover { + transition: all 0.3s ease; + background-color: var(--image-select); +} + +/* ------------------ Common Button No Style ------------------ */ + +.btn-nostyle { + background-color: inherit; + border: none; + padding: 0px; + width: fit-content; + cursor: pointer; } \ No newline at end of file diff --git a/static/css/style-privacy.css b/static/css/style-privacy.css new file mode 100644 index 0000000..5cfef4b --- /dev/null +++ b/static/css/style-privacy.css @@ -0,0 +1,95 @@ +/* Main content wrapper */ +.privacy-content-wrapper { + max-width: 800px; + margin: 80px auto 40px auto; + padding: 0 20px; +} + +/* Header section */ +.privacy-header { + text-align: center; + margin-bottom: 30px; +} + +.privacy-header h1 { + font-size: 2rem; + margin: 0; + color: var(--font-fg); +} + +.privacy-header p { + color: var(--fg); + margin-top: 10px; + font-size: 1.1rem; +} + +/* Section headings */ +.privacy-section h2 { + font-size: 1.5rem; + margin-bottom: 8px; + color: var(--font-fg); + border-bottom: 1px solid var(--border); + padding-bottom: 4px; +} + +/* Section text */ +.privacy-section p { + font-size: 1rem; + line-height: 1.6; + margin-bottom: 20px; + color: var(--fg); +} + +/* Footer */ +.privacy-footer { + text-align: center; + padding: 10px 0; + border-top: 1px solid var(--border); + color: var(--fg); + background-color: var(--html-bg); +} + +/* Links */ +.privacy-section a { + color: var(--link); + text-decoration: none; +} + +.privacy-section a:hover { + text-decoration: underline; +} + +/* Table styling */ +.cookie-table { + width: 100%; + margin: 20px auto; + border-collapse: collapse; + text-align: left; + font-size: 1rem; + color: var(--fg); + background-color: var(--html-bg); + border: 1px solid var(--border); +} + +.cookie-table th, +.cookie-table td { + padding: 12px 15px; + border: 1px solid var(--border); +} + +.cookie-table th { + background-color: var(--search-bg); + color: var(--font-fg); + text-align: center; + font-weight: bold; +} + +.cookie-table tr:nth-child(even) { + background-color: var(--snip-background); +} + +/* Center the table within its section */ +.privacy-section .cookie-table { + margin-left: auto; + margin-right: auto; +} diff --git a/static/js/imageviewer.js b/static/js/imageviewer.js index a68f0e2..4bd667f 100644 --- a/static/js/imageviewer.js +++ b/static/js/imageviewer.js @@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', function() { // Set the innerHTML of viewerOverlay viewerOverlay.innerHTML = `