Skip to content

Commit

Permalink
Dateispeicher Browser (#196)
Browse files Browse the repository at this point in the history
Add a browser for the "Dateispeicher" Applet based on the existing
Parser.
  • Loading branch information
alessioC42 committed Mar 17, 2024
2 parents 9e34fd7 + 4f47be7 commit 398d8bf
Show file tree
Hide file tree
Showing 16 changed files with 568 additions and 108 deletions.
49 changes: 25 additions & 24 deletions app/lib/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ class SPHclient {
} on LanisException {
rethrow;
} catch (e) {
debugPrint(e.toString());
throw LoggedOffOrUnknownException();
}
}
Expand Down Expand Up @@ -364,8 +363,6 @@ class SPHclient {
var userDataTableBody =
document.querySelector("div.col-md-12 table.table.table-striped tbody");

//TODO find out how "Zugeordnete Eltern/Erziehungsberechtigte" is used in this scope

if (userDataTableBody != null) {
var result = {};

Expand Down Expand Up @@ -410,43 +407,47 @@ class SPHclient {
await deleteSubfoldersAndFiles(tempDir);
}

// This function generates a unique hash for a given source string
String generateUniqueHash(String source) {
var bytes = utf8.encode(source);
var digest = sha256.convert(bytes);

var shortHash = digest
.toString()
.replaceAll(RegExp(r'[^A-z0-9]'), '')
.substring(0, 12);

return shortHash;
}

/// This function checks if a file exists in the temporary directory downloaded by [downloadFile]
Future<bool> doesFileExist(String url, String filename) async {
var tempDir = await getTemporaryDirectory();
String urlHash = generateUniqueHash(url);
String folderPath = "${tempDir.path}/$urlHash";
String filePath = "$folderPath/$filename";

File existingFile = File(filePath);
return existingFile.existsSync();
}

///downloads a file from an URL and returns the path of the file.
///
///The file is stored in the temporary directory of the device.
///So calling the same URL twice will result in the same file and one Download.
Future<String> downloadFile(String url, String filename) async {
String generateUniqueHash(String source) {
var bytes = utf8.encode(source);
var digest = sha256.convert(bytes);

var shortHash = digest
.toString()
.replaceAll(RegExp(r'[^A-z0-9]'), '')
.substring(0, 6);

return shortHash;
}

try {
var tempDir = await getTemporaryDirectory();

// To ensure unique file names, we store each file in a folder
// with a hashed value of the download URL.
// It is necessary for a teacher to upload files with unique file names.
String urlHash = generateUniqueHash(url);

String folderPath = "${tempDir.path}/$urlHash";
String savePath = "$folderPath/$filename";

// Check if the folder exists, create it if not
Directory folder = Directory(folderPath);
if (!folder.existsSync()) {
folder.createSync(recursive: true);
}

// Check if the file already exists
File existingFile = File(savePath);
if (existingFile.existsSync()) {
if (await doesFileExist(url, filename)) {
return savePath;
}

Expand Down
116 changes: 79 additions & 37 deletions app/lib/client/client_submodules/datastorage.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:html/parser.dart';

import '../../shared/exceptions/client_status_exceptions.dart';
import '../../shared/types/dateispeicher_node.dart';
import '../client.dart';

Expand All @@ -12,45 +15,84 @@ class DataStorageParser {
dio = dioClient;
}

Future<dynamic> getNode(int nodeID) async {
Future<dynamic> searchFiles(String query) async {
final response = await dio.get(
"https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID");
var document = parse(response.data);
List<FileNode> files = [];
List<String> headers = document
.querySelectorAll("table#files thead th")
.map((e) => e.text)
.toList();
for (var file in document.querySelectorAll("table#files tbody tr")) {
final fields = file.querySelectorAll("td");
var name = fields[headers.indexOf("Name")].text.trim();
var aenderung = fields[headers.indexOf("Änderung")].text.trim();
var groesse = fields[headers.indexOf("Größe")].text.trim();
var id = int.parse(file.attributes["data-id"]!.trim());
files.add(FileNode(
name,
id,
"https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id",
aenderung,
groesse));
}
List<FolderNode> folders = [];
for (var folder in document.querySelectorAll(".folder")) {
var name = folder.querySelector(".caption")!.text.trim();
var desc = folder.querySelector(".desc")!.text.trim();
var subfolders = int.tryParse(RegExp(r"\d+")
.firstMatch(folder
.querySelector("[title=\"Anzahl Ordner\"]")
?.text
.trim() ??
"")
?.group(0) ??
"") ??
0;
var id = int.parse(folder.attributes["data-id"]!);
folders.add(FolderNode(name, id, subfolders, desc));
"https://start.schulportal.hessen.de/dateispeicher.php",
queryParameters: {
"q": query,
"a": "searchFiles"
},
data: {
"q": query,
"a": "searchFiles"
},
options: Options(
contentType: "application/x-www-form-urlencoded"
)
);
final data = jsonDecode(response.data);
return data[0];
}

Future<(List<FileNode>, List<FolderNode>)> getNode(int nodeID) async {
try {
late final Response response;
try {
response = await dio.get(
"https://start.schulportal.hessen.de/dateispeicher.php?a=view&folder=$nodeID");
} catch (e) {
throw NoConnectionException();
}

var document = parse(response.data);
List<FileNode> files = [];
List<String> headers = document
.querySelectorAll("table#files thead th")
.map((e) => e.text)
.toList();
for (var file in document.querySelectorAll("table#files tbody tr")) {
final fields = file.querySelectorAll("td");
String? hinweis =
fields[headers.indexOf("Name")].querySelector("small")?.text.trim();
if (hinweis != null) {
fields[headers.indexOf("Name")].querySelector("small")?.text = "";
}
var name = fields[headers.indexOf("Name")].text.trim();
var aenderung = fields[headers.indexOf("Änderung")].text.trim();
var groesse = fields[headers.indexOf("Größe")].text.trim();
var id = int.parse(file.attributes["data-id"]!.trim());
files.add(FileNode(
name: name,
id: id,
downloadUrl: "https://start.schulportal.hessen.de/dateispeicher.php?a=download&f=$id",
aenderung: aenderung,
groesse: groesse,
hinweis: hinweis,
));
}

List<FolderNode> folders = [];
for (var folder in document.querySelectorAll(".folder")) {
var name = folder.querySelector(".caption")!.text.trim();
var desc = folder.querySelector(".desc")!.text.trim();
var subfolders = int.tryParse(RegExp(r"\d+")
.firstMatch(folder
.querySelector("[title=\"Anzahl Ordner\"]")
?.text
.trim() ??
"")
?.group(0) ??
"") ??
0;
var id = int.parse(folder.attributes["data-id"]!);
folders.add(FolderNode(name, id, subfolders, desc));
}
return (files, folders);
} on NoConnectionException {
rethrow;
} catch (e) {
throw LoggedOffOrUnknownException();
}
return (files, folders);
}

Future<dynamic> getRoot() async {
Expand Down
1 change: 0 additions & 1 deletion app/lib/client/client_submodules/substitutions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ class SubstitutionsParser {

try {
var dates = await getSubstitutionDates();
debugPrint(dates.toString());

if (dates.isEmpty) {
return getVplanNonAJAX();
Expand Down
13 changes: 12 additions & 1 deletion app/lib/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:sph_plan/client/client.dart';
import 'package:sph_plan/view/calendar/calendar.dart';
import 'package:sph_plan/view/conversations/conversations.dart';
import 'package:sph_plan/view/data_storage/data_storage.dart';
import 'package:sph_plan/view/mein_unterricht/mein_unterricht.dart';
import 'package:sph_plan/view/settings/settings.dart';
import 'package:sph_plan/view/bug_report/send_bugreport.dart';
Expand Down Expand Up @@ -86,6 +87,17 @@ class _HomePageState extends State<HomePage> {
enableBottomNavigation: true,
enableDrawer: true,
body: const MeinUnterrichtAnsicht()),
Destination(
label: "Dateispeicher",
icon: const Icon(Icons.folder_copy),
selectedIcon: const Icon(Icons.folder_copy_outlined),
isSupported: client.doesSupportFeature(SPHAppEnum.dateispeicher),
enableBottomNavigation: false,
enableDrawer: true,
action: (context) => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DataStorageAnsicht()),
)),
Destination(
label: "Lanis im Browser öffnen",
icon: const Icon(Icons.open_in_new),
Expand Down Expand Up @@ -307,7 +319,6 @@ class _HomePageState extends State<HomePage> {
indexNavbarTranslationLayer.add(null);
}
}

return NavigationBar(
destinations: barDestinations,
selectedIndex: indexNavbarTranslationLayer[selectedDestinationDrawer]!,
Expand Down
45 changes: 45 additions & 0 deletions app/lib/shared/launch_file.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:open_file/open_file.dart';

import '../client/client.dart';

void launchFile(BuildContext context, String url, String filename, String filesize, Function callback) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Download... $filesize"),
content: const Center(
heightFactor: 1.1,
child: CircularProgressIndicator(),
),
);
});
client
.downloadFile(url, filename)
.then((filepath) {
Navigator.of(context).pop();

if (filepath == "") {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Fehler!"),
content: Text(
"Beim Download der Datei $filename ist ein unerwarteter Fehler aufgetreten. Wenn dieses Problem besteht, senden Sie uns bitte einen Fehlerbericht."),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
} else {
OpenFile.open(filepath);
callback(); // Call the callback function after the file is opened
}
});
}
4 changes: 3 additions & 1 deletion app/lib/shared/types/dateispeicher_node.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
class FileNode {
String name;
int id;
int? folderID;
String downloadUrl;
String groesse;
String aenderung;
String? hinweis;

FileNode(this.name, this.id, this.downloadUrl, this.aenderung, this.groesse);
FileNode({required this.name, required this.id, required this.downloadUrl, this.aenderung = "", this.groesse = "", this.hinweis, this.folderID});
}

class FolderNode {
Expand Down
20 changes: 20 additions & 0 deletions app/lib/view/data_storage/data_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:sph_plan/view/data_storage/root_view.dart';

class DataStorageAnsicht extends StatefulWidget {
const DataStorageAnsicht({super.key});

@override
State<StatefulWidget> createState() => _DataStorageAnsichtState();
}

class _DataStorageAnsichtState extends State<DataStorageAnsicht> {



@override
Widget build(BuildContext context) {
return const DataStorageRootView();
}
}

Loading

0 comments on commit 398d8bf

Please sign in to comment.