Search for unused attachments in server instance

ABoerio November 25, 2020

Hi,

I've finally made un update of my initial script which I described here .

Now it checkes all the pages in our instance, and write the output in a Confluence page.

Quite sure many people here have already more sophisticated ways to do the job, but if this can help rookie-admins like me, then I'm happy.

I know I should stop using PageManager and SpaceManager get... calls, but honestly I haven't yet really understood how to make it work in the other way :-) so I'm staying on the good old managers I know ... :-)

Ciao Ciao, Andrea

 

Immagine 2020-11-26 005534.jpg

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager
import com.atlassian.confluence.core.DefaultSaveContext
import groovy.xml.MarkupBuilder
import java.math.RoundingMode
import org.jsoup.Jsoup

def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)


//All spaces to check
def spacesChecked=spaceManager.allSpaces.findAll{it instanceof Space}

//log.warn(spacesChecked)

//List and Map for attachments use cases
def attachmentUseCases=[]
//List and Map for attachment cases
def attachmentCases=[]
def attachmentCasesMap=[:]
//List and Map for unused attachment cases
def unusedAttachmentCases=[]
def unusedAttachmentCasesMap=[:]

////////////////////////////////////////
//Loop through spaces
////////////////////////////////////////

spacesChecked.each(){
spaceChecked->

//Pages to be checked
def pagesChecked=pageManager.getPages(spaceChecked, true).findAll{it instanceof Page}

////////////////////////////////////////
//Loop through space pages
////////////////////////////////////////

pagesChecked.each(){
pageChecked->

//////////////////////////////////////////
//Looking for attachment use cases in page
//////////////////////////////////////////

//Get content page for parsing
def body = pageChecked.bodyContent.body

//Parse page content
//Refs for use of Jsoup
//https://jsoup.org/cookbook/extracting-data/selector-syntax
//https://jsoup.org/cookbook/extracting-data/attributes-text-html
def parsedBody = Jsoup.parse(body)
//Looking for attachment tag
def attachments = parsedBody.select("ri|attachment")

//Going through all the attachment tags found
attachments.each(){
attachment ->

//Attachment file name
def attFileName=attachment.attr('ri:filename')

//Page of origin of the attachment
def attPageOrigin=attachment.select("ri|page").attr('ri:content-title')
if (attPageOrigin){
}
else{
attPageOrigin=pageChecked.title
}
//Space of origin of the attachment
def attSpaceOrigin=attachment.select("ri|page").attr('ri:space-key')
if (attSpaceOrigin){
}
else{
attSpaceOrigin=spaceChecked.key
}

//Attachment use case unique id
def attUseCaseId="${attSpaceOrigin}__${attPageOrigin}__${attFileName}"

//Add attachment use case id to list
if(!attachmentUseCases.contains(attUseCaseId)){
attachmentUseCases.add(attUseCaseId)
}
}

/////////////////////////////////
//Looking for attachments to page
/////////////////////////////////

def loadedAttachments=attachmentManager.getLatestVersionsOfAttachments(pageChecked)
loadedAttachments.each(){
lAttachment->

def lAttachmentFileName=lAttachment.fileName
def lAttachmentSize=(double)lAttachment.getFileSize()/(1024*1024)
BigDecimal lAttachmentSizeBD = new BigDecimal(lAttachmentSize).setScale(4, RoundingMode.HALF_UP)
def lAttachmentId="${spaceChecked.key}__${pageChecked.title}__${lAttachmentFileName}"

attachmentCases.add(lAttachmentId) //List for quick reference

def attachmentCasesMapAtt=[
space:spaceChecked.key,
page:pageChecked.title,
pageId:pageChecked.id,
pagePath:pageChecked.getUrlPath(),
name:lAttachmentFileName,
size:lAttachmentSizeBD.toString(),
path:lAttachment.getDownloadPath()
]
attachmentCasesMap.put(lAttachmentId, attachmentCasesMapAtt)
}
}
}

////////////////////////////////////////
//Check resulting lists from loops
////////////////////////////////////////

//Sort lists
attachmentCases.sort()
attachmentUseCases.sort()

//Compare lists to find unused attachment cases
attachmentCases.each(){
eAttachment->
if(!attachmentUseCases.contains(eAttachment)){
unusedAttachmentCases.add(eAttachment)
}
}

/////////////////////////////////
//Export to a table
/////////////////////////////////

def writer = new StringWriter()
def xml = new MarkupBuilder(writer)


xml.p("Unused attachment cases:")

xml.table(class: "aui") {
thead {
tr{
th('Space')
th('Page')
th('Page Id')
th('Attachment')
th('Size (MB)')
th('Download Path')
th('Delete Link')
}
}
tbody{
unusedAttachmentCases.each(){
unusedAttachmentCase->
tr{
td(attachmentCasesMap.get(unusedAttachmentCase).get('space'))
td{
a(href:[CONFLUENCE BASE DIR]+
attachmentCasesMap.get(unusedAttachmentCase).get('pagePath')){
p(attachmentCasesMap.get(unusedAttachmentCase).get('page'))
}
}
td(attachmentCasesMap.get(unusedAttachmentCase).get('pageId'))
td(attachmentCasesMap.get(unusedAttachmentCase).get('name'))
td(attachmentCasesMap.get(unusedAttachmentCase).get('size'))
td{
a(href:[CONFLUENCE BASE DIR]+attachmentCasesMap.get(unusedAttachmentCase).get('path')){
p('Download Link')
}
}
td{
a([CONFLUENCE BASE DIR]+'/lockpoint/confirmattachmentremoval.action?pageId='
+attachmentCasesMap.get(unusedAttachmentCase).get('pageId')+'&fileName='
+attachmentCasesMap.get(unusedAttachmentCase).get('name')){
p('Delete Link')
}
}
}
}
}

}

/////////////////////////////////
//Write Page in Confluence
/////////////////////////////////

//Define Space and Page for output
def outputPage=pageManager.getPage("MySpace", "MyPage")


def oldVersion=outputPage.clone()
def contentEntityObjectOut = outputPage.getEntity()
contentEntityObjectOut.setBodyAsString(writer.toString())
pageManager.saveContentEntity(outputPage, oldVersion, DefaultSaveContext.BULK_OPERATION)

//Finally write in console
return writer

 

2 comments

Fabienne Gerhard
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
November 25, 2020

Hi @ABoerio 

this looks really interesting - especially at the end of the year a good "house-clean" -> it's time for!

Definitely gonna give it a try! Thanks for sharing 😊

ABoerio December 1, 2020

Corrected this section of my previous code

Immagine 2020-12-01 191642.jpg

Added .title and .key

Ciao, Andrea

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events