Skip to content

Commit

Permalink
feat(connector): replace IliasService.getCourseItems(Course) with tra…
Browse files Browse the repository at this point in the history
…versal method

this mimics the groovy way of traversing a file tree (http://docs.groovy-lang.org/latest/html/documentation/working-with-io.html#_traversing_file_trees)
  • Loading branch information
thetric committed Mar 7, 2017
1 parent d9787b0 commit dab41ab
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import groovy.transform.CompileStatic
*/
@CompileStatic
interface IliasService {
enum VisitResult {
CONTINUE, TERMINATE
}

// Session management

/**
Expand All @@ -40,18 +44,11 @@ interface IliasService {
* Finds all courses without any course content.
*
* @return all courses of the current user
* @see #getCourseItems(com.github.thetric.iliasdownloader.service.model.Course)
* @see #visit(com.github.thetric.iliasdownloader.service.model.CourseItem, groovy.lang.Closure)
*/
Collection<Course> getJoinedCourses()

/**
* Searches the selected courses with their child nodes <b>without downloading them</b>.
*
* @param course {@link Course} to search for. The course must not be modified
* @return new list with {@link CourseItem}s and their child nodes
* @see CourseItem
*/
Collection<? extends CourseItem> getCourseItems(Course course)
void visit(CourseItem courseItem, Closure<VisitResult> visitMethod)

/**
* Downloads the content of the {@link CourseFile} from the Ilias.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ final class WebIliasService implements IliasService {
}

@Override
Collection<? extends CourseItem> getCourseItems(Course course) {
return courseSyncService.searchAllItems(course, connectWithSessionCookies())
void visit(final CourseItem courseItem, final Closure<IliasService.VisitResult> visitMethod) {
courseSyncService.visit(courseItem, visitMethod, connectWithSessionCookies())
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.github.thetric.iliasdownloader.service.webparser.impl.course

import com.github.thetric.iliasdownloader.service.IliasService
import com.github.thetric.iliasdownloader.service.model.Course
import com.github.thetric.iliasdownloader.service.model.CourseItem
import org.apache.http.client.fluent.Executor

interface CourseSyncService {
Collection<Course> getJoinedCourses(Executor httpRequestExecutor)

Collection<? extends CourseItem> searchAllItems(Course course, Executor httpRequestExecutor)
IliasService.VisitResult visit(CourseItem courseItem, Closure<IliasService.VisitResult> visitMethod, Executor httpRequestExecutor)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.github.thetric.iliasdownloader.service.webparser.impl.course

import com.github.thetric.iliasdownloader.service.IliasService
import com.github.thetric.iliasdownloader.service.model.Course
import com.github.thetric.iliasdownloader.service.model.CourseFile
import com.github.thetric.iliasdownloader.service.model.CourseFolder
import com.github.thetric.iliasdownloader.service.model.CourseItem
import com.github.thetric.iliasdownloader.service.model.IliasItem
import com.github.thetric.iliasdownloader.service.webparser.impl.IliasItemIdStringParsingException
import com.github.thetric.iliasdownloader.service.webparser.impl.course.datetime.RelativeDateTimeParser
import com.github.thetric.iliasdownloader.service.webparser.impl.course.jsoup.JSoupParserService
Expand All @@ -26,30 +27,34 @@ import static java.time.format.DateTimeFormatter.ofPattern
@CompileStatic
@Log4j2
final class CourseSyncServiceImpl implements CourseSyncService {
private static final String COURSE_SELECTOR = "a[href*='_crs_'].il_ContainerItemTitle"
private static final String ITEM_CONTAINER_SELECTOR = '.il_ContainerListItem'
private static final String ITEM_TITLE_SELECTOR = 'a.il_ContainerItemTitle'
private static final String ITEM_PROPERTIES_SELECTOR = '.il_ItemProperty'

// these types should be ignored in logs
private static final Set<String> IGNORED_ITEM_TYPES = new HashSet<>(['frm', 'grp'])

private final WebIoExceptionTranslator exceptionTranslator

private final JSoupParserService jSoupFactoryService

private static final Pattern ITEM_URL_SPLIT_PATTERN = Pattern.compile("[_.]")

// for German date time format: 23. Sep 2016, 17:34
private static final DateTimeFormatter lastModifiedFormatter = ofPattern('dd. MMM yyyy, HH:mm', Locale.GERMAN)
private final RelativeDateTimeParser relativeDateTimeParser

private final WebIoExceptionTranslator exceptionTranslator

private final JSoupParserService jSoupParserService

private final String iliasBaseUrl
private final String courseOverview
private final String courseLinkPrefix


CourseSyncServiceImpl(WebIoExceptionTranslator webIoExceptionTranslator,
JSoupParserService jSoupParserService,
String iliasBaseUrl, String clientId,
RelativeDateTimeParser relativeDateTimeParser) {
this.exceptionTranslator = webIoExceptionTranslator
this.jSoupFactoryService = jSoupParserService
this.jSoupParserService = jSoupParserService
this.relativeDateTimeParser = relativeDateTimeParser

this.iliasBaseUrl = iliasBaseUrl
Expand Down Expand Up @@ -94,31 +99,43 @@ final class CourseSyncServiceImpl implements CourseSyncService {
}

@Override
Collection<? extends CourseItem> searchAllItems(Course course, Executor httpRequestExecutor) {
return findCourseItems(course, httpRequestExecutor)
IliasService.VisitResult visit(
final IliasItem courseItem,
final Closure<IliasService.VisitResult> visitMethod, final Executor httpRequestExecutor) {

for (IliasItem item : findItems(courseItem, httpRequestExecutor)) {
def visitResult = visitMethod(item)
if (visitResult == IliasService.VisitResult.TERMINATE) {
return IliasService.VisitResult.TERMINATE
}
if (isNodeItem(item)) {
def childResult = visit(item, visitMethod, httpRequestExecutor)
if (childResult == IliasService.VisitResult.TERMINATE) {
return IliasService.VisitResult.TERMINATE
}
}
}
return IliasService.VisitResult.CONTINUE
}

private Collection<? extends CourseItem> findCourseItems(Course course, Executor httpRequestExecutor) {
log.debug('Find all children for {}', course.name)
return searchItemsRecursively(course.url, httpRequestExecutor)
private boolean isNodeItem(IliasItem iliasItem) {
return iliasItem instanceof Course || iliasItem instanceof CourseFolder
}

private Collection<? extends CourseItem> searchItemsRecursively(String itemUrl, Executor httpRequestExecutor) {
def list = new ArrayList<>()
for (Element element : getItemContainersFromUrl(itemUrl, httpRequestExecutor)) {
def item = toCourseItem(element, httpRequestExecutor)
if (item) list << item
}
return list
private Collection<? extends IliasItem> findItems(final IliasItem courseItem, Executor httpRequestExecutor) {
return getItemContainersFromUrl(courseItem.url, httpRequestExecutor)
.collect({ toIliasItem(courseItem, it) })
.findAll()
}

private Elements getItemContainersFromUrl(String itemUrl, Executor httpRequestExecutor) {
return connectAndGetDocument(itemUrl, httpRequestExecutor).
select(CssSelectors.ITEM_CONTAINER_SELECTOR.getCssSelector())
// TODO replace with Kevin's Ilias Downloader 1 Method (directory output)
// --> Performance?
return connectAndGetDocument(itemUrl, httpRequestExecutor).select(ITEM_CONTAINER_SELECTOR)
}

private CourseItem toCourseItem(Element itemContainer, Executor httpRequestExecutor) {
Elements itemTitle = itemContainer.select(CssSelectors.ITEM_TITLE_SELECTOR.getCssSelector())
private IliasItem toIliasItem(IliasItem parent, Element itemContainer) {
Elements itemTitle = itemContainer.select(ITEM_TITLE_SELECTOR)
String itemName = itemTitle.text()
String itemUrl = itemTitle.attr('href')
String idString = getItemIdFromUrl(itemUrl)
Expand All @@ -129,26 +146,19 @@ final class CourseSyncServiceImpl implements CourseSyncService {

List<String> properties = getSanitizedItemProperties(itemContainer)

return createCourseItem(type, itemId, itemName, itemUrl, properties, httpRequestExecutor)
return createIliasItem(parent, type, itemId, itemName, itemUrl, properties)
}

private List<String> getSanitizedItemProperties(Element itemContainer) {
def itemProps = new ArrayList<>()
for (Element element : getItemProperties(itemContainer)) {
String text = trimAllWhitespaces(element)
if (text && !text.empty) {
itemProps << text
}
}
return itemProps
getItemProperties(itemContainer).collect { trimAllWhitespaces(it) }.findAll()
}

private String trimAllWhitespaces(Element element) {
return trimNbspFromString(element.text()).trim()
}

private static Elements getItemProperties(Element itemContainer) {
return itemContainer.select(CssSelectors.ITEM_PROPERTIES_SELECTOR.getCssSelector())
return itemContainer.select(ITEM_PROPERTIES_SELECTOR)
}

/**
Expand All @@ -166,23 +176,22 @@ final class CourseSyncServiceImpl implements CourseSyncService {
return itemUrl.replaceFirst("${iliasBaseUrl}goto_ilias-fhdo_", '')
}

private CourseItem createCourseItem(
private IliasItem createIliasItem(
IliasItem parent,
String type, int itemId,
String itemName, String itemUrl, List<String> properties,
Executor httpRequestExecutor) {
String itemName, String itemUrl, List<String> properties) {
switch (type) {
case 'fold':
Collection<? extends CourseItem> courseItems = searchItemsRecursively(itemUrl, httpRequestExecutor)
return new CourseFolder(itemId, itemName, itemUrl, courseItems)
return new CourseFolder(itemId, itemName, itemUrl, parent)
case 'file':
log.debug("itemId {}, name {}, url {}", itemId, itemName, itemUrl)
log.debug('itemId {}, name {}, url {}', itemId, itemName, itemUrl)
String fileType = properties.get(0)
if (properties.size() < 3) {
throw new IllegalArgumentException("No last modified timestamp present! Item with " +
"ID $itemId (URL: $itemUrl) has only following properties: $properties")
"ID $itemId (URL: $itemUrl) has only following properties: $properties")
}
LocalDateTime lastModified = parseDateString(properties.get(2))
return new CourseFile(itemId, "$itemName.$fileType", itemUrl, lastModified)
return new CourseFile(itemId, "$itemName.$fileType", itemUrl, parent, lastModified)
default:
if (!IGNORED_ITEM_TYPES.contains(type)) {
log.warn('Unknown type: {}, URL: {}', type, itemUrl)
Expand All @@ -206,7 +215,7 @@ final class CourseSyncServiceImpl implements CourseSyncService {
def content = httpRequestExecutor.execute(Request.Get(url))
.returnContent()
def html = content.asString(StandardCharsets.UTF_8)
return jSoupFactoryService.parse(html)
return jSoupParserService.parse(html)
} catch (IOException e) {
log.error("Could not GET: $url", e)
throw exceptionTranslator.translate(e)
Expand Down

This file was deleted.

0 comments on commit dab41ab

Please sign in to comment.