Skip to content

Conversation

@Artyom321
Copy link
Collaborator

No description provided.

Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ещё из общего, постарайся сообщения об ошибках поподробнее писать, а то пока по ним не всегда можно понять что конкретно не так пошло со всеми подробностями

@grphil grphil requested a review from Copilot April 6, 2025 13:42
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • go.mod: Language not supported

Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну у меня тут главное замечание по тому, что формат запроса к стораджу довольно странный, после того, как это финализируем, досмотрю ревью

@Artyom321 Artyom321 force-pushed the dev/storage branch 3 times, most recently from 5e3a5cc to 49b4d2e Compare April 12, 2025 13:11
Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Глобально довольно неплохо, но мне очень не нравится что ты просишь имя файла указать, написал в комментах как это поправить можно

if request.TestID == 0 {
return "", errors.New("TestID is not specified for test resource")
}
return fmt.Sprintf("tests/%s/%s", strconv.FormatUint(request.TestID, 10), request.StorageFilename), nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А, я понял зачем тебе StorageFilename. Смотри, мне кажется проще будет захардкодить определенное имя для разных типов файлов, чтобы всем было проще пользоваться стораджем.

  • SourceCode это source/%s с именем файла (лучше имя сорса сохранить)
  • CompiledBinary это soultion
  • CompileOutput это compile.out
  • TestInput это tests/%02d с номером теста (тут общепринятое название с полигона)
  • TestAnswer это tests/%02d.a с номером теста (тут общепринятое название с полигона)
  • TestOutput это tests/%02d.out с номером теста
  • TestStderr это tests/%02d.err c номером теста
  • CheckerOutput это tests/%02d.check с номером теста
  • Checker это check
  • Interactor это interactor

А дальше можно делать так:
При загрузке файла у тебя кастомное имя просится только для сорса, а остальных случаях ты говоришь что не надо мне кастомных имен.
При скачивании файла если кастомное имя захардкожено, то берешь его, иначе смотришь на папку, содержащую файл - если там единственный файл, выдаешь его, иначе падаешь с ошибкой и просьбой подробнее указать имя файла

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я долго над этим думал, и решил сделать везде кастомное имя, потому что в полигоне например checker/validator/generator можно как угодно называть. Но если хочешь, давай докостыляю...

Copy link
Collaborator

@grphil grphil Apr 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну можно checker, validator, generator, interactor в отдельные папочки поместить и как сорс брать (если в папке один файл, значит это он, иначе падаем с ошибкой)

Copy link
Collaborator Author

@Artyom321 Artyom321 Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я не стал добавлять логику с проверкой что файл единственный в папке. Там и так код не очень, не очень хочется делать разную логику составления имени файла при upload и get)

}
}

func getFilepath(request *storageconn.Request) (string, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Есть идеи как это написать лучше?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут красиво вряд ли получится написать, но я бы так вообще сделал бы: в getInfoFromRequest ты создаешь тип resourceInfo, сохраняя туда request. А дальше для resourceInfo делаешь методы, которые заполняют его поля (вместо текущих функций которые строки возвращают).

Соответственно у тебя получаются методы:

  • getDataType
  • getDataID
  • getFilepath

Теперь про getFilepath, тут можно разделить на 2 части: папка (getFilepathFolder) и название файла (parseFilepathFilename)

parseFilepathFolder - ты делаешь стандартный switch, в котором в куче случаев просто не делаешь ничего, а для особых случаев (тест, исходник решения, чекер, валидатор) в поле filepath пишешь имя папки.

parseFilepathFilename - тут заводишь переменную filename в случае фиксированных имен файлов, записываешь его. Если же тип ресурса позволяет указывать Filename, то в одном и том же case для них всех ты чекаешь, указан ли Filename. Если указан, дописываешь его, если не указан, то в отдельное поле внутри структуры resourceInfo указываешь, что файл надо найти внутри папки. А дальше этим поиском пусть filesystem занимается, где все будет консистентно. Дальше сответственно если folder не пустая, дописываешь к ней имя файла, иначе ставишь его сразу на место filepath.

Ещё кстати парсинг тестов можно как нибудь удобно объединить, типа там сделать отдельный case на них всех, проверить что Test есть, и потом в какой нибудь отдельной функции построить суффикс уже четким switch по всем видам тестов

При таком подходе количество switch case будет ещё больше, но зато минимизируется копипаста внутри них

Copy link
Collaborator Author

@Artyom321 Artyom321 Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если указан, дописываешь его, если не указан, то в отдельное поле внутри структуры resourceInfo указываешь, что файл надо найти внутри папки. - Я всё-таки оставил логику, что в таком случае берётся файл с дефолтным названием (которое есть для любого типа). У меня нет понимания почему это плохо (ну только если ты боишься что не будешь его указывать когда надо, и при этом в папке несколько файлов), но если хочешь, то могу дописать.

}
}

func getFilepath(request *storageconn.Request) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут красиво вряд ли получится написать, но я бы так вообще сделал бы: в getInfoFromRequest ты создаешь тип resourceInfo, сохраняя туда request. А дальше для resourceInfo делаешь методы, которые заполняют его поля (вместо текущих функций которые строки возвращают).

Соответственно у тебя получаются методы:

  • getDataType
  • getDataID
  • getFilepath

Теперь про getFilepath, тут можно разделить на 2 части: папка (getFilepathFolder) и название файла (parseFilepathFilename)

parseFilepathFolder - ты делаешь стандартный switch, в котором в куче случаев просто не делаешь ничего, а для особых случаев (тест, исходник решения, чекер, валидатор) в поле filepath пишешь имя папки.

parseFilepathFilename - тут заводишь переменную filename в случае фиксированных имен файлов, записываешь его. Если же тип ресурса позволяет указывать Filename, то в одном и том же case для них всех ты чекаешь, указан ли Filename. Если указан, дописываешь его, если не указан, то в отдельное поле внутри структуры resourceInfo указываешь, что файл надо найти внутри папки. А дальше этим поиском пусть filesystem занимается, где все будет консистентно. Дальше сответственно если folder не пустая, дописываешь к ней имя файла, иначе ставишь его сразу на место filepath.

Ещё кстати парсинг тестов можно как нибудь удобно объединить, типа там сделать отдельный case на них всех, проверить что Test есть, и потом в какой нибудь отдельной функции построить суффикс уже четким switch по всем видам тестов

При таком подходе количество switch case будет ещё больше, но зато минимизируется копипаста внутри них

return &req, nil
}

func parseDataType(resourseInfo *filesystem.ResourceInfo) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А может все функции вида parse* просто в методы ResourceInfo перенести? Зачем им отдельно лежать?

case resource.CompiledBinary, resource.CompileOutput, resource.CheckerOutput:
return "", nil
case resource.SourceCode:
return "sources", nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вообще хз, зачем эту папку во множественном числе указывать. А ещё в идеале все такие имена в константы в одном месте вынести

return "", errors.New("TestID is not specified for test resource")
}
return getTestResourceFilename(request.TestID, request.Resource)
default:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как же заифать что Filename нет для source, checker и interactor...

request := resourseInfo.Request

switch request.Resource {
case resource.CompiledBinary, resource.CompileOutput, resource.CheckerOutput:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так, а CheckerOutput это же ресурс для всех тестов. Он по сути как аутпут на тесте должен храниться, он же не общий для всей посылки

return nil
func (s *Connector) Download(request *Request) *FileResponse {
response := NewFileResponse(*request)
if request.StorageFilename == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Теперь у тебя вообще очень странно поддержан Filename, в половине случаев он есть, в половине нет, ты всегда требуешь его...

}

if resp.IsError() {
response.Error = fmt.Errorf("get request failed with status: %v", resp.Status())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

О, а давай ещё как-то нормально захендлим, что файла не существует. Какую-то специальную ошибку можем например в этом случае возвращать

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вынес это в отдельный 404

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А где? Просто текст писать не оч, это парсить больно. Лучше сделай отдельную ошибку ErrFileNotFound и если статус 404, то возвращай ее. Можешь посмотреть как в lib/cache это сделано, там тоже кастомные ошибки есть

@Artyom321 Artyom321 force-pushed the dev/storage branch 2 times, most recently from b702cc5 to 0ffa07f Compare April 16, 2025 22:12
Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну пока тут баги есть

}

if resourceInfo.EmptyStorageFilename {
if len(entries) > 1 || (len(entries) == 1 && entries[0] != filepath.Base(resourceInfo.Filepath)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А ты же тут все ещё будешь пытаться достать именно определенное имя, которое дефолтно забрал из FilepathFolderMapping

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут же такая логика: если нам не передали имя, то достаём из папки дефолтный если есть. Или ты хочешь доставать один всегда c любым названием (если не передали)?

Copy link
Collaborator

@grphil grphil Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да. Вообще кажется лучше разделить на 2 типа все ресурсы: с фиксированным именем, и нефиксированным именем. Соответственно для ресурсов с фиксированным именем мы юзаем только его. Для ресурсов без фиксированного имени мы или пытаемся по filename найти (если есть), или пытаемся просто взять в папке (если такой один)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А всё-таки сохранять ресурсы с нефиксированным именем мы разрешаем, если StorageFilename не указан (с каким-то дефолтным)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай запретим. Но оно скорее всего само запретится - в папке ничего не будет, поэтому и не сохранится

resourseInfo.EmptyStorageFilename = true

filepathFilename, ok := resource.FilepathFilenameMapping[request.Resource]
if !ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне кажется лучше не добавлять дефолтное имя для тех файлов, которые не имеют StorageFilename, и как раз !ok тут будет индикатором того, что имя файла надо вычислять самому. И как раз только в этом случае будет осмысленно resourseInfo.EmptyStorageFilename делать равным true

Кстати у тебя сейчас есть ещё одна бага - ты для всех видов файлов указываешь EmptyStorageFilename, даже даже если у них есть дефолтное имя в сторадже (например тесты). И когда ты их берешь, то папка с ними содержит ещё файлы и ты кидаешь ошибку

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так здесь маппинг говорит дефолтный filename для ресурса, если нет StorageFilename. Ты типо хочешь, чтобы на уровне ResourceInfo было 2 варианта filename: есть переданный StorageFilename и нет?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Написал в другом комменте:
Вообще кажется лучше разделить на 2 типа все ресурсы: с фиксированным именем, и нефиксированным именем. Соответственно для ресурсов с фиксированным именем мы юзаем только его. Для ресурсов без фиксированного имени мы или пытаемся по filename найти (если есть), или пытаемся просто взять в папке (если такой один)

}

dir := filepath.Dir(fullPath)
entries, err := filesystem.GetDirEntriesNames(dir)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне кажется все такие штуки лучше вызвать именно на этапе BuildFilePath, типа просто там чекаешь - если resourceInfo.EmptyStorageFilename = false, то берешь resourceInfo.Filepath как путь к файлу, иначе берешь resourceInfo.Filepath как путь к папке и дальше берешь все содержимое папки и там ищешь файл.

}

func (filesystem *Filesystem) BuildFilePath(resourceInfo *ResourceInfo) (string, error) {
cleanFilename := filepath.Base(resourceInfo.Filepath)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты же тут теряешь полностью весь путь до resourceInfo.Filepath со всеми этими папочками

subpathID := filesystem.generatePathFromID(resourceInfo.DataType.String(), strconv.FormatUint(resourceInfo.ID, 10))
fullPath := filepath.Join(filesystem.Basepath, subpathID, cleanFilename)

absBasepath, err := filepath.Abs(filesystem.Basepath)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем тебе вообще все эти чеки с absBasepath и absFullPath?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чтобы проверить, что мы случайно за пределы корня файловой системы не ушли (если какие-нибудь ../ случайно передали в StorageFilename).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну видимо действительно уберу всё это

}

if resp.IsError() {
response.Error = fmt.Errorf("get request failed with status: %v", resp.Status())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А где? Просто текст писать не оч, это парсить больно. Лучше сделай отдельную ошибку ErrFileNotFound и если статус 404, то возвращай ее. Можешь посмотреть как в lib/cache это сделано, там тоже кастомные ошибки есть

Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну сейчас тут пара фиксов осталось сделать


err = s.filesystem.GetFile(resourceInfo)
if err != nil {
logger.Error("Failed to get file: id=%d, dataType=%s, filePath=%s\n %v",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай ошибку будем в лог писать если это не os.NotExistErr, а то если файла нет, то у нас ошибки то не произошло, просто запрос плохой. То есть перенести на 4 строчки вниз сообщение

return "", fmt.Errorf("StorageFilename is not specified, but directory %s does not contain exactly one file", fullPath)
}
fullPath = filepath.Join(fullPath, entries[0].Name())
resourceInfo.Filepath = fullPath // It lets use resourceInfo.Filepath after successful SaveFile, RemoveFile and GetFile calls
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Смотри, тут несколько проблем:

Во первых, ты в половине случаев обновляешь Filepath, а в половине случаев нет. У тебя кстати и download вроде не будет работать если не было EmptyStorageFilename.

Во вторых, в целом мне кажется не оч хорошо менять resourceInfo, оно же как бы константно нам передается. Мне кажется лучше только возвращать именно строку с полным путем до файла

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filepath всегда будет заполнен (при корректном вызове), потому что мы его в resouce_info.go заполняем ещё (и там если не EmptyStorageFilename, то мы полный путь запишем), поэтому Download кажется должен работать.

Вообще я эту штуку в последний момент поменял) мне очень захотелось сделать одинаковую сигнатуру всех методов filesystem + не писать путь в двух местах

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filepath всегда будет заполнен (при корректном вызове), потому что мы его в resouce_info.go заполняем ещё (и там если не EmptyStorageFilename, то мы полный путь запишем), поэтому Download кажется должен работать.

Не, там у тебя Filepath заполняется как путь внутри пакета задачи/посылки. Но мы не можем его использовать как просто путь до файла, у тебя же куча промежуточных частей

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Согласен, хуйню написал

return nil
}

func (filesystem *Filesystem) GetFile(resourceInfo *ResourceInfo) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне кажется тут скорее логично именно путь до файла возвращать, а не resourceInfo менять и туда куда-то этот путь проставлять

)

// FilepathFolderMapping should contain all types
var FilepathFolderMapping = map[Type]string{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А давай эти два мапа в storage перетащим, оно же никакой другой компоненте не нужно

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я хотел тут их оставить, чтобы точно не забыли. Но там при добавлении типа всё равно скорее всего придётся код в storage дописывать, так что ок

return response
}

filename := ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше пустые типы через var filename string создавать

}
defer file.Close()

_, err = io.Copy(file, bytes.NewReader(resp.Body()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

О, а я вроде уже писал раньше, видимо потерялось. Давай не будем целиком resp.Body() хранить. Оно будет очень сильно заполнять оперативку когда ты грузишь тесты по 100-200мб и там реально может просто оперативки не хватить. Лучше вместо этого в запросе у resty настрой чтобы body не сохранялось, а вместо этого был ридер по нему, и из него все вычитай. (не забудь ещё этот ридер закрыть)

Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Только один супермелкий коммент! Ну и порезолви там мердж конфликт

}
defer resp.RawBody().Close()

if resp.StatusCode() >= 400 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай лучше лучше resp.StatusCode() == http.StatusOK

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Наверно всё-таки !=)

Copy link
Collaborator

@grphil grphil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ладно, держи свой шип!

@Artyom321 Artyom321 merged commit f90aa81 into main Apr 18, 2025
2 checks passed
@misha567889 misha567889 deleted the dev/storage branch May 5, 2025 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants