Первый коммит.

This commit is contained in:
Nikita Osokin 2023-11-09 23:11:16 +05:00
commit f8b8e8c02b
6 changed files with 961 additions and 0 deletions

34
404.html Normal file
View file

@ -0,0 +1,34 @@
<!--
Copyright (c) 2023 Nikita Osokin.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>:(</h1>
<p>Страница не найдена.</p>
</body>
</html>

20
COPYRIGHT Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2023 Nikita Osokin.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

247
editpage.html Normal file
View file

@ -0,0 +1,247 @@
<!--
Copyright (c) 2023 Nikita Osokin.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<input type="date" id="date" />
<p><p/>
<table id="schedule" border=1>
<tbody>
<tr class="schedRow">
<td>
1.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
2.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
3.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
4.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
5.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
6.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
<tr class="schedRow">
<td>
7.
</td>
<td>
<input type="checkbox" checked />
</td>
<td>
<input type="text" />
</td>
<td>
<input type="text" />
</td>
</tr>
</tbody>
</table>
<p></p>
<input id="password" type="password" placeholder="Пароль" />
<input id="send" type="button" onclick="sendEdits()" value="Отправить изменения" />
<script>
function numToUint8Array(num, arr, off) {
for(var i = 0; i < 8; i++) {
arr[off + 7 - i] = num % 0x100;
num >>= 8;
}
}
function displayStatus(response) {
let sendButton = document.getElementById("send");
let statusText;
if(response.status == 200) {
statusText = document.createTextNode(" Расписание успешно изменено.");
} else if(response.status == 400) {
statusText = document.createTextNode(" Ошибка при изменении расписания: неправильный формат данных.");
} else if(response.status == 401) {
statusText = document.createTextNode(" Неверный пароль.");
} else if(response.status == 500) {
statusText = document.createTextNode(" Ошибка при изменении расписания: внутренняя ошибка сервера. Попробуйте отправить изменения ещё раз прямо сейчас или чуть позже.");
}
sendButton.after(statusText);
}
function displayNetError(e) {
let sendButton = document.getElementById("send");
let statusText = document.createTextNode(" Сетевая ошибка при отправке изменений. Возможно, вам стоит проверить своё интернет-соединение или подождать, а затем отправить изменения снова.");
sendButton.after(statusText);
}
function sendEdits() {
const schedule = document.getElementById("schedule");
let lessonsCount = 0;
let indices = [];
let names = [];
let namesLens = [];
let descs = [];
let descsLens = [];
let index = 0;
let textEncoder = new TextEncoder();
let password = textEncoder.encode(document.getElementById("password").value);
let bufferSize = 24 + password.length;
for(const row of schedule.children[0].children) {
if(row.children[1].children[0].checked) {
lessonsCount++;
indices.push(index);
let name = textEncoder.encode(row.children[2].children[0].value);
names.push(name);
namesLens.push(name.length);
let desc = textEncoder.encode(row.children[3].children[0].value);
descs.push(desc);
descsLens.push(desc.length);
bufferSize += 24 + name.length + desc.length;
}
index++;
}
let data = new Uint8Array(new ArrayBuffer(bufferSize));
numToUint8Array(password.length, data, 0);
for(let i = 0; i < password.length; i++) {
data[8 + i] = password[i];
}
let currentOff = 16 + password.length;
numToUint8Array(lessonsCount, data, currentOff);
currentOff += 8;
for(let i = 0; i < lessonsCount; i++) {
numToUint8Array(indices[i], data, currentOff);
currentOff += 8;
numToUint8Array(namesLens[i], data, currentOff);
currentOff += 8;
for(let j = 0; j < namesLens[i]; j++) {
data[currentOff + j] = names[i][j];
}
currentOff += namesLens[i];
numToUint8Array(descsLens[i], data, currentOff);
currentOff += 8;
for(let j = 0; j < descsLens[i]; j++) {
data[currentOff + j] = descs[i][j];
}
currentOff += descsLens[i];
}
let params = "?c=" + (new URLSearchParams(document.location.search)).get("c")
let day = document.getElementById("date").valueAsNumber;
if(day != NaN) {
params += "&d=" + (day / 86400000).toString();
}
fetch(
params,
{
method: "POST",
body: data
}
).then(
displayStatus,
displayNetError
);
}
</script>
</body>
</html>

123
index.html Normal file
View file

@ -0,0 +1,123 @@
<!--
Copyright (c) 2023 Nikita Osokin.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>Добро пожаловать!</h1>
<p>Здравствуй, клиент!</p>
<p>Список классов:</p>
<table id="classes" border=1>
<tbody>
</tbody>
</table>
<script>
const classes = document.getElementById("classes");
function uint8ArrayToNum(arr, off) {
let result = 0;
for(var i = 0; i < 8; i++) {
result <<= 8;
result |= arr[off + i];
}
return result;
}
function displayGetError(e) {
let p = document.createElement("p");
p.appendChild(document.createTextNode("Не удалось получить список классов от сервера."));
classes.before(p);
}
function getClasses() {
fetch(
"../getClassesList",
{
method: "GET"
}
).then(
getData,
displayGetError
);
}
function getData(response) {
if(response.status != 200) {
displayGetError();
return;
}
response.arrayBuffer().then(
displayData,
displayGetError
);
}
function displayData(dataBuffer) {
let data = new Uint8Array(dataBuffer);
let classesCount = uint8ArrayToNum(data, 0);
let textDecoder = new TextDecoder();
let currentOff = 8;
for(let i = 0; i < classesCount; i++) {
let nRow = document.createElement("tr");
let len = uint8ArrayToNum(data, currentOff);
currentOff += 8;
let className = textDecoder.decode(data.slice(currentOff, currentOff + len));
let nCell1 = document.createElement("th");
nCell1.appendChild(document.createTextNode(className));
currentOff += len;
nRow.appendChild(nCell1);
let nCell2 = document.createElement("th");
let nA1 = document.createElement("a");
nA1.href = "../schedule?c=" + className;
nA1.appendChild(document.createTextNode("Расписание"));
nCell2.appendChild(nA1);
nRow.appendChild(nCell2);
let nCell3 = document.createElement("th");
let nA2 = document.createElement("a");
nA2.href = "../editpage?c=" + className;
nA2.appendChild(document.createTextNode("Изменить"));
nCell3.appendChild(nA2);
nRow.appendChild(nCell3);
classes.children[0].appendChild(nRow);
}
}
getClasses();
</script>
</body>
</html>

411
main.go Normal file
View file

@ -0,0 +1,411 @@
// citrusNikOs' journal -- минималистичный сервер для веб-журнала с расписанием.
//
// Copyright (c) 2023 Nikita Osokin.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package main
import (
"fmt"
"io"
"os"
"sync"
"net/http"
"time"
"strconv"
)
var schedFileRWMutex sync.RWMutex
var classesRWMutexes map[string]*sync.RWMutex
var classesNames []string
func isValidClass(class string) bool {
for i := 0; i < len(classesNames); i++ {
if classesNames[i] == class {
return true
}
}
return false
}
func getCurrentDay() uint64 {
return uint64(time.Now().Unix()) / 86400
}
func isValidDay(day string) bool {
if len(day) == 0 || (day[0] == '0' && len(day) != 1) {
return false
}
for i := 0; i < len(day); i++ {
if day[i] < '0' || day[i] > '9' {
return false
}
}
return true
}
func read8BytesBE(reader io.Reader, file *os.File) (uint64, error) {
var result uint64
var err error
var count int
buffer := make([]byte, 8)
count, err = reader.Read(buffer)
if count != 8 {
return 0, err
}
for i := 0; i < 8; i++ {
result <<= 8
result |= uint64(buffer[i])
}
_, err = (*file).Write(buffer)
if err != nil {
return 0, err
}
return result, nil
}
func read8BytesBENoCopy(reader io.Reader) (uint64, error) {
var result uint64
var err error
var count int
buffer := make([]byte, 8)
count, err = reader.Read(buffer)
if count != 8 {
return 0, err
}
for i := 0; i < 8; i++ {
result <<= 8
result |= uint64(buffer[i])
}
return result, nil
}
func writeUint64BE(writer io.Writer, num uint64) error {
buffer := make([]byte, 8)
bufferI := 0
for i := 56; i >= 0; i -= 8 {
buffer[bufferI] = uint8((num >> i) & 0xFF)
bufferI++
}
_, err := writer.Write(buffer)
if err != nil {
return err
}
return nil
}
func respondWithFile(writer http.ResponseWriter, request *http.Request, filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println(" !!! Файл", filename, "не найден!")
return
}
stat, err := file.Stat()
if err != nil {
fmt.Println(" !!! Не удалось получить информацию о файле", filename + "!")
return
}
buffer := make([]byte, stat.Size())
count, err := file.Read(buffer);
if err != nil {
fmt.Println(" !!! Не удалось прочитать содержимое файла", filename + "!")
return
}
if int64(count) != stat.Size() {
fmt.Println(" *** Файл", filename, "был прочитан не полностью.")
}
writer.Write(buffer)
}
func handleRootAnd404(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/" {
writer.WriteHeader(http.StatusNotFound)
respondWithFile(writer, request, "404.html")
} else {
respondWithFile(writer, request, "index.html")
}
}
func handleGetClassesList(writer http.ResponseWriter, request *http.Request) {
err := writeUint64BE(writer, uint64(len(classesNames)))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
for i := 0; i < len(classesNames); i++ {
err = writeUint64BE(writer, uint64(len(classesNames[i])))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
_, err = writer.Write([]uint8(classesNames[i]))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
}
}
func handleEditPage(writer http.ResponseWriter, request *http.Request) {
if request.Method == http.MethodPost {
queryValues := request.URL.Query()
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
writer.WriteHeader(http.StatusBadRequest)
return
}
var day string
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
day = strconv.FormatUint(getCurrentDay(), 10)
} else {
day = queryValues["d"][0]
}
passwordLen, err := read8BytesBENoCopy(request.Body);
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return
}
passwordFile, err := os.Open("classes/" + queryValues["c"][0] + "/password")
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
}
passwordFileInfo, err := passwordFile.Stat()
if err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusInternalServerError)
return
}
if uint64(passwordFileInfo.Size() - 1) != passwordLen {
passwordFile.Close()
writer.WriteHeader(http.StatusUnauthorized)
return
}
userPassword := make([]byte, passwordLen)
passwordCount, err := request.Body.Read(userPassword)
if uint64(passwordCount) != passwordLen || err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusBadRequest)
return
}
password := make([]byte, passwordLen)
passwordCount, err = passwordFile.Read(password)
if uint64(passwordCount) != passwordLen || err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusInternalServerError)
return
}
for i := uint64(0); i < passwordLen; i++ {
if password[i] != userPassword[i] {
passwordFile.Close()
writer.WriteHeader(http.StatusUnauthorized)
return
}
}
tmpSchedFile, err := os.CreateTemp("", "sched")
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return
}
layoutID, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil || layoutID != 0 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove(tmpSchedFile.Name())
return
}
lessonsCount, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil || lessonsCount > 7 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
buffer := make([]byte, 1)
lastNumber := uint64(0)
isFirst := true
for i := uint64(0); i < lessonsCount; i++ {
number, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
if !isFirst && number <= lastNumber {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
isFirst = false
lastNumber = number
nameLen, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
for j := uint64(0); j < nameLen; j++ {
beenRead, _ := request.Body.Read(buffer)
_, err := (*tmpSchedFile).Write(buffer)
if beenRead == 0 || err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
}
descrLen, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
for j := uint64(0); j < descrLen; j++ {
beenRead, _ := request.Body.Read(buffer)
_, err := (*tmpSchedFile).Write(buffer)
if beenRead == 0 || err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
}
}
beenRead, err := request.Body.Read(buffer)
if beenRead != 0 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
classesRWMutexes[queryValues["c"][0]].Lock()
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
os.Remove(schedFileName)
os.Rename((*tmpSchedFile).Name(), schedFileName)
classesRWMutexes[queryValues["c"][0]].Unlock()
} else {
respondWithFile(writer, request, "editpage.html")
}
}
/* TODO: в эту и похожие функции нужно добавть возможность отправить статус 500 */
func handleSchedule(writer http.ResponseWriter, request *http.Request) {
respondWithFile(writer, request, "schedule.html")
}
/* TODO: возможно стоит отправлять что-то кроме просто ошибки 500 */
func handleScheduleGet(writer http.ResponseWriter, request *http.Request) {
queryValues := request.URL.Query()
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
writer.WriteHeader(http.StatusBadRequest)
return
}
var day string
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
day = strconv.FormatUint(getCurrentDay(), 10)
} else {
day = queryValues["d"][0]
}
classesRWMutexes[queryValues["c"][0]].RLock()
defer classesRWMutexes[queryValues["c"][0]].RUnlock()
i := 0
var schedFile *os.File
var err error
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
for schedFile, err = os.Open(schedFileName); err != nil; schedFile, err = os.Open(schedFileName) {
if i == 1000 {
return
}
i++
}
_, err = io.Copy(writer, schedFile)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
schedFile.Close()
}
func main() {
classesRWMutexes = make(map[string]*sync.RWMutex)
var classesDir *os.File
var classes []os.DirEntry
var err error
classesDir, err = os.Open("classes")
if err != nil {
fmt.Printf("[!!!]Не удалось открыть директорию classes.\n")
return
}
classes, err = classesDir.ReadDir(0)
if err != nil {
fmt.Printf("[!!!]Не удалось прочитать содержимое директории classes.\n")
return
}
classesNames = make([]string, len(classes))
for i := 0; i < len(classes); i++ {
if !classes[i].IsDir() {
fmt.Printf("[!!!]Директория classes должна содержать только директории.\n")
return
}
classesNames[i] = classes[i].Name()
classesRWMutexes[classes[i].Name()] = new(sync.RWMutex)
}
http.HandleFunc("/", handleRootAnd404)
http.HandleFunc("/getClassesList", handleGetClassesList)
http.HandleFunc("/schedule", handleSchedule)
http.HandleFunc("/scheduleGet", handleScheduleGet)
http.HandleFunc("/editpage", handleEditPage)
http.ListenAndServe(":62314", nil)
}

126
schedule.html Normal file
View file

@ -0,0 +1,126 @@
<!--
Copyright (c) 2023 Nikita Osokin.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<input type=date id="date" />
<input type=button onclick="changeDay()" value="Получить расписание" />
<p></p>
<table id="schedule" border=1>
<tbody>
</tbody>
</table>
<script>
const schedule = document.getElementById("schedule");
function changeDay() {
document.location.assign("?c=" + (new URLSearchParams(document.location.search)).get("c") + "&d=" + (document.getElementById("date").valueAsNumber / 86400000).toString());
}
function uint8ArrayToNum(arr, off) {
let result = 0;
for(var i = 0; i < 8; i++) {
result <<= 8;
result |= arr[off + i];
}
return result;
}
function displayGetError(e) {
let p = document.createElement("p");
p.appendChild(document.createTextNode("Не удалось получить расписание от сервера."));
schedule.before(p);
}
function getSchedule() {
fetch(
"../scheduleGet" + document.location.search,
{
method: "GET"
}
).then(
getData,
displayGetError
);
}
function getData(response) {
if(response.status != 200) {
displayGetError();
return;
}
response.arrayBuffer().then(
displayData,
displayGetError
);
}
function displayData(dataBuffer) {
let data = new Uint8Array(dataBuffer);
let lessonsCount = uint8ArrayToNum(data, 8);
let textDecoder = new TextDecoder();
let currentOff = 16;
for(let i = 0; i < lessonsCount; i++) {
let nRow = document.createElement("tr");
let nCell1 = document.createElement("th");
nCell1.appendChild(document.createTextNode(uint8ArrayToNum(data, currentOff) + 1));
currentOff += 8;
nRow.appendChild(nCell1);
let len = uint8ArrayToNum(data, currentOff);
currentOff += 8;
let nCell2 = document.createElement("th");
nCell2.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
currentOff += len;
nRow.appendChild(nCell2);
len = uint8ArrayToNum(data, currentOff);
currentOff += 8;
let nCell3 = document.createElement("th");
nCell3.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
currentOff += len;
nRow.appendChild(nCell3);
schedule.children[0].appendChild(nRow);
}
}
let day = (new URLSearchParams(document.location.search)).get("d");
if(day != null) {
document.getElementById("date").valueAsNumber = day * 86400000;
}
getSchedule();
</script>
</body>
</html>