Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dateispeicher Browser #196

Merged
merged 7 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}
}