package Ġio . aes y . yum me . service Ċ Ċ import Ġio . aes y . yum me . entity .* Ċ import Ġio . aes y . yum me . entity . Recipe Has Image Upload . Type Ċ import Ġio . aes y . yum me . repository . Image Upload Repository Ċ import Ġio . aes y . yum me . repository . Recipe Has Image Upload Repository Ċ import Ġio . aes y . yum me . util . Images Ċ import Ġio . aes y . yum me . util . Logging . getLogger Ċ import Ġio . aes y . yum me . util . MD 5 Ċ import Ġorg . springframework . cache . annotation . Cache able Ċ import Ġorg . springframework . scheduling . annotation . Scheduled Ċ import Ġorg . springframework . stereotype . Service Ċ import Ġjava . awt . image . Buffered Image Ċ import Ġjava . io .* Ċ import Ġjava . util .* Ċ import Ġjavax . image io . Image IO Ċ import Ġjavax . transaction . Transactional Ċ import Ġkotlin . math . min Ċ Ċ @ Service Ċ class ĠImage Upload Service ( ĊĠĠĠ Ġprivate Ġval Ġfile Storage Service : ĠFile Storage Service , ĊĠĠĠ Ġprivate Ġval Ġslug Service : ĠSl ug Service , ĊĠĠĠ Ġprivate Ġval Ġimage Upload Repository : ĠImage Upload Repository , ĊĠĠĠ Ġprivate Ġval Ġrecipe Has Image Upload Repository : ĠRecipe Has Image Upload Repository Ċ ) Ġ{ ĊĠĠĠ Ġcompanion Ġobject Ġ{ ĊĠĠĠĠĠĠĠ Ġprivate Ġconst Ġval ĠFIL ENAME _ PREFIX Ġ= Ġ" recipe - image " ĊĊĠĠĠĠĠĠĠ Ġprivate Ġval Ġlogger Ġ= Ġget Logger () ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Cache able (" default - upload - image ") ĊĠĠĠ Ġfun ĠgetDefault Image ( type : ĠType ): ĠBufferedImage Ġ{ ĊĠĠĠĠĠĠĠ Ġthrow ĠNotImplementedError () ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Transactional ĊĠĠĠ Ġfun Ġget Upload ( recipe : ĠRecipe , Ġname : ĠString , Ġtype : ĠType ): ĠOptional < Image Upload > Ġ{ ĊĠĠĠĠĠĠĠ Ġreturn Ġrecipe Has Image Upload Repository . findBy Recipe And Name And Type ( recipe , Ġname , Ġtype ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ. map ( Recipe Has Image Upload :: upload ) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Transactional ĊĠĠĠ Ġfun Ġdelete Upload ( recipe : ĠRecipe , Ġname : ĠString ): ĠBoolean Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġmappings Ġ= Ġrecipe Has Image Upload Repository . findBy Recipe And Name ( recipe , Ġname ) ĊĊĠĠĠĠĠĠĠ Ġfor Ġ( mapping Ġin Ġmappings ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġfile Storage Service . delete ( mapping . upload . fileName ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġrecipe Has Image Upload Repository . delete ( mapping ) ĊĠĠĠĠĠĠĠ Ġ} ĊĊĠĠĠĠĠĠĠ Ġreturn Ġmappings . isNotEmpty () ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġfun Ġread Image ( upload : ĠImage Upload ): ĠOptional < Buffered Image > Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġimage Ġ= Ġtry Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġupload . read () ĊĠĠĠĠĠĠĠ Ġ} Ġcatch Ġ( e : ĠFileNotFoundException ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . warn (" Image Ġuploads ' Ġcorresponding Ġfile Ġon Ġdisk Ġcouldn 't Ġbe Ġfound ", Ġe ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġreturn ĠOptional . empty () ĊĠĠĠĠĠĠĠ Ġ} Ġcatch Ġ( e : ĠIOException ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . error (" Failed Ġto Ġread Ġimage Ġ${ upload . fileName }", Ġe ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġreturn ĠOptional . empty () ĊĠĠĠĠĠĠĠ Ġ} ĊĊĠĠĠĠĠĠĠ Ġreturn ĠOptional . of ( image ) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Transactional ĊĠĠĠ Ġ@ Throws ( IOException :: class ) ĊĠĠĠ Ġfun Ġstore Image ( recipe : ĠRecipe , Ġimage : ĠBufferedImage ): ĠRecipe Has Image Upload Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġbytes Ġ= Ġimage . apply ( Images :: remove Transparency ). to Bytes () ĊĠĠĠĠĠĠĠ Ġval Ġimage Name Ġ= Ġgenerate Image Name ( recipe ) ĊĠĠĠĠĠĠĠ Ġval ĠfileName Ġ= Ġgenerate New FileName ( recipe ) ĊĠĠĠĠĠĠĠ Ġval Ġhash Ġ= ĠMD 5 . hash ( bytes ) ĊĠĠĠĠĠĠĠ Ġval Ġupload Ġ= ĠImage Upload ( ĊĠĠĠĠĠĠĠĠĠĠĠ ĠfileName Ġ= ĠfileName , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġwidth Ġ= Ġimage . width , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġheight Ġ= Ġimage . height , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġhash Ġ= Ġhash ĊĠĠĠĠĠĠĠ Ġ) ĊĊĠĠĠĠĠĠĠ Ġimage Upload Repository . save ( upload ) ĊĊĠĠĠĠĠĠĠ Ġval Ġmapping Ġ= ĠRecipe Has Image Upload ( ĊĠĠĠĠĠĠĠĠĠĠĠ Ġname Ġ= Ġimage Name , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġtype Ġ= ĠType . ORIGIN AL , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġrecipe Ġ= Ġrecipe , ĊĠĠĠĠĠĠĠĠĠĠĠ Ġupload Ġ= Ġupload ĊĠĠĠĠĠĠĠ Ġ) ĊĊĠĠĠĠĠĠĠ Ġrecipe Has Image Upload Repository . save ( mapping ) ĊĠĠĠĠĠĠĠ Ġfile Storage Service . write ( fileName , Ġbytes ) ĊĊĠĠĠĠĠĠĠ Ġreturn Ġmapping ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Transactional ĊĠĠĠ Ġ@ Scheduled ( fixed Rate Ġ= Ġ 1 0 Ġ* Ġ 1 0 0 0 ) ĊĠĠĠ Ġinternal Ġfun Ġgenerate Vari ations () Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġtypes Ġ= ĠarrayOf ( Type . LARGE , ĠType . MEDIUM , ĠType . TH UMB NA IL ) ĊĊĠĠĠĠĠĠĠ Ġfor Ġ( type Ġin Ġtypes ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġoriginal s Ġ= Ġrecipe Has Image Upload Repository . find Un processed Original sBy Type ( type . name ) ĊĊĠĠĠĠĠĠĠĠĠĠĠ Ġfor Ġ( original Ġin Ġoriginal s ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . info (" Generating Ġimage Ġvariation Ġof Ġtype Ġ'$ type ' Ġfor Ġimage Ġupload Ġ'${ original . upload . fileName }' ") ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġrecipe Ġ= Ġoriginal . recipe ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġimage Ġ= Ġoriginal . upload . read () ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġscaled Ġ= ĠImages . re scale To Fit ( image , Ġtype . dimensions ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġbytes Ġ= Ġscaled . to Bytes () ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval ĠfileName Ġ= Ġgenerate FileName Variation ( original , Ġtype ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġhash Ġ= ĠMD 5 . hash ( bytes ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġfile Storage Service . write ( fileName , Ġbytes ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġthumbnail Ġ= ĠImage Upload ( ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠfileName Ġ= ĠfileName , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġwidth Ġ= Ġscaled . width , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġheight Ġ= Ġscaled . height , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġhash Ġ= Ġhash ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġimage Upload Repository . save ( thumbnail ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġmapping Ġ= ĠRecipe Has Image Upload ( ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġname Ġ= Ġoriginal . name , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġtype Ġ= Ġtype , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrecipe Ġ= Ġrecipe , ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġupload Ġ= Ġthumbnail ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrecipe Has Image Upload Repository . save ( mapping ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Transactional ĊĠĠĠ Ġ@ Scheduled ( fixed Rate Ġ= Ġ 1 5 Ġ* Ġ 6 0 Ġ* Ġ 1 0 0 0 ) ĊĠĠĠ Ġinternal Ġfun Ġdelete Or phan ed Images () Ġ{ ĊĠĠĠĠĠĠĠ Ġlogger . debug (" Checking Ġfor Ġorphan ed Ġimage Ġuploads ") ĊĊĠĠĠĠĠĠĠ Ġval Ġpaths Ġ= Ġfile Storage Service . getAll FilePath s () ĊĊĠĠĠĠĠĠĠ Ġfor Ġ( path Ġin Ġpaths ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval ĠfileName Ġ= Ġpath . fileName . toString () ĊĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġupload Ġ= Ġimage Upload Repository . findBy FileName ( fileName ) ĊĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( upload . isEmpty ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . info (" Deleting Ġorphan ed Ġimage Ġupload Ġ'$ fileName '") ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġval Ġdeleted Ġ= Ġfile Storage Service . delete ( fileName ) ĊĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ(! deleted ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . warn (" Failed Ġto Ġdelete Ġorphan ed Ġimage Ġupload Ġ'$ fileName ': ĠImage Ġnot Ġfound ") ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġprivate Ġfun Ġgenerate Image Name ( recipe : ĠRecipe ): ĠString Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġname Ġ= Ġrecipe . title . substring ( 0 , Ġmin ( 2 0 , Ġrecipe . title . length )) ĊĊĠĠĠĠĠĠĠ Ġreturn Ġslug Service . generate Slug ( name , Ġ 3 0 ) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġprivate Ġfun Ġgenerate New FileName ( recipe : ĠRecipe ): ĠString Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġname Ġ= Ġrecipe . title . substring ( 0 , Ġmin ( 2 0 , Ġrecipe . title . length )) ĊĠĠĠĠĠĠĠ Ġval Ġslug Ġ= Ġslug Service . generate Slug ( name , Ġ 5 0 ) ĊĠĠĠĠĠĠĠ Ġval Ġsuffix Ġ= ĠType . ORIGIN AL . to FileName Part () ĊĊĠĠĠĠĠĠĠ Ġreturn Ġ"$ FILENAME _ PREFIX -$ slug -$ suffix . png " ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġprivate Ġfun Ġgenerate FileName Variation ( upload : ĠRecipe Has Image Upload , Ġvariation : ĠType ): ĠString Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġsuffix Ġ= Ġvariation . to FileName Part () ĊĊĠĠĠĠĠĠĠ Ġreturn Ġ"$ FILENAME _ PREFIX -${ upload . name }- $ suffix . png " ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġprivate Ġfun ĠType . to FileName Part () Ġ= Ġwhen Ġ( this ) Ġ{ ĊĠĠĠĠĠĠĠ ĠType . ORIGIN AL Ġ-> Ġ" original " ĊĠĠĠĠĠĠĠ ĠType . LARGE Ġ-> Ġ" large " ĊĠĠĠĠĠĠĠ ĠType . MEDIUM Ġ-> Ġ" medium " ĊĠĠĠĠĠĠĠ ĠType . TH UMB NA IL Ġ-> Ġ" thumbnail " ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Throws ( IOException :: class ) ĊĠĠĠ Ġprivate Ġfun ĠImage Upload . read (): ĠBufferedImage Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġfile Ġ= Ġfile Storage Service . read ( fileName ) ĊĊĠĠĠĠĠĠĠ Ġreturn ĠImage IO . read ( file ) ĊĠĠĠ Ġ} ĊĊĠĠĠ Ġ@ Throws ( IOException :: class ) ĊĠĠĠ Ġprivate Ġfun ĠBufferedImage . to Bytes (): ĠByteArray Ġ{ ĊĠĠĠĠĠĠĠ ĠByteArrayOutputStream (). use Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ ĠImage IO . write ( this , Ġ" png ", Ġit ) ĊĊĠĠĠĠĠĠĠĠĠĠĠ Ġreturn Ġit . toByteArray () ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} Ċ } Ċ