; для создания каталога своего яндекс-диска из плоского списка
; с датами по самому раннему файлу в папке, без пустых папок
; токен должен быть в файле YandexToken.txt, рядом со скриптом
#Requires AutoHotkey v2
#DllLoad 'WinHttp.dll'
#Include <LightJson> ; https://forum.script-coding.com/viewtopic.php?id=18483
#Include <WebRequest> ; https://forum.script-coding.com/viewtopic.php?pid=162651#p162651
#Include <UrlCodec> ; https://forum.script-coding.com/viewtopic.php?pid=162669#p162669
; сортировка вставками из Википедии
InsSortAMap(A, item := '', lo := 1, hi?) {
if !IsSet(hi)
hi := A.Length
j := lo + 1
while j <= hi {
Aj := A[j]
pivot:= item ? Aj[item] : Aj
i := j - 1
while i >= lo && StrCompare(item ? A[i][item] : A[i], pivot, 'On') > 0
A[i + 1] := A[i--]
A[i+1] := Aj
j++
}
}
; сортировка по схеме Хоара из Википедии, модифицированная
qSortAMap(A, item := '', lo := 1, hi?, threshold := 15, depth := 0) {
static partition(A, item, low, high) {
pivot:= item ? A[(low + high) >> 1][item] : A[(low + high) >> 1]
i := low
j := high
loop {
while StrCompare(item ? A[i][item] : A[i], pivot, 'On') < 0
i++
while StrCompare(item ? A[j][item] : A[j], pivot, 'On') > 0
j--
if i >= j
return j
; swap
temp := A[i], A[i] := A[j], A[j] := temp
i++, j--
}
}
if !IsSet(hi)
hi := A.Length
while hi - lo > threshold {
p:= partition(A, item, lo, hi)
if p - lo < hi - p {
qSortAMap(A, item, lo, p, threshold, 1)
lo := p + 1
} else {
qSortAMap(A, item, p + 1, hi, threshold, 1)
hi := p
}
}
if !depth
InsSortAMap A, item
}
; найти в отсортированном массиве 1-ю строку с указанным началом
SASearch(A, &str, item := '') {
if !j := A.Length
return 0
i := 1, L := StrLen(str)
loop {
m := i + ((j - i) >> 1)
if StrCompare(str, substr(item ? A[m][item] : A[m], 1, L), 'On') <= 0
j := m - 1
else i := m + 1
} until i > j
return ++j <= A.Length && StrCompare(str, substr(item ? A[j][item] : A[j], 1, L), 'On') = 0 ? j : 0
}
GetYD(req, &Response, &status, &dir?, &token?, limit := 1000, offset := 0) { ; запрос к облаку
url := 'https://cloud-api.yandex.net/v1/disk' . (req = 'p_res' || req = 'p_nam' ? '/public/resources?public_key=' . token : '')
if req = 'res' || req = 'p_res'
P := '_embedded.items.', fields := 'fields=' P 'type%2C,' P 'path%2C,' P 'name%2C,' P 'created'
switch {
case req = 'res' : url .= '/resources?' fields '&limit=' limit '&path=' UrlCodec.Encode(dir)
case req = 'disk' : url .= '?fields=total_space%2Cused_space'
case req = 'p_res' : url .= '&' fields '&limit=' limit '&path=' UrlCodec.Encode(dir)
case req = 'p_nam' : url .= '&fields=name'
case req = 'plane' : url .= '/resources/files?limit=' limit '&offset=' offset '&fields=items.path%2Citems.created'
Default:
Response := 'Неверный запрос', status := -1
return 0
}
headers := Map("Accept", "application/json")
if instr('res disk plane', req)
headers["Authorization"] := token
Response := WebRequest(url,, headers,,, &status)
return status = 200
}
; Функция для рекурсивного обхода списка директорий и построения дерева
DirTreeText(dir, prefix, isLast := true, depth := 0, created := '') {
global tree, info, List
static UTCPlus := DateDiff(A_Now, A_NowUTC, 'H')
if NOT idx := SASearch(List, &dir, 'path') {
MsgBox 'Не найдена папка`n' dir
ExitApp
}
; список папок, вложенных в папку dir
dirs := []
while idx <= List.Length && substr(List[idx]['path'], 1, strlen(dir)) = dir {
if subDirEndPos := InStr(List[idx]['path'], '/', 'On', strlen(dir) + 1) {
; subDirEndPos = 0 для папки dir, поэтому она не попадёт в dirs
path := SubStr(List[idx]['path'], 1, subDirEndPos)
if !SASearch(dirs, &path, 'path')
dirs.Push Map('path', path
, 'created', FormatTime(DateAdd(RegExReplace(substr(List[idx]['created'], 1, -6), '-|T|:'), UTCPlus, 'H'), 'dd.MM.yy HH:mm'))
}
idx++
}
if depth {
namePos := instr(dir, '/', 'On',, -2)
currentFolder := SubStr(dir, namePos+1, StrLen(dir)-NamePos-1)
tree .= prefix (isLast ? '└───' : '├───') currentFolder '`t`t' created '`n'
prefix := prefix (isLast ? ' ' : '│ ')
}
for idx, subDir in dirs
DirTreeText subDir['path'], prefix, idx = dirs.Length, 1, subDir['created']
}
FileName := A_ScriptDir '\YandexToken.txt'
try
FileObj := FileOpen(FileName, "r")
catch as Err {
MsgBox "Не читается файл с токеном `n" FileName
. "`n`n" Type(Err) ": " Err.Message
ExitApp
}
token := "OAuth " rtrim(FileObj.Read(), '`r`n')
FileObj.Close()
startFolder := '/'
if !GetYD('disk', &Response, &status,, &token) {
MsgBox 'Ошибка ' status ' на информации о диске`n' Response
ExitApp
}
DiskInfo := LightJson.Parse(Response)
SpaceFree := (Integer(DiskInfo['total_space'])-Integer(DiskInfo['used_space']))>>>20 ' GiB свободно из ' Integer(DiskInfo['total_space'])>>>20
DriveLabel := 'YD'
outputFile := A_ScriptDir '\' DriveLabel '.txt'
; информационное окно
BarWidth := 300
global Info := Gui("+LastFound", 'Info - ' A_ScriptName)
Info.AddText 'vText1 r2 w' BarWidth, 'Построение дерева...'
Info.AddButton 'vButton Default w' BarWidth, 'Cancel'
Info['Button'].OnEvent('Click', (*) => ExitApp())
Info.AddStatusBar 'vStatus'
Info.Show 'w' BarWidth+Info.MarginX*2
SetTimer Wink, 1000
Wink() {
static ColorInd := 1, Colour := ['cDefault', 'cWhite'], Period := [1000, 500]
SetTimer , Period[ColorInd+1]
Info['Text1'].Opt(Colour[ColorInd+1])
ColorInd := !ColorInd
}
Time1 := A_TickCount
limit := 3000, offset := 0
global List := []
loop {
Info['Status'].SetText('Запрос, offset=' offset)
if !GetYD('plane', &Response, &status,, &token, limit, offset) {
MsgBox 'Ошибка ' status ' на получении списка`n' Response
ExitApp
}
Info['Status'].SetText('Обработка, offset=' offset)
Lst := LightJson.Parse(Response)['items']
for idx, Item in Lst ; убираем имена файлов и префикс disk:
Lst[idx]['path'] := SubStr(Item['path'], 6, instr(Item['path'], '/', 'On', -1) - 5)
qSortAMap Lst, 'path'
idx := 1
while idx <= Lst.Length { ; неодинаковые добавляем в List
if substr(Lst[idx]['path'], 1, StrLen(startFolder)) != startFolder {
idx++
continue ; ненужные пропускаем
}
List.Push Lst[idx]
path := Lst[idx]['path']
while ++idx <= Lst.Length && Lst[idx]['path'] = path { ; одинаковые пропускаем, корректируя дату
if StrCompare(Lst[idx]['created'], List[List.Length]['created'], 'On') < 0
List[List.Length]['created'] := Lst[idx]['created']
}
}
offset += limit
} until lst.Length < limit
if offset > limit { ; удаляем одинаковые из List, корректируя дату
Info['Status'].SetText('Обработка...')
qSortAMap List, 'path'
idx := 2
while idx <= List.Length
if List[idx]['path'] = List[idx-1]['path'] {
if StrCompare(List[idx]['created'], List[idx-1]['created'], 'On') < 0
List[idx-1]['created'] := List[idx]['created']
List.RemoveAt(idx)
} else idx++
}
; теперь строим дерево
Info['Status'].SetText('Строим...')
global tree := DriveLabel ', ' SpaceFree '`n' startFolder '`n'
DirTreeText startFolder, DriveLabel
statistics := 'Затрачено ' (A_TickCount - Time1) // 1000 ' с'
tree .= '`n' statistics '`n'
; Сохраняем в файл
try FileDelete outputFile
try FileAppend tree, outputFile, 'UTF-8'
catch as e {
MsgBox 'Ошибка при записи в файл: ' e.Message
ExitApp
}
SetTimer Wink, 0
Info['Text1'].Opt('cDefault')
Info['Text1'].Text := statistics '. Сохранено в `n' outputFile
Info['Status'].SetText('Готово')
Info['Button'].Text := 'OK'
Info['Button'].GetPos(&X, &Y, &Width, &Height)
WinActivate
MouseMove X + Width // 2, Y + Height // 2
SoundPlay "*-1"
; для создания каталога публичного ресурса на яндекс-диске или каталога своего яндекс-диска
; если на запрос публичного URL вводится пустое значение, токен берётся из файла YandexToken.txt, расположенного рядом со скриптом
#Requires AutoHotkey v2
#DllLoad 'WinHttp.dll'
#Include <LightJson> ; https://forum.script-coding.com/viewtopic.php?id=18483
#Include <WebRequest> ; https://forum.script-coding.com/viewtopic.php?pid=162651#p162651
#Include <UrlCodec> ; https://forum.script-coding.com/viewtopic.php?pid=162669#p162669
GetYD(req, &Response, &status, &dir?, &token?, limit := 1000, offset := 0) { ; запрос к облаку
url := 'https://cloud-api.yandex.net/v1/disk' . (req = 'p_res' || req = 'p_nam' ? '/public/resources?public_key=' . token : '')
if req = 'res' || req = 'p_res'
P := '_embedded.items.', fields := 'fields=' P 'type%2C,' P 'path%2C,' P 'name%2C,' P 'created'
switch {
case req = 'res' : url .= '/resources?' fields '&limit=' limit '&path=' UrlCodec.Encode(dir)
case req = 'disk' : url .= '?fields=total_space%2Cused_space'
case req = 'p_res' : url .= '&' fields '&limit=' limit '&path=' UrlCodec.Encode(dir)
case req = 'p_nam' : url .= '&fields=name'
case req = 'plane' : url .= '/resources/files?limit=' limit '&offset=' offset '&fields=items.path%2Citems.created'
Default:
Response := 'Неверный запрос', status := -1
return 0
}
headers := Map("Accept", "application/json")
if instr('res disk plane', req)
headers["Authorization"] := token
Response := WebRequest(url,, headers,,, &status)
return status = 200
}
; Функция для рекурсивного обхода директории и построения дерева
DirTreeText(dir, prefix, isLast := true, depth := 0, name := '', created := '') {
global tree, info, token
static FoldersCount := 0, AlwaysContinue := false, UTCPlus := DateDiff(A_Now, A_NowUTC, 'H'), req := substr(token, 1, 6) = "OAuth " ? 'res' : 'p_res'
dirs := []
if GetYD(req, &Response, &status, &dir, &token) {
for idx, Item in LightJson.Parse(Response)["_embedded"]["items"]
if Item['type'] = 'dir'
dirs.Push Map('path', Item['path'], 'name', Item['name']
, 'created', FormatTime(DateAdd(RegExReplace(substr(Item['created'], 1, -6), '-|T|:'), UTCPlus, 'H'), 'dd.MM.yy HH:mm'))
}
else {
if !AlwaysContinue {
WhatToDo := MsgBox('Ошибка ' status ' на папке `n' dir '`n' Response '`n`n' "
(
Да`t- продолжить и больше не спрашивать;
Нет`t- продолжить до следующей ошибки;
Отмена`t- прекратить.
)",, 'YNC Icon?')
if WhatToDo = 'Cancel'
ExitApp
AlwaysContinue := WhatToDo = 'Yes'
}
name := dir '`n' UrlCodec.Encode(dir)
created := 'ошибка'
}
if depth {
tree .= prefix (isLast ? '└───' : '├───') name '`t`t' created '`n'
Info['Status'].SetText('Папка № ' . ++FoldersCount, 1), Info['Status'].SetText(name, 2)
prefix := prefix (isLast ? ' ' : '│ ')
} else Info['Progress'].Opt('Range0-' dirs.Length)
for idx, subDir in dirs {
if !depth
Info['Progress'].Value++
DirTreeText subDir['path'], prefix, idx = dirs.Length, 1, subDir['name'], subDir['created']
}
}
startFolder := '/'
IB := InputBox("Публичный URL",,'h100')
if IB.Result = "Cancel"
ExitApp
global token
if token := IB.Value {
if !GetYD('p_nam', &Response, &status,, &token) {
MsgBox 'Ошибка ' status ' на информации о публичном имени`n' Response
ExitApp
}
DriveLabel := 'YD-' LightJson.Parse(Response)['name']
SpaceFree := ''
IB := InputBox("Имя выходного файла",,'h100', DriveLabel)
if IB.Result = "Cancel"
ExitApp
DriveLabel := IB.Value
} else {
FileName := A_ScriptDir '\YandexToken.txt'
try
FileObj := FileOpen(FileName, "r")
catch as Err {
MsgBox "Не читается файл с токеном `n" FileName
. "`n`n" Type(Err) ": " Err.Message
ExitApp
}
token := "OAuth " rtrim(FileObj.Read(), '`r`n')
FileObj.Close()
if !GetYD('disk', &Response, &status,, &token) {
MsgBox 'Ошибка ' status ' на информации о диске`n' Response
ExitApp
}
DiskInfo := LightJson.Parse(Response)
SpaceFree := (Integer(DiskInfo['total_space'])-Integer(DiskInfo['used_space']))>>20 ' GiB свободно из ' Integer(DiskInfo['total_space'])>>20
DriveLabel := 'YD'
}
outputFile := A_ScriptDir '\' DriveLabel '.txt'
; информационное окно
BarWidth := 300
global Info := Gui("+LastFound", 'Info - ' A_ScriptName)
Info.AddText 'vText1 r2 w' BarWidth, 'Построение дерева...'
Info.AddProgress 'vProgress h10 w' BarWidth
Info.AddButton 'vButton Default w' BarWidth, 'Cancel'
Info['Button'].OnEvent('Click', (*) => ExitApp())
Info.AddStatusBar 'vStatus'
Info['Status'].SetParts(85)
Info.Show 'w' BarWidth+Info.MarginX*2
; Построение дерева
global tree := DriveLabel ', ' SpaceFree '`n' startFolder '`n'
Time1 := A_TickCount
DirTreeText startFolder, DriveLabel
statistics := 'Затрачено ' (A_TickCount - Time1) // 1000 ' с'
tree .= '`n' statistics '`n'
; Сохраняем в файл
try FileDelete outputFile
try FileAppend tree, outputFile, 'UTF-8'
catch as e {
MsgBox 'Ошибка при записи в файл: ' e.Message
ExitApp
}
Info['Text1'].Text := statistics '. Сохранено в `n' outputFile
Info['Button'].Text := 'OK'
Info['Button'].GetPos(&X, &Y, &Width, &Height)
WinActivate
MouseMove X + Width // 2, Y + Height // 2
SoundPlay "*-1"