diff --git a/library/src/commonMain/kotlin/ink/literate/pawnote/api/assignment-upload-file.kt b/library/src/commonMain/kotlin/ink/literate/pawnote/api/assignment-upload-file.kt new file mode 100644 index 0000000..909e9ff --- /dev/null +++ b/library/src/commonMain/kotlin/ink/literate/pawnote/api/assignment-upload-file.kt @@ -0,0 +1,53 @@ +package ink.literate.pawnote.api + +import ink.literate.pawnote.api.helpers.createEntityID +import ink.literate.pawnote.core.RequestFN +import ink.literate.pawnote.core.RequestUpload +import ink.literate.pawnote.models.DocumentKind +import ink.literate.pawnote.models.EntityState +import ink.literate.pawnote.models.SessionHandle +import ink.literate.pawnote.models.TabLocation +import ink.literate.pawnote.models.errors.UploadSizeError +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.* +import java.io.File + +suspend fun assignmentUploadFile (session: SessionHandle, assignmentID: String, file: File, fileName: String) { + // Check if the file can be uploaded. + // Otherwise we'll get an error during the upload. + + val fileSize = file.length() + val maxFileSize = session.user.authorizations.maxAssignmentFileUploadSize + + if (fileSize > maxFileSize) + throw UploadSizeError(maxFileSize) + + // Ask to the server to store the file for us. + val fileUpload = RequestUpload(session, "SaisieTAFARendreEleve", file, fileName) + fileUpload.send() + + val request = RequestFN(session.information, "SaisieTAFARendreEleve", Json.encodeToString( + buildJsonObject { + putJsonObject("_Signature_") { + put("onglet", TabLocation.Assignments.code) + } + + putJsonObject("donnees") { + putJsonArray("listeFichiers") { + addJsonObject { + put("E", EntityState.CREATION.code) + put("G", DocumentKind.FILE.code) + put("L", fileName) + put("N", createEntityID()) + put("idFichier", fileUpload.id) + putJsonObject("TAF") { + put("N", assignmentID) + } + } + } + } + } + )) + + request.send() +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/ink/literate/pawnote/api/helpers/entity-id.kt b/library/src/commonMain/kotlin/ink/literate/pawnote/api/helpers/entity-id.kt new file mode 100644 index 0000000..2dd46c6 --- /dev/null +++ b/library/src/commonMain/kotlin/ink/literate/pawnote/api/helpers/entity-id.kt @@ -0,0 +1,12 @@ +package ink.literate.pawnote.api.helpers + +/** + * Defaults to `-1000`, used to generate IDs for newly created objects. + * Whenever an object is created using this ID, it should be decremented by `1`. + */ +var id = -1000 + +fun createEntityID(): Int { + id -= 1 + return id +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/ink/literate/pawnote/core/request-upload.kt b/library/src/commonMain/kotlin/ink/literate/pawnote/core/request-upload.kt new file mode 100644 index 0000000..e76372d --- /dev/null +++ b/library/src/commonMain/kotlin/ink/literate/pawnote/core/request-upload.kt @@ -0,0 +1,75 @@ +package ink.literate.pawnote.core + +import ink.literate.pawnote.api.private.AES +import ink.literate.pawnote.api.private.aesKeys +import ink.literate.pawnote.models.SessionHandle +import ink.literate.pawnote.models.errors.UploadFailedError +import io.ktor.client.request.* +import io.ktor.client.request.forms.* +import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.util.* +import io.ktor.utils.io.* +import kotlinx.datetime.Clock +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.io.File +import java.nio.file.Files + +@OptIn(InternalAPI::class) +class RequestUpload(private val session: SessionHandle, functionName: String, file: File, private val fileName: String) { + val id = "selecfile_1_${Clock.System.now().toEpochMilliseconds()}" + var order: String = "" + + private var url = "" + private var form: List? = null + + + init { + session.information.order++ + + val keys = aesKeys(session.information) + + order = AES.encrypt(session.information.order.toString().toByteArray(), keys.key, keys.iv) + + val form = formData { + append("\"numeroOrdre\"", order) + append("\"numeroSession\"", session.information.id.toString()) + append("\"nomRequete\"", functionName) + append("\"idFichier\"", id) + append("\"md5\"", "") + append("\"files[]\"", file.readBytes(), Headers.build { + append(HttpHeaders.ContentDisposition, "filename=\"${fileName}\"") + append(HttpHeaders.ContentType, Files.probeContentType(file.toPath())) + }) + } + + this.form = form + this.url = session.information.url + "/uploadfilesession/${session.information.accountKind.code}/${session.information.id}" + } + + suspend fun send() { + var state = 3 + + val url = this.url + + while (state == 3) { + val response = this.session.client.submitFormWithBinaryData(url = url,form!!) { + headers { + append(HttpHeaders.ContentDisposition, "attachment; filename=\"${fileName}\"") + } + } + + val json = Json.parseToJsonElement(response.content.readUTF8Line()!!) + state = json.jsonObject["etat"]!!.jsonPrimitive.int + } + + // Even if there's an error, it bumped. + this.session.information.order++ + + if (state == 0 || state == 2) + throw UploadFailedError() + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/ink/literate/pawnote/models/DocumentKind.kt b/library/src/commonMain/kotlin/ink/literate/pawnote/models/DocumentKind.kt new file mode 100644 index 0000000..bc35e98 --- /dev/null +++ b/library/src/commonMain/kotlin/ink/literate/pawnote/models/DocumentKind.kt @@ -0,0 +1,9 @@ +package ink.literate.pawnote.models + +enum class DocumentKind (val code: Int) { + URL(0), + FILE(1), + CLOUD(2), + KIOSK_LINK(3), + CONFERENCE_LINK(4) +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/ink/literate/pawnote/models/errors/upload.kt b/library/src/commonMain/kotlin/ink/literate/pawnote/models/errors/upload.kt new file mode 100644 index 0000000..39b2e7e --- /dev/null +++ b/library/src/commonMain/kotlin/ink/literate/pawnote/models/errors/upload.kt @@ -0,0 +1,5 @@ +package ink.literate.pawnote.models.errors + +class UploadSizeError (maxSizeInBytes: Double) : Error("The file you are trying to upload is too large, maximum allowed is $maxSizeInBytes bytes") + +class UploadFailedError : Error("The file upload failed") \ No newline at end of file