Skip to content

🇬🇧 English | 🇺🇦 Українська

← До змісту

5. Передавання файлів

Розділ про те, як плагін поводиться з великими файлами, як показати прогрес і як дати користувачеві кнопку «Скасувати».


Прогрес

Структура S3 Transfer Progress приходить у пін On Progress:

Поле Що це
Bytes Transferred Скільки байтів пройшло
Total Bytes Скільки очікується. 0, поки невідомо
Percentage Від 0 до 100
Current Part / Total Parts Номер частини, для багаточастинних операцій
bTotalKnown Чи можна вірити Total Bytes

bTotalKnown існує не для краси. Чанкове зчитування дізнається розмір об'єкта лише з першої відповіді, тож найперший виклик прогресу може прийти ще без нього. Поки bTotalKnown дорівнює false, показуйте невизначений індикатор, а не смужку на нулі — інакше інтерфейс виглядатиме зависшим.

Для смужки прогресу зручніше брати Get Progress Fraction із дескриптора: там уже готове значення від 0 до 1.


Скасування

(гравець натиснув «Скасувати»)
   │
   └─► Cancel (на збереженому Transfer)

Що відбувається далі:

  1. Запит, який зараз у польоті, обривається — плагін не чекає, доки той завершиться сам.
  2. Багатокрокова операція не починає наступний крок.
  3. Спрацьовує пін On Failure із результатом Cancelled.

Останнє важливе: скасування не «зникає тихо». Один із фінальних пінів спрацьовує завжди, тож очищення інтерфейсу не треба дублювати в двох місцях.

Про перервані (не скасовані) завантаження. Якщо процес просто зник — застосунок закрився, пропала мережа — частини лишаються в бакеті навмисно, щоб завантаження можна було продовжити. Щоб те, що ніхто не продовжив, не оплачувалося вічно, задайте бакету правило S3 Set Incomplete Upload Cleanup — див. «Правила життєвого циклу».

Скасування багаточастинного завантаження до того ж скасовує його в провайдера. Без цього вже надіслані частини лишилися б у бакеті: вони не видно в переліку об'єктів, але місце займають і оплачуються, доки їх не прибере правило життєвого циклу. Плагін робить це за вас.

Скасоване зчитування не лишає недописаного файлу за шляхом призначення. Наполовину завантажений файл гірший за відсутній: для будь-якої перевірки «файл на місці?» він виглядає успішним. У S3 Download File Chunked те, що встигло прийти, лишається поруч у .s3part, щоб наступна спроба продовжила з цього місця — див. «Відновлення перерваного зчитування».


Великі файли

Що відбувається автоматично

Усе, що більше за Multipart Part Size Bytes, стає багаточастинним завантаженням. Обирати не треба — поріг єдиний і для файлів, і для масивів у пам'яті.

Порядок дій: плагін відкриває багаточастинне завантаження, надсилає частини (кілька одночасно), збирає їхні ідентифікатори й завершує завантаження. Якщо якась частина остаточно не пройшла, усе завантаження скасовується в провайдера — зібрати об'єкт із дірою однаково неможливо.

Пам'ять

S3 Upload File не читає файл у пам'ять. Він описує його, а далі:

  • звичайне завантаження — транспорт стримить файл із диска;
  • багаточастинне — кожна частина називає свій відрізок того самого файлу за зміщенням.

Тому витрата пам'яті пропорційна Max Concurrent Parts × Multipart Part Size Bytes, а не розміру файлу. Завантаження на 2 ГБ із типовими налаштуваннями — це приблизно 20 МБ, а не 2 ГБ.

S3 Upload Bytes очевидно тримає масив у пам'яті — він там уже є.

Обмеження провайдера

S3 дозволяє не більше 10 000 частин. Якщо файл настільки великий, що за поточного розміру частини їх було б більше, плагін збільшує розмір частини сам і пише про це в лог. Провалити завантаження на останній частині було б гіршим варіантом.

Відновлення перерваного завантаження

Коли Resume Interrupted Uploads увімкнено (за замовчуванням так), наступна спроба завантажити той самий файл питає провайдера, які частини вже на місці, і досилає лише відсутні — замість того, щоб починати з нуля. Працює тільки для S3 Upload File (дані з пам'яті не переживають перезапуск процесу) і тільки поки файл та розмір частини не змінилися з моменту переривання. Докладно, з усіма межовими випадками — у FAQ: «Чи можна відновити перерване завантаження?».


Зчитування великих файлів

S3 Download File

Пише у файл у міру надходження. Пам'ять постійна, прогрес побайтовий.

Це один потоковий запит — і саме тому перерване зчитування починається наново: щоб продовжити з середини, потрібні діапазони. Якщо файл достатньо великий, щоб втрата на половині мала значення, беріть S3 Download File Chunked.

S3 Download File Chunked

Зчитує серією діапазонних запитів — і тому вміє продовжувати перерване зчитування.

Chunk Size Bytes = 0 означає «взяти розмір частини з налаштувань».

Відновлення перерваного зчитування

Коли Resume Interrupted Downloads увімкнено (за замовчуванням так), S3 Download File Chunked пише не одразу за призначенням, а у файл <файл>.s3part поруч. На місце призначення він потрапляє одним рухом лише тоді, коли зчитаний повністю.

Це дає дві речі одразу:

  • За шляхом призначення ніколи не лежить обрізаний файл. Наполовину завантажений файл гірший за відсутній: будь-яка перевірка «файл на місці?» вважає його успіхом.
  • Те, що вже прийшло, не втрачається. Обрив зв'язку, тайм-аут, скасування користувачем — .s3part лишається, і наступна спроба зчитати той самий об'єкт у той самий файл просить лише байти після нього.

Скільки вже зчитано, ніде окремо не записується — це розмір самого .s3part. Лічильник міг би розійтися з дійсністю (запис ліг, процес помер, лічильник застарів), а файл із собою розійтися не може.

Розмір шматка між спробами може бути іншим: діапазон починається з будь-якого байта, тож різати об'єкт однаково не обов'язково. Це відмінність від завантаження в сховище, де розмір частини змінювати не можна.

Чому це не «просто дописати з потрібного місця». Об'єкт міг змінитися між спробами. Тоді дописування пришило б хвіст нової версії до голови старої — і жодна пізніша перевірка цього б не помітила: розмір збігається, файл читається, а даних такого об'єкта ніколи не існувало.

Щоб цього не сталося, плагін запам'ятовує entity tag об'єкта (у Saved/S3/Downloads/) і перевіряє версію на кожному діапазоні, трьома незалежними шарами:

Перевірка Що ловить
Заголовок If-Match на кожному запиті Провайдер сам відмовляє (412) — до того, як чужі байти поїхали мережею
Порівняння ETag у відповіді Провайдера, який прийняв умову, але не застосував її
Порівняння розміру з Content-Range Провайдера, який на діапазон взагалі не віддає ETag

Що відбувається, коли версія не збіглася, залежить від того, коли це виявлено:

  • На першому ж діапазоні продовження — тобто в цьому запуску ще нічого не дописано: .s3part викидається, і об'єкт зчитується з нуля. Це звичайна ситуація «файл оновили відучора»; ви просили об'єкт — ви отримуєте його поточну версію цілою.
  • Посеред зчитування — об'єкт переписують просто зараз. Операція завершується з Precondition Failed, а незавершений файл видаляється. Мовчки перезавантажити означало б оплатити трафік двічі без відома того, хто викликав, і без гарантії, що об'єкт не змінять знову.

Якщо провайдер не віддає ETag на діапазон, запис не створюється: зчитування працює, але відновити його не вийде — продовжувати наосліп означало б рівно ту саму склейку двох версій.

Ця перевірка версії працює завжди, а не лише під час продовження. Об'єкт, переписаний посеред звичайного чанкового зчитування, — така сама склейка, просто в межах однієї операції.

Побачити, що чекає на продовження, можна командою S3ResumeRecords — див. «Консоль розробника».


Одночасні передавання

Кілька операцій на одному клієнті працюють паралельно й не заважають одна одній. Кожна має власний дескриптор, власний прогрес і власний бюджет повторів.

Щоб зупинити все одразу:

Get S3 Subsystem → Cancel All Transfers

Дізнатися, чи щось іще виконується:

Get Default S3 Client → Get Active Transfer Count

Це зручно для екрана виходу з гри: не закривати застосунок, доки збереження не долетіло.


Повтори

Повторюються лише ті збої, від повтору яких є користь:

Повторюється Не повторюється
Обрив з'єднання, немає відповіді 400 — некоректний запит
Таймаут 401, 403 — проблема з ключами чи політикою
429 — обмеження швидкості 404 — немає такого об'єкта
500, 502, 503, 504 409 — уже існує

Затримка подвоюється з кожною спробою й береться випадково з проміжку від нуля до поточної стелі. Випадковість тут не косметична: коли провайдер обмежує швидкість пачці запитів, вони падають одночасно — і без розкидання повернулися б теж одночасно, відтворивши ту саму пачку.

У результаті операції є поле Retry Count — скільки повторів було витрачено. Ненульове значення на успішній операції варто логувати: воно говорить про якість каналу або про те, що ви близько до ліміту провайдера.


Кілька байтів про потоки

Усі зворотні виклики приходять в ігровому потоці. Це гарантує транспортний шар плагіна, а не кожне місце виклику окремо, тож із будь-якого піна можна безпечно чіпати актори, віджети й UObject-и без жодного маршалінгу.


Далі: 6. C++ API