Merge branch 'main' into single_file_link

This commit is contained in:
Neeraj Gupta
2025-07-25 14:58:49 +05:30
31 changed files with 1247 additions and 145 deletions

View File

@@ -10,6 +10,16 @@
{
"title": "3Commas"
},
{
"title": "Accredible",
"slug": "accredible",
"altNames": [
"Accredible Certificates",
"Accredible Badges",
"Digital Credentials",
"certificates.zaka.ai"
]
},
{
"title": "Addy.io",
"slug": "addy_io"
@@ -71,6 +81,13 @@
],
"hex": "fd4b2d"
},
{
"title": "Autenticacion Digital",
"slug": "autenticacion-digital",
"altNames": [
"autenticaciondigital.and.gov.co"
]
},
{
"title": "availity"
},
@@ -279,6 +296,13 @@
"title": "CERN",
"slug": "cern"
},
{
"title": "Chaturbate",
"slug": "chaturbate",
"altNames": [
"Chaturbate.com"
]
},
{
"title": "ChangeNOW"
},
@@ -436,7 +460,7 @@
"title": "emeritihealth",
"altNames": [
"Emeriti Health",
"Emeriti Retirement Health",
"Emeriti Retirement Health"
]
},
{
@@ -742,6 +766,15 @@
{
"title": "Letterboxd"
},
{
"title": "LifeMiles",
"slug": "lifemiles",
"altNames": [
"Life Miles",
"lifemiles.com",
"Avianca LifeMiles"
]
},
{
"title": "lincolnfinancial",
"altNames": [
@@ -1295,6 +1328,19 @@
"PAYDAY 3"
]
},
{
"title": "Startmail",
"slug": "startmail"
},
{
"title": "Stripchat",
"slug": "stripchat",
"altNames": [
"Strip Chat",
"stripchat.com",
"StripChat Live"
]
},
{
"title": "STRATO",
"hex": "FF8800"
@@ -1313,6 +1359,9 @@
"T-Mobile ID"
]
},
{
"title": "Tableau"
},
{
"title": "TCPShield"
},
@@ -1522,6 +1571,12 @@
{
"title": "WYZE"
},
{
"title": "X",
"altNames": [
"Twitter"
]
},
{
"title": "Xbox",
"hex": "107C10"
@@ -1577,6 +1632,14 @@
"title": "xAI",
"slug": "xai"
},
{
"title": "XVideos",
"slug": "xvideos",
"altNames": [
"X Videos",
"xvideos.com"
]
},
{
"title": "Cronometer",
"slug": "cronometer"

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 240 240" version="1.1" viewBox="0 0 240 240" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<path d="m1 214c0-46.021 0-92.042 0.27671-138.19 0.74988-0.75875 1.4558-1.3222 1.6616-2.0308 3.4637-11.931 9.7863-22.061 19.083-30.291 10.734-9.5027 23.139-15.388 37.612-15.425 60.455-0.15068 120.91-0.063334 181.37-0.063242 0 45.688 0 91.375-0.29245 137.19-2.9994 6.1167-5.1585 12.433-8.5123 18.034-11.681 19.506-28.965 30.541-51.873 30.666-59.773 0.32539-119.55 0.10936-179.32 0.10925m151.5-107h36.106v-14.744h-126.3v14.744h90.195m15.11 63.164c1.9138 0.27864 3.8409 0.86449 5.7391 0.78206 7.6904-0.33391 15.992-9.1312 15.551-16.275-0.4837-7.8375-8.5467-15.644-16.184-15.67-8.5565-0.02861-14.433 4.8247-16.567 13.682-1.4658 6.0848 3.2382 13.739 11.461 17.481z" fill="#5B4DFC"/>
</svg>

After

Width:  |  Height:  |  Size: 862 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="50%" y1="23.373%" x2="50%" y2="85.937%" id="a"><stop stop-color="#02729D" offset="0%"/><stop stop-color="#00333F" offset="100%"/></linearGradient><linearGradient x1="63.263%" y1="134.632%" x2="51.527%" y2="36.554%" id="b"><stop stop-color="#FFE5BE" offset="0%"/><stop stop-color="#FEBE1D" offset="58%"/><stop stop-color="#FCAE1E" offset="69%"/><stop stop-color="#F58420" offset="92%"/><stop stop-color="#F37321" offset="100%"/></linearGradient></defs><g fill="none"><path d="M46.126 61.143c-13.37 0-22.008-10.053-22.008-25.606.006-1.882.137-3.762.392-5.626C9.072 27.993 0 22.686 0 15.412c0-4.811 3.898-9.02 11.91-12.899L17.094 0l.207 5.767c.278 7.462 5.656 11.056 10.286 12.777C32.346 7.41 41.096.644 51.042.644c10.545 0 15.264 5.889 15.264 11.726 0 7.621-7.984 15.956-23.278 17.848-1.225 3.027-1.936 6.148-1.936 8.642 0 4.753 2.143 4.753 3.424 4.753 1.799 0 6.192-3.657 6.848-7.725l.507-3.168h3.209c6.214 0 10.234 3.797 10.234 9.679-.004 7.64-7.477 18.744-19.188 18.744Z" fill="url(#a)" transform="translate(6 15)"/><path d="M44.516 47.374c4.038 0 9.675-5.364 10.564-10.894 4.2 0 6.47 2.221 6.47 5.923 0 5.807-6.026 14.986-15.428 14.986-12.717 0-18.247-10.286-18.247-21.838a41.255 41.255 0 0 1 1.051-8.957c-14.435-1.162-25.17-5.53-25.17-11.17.008-2.936 2.94-6.208 9.798-9.524.33 9.29 7.185 15.101 16.308 17.252 3.372-10.23 10.894-18.747 21.18-18.747 7.632 0 11.503 3.65 11.503 7.961 0 5.922-7.243 13.162-22.063 14.32-1.88 3.872-3.154 8.35-3.154 12.167 0 4.815 1.991 8.52 7.188 8.52ZM42.03 23.98c7.402-.551 9.993-2.876 9.993-4.534 0-.94-.884-1.714-2.157-1.714-2.691 0-5.567 2.598-7.836 6.248Z" fill="url(#b)" transform="translate(6 15)"/><path d="M36.41 41.587a1.377 1.377 0 0 1 1.233 1.676 39.89 39.89 0 0 0-1.018 8.654c0 5.6 1.436 11.419 4.915 15.427-4.497-3.949-6.292-10.5-6.292-16.793a39.882 39.882 0 0 1 1.018-8.653c.023-.102.034-.207.033-.311Zm25.769 11.33c1.68.196 2.832.877 3.453 2.024a5.415 5.415 0 0 0-2.08-.648c-1.506 5.552-7.047 10.834-11.659 10.834-2.902 0-5.137-1.036-6.596-2.95 1.362 1.03 3.117 1.573 5.223 1.573 4.626 0 10.153-5.281 11.659-10.833Zm-5.134-32.135c4.33 0 7.577 1.288 9.12 3.442-1.773-1.325-4.441-2.066-7.747-2.066-10.323 0-17.026 9.198-19.872 17.793a1.377 1.377 0 0 1-1.625.91c-.359-.085-.703-.188-1.051-.281a1.362 1.362 0 0 0-.837-.37l-.618-.056c-4.293-1.395-7.799-3.586-10.312-6.41 2.898 2.657 6.77 4.637 11.449 5.748a1.377 1.377 0 0 0 1.625-.91c2.842-8.606 9.545-17.8 19.868-17.8Zm2.073 12.41a2.99 2.99 0 0 1 1.673 2.617c-.001.124-.01.247-.026.37a24.799 24.799 0 0 1-3.779 1.85c1.714-1.08 2.428-2.38 2.428-3.582 0-.436-.101-.865-.296-1.255ZM18.366 23.047c.1.65.235 1.293.404 1.928-4.053 2.35-6.248 4.723-6.248 6.81a3.83 3.83 0 0 0 .877 2.329c-1.458-1.162-2.258-2.414-2.258-3.702 0-2.25 2.547-4.834 7.225-7.365Z" fill-opacity=".4" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 26.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1000 425.96859"
xml:space="preserve"
sodipodi:docname="lifemiles-logo-white.svg"
width="1000"
height="425.9686"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata39"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs37">
</defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#111111"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview35"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.21047873"
inkscape:cx="874.3819"
inkscape:cy="-478.96841"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"><inkscape:grid
type="xygrid"
id="grid80"
originx="112"
originy="919.50723" /></sodipodi:namedview>
<style
type="text/css"
id="style2">
.st0{fill:#FFFFFF;}
</style>
<path
inkscape:connector-curvature="0"
style="fill:#2c3d47;fill-opacity:1;stroke-width:17.97037315"
id="path10"
d="m 503.10835,200.17953 c -10.36162,0 -19.4727,2.34995 -27.33325,7.23061 -7.8605,4.88066 -14.64912,10.48438 -20.18731,16.81116 1.6079,6.86908 2.67979,14.64198 3.21574,23.49947 0.53591,8.85749 0.89324,17.35347 0.89324,25.30714 v 152.56585 h -95.57696 v -154.3735 c 0,-24.58407 -3.03703,-42.47983 -9.28969,-54.0488 -6.0741,-11.38821 -16.97163,-17.17269 -32.51405,-17.17269 -10.0043,0 -18.57946,2.16919 -25.90406,6.68831 -7.32459,4.51913 -13.21998,9.94208 -17.50752,16.26887 V 425.59376 H 183.50616 V 129.6811 h 91.82531 v 39.22605 c 9.11113,-16.0881 21.08054,-27.47631 36.08706,-34.3454 14.82782,-6.86907 30.01291,-10.30361 45.55533,-10.30361 19.11543,0 34.83648,3.97684 47.34185,11.74974 12.50539,7.7729 24.29616,20.60723 35.5511,38.32222 20.7232,-33.44156 53.05854,-50.07196 97.18473,-50.07196 19.11537,0 35.19376,4.1576 48.2351,12.29204 13.04134,8.3152 23.76025,19.16112 31.79944,32.89926 8.21784,13.73816 13.93458,29.46473 17.50754,47.36049 3.573,17.89575 5.35947,36.69535 5.35947,56.39874 v 152.56589 h -95.39833 v -154.5543 c 0,-24.58407 -3.03703,-42.47983 -9.28969,-54.0488 -5.89539,-11.38821 -16.79299,-16.99193 -32.15672,-16.99193"
class="st0" /><path
inkscape:connector-curvature="0"
style="fill:#2c3d47;fill-opacity:1;stroke-width:18.07651901"
id="path12"
d="m 94.576402,332.35756 c 0,12.5291 8.918858,22.3039 25.860738,22.3039 h 65.76015 l -2.21663,70.9323 H 120.43714 C 81.005734,425.59376 0,419.93116 0,349.78556 V 0 h 94.576402 z"
class="st0"
sodipodi:nodetypes="ccccccccc" /><path
class="st0"
d="m 733.71651,296.61048 c 29.55738,-29.55737 77.63263,-29.55737 107.19006,0 29.55734,29.5574 29.55734,77.63268 0,107.19008 -29.55743,29.5574 -77.63268,29.5574 -107.19006,0 -29.55738,-29.7355 -29.55738,-77.63268 0,-107.19008"
id="path24"
style="fill:#ff1d1e;fill-opacity:1;stroke-width:17.80565071"
inkscape:connector-curvature="0" /><path
class="st0"
d="m 733.71651,22.93762 c 29.55738,-29.55739 77.63263,-29.55739 107.19006,0 29.55734,29.55739 29.55734,77.63263 0,107.19003 -29.55743,29.55737 -77.63268,29.55737 -107.19006,0 -29.55738,-29.73544 -29.55738,-77.63264 0,-107.19003"
id="path26"
style="fill:#ff1d1e;fill-opacity:1;stroke-width:17.80565071"
inkscape:connector-curvature="0" /><path
class="st0"
d="m 870.64206,159.68502 c 29.55734,-29.55737 77.63259,-29.55737 107.18993,0 29.55731,29.55739 29.55731,77.63264 0,107.19001 -29.55734,29.5574 -77.63259,29.5574 -107.18993,0 -29.73549,-29.55737 -29.73549,-77.63262 0,-107.19001"
id="path28"
style="fill:#ff1d1e;fill-opacity:1;stroke-width:17.80565071"
inkscape:connector-curvature="0" /></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="500px" height="500px" viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6573FF;}
.st1{fill:#202945;}
</style>
<g>
<path class="st0" d="M500,47.2C500,20.9,478.6,0,452.9,0H47.7C21.4-0.5,0,20.9,0,47.2v43.9c0,0,186.4,180.6,250.5,180.6
C319.6,271.7,500,92.2,500,92.2S500,56.5,500,47.2z"/>
<path class="st1" d="M0,452.8C0,479.1,21.4,500,47.2,500h405.6c26.3,0,47.2-21.4,47.2-47.2V142.7c0,0-159.2,184.4-249.7,184.4
C160.8,327.1,0,178.4,0,178.4C0,236.6,0,395.2,0,452.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 843 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg"><path d="M44.86 8.486c23.44 0 42.423 15.87 42.423 35.463 0 18.47-16.877 33.633-38.366 35.318-.749.072-1.57-.61-1.57-1.406V57c0-1.032.538-1.853 1.31-2.602l15.893-17.04c1.172-1.267.26-2.275-.513-2.275H24.893c-.749 0-1.901 1.008-.47 2.506l15.892 16.78c.773.773 1.032 1.546 1.032 2.602v20.602c0 .797-.869 1.641-1.545 1.57a32.528 32.528 0 0 1-3.399-.447c-10.08-1.709-2.673 2.414-28.968 7.22 6.048-11.343 10.455-13.969 5.626-18.514A32.292 32.292 0 0 1 2.49 43.915c0-19.593 18.984-35.462 42.375-35.462v.033h-.005Z" fill="#A02831"/></svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -0,0 +1,52 @@
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100.2 98" style="enable-background:new 0 0 100.2 98;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E8762C;}
.st1{fill:#C72035;}
.st2{fill:#59879B;}
.st3{fill:#5B6591;}
.st4{fill:#EB912C;}
.st5{fill:#1F447E;}
.st6{fill:#7099A6;}
</style>
<metadata>
<sfw xmlns="ns_sfw;">
<slices>
</slices>
<sliceSourceBounds bottomLeftOrigin="true" height="98" width="100.2" x="-301.9" y="-233.3">
</sliceSourceBounds>
</sfw>
</metadata>
<g>
<g>
<g id="icon_1_">
<polygon class="st0" points="65.7,51.8 52,51.8 52,66.8 46.6,66.8 46.6,51.8 32.8,51.8 32.8,46.6 46.6,46.6 46.6,31.6 52,31.6
52,46.6 65.7,46.6 ">
</polygon>
<polygon class="st1" points="38.2,70.3 25.9,70.3 25.9,56.8 21.3,56.8 21.3,70.3 8.8,70.3 8.8,74.3 21.3,74.3 21.3,87.6
25.9,87.6 25.9,74.3 38.2,74.3 ">
</polygon>
<polygon class="st2" points="90.7,23 78.3,23 78.3,9.6 73.7,9.6 73.7,23 61.4,23 61.4,27.2 73.7,27.2 73.7,40.5 78.3,40.5
78.3,27.2 90.7,27.2 ">
</polygon>
<polygon class="st3" points="59.8,84.9 51.5,84.9 51.5,75.6 47.5,75.6 47.5,84.9 39,84.9 39,88.5 47.5,88.5 47.5,98 51.5,98
51.5,88.5 59.8,88.5 ">
</polygon>
<polygon class="st4" points="38.1,22.9 25.6,22.9 25.6,9.6 21.1,9.6 21.1,22.9 8.6,22.9 8.6,26.9 21.1,26.9 21.1,40.5 25.6,40.5
25.6,26.9 38.1,26.9 ">
</polygon>
<polygon class="st3" points="100.2,47.4 91.9,47.4 91.9,38.1 87.8,38.1 87.8,47.4 79.4,47.4 79.4,51 87.8,51 87.8,60.3
91.9,60.3 91.9,51 100.2,51 ">
</polygon>
<polygon class="st5" points="89.9,70.3 77.6,70.3 77.6,56.8 73,56.8 73,70.3 60.6,70.3 60.6,74.3 73,74.3 73,87.6 77.6,87.6
77.6,74.3 89.9,74.3 ">
</polygon>
<polygon class="st6" points="59.2,9.3 50.9,9.3 50.9,0 47.9,0 47.9,9.3 39.6,9.3 39.6,12.1 47.9,12.1 47.9,21.2 50.9,21.2
50.9,12.1 59.2,12.1 ">
</polygon>
<polygon class="st6" points="19.6,47.8 11.3,47.8 11.3,38.7 8.3,38.7 8.3,47.8 0,47.8 0,50.6 8.3,50.6 8.3,59.7 11.3,59.7
11.3,50.6 19.6,50.6 ">
</polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,18 @@
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 49.8 45" style="enable-background:new 0 0 49.8 45;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<metadata>
<sfw xmlns="ns_sfw;">
<slices>
</slices>
<sliceSourceBounds bottomLeftOrigin="true" height="45" width="49.8" x="-67.2" y="-209.8">
</sliceSourceBounds>
</sfw>
</metadata>
<g>
<path class="st0" d="M39.2,0h7.6L30.2,19.1L49.8,45H34.4l-12-15.7L8.6,45H1l17.8-20.4L0,0h15.8l10.9,14.4L39.2,0z M36.5,40.4h4.2
L13.5,4.3H8.9L36.5,40.4z">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#000" d="M0 0h90v90H0z"/><path d="m32 11 13 19.217L58 11h17L53.5 42.783 78 79H61L45 55.348 29 79H12l24.5-36.217L15 11h17Z" fill="#F40000"/></g></svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -979,6 +979,55 @@ class FilesDB with SqlDbBase {
return result;
}
// remove references for local files which are either already uploaded
// or queued for upload but not yet uploaded
Future<int> removeQueuedLocalFiles(Set<String> localIDs) async {
if (localIDs.isEmpty) {
_logger.finest("No local IDs provided for removal");
return 0;
}
final db = await instance.sqliteAsyncDB;
const batchSize = 10000;
int totalRemoved = 0;
final localIDsList = localIDs.toList();
for (int i = 0; i < localIDsList.length; i += batchSize) {
final endIndex = (i + batchSize > localIDsList.length)
? localIDsList.length
: i + batchSize;
final batch = localIDsList.sublist(i, endIndex);
final placeholders = List.filled(batch.length, '?').join(',');
final r = await db.execute(
'''
DELETE FROM $filesTable
WHERE $columnLocalID IN ($placeholders)
AND ($columnCollectionID IS NULL OR $columnCollectionID = -1)
AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1)
''',
batch,
);
if (r.isNotEmpty) {
_logger
.fine("Batch ${(i ~/ batchSize) + 1}: Removed ${r.length} files");
totalRemoved += r.length;
}
}
if (totalRemoved > 0) {
_logger.warning(
"Removed $totalRemoved potential dups for already queued local files",
);
} else {
_logger.finest("No duplicate id found for queued/uploaded files");
}
return totalRemoved;
}
Future<Set<String>> getLocalFileIDsForCollection(int collectionID) async {
final db = await instance.sqliteAsyncDB;
final rows = await db.getAll(

View File

@@ -383,8 +383,12 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
}
}
if (personID == null && clusterID == null) {
_logger.severe("personID and clusterID cannot be null both");
throw Exception("personID and clusterID cannot be null");
}
_logger.severe(
"Something went wrong finding a face from `getCoverFaceForPerson` (personID: $personID, clusterID: $clusterID)",
);
return null;
}

View File

@@ -1427,12 +1427,20 @@ class CollectionsService {
}
// group files by collectionID
final Map<int, List<EnteFile>> filesByCollection = {};
final Map<int, Set<int>> fileSeenByCollection = {};
for (final file in filesToCopy) {
if (filesByCollection.containsKey(file.collectionID!)) {
filesByCollection[file.collectionID!]!.add(file.copyWith());
} else {
filesByCollection[file.collectionID!] = [file.copyWith()];
fileSeenByCollection.putIfAbsent(file.collectionID!, () => <int>{});
if (fileSeenByCollection[file.collectionID]!
.contains(file.uploadedFileID)) {
_logger.warning(
"skip copy, duplicate ID: ${file.uploadedFileID} in collection "
"${file.collectionID}",
);
continue;
}
filesByCollection
.putIfAbsent(file.collectionID!, () => [])
.add(file.copyWith());
}
for (final entry in filesByCollection.entries) {
final srcCollectionID = entry.key;
@@ -1579,9 +1587,6 @@ class CollectionsService {
params["files"] = [];
for (final batchFile in batch) {
final fileKey = getFileKey(batchFile);
_logger.info(
"srcCollection : $srcCollectionID file: ${batchFile.uploadedFileID} key: ${CryptoUtil.bin2base64(fileKey)} ",
);
final encryptedKeyData =
CryptoUtil.encryptSync(fileKey, getCollectionKey(dstCollectionID));
batchFile.encryptedKey =
@@ -1643,17 +1648,27 @@ class CollectionsService {
);
final List<EnteFile> filesToCopy = [];
final List<EnteFile> filesToAdd = [];
final Set<int> seenForAdd = {};
final Set<int> seenForCopy = {};
for (final EnteFile file in othersFile) {
if (hashToUserFile.containsKey(file.hash ?? '')) {
final userFile = hashToUserFile[file.hash]!;
if (userFile.fileType == file.fileType) {
filesToAdd.add(userFile);
} else {
filesToCopy.add(file);
}
} else {
filesToCopy.add(file);
final userFile = hashToUserFile[file.hash ?? ''];
final bool shouldAdd =
userFile != null && userFile.fileType == file.fileType;
final targetList = shouldAdd ? filesToAdd : filesToCopy;
final seenSet = shouldAdd ? seenForAdd : seenForCopy;
final fileToProcess = shouldAdd ? userFile : file;
final uploadID = fileToProcess.uploadedFileID;
if (seenSet.contains(uploadID)) {
final action = shouldAdd ? "adding" : "copying";
_logger.warning(
"skip $action file $uploadID as it is already ${action}ed",
);
continue;
}
targetList.add(fileToProcess);
seenSet.add(uploadID!);
}
return (filesToAdd, filesToCopy);
}

View File

@@ -0,0 +1,365 @@
import 'dart:collection';
class PartialDate {
final int? day;
final int? month;
final int? year;
const PartialDate({this.day, this.month, this.year});
static const empty = PartialDate();
bool get isEmpty => day == null && month == null && year == null;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PartialDate &&
runtimeType == other.runtimeType &&
day == other.day &&
month == other.month &&
year == other.year;
@override
int get hashCode => day.hashCode ^ month.hashCode ^ year.hashCode;
@override
String toString() {
return 'PartialDate(day: $day, month: $month, year: $year)';
}
}
class DateParseService {
static final DateParseService instance = DateParseService._private();
DateParseService._private();
static const int _MIN_YEAR = 1900;
static const int _MAX_YEAR = 2100;
static const int _TWO_DIGIT_YEAR_PIVOT = 50;
static final _ordinalRegex = RegExp(r'\b(\d{1,2})(st|nd|rd|th)\b');
static final _normalizeRegex = RegExp(r'\bof\b|[,\.]+|\s+');
static final _isoFormatRegex =
RegExp(r'^(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})$');
static final _standardFormatRegex =
RegExp(r'^(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})$');
static final _dotFormatRegex = RegExp(r'^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$');
static final _compactFormatRegex = RegExp(r'^(\d{8})$');
static final _yearOnlyRegex = RegExp(r'^\s*(\d{4})\s*$');
static final _shortFormatRegex = RegExp(r'^(\d{1,2})[\/-](\d{1,2})$');
static final Map<String, int> _monthMap = UnmodifiableMapView({
"january": 1,
"february": 2,
"march": 3,
"april": 4,
"may": 5,
"june": 6,
"july": 7,
"august": 8,
"september": 9,
"october": 10,
"november": 11,
"december": 12,
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"sept": 9,
"oct": 10,
"nov": 11,
"dec": 12,
"janu": 1,
"febr": 2,
"marc": 3,
"apri": 4,
"juli": 7,
"augu": 8,
"sepe": 9,
"octo": 10,
"nove": 11,
"dece": 12,
});
static const Map<int, String> monthNumberToName = {
1: "January",
2: "February",
3: "March",
4: "April",
5: "May",
6: "June",
7: "July",
8: "August",
9: "September",
10: "October",
11: "November",
12: "December",
};
PartialDate parse(String input) {
if (input.trim().isEmpty) return PartialDate.empty;
final lowerInput = input.toLowerCase();
var result = _parseRelativeDate(lowerInput);
if (!result.isEmpty) return result;
result = _parseStructuredFormats(lowerInput);
if (!result.isEmpty) return result;
final normalized = _normalizeDateString(lowerInput);
result = _parseTokenizedDate(normalized);
return result;
}
String getMonthName(int month) {
return monthNumberToName[month] ?? 'Unknown';
}
String _normalizeDateString(String input) {
return input
.replaceAllMapped(_ordinalRegex, (match) => match.group(1)!)
.replaceAll(_normalizeRegex, ' ')
.trim();
}
int _convertTwoDigitYear(int year) {
return year < _TWO_DIGIT_YEAR_PIVOT ? 2000 + year : 1900 + year;
}
PartialDate _parseRelativeDate(String lowerInput) {
final bool hasToday = lowerInput.contains('today');
final bool hasTomorrow = lowerInput.contains('tomorrow');
final bool hasYesterday = lowerInput.contains('yesterday');
final int count =
(hasToday ? 1 : 0) + (hasTomorrow ? 1 : 0) + (hasYesterday ? 1 : 0);
if (count > 1) {
return PartialDate.empty;
}
final now = DateTime.now();
if (hasToday) {
return PartialDate(day: now.day, month: now.month, year: now.year);
}
if (hasTomorrow) {
final tomorrow = now.add(const Duration(days: 1));
return PartialDate(
day: tomorrow.day,
month: tomorrow.month,
year: tomorrow.year,
);
}
if (hasYesterday) {
final yesterday = now.subtract(const Duration(days: 1));
return PartialDate(
day: yesterday.day,
month: yesterday.month,
year: yesterday.year,
);
}
return PartialDate.empty;
}
PartialDate _parseStructuredFormats(String input) {
final cleanInput = input.replaceAll(' ', '');
Match? match = _isoFormatRegex.firstMatch(cleanInput);
if (match != null) {
final yearVal = int.tryParse(match.group(1)!);
final monthVal = int.tryParse(match.group(2)!);
final dayVal = int.tryParse(match.group(3)!);
if (yearVal != null &&
yearVal >= _MIN_YEAR &&
yearVal <= _MAX_YEAR &&
monthVal != null &&
monthVal >= 1 &&
monthVal <= 12 &&
dayVal != null &&
dayVal >= 1 &&
dayVal <= 31) {
return PartialDate(day: dayVal, month: monthVal, year: yearVal);
}
return PartialDate.empty;
}
match = _standardFormatRegex.firstMatch(cleanInput);
if (match != null) {
final p1 = int.parse(match.group(1)!);
final p2 = int.parse(match.group(2)!);
final yearRaw = int.parse(match.group(3)!);
final year = yearRaw > 99 ? yearRaw : _convertTwoDigitYear(yearRaw);
if (year < _MIN_YEAR || year > _MAX_YEAR) return PartialDate.empty;
if (p1 > 12) {
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
return PartialDate(day: p1, month: p2, year: year);
}
} else if (p2 > 12) {
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
return PartialDate(day: p2, month: p1, year: year);
}
} else {
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
return PartialDate(day: p2, month: p1, year: year);
}
}
return PartialDate.empty;
}
match = _shortFormatRegex.firstMatch(cleanInput);
if (match != null) {
final p1 = int.parse(match.group(1)!);
final p2 = int.parse(match.group(2)!);
if (p1 > 12) {
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
return PartialDate(day: p1, month: p2);
}
} else if (p2 > 12) {
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
return PartialDate(day: p2, month: p1);
}
} else {
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
return PartialDate(day: p2, month: p1);
}
}
return PartialDate.empty;
}
match = _dotFormatRegex.firstMatch(cleanInput);
if (match != null) {
final yearRaw = int.parse(match.group(3)!);
final year = yearRaw > 99 ? yearRaw : _convertTwoDigitYear(yearRaw);
final dayVal = int.tryParse(match.group(1)!);
final monthVal = int.tryParse(match.group(2)!);
if (year >= _MIN_YEAR &&
year <= _MAX_YEAR &&
dayVal != null &&
dayVal >= 1 &&
dayVal <= 31 &&
monthVal != null &&
monthVal >= 1 &&
monthVal <= 12) {
return PartialDate(day: dayVal, month: monthVal, year: year);
}
return PartialDate.empty;
}
match = _compactFormatRegex.firstMatch(cleanInput);
if (match != null) {
final yearVal = int.tryParse(cleanInput.substring(0, 4));
final monthVal = int.tryParse(cleanInput.substring(4, 6));
final dayVal = int.tryParse(cleanInput.substring(6, 8));
if (yearVal != null &&
yearVal >= _MIN_YEAR &&
yearVal <= _MAX_YEAR &&
monthVal != null &&
monthVal >= 1 &&
monthVal <= 12 &&
dayVal != null &&
dayVal >= 1 &&
dayVal <= 31) {
return PartialDate(day: dayVal, month: monthVal, year: yearVal);
}
return PartialDate.empty;
}
return PartialDate.empty;
}
PartialDate _parseTokenizedDate(String normalized) {
final tokens = normalized.split(' ');
int? day, month, year;
if (tokens.length == 1) {
final token = tokens[0];
final match = _yearOnlyRegex.firstMatch(token);
if (match != null) {
final parsedYear = int.tryParse(match.group(1)!);
if (parsedYear != null &&
parsedYear >= _MIN_YEAR &&
parsedYear <= _MAX_YEAR) {
return PartialDate(year: parsedYear);
}
}
if (_monthMap.containsKey(token)) {
return PartialDate(month: _monthMap[token]!);
}
final singleValue = int.tryParse(token);
if (singleValue != null && singleValue >= 1 && singleValue <= 31) {
return PartialDate(day: singleValue);
}
return PartialDate.empty;
}
for (final token in tokens) {
if (_monthMap.containsKey(token) && month == null) {
month = _monthMap[token];
continue;
}
final value = int.tryParse(token);
if (value == null) {
continue;
}
if (value >= _MIN_YEAR && value <= _MAX_YEAR && year == null) {
year = value;
} else if (value >= 1 && value <= 31 && day == null) {
day = value;
} else if (value >= 0 && value <= 99 && year == null) {
final convertedYear = _convertTwoDigitYear(value);
if (convertedYear >= _MIN_YEAR && convertedYear <= _MAX_YEAR) {
year = convertedYear;
}
} else if (value >= 1 && value <= 12 && month == null) {
month = value;
}
}
if (day != null && (day < 1 || day > 31)) {
day = null;
}
if (month != null && (month < 1 || month > 12)) {
month = null;
}
final bool inputHadMonthWord = tokens.any((t) => _monthMap.containsKey(t));
final bool inputHadYearWord = tokens.any((t) {
final v = int.tryParse(t);
return v != null && v >= 1000 && v <= 9999;
});
if (day != null && month == null && year == null && tokens.length > 1) {
if (normalized.contains('of') &&
!inputHadMonthWord &&
!inputHadYearWord) {
return PartialDate.empty;
}
if (!inputHadMonthWord && !inputHadYearWord && tokens.length > 1) {}
}
if (day == null && month == null && year == null) {
return PartialDate.empty;
}
if (day != null && month == null && year == null && tokens.length > 1) {
if (!inputHadMonthWord && !inputHadYearWord) {
return PartialDate.empty;
}
}
return PartialDate(day: day, month: month, year: year);
}
}

View File

@@ -8,11 +8,11 @@ import "package:ml_linalg/dtype.dart";
import "package:ml_linalg/vector.dart";
import "package:photos/generated/protos/ente/common/vector.pb.dart";
import "package:photos/models/base/id.dart";
import "package:photos/services/isolate_functions.dart";
import "package:photos/services/isolate_service.dart";
import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart";
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
import "package:photos/services/machine_learning/ml_result.dart";
import "package:photos/utils/isolate/isolate_operations.dart";
import "package:photos/utils/isolate/super_isolate.dart";
class FaceInfo {
final String faceID;
@@ -507,7 +507,8 @@ ClusteringResult _runCompleteClustering(Map args) {
EVector.fromBuffer(entry.value).values,
dtype: DType.float32,
),
fileCreationTime: fileIDToCreationTime?[getFileIdFromFaceId<int>(entry.key)],
fileCreationTime:
fileIDToCreationTime?[getFileIdFromFaceId<int>(entry.key)],
),
);
}

View File

@@ -1,15 +1,13 @@
import 'dart:async';
import 'dart:typed_data' show Uint8List;
import "package:computer/computer.dart";
import "package:logging/logging.dart";
import "package:photos/models/ml/face/box.dart";
import "package:photos/services/isolate_functions.dart";
import "package:photos/services/isolate_service.dart";
import "package:photos/utils/image_ml_util.dart";
import "package:photos/utils/isolate/isolate_operations.dart";
import "package:photos/utils/isolate/super_isolate.dart";
final Computer _computer = Computer.shared();
@pragma('vm:entry-point')
class FaceThumbnailGenerator extends SuperIsolate {
@override
Logger get logger => _logger;
@@ -37,20 +35,30 @@ class FaceThumbnailGenerator extends SuperIsolate {
String imagePath,
List<FaceBox> faceBoxes,
) async {
final List<Map<String, dynamic>> faceBoxesJson =
faceBoxes.map((box) => box.toJson()).toList();
final List<Uint8List> faces = await runInIsolate(
IsolateOperation.generateFaceThumbnails,
{
'imagePath': imagePath,
'faceBoxesList': faceBoxesJson,
},
).then((value) => value.cast<Uint8List>());
final compressedFaces =
await compressFaceThumbnails({'listPngBytes': faces});
_logger.fine(
"Compressed face thumbnails from sizes ${faces.map((e) => e.length / 1024).toList()} to ${compressedFaces.map((e) => e.length / 1024).toList()} kilobytes",
);
return compressedFaces;
try {
_logger.info(
"Generating face thumbnails for ${faceBoxes.length} face boxes in $imagePath",
);
final List<Map<String, dynamic>> faceBoxesJson =
faceBoxes.map((box) => box.toJson()).toList();
final List<Uint8List> faces = await runInIsolate(
IsolateOperation.generateFaceThumbnails,
{
'imagePath': imagePath,
'faceBoxesList': faceBoxesJson,
},
).then((value) => value.cast<Uint8List>());
_logger.info("Generated face thumbnails");
final compressedFaces =
await compressFaceThumbnails({'listPngBytes': faces});
_logger.fine(
"Compressed face thumbnails from sizes ${faces.map((e) => e.length / 1024).toList()} to ${compressedFaces.map((e) => e.length / 1024).toList()} kilobytes",
);
return compressedFaces;
} catch (e, s) {
_logger.severe("Failed to generate face thumbnails", e, s);
rethrow;
}
}
}

View File

@@ -2,12 +2,12 @@ import 'dart:async';
import "package:logging/logging.dart";
import "package:photos/models/ml/vector.dart";
import "package:photos/services/isolate_functions.dart";
import "package:photos/services/isolate_service.dart";
import "package:photos/services/machine_learning/ml_constants.dart";
import "package:photos/services/machine_learning/semantic_search/clip/clip_text_encoder.dart";
import "package:photos/services/machine_learning/semantic_search/query_result.dart";
import "package:photos/services/remote_assets_service.dart";
import "package:photos/utils/isolate/isolate_operations.dart";
import "package:photos/utils/isolate/super_isolate.dart";
import "package:synchronized/synchronized.dart";
class MLComputer extends SuperIsolate {

View File

@@ -2,14 +2,14 @@ import "dart:async";
import "package:flutter/foundation.dart" show debugPrint;
import "package:logging/logging.dart";
import "package:photos/services/isolate_functions.dart";
import "package:photos/services/isolate_service.dart";
import 'package:photos/services/machine_learning/face_ml/face_detection/face_detection_service.dart';
import 'package:photos/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart';
import "package:photos/services/machine_learning/ml_models_overview.dart";
import 'package:photos/services/machine_learning/ml_result.dart';
import "package:photos/services/machine_learning/semantic_search/clip/clip_image_encoder.dart";
import "package:photos/services/remote_assets_service.dart";
import "package:photos/utils/isolate/isolate_operations.dart";
import "package:photos/utils/isolate/super_isolate.dart";
import "package:photos/utils/ml_util.dart";
import "package:photos/utils/network_util.dart";
import "package:synchronized/synchronized.dart";

View File

@@ -43,6 +43,7 @@ import "package:photos/models/search/search_types.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/account/user_service.dart";
import 'package:photos/services/collections_service.dart';
import "package:photos/services/date_parse_service.dart";
import "package:photos/services/filter/db_filters.dart";
import "package:photos/services/location_service.dart";
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
@@ -59,7 +60,6 @@ import "package:photos/utils/cache_util.dart";
import "package:photos/utils/file_util.dart";
import "package:photos/utils/navigation_util.dart";
import 'package:photos/utils/standalone/date_time.dart';
import 'package:tuple/tuple.dart';
class SearchService {
Future<List<EnteFile>>? _cachedFilesFuture;
@@ -1063,20 +1063,59 @@ class SearchService {
String query,
) async {
final List<GenericSearchResult> searchResults = [];
final potentialDates = _getPossibleEventDate(context, query);
for (var potentialDate in potentialDates) {
final int day = potentialDate.item1;
final int month = potentialDate.item2.monthNumber;
final int? year = potentialDate.item3; // nullable
final parsedDate = DateParseService.instance.parse(query);
if (parsedDate.isEmpty) {
return searchResults;
}
// Handle month-year queries
if (parsedDate.day == null &&
parsedDate.month != null &&
parsedDate.year != null) {
final month = parsedDate.month!;
final year = parsedDate.year!;
final monthYearFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
[_getDurationForMonthInYear(month, year)],
ignoreCollections(),
order: 'DESC',
);
if (monthYearFiles.isNotEmpty) {
final monthName = DateParseService.instance.getMonthName(month);
final name = '$monthName $year';
searchResults.add(
GenericSearchResult(
ResultType.month,
name,
monthYearFiles,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.month,
matchedUploadedIDs: filesToUploadedFileIDs(monthYearFiles),
filterIcon: Icons.calendar_month_outlined,
),
),
);
}
}
// Handle day-month queries (with or without year)
else if (parsedDate.day != null && parsedDate.month != null) {
final int day = parsedDate.day!;
final int month = parsedDate.month!;
final int? year = parsedDate.year; // nullable for generic dates
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsForCalendarDateInEveryYear(day, month, year: year),
ignoreCollections(),
order: 'DESC',
);
if (matchedFiles.isNotEmpty) {
final name = '$day ${potentialDate.item2.name} ${year ?? ''}';
final monthName = DateParseService.instance.getMonthName(month);
final name = '$day $monthName${year != null ? ' $year' : ''}';
searchResults.add(
GenericSearchResult(
ResultType.event,
@@ -1482,55 +1521,12 @@ class SearchService {
return durationsOfMonthInEveryYear;
}
List<Tuple3<int, MonthData, int?>> _getPossibleEventDate(
BuildContext context,
String query,
) {
final List<Tuple3<int, MonthData, int?>> possibleEvents = [];
if (query.trim().isEmpty) {
return possibleEvents;
}
final result = query
.trim()
.split(RegExp('[ ,-/]+'))
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
final resultCount = result.length;
if (resultCount < 1 || resultCount > 4) {
return possibleEvents;
}
final int? day = int.tryParse(result[0]);
if (day == null || day < 1 || day > 31) {
return possibleEvents;
}
final List<MonthData> potentialMonth = resultCount > 1
? _getMatchingMonths(context, result[1])
: getMonthData(context);
final int? parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null;
final List<int> matchingYears = [];
if (parsedYear != null) {
bool foundMatch = false;
for (int i = searchStartYear; i <= currentYear; i++) {
if (i.toString().startsWith(parsedYear.toString())) {
matchingYears.add(i);
foundMatch = foundMatch || (i == parsedYear);
}
}
if (!foundMatch && parsedYear > 1000 && parsedYear <= currentYear) {
matchingYears.add(parsedYear);
}
}
for (var element in potentialMonth) {
if (matchingYears.isEmpty) {
possibleEvents.add(Tuple3(day, element, null));
} else {
for (int yr in matchingYears) {
possibleEvents.add(Tuple3(day, element, yr));
}
}
}
return possibleEvents;
List<int> _getDurationForMonthInYear(int month, int year) {
return [
DateTime(year, month, 1).microsecondsSinceEpoch,
month == 12
? DateTime(year + 1, 1, 1).microsecondsSinceEpoch
: DateTime(year, month + 1, 1).microsecondsSinceEpoch,
];
}
}

View File

@@ -51,6 +51,10 @@ class RemoteSyncService {
Completer<void>? _existingSync;
bool _isExistingSyncSilent = false;
// _hasCleanupStaleEntry is used to track if we have already cleaned up
// statle db entries in this sync session.
bool _hasCleanupStaleEntry = false;
static const kHasSyncedArchiveKey = "has_synced_archive";
/* This setting is used to maintain a list of local IDs for videos that the user has manually
marked for upload, even if the global video upload setting is currently disabled.
@@ -371,6 +375,14 @@ class RemoteSyncService {
final Set<String> alreadyClaimedLocalIDs =
await _db.getLocalIDsMarkedForOrAlreadyUploaded(ownerID);
localIDsToSync.removeAll(alreadyClaimedLocalIDs);
if (alreadyClaimedLocalIDs.isNotEmpty && !_hasCleanupStaleEntry) {
try {
await _db.removeQueuedLocalFiles(alreadyClaimedLocalIDs);
} catch(e, s) {
_logger.severe("removeQueuedLocalFiles failed",e,s);
}
}
}
if (localIDsToSync.isEmpty) {
@@ -439,6 +451,7 @@ class RemoteSyncService {
// "force reload due to display new files"
Bus.instance.fire(ForceReloadHomeGalleryEvent("newFilesDisplay"));
}
_hasCleanupStaleEntry = true;
}
Future<void> updateDeviceFolderSyncStatus(

View File

@@ -21,6 +21,7 @@ class PersonFaceWidget extends StatefulWidget {
final String? clusterID;
final bool useFullFile;
final VoidCallback? onErrorCallback;
final bool keepAlive;
// PersonFaceWidget constructor checks that both personId and clusterID are not null
// and that the file is not null
@@ -29,6 +30,7 @@ class PersonFaceWidget extends StatefulWidget {
this.clusterID,
this.useFullFile = true,
this.onErrorCallback,
this.keepAlive = false,
super.key,
}) : assert(
personId != null || clusterID != null,
@@ -39,12 +41,16 @@ class PersonFaceWidget extends StatefulWidget {
State<PersonFaceWidget> createState() => _PersonFaceWidgetState();
}
class _PersonFaceWidgetState extends State<PersonFaceWidget> {
class _PersonFaceWidgetState extends State<PersonFaceWidget>
with AutomaticKeepAliveClientMixin {
Future<Uint8List?>? faceCropFuture;
EnteFile? fileForFaceCrop;
bool get isPerson => widget.personId != null;
@override
bool get wantKeepAlive => widget.keepAlive;
@override
void initState() {
super.initState();
@@ -64,6 +70,10 @@ class _PersonFaceWidgetState extends State<PersonFaceWidget> {
@override
Widget build(BuildContext context) {
super.build(
context,
); // Calling super.build for AutomaticKeepAliveClientMixin
return FutureBuilder<Uint8List?>(
future: faceCropFuture,
builder: (context, snapshot) {
@@ -163,7 +173,7 @@ class _PersonFaceWidgetState extends State<PersonFaceWidget> {
}
}
if (fileForFaceCrop == null) {
_logger.warning(
_logger.severe(
"No suitable file found for face crop for person: ${widget.personId} or cluster: ${widget.clusterID}",
);
return null;
@@ -176,7 +186,7 @@ class _PersonFaceWidgetState extends State<PersonFaceWidget> {
clusterID: widget.clusterID,
);
if (face == null) {
debugPrint(
_logger.severe(
"No cover face for person: ${widget.personId} or cluster ${widget.clusterID} and fileID ${fileForFaceCrop.uploadedFileID!}",
);
return null;
@@ -188,7 +198,13 @@ class _PersonFaceWidgetState extends State<PersonFaceWidget> {
personOrClusterID: personOrClusterId,
useTempCache: false,
);
return cropMap?[face.faceID];
final result = cropMap?[face.faceID];
if (result == null) {
_logger.severe(
"Null cover face crop for person: ${widget.personId} or cluster ${widget.clusterID} and fileID ${fileForFaceCrop.uploadedFileID!}",
);
}
return result;
} catch (e, s) {
_logger.severe(
"Error getting cover face for person: ${widget.personId} or cluster ${widget.clusterID}",

View File

@@ -95,12 +95,14 @@ class SelectablePersonSearchExample extends StatelessWidget {
final GenericSearchResult searchResult;
final double size;
final SelectedPeople selectedPeople;
final bool isDefaultFace;
const SelectablePersonSearchExample({
super.key,
required this.searchResult,
required this.selectedPeople,
this.size = 102,
this.isDefaultFace = false,
});
void _handleTap(BuildContext context) {
@@ -192,7 +194,10 @@ class SelectablePersonSearchExample extends StatelessWidget {
searchResult.previewThumbnail()!,
shouldShowSyncStatus: false,
)
: FaceSearchResult(searchResult);
: FaceSearchResult(
searchResult,
isDefaultFace: isDefaultFace,
);
} else {
child = const NoThumbnailWidget(
addBorder: false,
@@ -301,8 +306,13 @@ class SelectablePersonSearchExample extends StatelessWidget {
class FaceSearchResult extends StatelessWidget {
final SearchResult searchResult;
final bool isDefaultFace;
const FaceSearchResult(this.searchResult, {super.key});
const FaceSearchResult(
this.searchResult, {
super.key,
this.isDefaultFace = false,
});
@override
Widget build(BuildContext context) {
@@ -313,6 +323,7 @@ class FaceSearchResult extends StatelessWidget {
key: params.containsKey(kPersonWidgetKey)
? ValueKey(params[kPersonWidgetKey])
: ValueKey(params[kPersonParamID] ?? params[kClusterParamId]),
keepAlive: isDefaultFace,
);
}
}
@@ -486,6 +497,7 @@ class _PeopleSectionAllWidgetState extends State<PeopleSectionAllWidget> {
searchResult: normalFaces[index],
size: itemSize,
selectedPeople: widget.selectedPeople!,
isDefaultFace: true,
)
: PersonSearchExample(
searchResult: normalFaces[index],
@@ -525,6 +537,7 @@ class _PeopleSectionAllWidgetState extends State<PeopleSectionAllWidget> {
searchResult: extraFaces[index],
size: itemSize,
selectedPeople: widget.selectedPeople!,
isDefaultFace: false,
)
: PersonSearchExample(
searchResult: extraFaces[index],

View File

@@ -136,7 +136,7 @@ Future<Map<String, Uint8List>?> getCachedFaceCrops(
facesWithoutCrops[face.faceID] = face.detection.box;
}
} catch (e, s) {
_logger.severe(
_logger.warning(
"Error reading cached face crop for faceID ${face.faceID} from file ${faceCropCacheFile.path}",
e,
s,
@@ -212,7 +212,7 @@ Future<Map<String, Uint8List>?> getCachedFaceCrops(
milliseconds: 100 * pow(2, fetchAttempt + 1).toInt(),
);
await Future.delayed(backoff);
_logger.warning(
_logger.fine(
"Error getting face crops for faceIDs: ${faces.map((face) => face.faceID).toList()}, retrying (attempt ${fetchAttempt + 1}) in ${backoff.inMilliseconds} ms",
e,
s,
@@ -225,13 +225,13 @@ Future<Map<String, Uint8List>?> getCachedFaceCrops(
useTempCache: useTempCache,
);
}
_logger.severe(
_logger.warning(
"Error getting face crops for faceIDs: ${faces.map((face) => face.faceID).toList()}",
e,
s,
);
} else {
_logger.info(
_logger.severe(
"Stopped getting face crops for faceIDs: ${faces.map((face) => face.faceID).toList()} due to $e",
);
}
@@ -334,12 +334,14 @@ Future<Map<String, Uint8List>?> _getFaceCrops(
if (useFullFile && file.fileType != FileType.video) {
final File? ioFile = await getFile(file);
if (ioFile == null) {
_logger.severe("Failed to get file for face crop generation");
return null;
}
imagePath = ioFile.path;
} else {
final thumbnail = await getThumbnailForUploadedFile(file);
if (thumbnail == null) {
_logger.severe("Failed to get thumbnail for face crop generation");
return null;
}
imagePath = thumbnail.path;

View File

@@ -7,9 +7,10 @@ import "package:flutter/services.dart";
import "package:logging/logging.dart";
import "package:photos/core/error-reporting/isolate_logging.dart";
import "package:photos/models/base/id.dart";
import "package:photos/services/isolate_functions.dart";
import "package:photos/utils/isolate/isolate_operations.dart";
import "package:synchronized/synchronized.dart";
@pragma('vm:entry-point')
abstract class SuperIsolate {
Logger get logger;
@@ -80,6 +81,8 @@ abstract class SuperIsolate {
if (rootToken != null) {
BackgroundIsolateBinaryMessenger.ensureInitialized(rootToken);
}
final logger = Logger('SuperIsolate');
logger.info('IsolateMain started');
receivePort.listen((message) async {
final taskID = message[0] as String;
@@ -87,6 +90,7 @@ abstract class SuperIsolate {
final function = IsolateOperation.values[functionIndex];
final args = message[2] as Map<String, dynamic>;
final sendPort = message[3] as SendPort;
logger.info("Starting isolate operation $function in isolate");
late final Object data;
try {

View File

@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev"
source: hosted
version: "72.0.0"
version: "76.0.0"
_flutterfire_internals:
dependency: transitive
description:
@@ -21,7 +21,7 @@ packages:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
version: "0.3.3"
adaptive_theme:
dependency: "direct main"
description:
@@ -34,10 +34,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev"
source: hosted
version: "6.7.0"
version: "6.11.0"
android_intent_plus:
dependency: "direct main"
description:
@@ -317,10 +317,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
computer:
dependency: "direct main"
description:
@@ -1416,18 +1416,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
@@ -1536,10 +1536,10 @@ packages:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
version: "0.1.3-main.0"
maps_launcher:
dependency: "direct main"
description:
@@ -2309,7 +2309,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
version: "0.0.0"
source_gen:
dependency: transitive
description:
@@ -2434,10 +2434,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
version: "1.12.0"
step_progress_indicator:
dependency: "direct main"
description:
@@ -2466,10 +2466,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.3.0"
styled_text:
dependency: "direct main"
description:
@@ -2530,26 +2530,26 @@ packages:
dependency: "direct dev"
description:
name: test
sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
url: "https://pub.dev"
source: hosted
version: "1.25.7"
version: "1.25.8"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.3"
test_core:
dependency: transitive
description:
name: test_core
sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
url: "https://pub.dev"
source: hosted
version: "0.6.4"
version: "0.6.5"
thermal:
dependency: "direct main"
description:
@@ -2813,10 +2813,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.3.0"
volume_controller:
dependency: transitive
description:
@@ -2877,10 +2877,10 @@ packages:
dependency: transitive
description:
name: webdriver
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
webkit_inspection_protocol:
dependency: transitive
description:

View File

@@ -0,0 +1,346 @@
import 'package:photos/services/date_parse_service.dart';
import 'package:test/test.dart';
void main() {
// Get an instance of the service
final DateParseService dateParseService = DateParseService.instance;
// --- Natural Language Date Parsing ---
group('Natural Language Date Parsing', () {
// Relative dates: today, tomorrow, yesterday
test('should parse "today" correctly', () {
final DateTime now = DateTime.now();
final PartialDate expectedDate =
PartialDate(day: now.day, month: now.month, year: now.year);
final PartialDate parsedDate = dateParseService.parse('today');
expect(
parsedDate.day,
expectedDate.day,
reason: 'Day mismatch for today',
);
expect(
parsedDate.month,
expectedDate.month,
reason: 'Month mismatch for today',
);
expect(
parsedDate.year,
expectedDate.year,
reason: 'Year mismatch for today',
);
});
test('should parse "tomorrow" correctly', () {
final DateTime tomorrow = DateTime.now().add(const Duration(days: 1));
final PartialDate expectedDate = PartialDate(
day: tomorrow.day,
month: tomorrow.month,
year: tomorrow.year,
);
final PartialDate parsedDate = dateParseService.parse('tomorrow');
expect(
parsedDate.day,
expectedDate.day,
reason: 'Day mismatch for tomorrow',
);
expect(
parsedDate.month,
expectedDate.month,
reason: 'Month mismatch for tomorrow',
);
expect(
parsedDate.year,
expectedDate.year,
reason: 'Year mismatch for tomorrow',
);
});
test('should parse "yesterday" correctly', () {
final DateTime yesterday =
DateTime.now().subtract(const Duration(days: 1));
final PartialDate expectedDate = PartialDate(
day: yesterday.day,
month: yesterday.month,
year: yesterday.year,
);
final PartialDate parsedDate = dateParseService.parse('yesterday');
expect(
parsedDate.day,
expectedDate.day,
reason: 'Day mismatch for yesterday',
);
expect(
parsedDate.month,
expectedDate.month,
reason: 'Month mismatch for yesterday',
);
expect(
parsedDate.year,
expectedDate.year,
reason: 'Year mismatch for yesterday',
);
});
// Month names: Full (February), abbreviated (Feb), and partial (Febr)
test('should parse full month name "February 2025"', () {
final PartialDate parsedDate = dateParseService.parse('February 2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse abbreviated month name "Feb 2025"', () {
final PartialDate parsedDate = dateParseService.parse('Feb 2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse partial month name "Febr 2025"', () {
final PartialDate parsedDate = dateParseService.parse('Febr 2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
// Ordinal numbers: 25th, 22nd, 3rd, 1st
test('should parse ordinal number "25th Jan 2024"', () {
final PartialDate parsedDate = dateParseService.parse('25th Jan 2024');
expect(parsedDate.day, 25);
expect(parsedDate.month, 1);
expect(parsedDate.year, 2024);
});
test('should parse ordinal number "22nd Feb"', () {
final PartialDate parsedDate = dateParseService.parse('22nd Feb');
expect(parsedDate.day, 22);
expect(parsedDate.month, 2);
expect(parsedDate.year, isNull);
});
test('should parse ordinal number "3rd March"', () {
final PartialDate parsedDate = dateParseService.parse('3rd March');
expect(parsedDate.day, 3);
expect(parsedDate.month, 3);
expect(parsedDate.year, isNull);
});
// Flexible combinations
test('should parse "25th Feb" (generic date)', () {
final PartialDate parsedDate = dateParseService.parse('25th Feb');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, isNull);
});
test('should parse "February 2025" (month-year query)', () {
final PartialDate parsedDate = dateParseService.parse('February 2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse "25th of February 2025"', () {
final PartialDate parsedDate =
dateParseService.parse('25th of February 2025');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
});
// --- Structured Date Format Support ---
group('Structured Date Format Support', () {
// ISO format: 2025-02-25, 2025/02/25
test('should parse ISO format "2025-02-25"', () {
final PartialDate parsedDate = dateParseService.parse('2025-02-25');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse ISO format "2025/02/25"', () {
final PartialDate parsedDate = dateParseService.parse('2025/02/25');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
// Standard formats: 02/25/2025, 25/02/2025 (with MM/DD vs DD/MM detection)
test('should parse standard MM/DD/YYYY format "02/25/2025"', () {
// Your parser assumes MM/DD if ambiguous (e.g., both parts <= 12)
// but for 02/25/2025, 25 > 12, so it correctly interprets 02 as month and 25 as day.
final PartialDate parsedDate = dateParseService.parse('02/25/2025');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse standard DD/MM/YYYY format "25/02/2025"', () {
// Your parser handles DD/MM explicitly when day part > 12
final PartialDate parsedDate = dateParseService.parse('25/02/2025');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse ambiguous "01/02/2024" as MM/DD/YYYY (Jan 2)', () {
// Test your specific heuristic for ambiguous cases
final PartialDate parsedDate = dateParseService.parse('01/02/2024');
expect(parsedDate.day, 2);
expect(parsedDate.month, 1);
expect(parsedDate.year, 2024);
});
// Dot notation: 25.02.2025, 25.02.25
test('should parse dot notation "25.02.2025"', () {
final PartialDate parsedDate = dateParseService.parse('25.02.2025');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse dot notation with two-digit year "25.02.25"', () {
// Assumes century detection (e.g., 25 -> 2025)
final PartialDate parsedDate = dateParseService.parse('25.02.25');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
// Compact format: 20250225
test('should parse compact format "20250225"', () {
final PartialDate parsedDate = dateParseService.parse('20250225');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
// Short formats: 02/25, 25/02 (your parser doesn't explicitly handle short yearless formats)
// Based on your _standardFormatRegex: RegExp(r'^(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})$');
// and _parseTokenizedDate, "02/25" would be processed by _parseTokenizedDate.
// Let's test how your current parser handles these.
test(
'should parse short MM/DD format "02/25" (no year, handled by tokenized)',
() {
final PartialDate parsedDate = dateParseService.parse('02/25');
expect(parsedDate.day, 25); // value 25 is assigned to day first
expect(parsedDate.month, 2); // value 02 is assigned to month
expect(parsedDate.year, isNull);
});
test(
'should parse short DD/MM format "25/02" (no year, handled by tokenized)',
() {
// This will be parsed by _parseTokenizedDate
final PartialDate parsedDate = dateParseService.parse('25/02');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, isNull);
});
// Two-digit years: 25/02/25 (with century detection)
test('should parse two-digit year "25/02/25"', () {
final PartialDate parsedDate = dateParseService.parse('25/02/25');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025); // Based on _convertTwoDigitYear pivot
});
test('should parse two-digit year "01/01/01" as 2001', () {
final PartialDate parsedDate = dateParseService.parse('01/01/01');
expect(parsedDate.day, 1);
expect(parsedDate.month, 1);
expect(parsedDate.year, 2001); // 01 < _TWO_DIGIT_YEAR_PIVOT
});
test('should parse two-digit year "01/01/99" as 1999', () {
final PartialDate parsedDate = dateParseService.parse('01/01/99');
expect(parsedDate.day, 1);
expect(parsedDate.month, 1);
expect(parsedDate.year, 1999); // 99 > _TWO_DIGIT_YEAR_PIVOT
});
});
// --- Smart Query Types ---
group('Smart Query Types', () {
test('should parse year-only query "2025"', () {
final PartialDate parsedDate = dateParseService.parse('2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, isNull);
expect(parsedDate.year, 2025);
});
test('should parse month-year query "February 2025"', () {
final PartialDate parsedDate = dateParseService.parse('February 2025');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
test('should parse generic date query "25th Feb" (year is null)', () {
final PartialDate parsedDate = dateParseService.parse('25th Feb');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, isNull);
});
test('should parse specific date query "25/02/2025"', () {
final PartialDate parsedDate = dateParseService.parse('25/02/2025');
expect(parsedDate.day, 25);
expect(parsedDate.month, 2);
expect(parsedDate.year, 2025);
});
});
// --- Invalid Date Queries ---
group('Invalid Date Queries', () {
test('should parse "February 30000" as month-only (invalid year ignored)',
() {
final PartialDate parsedDate = dateParseService.parse('February 30000');
expect(parsedDate.day, isNull);
expect(parsedDate.month, 2);
expect(
parsedDate.year,
isNull,
reason: 'Year 30000 is out of range and should be ignored',
);
});
// Specific case to test if invalid day/month values are set to null
test('should return null for invalid day/month in tokenized parsing', () {
final PartialDate parsedDate = dateParseService.parse('32 Jan 2024');
expect(parsedDate.day, isNull, reason: 'Day should be null for 32');
expect(parsedDate.month, 1);
expect(
parsedDate.year,
2024,
);
// "Jan 13 2024" - This is a valid date (Jan 13, 2024), should parse completely.
final PartialDate parsedDate2 = dateParseService.parse('Jan 13 2024');
expect(parsedDate2.day, 13);
expect(parsedDate2.month, 1);
expect(parsedDate2.year, 2024);
// "Feb 0 2024" - Day 0 should be null, but month and year are valid.
final PartialDate parsedDate3 = dateParseService.parse('Feb 0 2024');
expect(parsedDate3.day, isNull, reason: 'Day should be null for 0');
expect(parsedDate3.month, 2);
expect(parsedDate3.year, 2024);
});
test('should handle invalid day/month in tokenized parsing gracefully', () {
final PartialDate parsedDate = dateParseService.parse('32 Jan 2024');
expect(parsedDate.day, isNull, reason: 'Day should be null for 32');
expect(parsedDate.month, 1);
expect(
parsedDate.year,
2024,
);
});
});
}

View File

@@ -0,0 +1,6 @@
ALTER TABLE trash RESET (
autovacuum_analyze_scale_factor,
autovacuum_vacuum_scale_factor,
autovacuum_analyze_threshold,
autovacuum_vacuum_threshold
);

View File

@@ -0,0 +1,6 @@
ALTER TABLE trash SET (
autovacuum_analyze_scale_factor = 0.01, -- Trigger ANALYZE after 1% of rows change
autovacuum_vacuum_scale_factor = 0.02, -- Trigger VACUUM after 2% of rows change
autovacuum_analyze_threshold = 1000,
autovacuum_vacuum_threshold = 1000
);

View File

@@ -347,7 +347,7 @@ func (t *TrashRepository) GetTimeStampForLatestNonDeletedEntry(userID int64) (*i
// GetUserIDToFileIDsMapForDeletion returns map of userID to fileIds, where the file ids which should be deleted by now
func (t *TrashRepository) GetUserIDToFileIDsMapForDeletion() (map[int64][]int64, error) {
rows, err := t.DB.Query(`SELECT user_id, file_id FROM trash
WHERE delete_by <= $1 AND is_deleted = FALSE AND is_restored = FALSE limit $2`,
WHERE delete_by <= $1 AND is_deleted IS FALSE AND is_restored IS FALSE limit $2`,
time.Microseconds(), TrashDiffLimit)
if err != nil {
return nil, stacktrace.Propagate(err, "")

View File

@@ -404,10 +404,7 @@ const Footer: React.FC = () => {
return (
<Stack sx={{ my: "4rem", gap: 2, alignItems: "center" }}>
<Typography>{t("auth_download_mobile_app")}</Typography>
<a
href="https://github.com/ente-io/ente/tree/main/auth#-download"
download
>
<a href="https://ente.io/auth/#download-auth" download>
<Button color="accent">{t("download")}</Button>
</a>
</Stack>