Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions CommonsAPI/Sources/CommonsAPI/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1049,30 +1049,53 @@ LIMIT \(limit)
return entitiesDict
}

/// Check if a media file already exists by filename
public func checkIfFileExists(filename: String) async throws -> FilenameExistsResult {
public func checkIfFileExists(filename: String) async throws -> FilenameExistsResult? {
let result = try await checkIfFilesExists(filenames: [filename])
return result[filename]
}

/// Check if a media file already exists by filename, returns [filename: FilenameExistsResult]
public func checkIfFilesExists(filenames: [String]) async throws -> [String: FilenameExistsResult] {
let titles = filenames.map { "File:" + $0 }
let query: Parameters = [
"action": "query",
"format": "json",
"titles": "File:" + filename,
"titles": titles.joined(separator: "|"),
"formatversion": "2",
"curtimestamp": "1"
]

let request = try GET(url: commonsEndpoint, query: query)
let (data, response) = try await response(for: request)
let parsedResponse = try parse(QueryResponse<FileExistenceResponse>.self, from: data, response: response)
guard let fileInfo = parsedResponse.query?.pages?.first else {

guard let pages = parsedResponse.query?.pages else {
throw CommonAPIError.missingResponseValues
}

if fileInfo.invalid == true {
return .invalidFilename
} else if fileInfo.missing == true {
return .doesNotExist
} else {
return .exists
var result: [String: FilenameExistsResult] = [:]

for page in pages {
guard let title = page.title else {
continue
}

var fromTitle = parsedResponse.query?.normalized?.first { normalizedResult in
normalizedResult.to == title
}?.from ?? title

fromTitle = String(fromTitle.split(separator: "File:")[0])

if page.invalid == true {
result[fromTitle] = .invalidFilename
} else if page.missing == true {
result[fromTitle] = .doesNotExist
} else {
result[fromTitle] = .exists
}
}

return result
}

// action=titleblacklist
Expand Down Expand Up @@ -1190,7 +1213,6 @@ LIMIT \(limit)
}
}
}


/// https://commons.wikimedia.org/w/api.php?action=upload&format=json&filename=&comment=&file=...&stash=1&token=&formatversion=2
public func publish(file: MediaFileUploadable, startStep: PublishingStep = .uploadData) -> AsyncStream<UploadStatus> {
Expand All @@ -1199,6 +1221,7 @@ LIMIT \(limit)

AsyncStream<UploadStatus> { continuation in
Task<Void, Never> {

do {
let csrfToken = try await fetchCSRFToken()

Expand Down Expand Up @@ -1280,7 +1303,7 @@ LIMIT \(limit)
title: "File:\(file.filename)",
labels: file.captions,
statements: file.claims,
comment: "Created initial structured data after upload"
summary: "initial structured data"
)

continuation.yield(.published)
Expand Down Expand Up @@ -1467,7 +1490,7 @@ LIMIT \(limit)
title: String,
labels: [LanguageString],
statements: [WikidataClaim],
comment: String?
summary: String?
) async throws {
let token = try await fetchCSRFToken()

Expand All @@ -1494,24 +1517,24 @@ LIMIT \(limit)
throw CommonAPIError.failedToEncodeJSONData
}

let commentString: String
let summaryString: String

if let comment {
commentString = comment
if let summary {
summaryString = summary
} else if !labels.isEmpty, !statements.isEmpty {
commentString = "Edited labels (\(labels.map(\.languageCode).joined(separator: ", "))) and structured data statements"
summaryString = "Edited labels (\(labels.map(\.languageCode).joined(separator: ", "))) and structured data statements"
} else if labels.isEmpty {
commentString = "Edited structured data statements"
summaryString = "Edited structured data statements"
} else if statements.isEmpty {
commentString = "Edited labels (\( labels.map(\.languageCode).joined(separator: ", ")))"
summaryString = "Edited labels (\( labels.map(\.languageCode).joined(separator: ", ")))"
} else {
commentString = "Edited labels or structured data statements"
summaryString = "Edited labels or structured data statements"
assertionFailure()
}

let form: Parameters = [
"action": "wbeditentity",
"comment": commentString,
"summary": summaryString,
"token": token,
"format": "json",
"title": title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,13 @@ internal struct ContentLanguageResponse: Decodable, Sendable {

internal struct FileExistenceResponse: Decodable, Sendable {
let pages: [Item]?
let normalized: [Normalized]?

struct Normalized: Decodable, Sendable {
let fromencoded: Bool
let from: String
let to: String
}

struct Item: Decodable, Sendable {
let pageid: Int64?
Expand Down
4 changes: 4 additions & 0 deletions CommonsAPI/Tests/CommonsAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ struct CommonsEndToEndTests {

@Test("list sub-categories", arguments: ["Physics"])
func fetchCategoryInfo(category: String) async throws {
<<<<<<< HEAD
let info = try await api.fetchCategoryMembers(of: category, sort: .none)
=======
let info = try await api.fetchCategoryMembers(of: category, sort: nil)
>>>>>>> e4e431c (basic multifile support)

#expect(info != nil)
guard let info else { return }
Expand Down
96 changes: 63 additions & 33 deletions CommonsFinder.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
E2035C452D52544D0079235B /* CommonsAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E2035C442D52544D0079235B /* CommonsAPI */; };
E206777A2EBE52FF00981A79 /* H3kit in Frameworks */ = {isa = PBXBuildFile; productRef = E20677792EBE52FF00981A79 /* H3kit */; };
E213C6B92EA29F570085C60E /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = E213C6B82EA29F570085C60E /* SwiftSoup */; };
E2390F962EBCC64C0013F0FC /* ObservableLRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = E2390F952EBCC64C0013F0FC /* ObservableLRUCache */; };
E2390F982EBCC7250013F0FC /* ObservableLRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = E2390F972EBCC7250013F0FC /* ObservableLRUCache */; };
E24D3BFD2CF3ACDC003484CC /* FrameUp in Frameworks */ = {isa = PBXBuildFile; productRef = E24D3BFC2CF3ACDC003484CC /* FrameUp */; };
Expand All @@ -19,6 +18,8 @@
E2549B262CBD68B9005DFE14 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = E2549B252CBD68B9005DFE14 /* Algorithms */; };
E2549B2D2CBD69C7005DFE14 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E2549B2C2CBD69C7005DFE14 /* OrderedCollections */; };
E26D503C2DA5A75F00621D1C /* CommonsAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E26D503B2DA5A75F00621D1C /* CommonsAPI */; };
E26ED9932FFC11680089B9C9 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = E26ED9922FFC11680089B9C9 /* SwiftSoup */; };
E26ED9952FFC11680089B9C9 /* SwiftSoupProfile in Frameworks */ = {isa = PBXBuildFile; productRef = E26ED9942FFC11680089B9C9 /* SwiftSoupProfile */; };
E28B17662CB03CBB00D6FEA0 /* GRDBQuery in Frameworks */ = {isa = PBXBuildFile; productRef = E28B17652CB03CBB00D6FEA0 /* GRDBQuery */; };
E290323A2D9EA52000A0CD8F /* Pulse in Frameworks */ = {isa = PBXBuildFile; productRef = E29032392D9EA52000A0CD8F /* Pulse */; settings = {ATTRIBUTES = (Required, ); }; };
E290323C2D9EA52000A0CD8F /* PulseUI in Frameworks */ = {isa = PBXBuildFile; productRef = E290323B2D9EA52000A0CD8F /* PulseUI */; settings = {ATTRIBUTES = (Required, ); }; };
Expand Down Expand Up @@ -131,17 +132,24 @@
Database/FTS5Tokenizer.swift,
Database/Model/Category.swift,
Database/Model/CategoryInfo.swift,
Database/Model/Drafts/Draftable.swift,
Database/Model/Drafts/MediaFileDraft.swift,
Database/Model/Drafts/MultiDraft.swift,
Database/Model/Drafts/MultiDraftInfo.swift,
Database/Model/Drafts/Types/DraftAuthor.swift,
Database/Model/Drafts/Types/DraftSource.swift,
Database/Model/Drafts/Types/LocationHandling.swift,
"Database/Model/Extensions/MediaFile+createAttributedStringDescription.swift",
"Database/Model/Extensions/MediaFile+InitFromAPI.swift",
"Database/Model/Extensions/MediaFile+makeRandomUploaded.swift",
"Database/Model/Extensions/MediaFileDraft+DebugDraft.swift",
"Database/Model/Extensions/MediaFileInfo+ImageRequest.swift",
"Database/Model/Extensions/MediaFileInfo+sortedByLastViewed.swift",
"Database/Model/Extensions/MultiDraftInfo+DebugDraft.swift",
"Database/Model/Extensions/WikidataItem+InitFromAPI.swift",
"Database/Model/Extensions/WikidataItem+Thumbnail.swift",
Database/Model/ItemInteraction.swift,
Database/Model/MediaFile.swift,
Database/Model/MediaFileDraft.swift,
Database/Model/MediaFileInfo.swift,
DataFetching/DataAccess.swift,
"Generic Extensions/Array+popFirstN.swift",
Expand Down Expand Up @@ -193,12 +201,13 @@
Tests/PublishHelpersTests.swift,
Tests/WikimediaLanguageStoreTests.swift,
Types/CaptionWithDescription.swift,
Types/DraftIDType.swift,
Types/DraftMediaLicense.swift,
"Types/DraftMediaLicense+CommonsAPI.swift",
Types/FileNameType.swift,
Types/FileNameTypeTuple.swift,
Types/ImageAnalysisResult.swift,
"Types/MediaFileDraft+uploadDisabledReason.swift",
Types/NameValidationError.swift,
Types/NewDraftOptions.swift,
Types/OptionBarState.swift,
Types/RelatedCategoriesType.swift,
Expand All @@ -211,6 +220,7 @@
"Utilities/CLLocation+gpsDictionary.swift",
Utilities/ExifData.swift,
Utilities/FileAnalysisHelpers.swift,
Utilities/FilenameUtils.swift,
Utilities/GeoPlacemarkCache.swift,
Utilities/GeoVectorMath.swift,
Utilities/Logging.swift,
Expand All @@ -227,23 +237,37 @@
Views/CategoryView/RelatedCategoryView.swift,
Views/ContextMenus/CategoryContextMenu.swift,
Views/ContextMenus/MediaFileContextMenu.swift,
Views/DraftViews/BaseDraftImageView.swift,
Views/DraftViews/DraftListItem.swift,
Views/DraftViews/DraftSheetModifer.swift,
Views/DraftViews/FileImportProgressOverlay.swift,
Views/DraftViews/FileLocationMapView.swift,
Views/DraftViews/FilenameErrorButton.swift,
Views/DraftViews/FilenameTip.swift,
Views/DraftViews/LicensePicker.swift,
Views/DraftViews/Model/FileImportModel.swift,
Views/DraftViews/Model/FileItem.swift,
Views/DraftViews/Model/MultiDraftModel.swift,
Views/DraftViews/Model/NameValidationResult.swift,
Views/DraftViews/Model/SingleDraftModel.swift,
Views/DraftViews/MultiDraftListItem.swift,
Views/DraftViews/MultiDraftOverviewList.swift,
Views/DraftViews/MultiDraftOverviewTip.swift,
Views/DraftViews/MultiDraftSheetModifier.swift,
Views/DraftViews/MultiDraftView.swift,
Views/DraftViews/SingleDraftSheetModifier.swift,
Views/DraftViews/SingleDraftView.swift,
Views/DraftViews/TagPicker/TagButton.swift,
Views/DraftViews/TagPicker/TagLabel.swift,
Views/DraftViews/TagPicker/TagModel.swift,
Views/DraftViews/TagPicker/TagPicker.swift,
Views/DraftViews/TagPicker/TagPickerModel.swift,
Views/DraftViews/TimezonePicker.swift,
"Views/Extensions/CLLocationCoordinate2D+description.swift",
"Views/Extensions/CLLocationManager+isLocationAutorized.swift",
"Views/Extensions/MediaFile+localizedDisplayCaption.swift",
"Views/Extensions/MediaFileDraftModel+ImageRequest.swift",
"Views/Extensions/UserDefaults+accessors.swift",
Views/FileCreateView/DraftSheetModifer.swift,
Views/FileCreateView/FilenameTip.swift,
Views/FileCreateView/LicensePicker.swift,
Views/FileCreateView/Model/FileImportModel.swift,
Views/FileCreateView/Model/FileItem.swift,
Views/FileCreateView/Model/MediaFileDraftModel.swift,
Views/FileCreateView/SingleImageDraftView.swift,
Views/FileCreateView/TagPicker/TagButton.swift,
Views/FileCreateView/TagPicker/TagLabel.swift,
Views/FileCreateView/TagPicker/TagModel.swift,
Views/FileCreateView/TagPicker/TagPicker.swift,
Views/FileCreateView/TagPicker/TagPickerModel.swift,
Views/FileCreateView/TimezonePicker.swift,
Views/FileDetailView/FileDetailView.swift,
Views/FileDetailView/FileGroupBoxStyle.swift,
Views/FileDetailView/FileLoadView.swift,
Expand Down Expand Up @@ -301,7 +325,6 @@
"Views/Reusable Views/DeepCategoryToggle.swift",
"Views/Reusable Views/Deprecated/DraggablePseudoSheet.swift",
"Views/Reusable Views/Deprecated/PseudoSheet.swift",
"Views/Reusable Views/DraftFileListItem.swift",
"Views/Reusable Views/InlineMap.swift",
"Views/Reusable Views/InputLanguageButtons.swift",
"Views/Reusable Views/MediaDowloadSheet.swift",
Expand Down Expand Up @@ -397,6 +420,7 @@
E2D8C66A2ECF5B8200CEBB37 /* GEOSwiftMapKit in Frameworks */,
E2FF0A2E2D9EAA29008F915C /* GRDB in Frameworks */,
E290323A2D9EA52000A0CD8F /* Pulse in Frameworks */,
E26ED9952FFC11680089B9C9 /* SwiftSoupProfile in Frameworks */,
E2549B2D2CBD69C7005DFE14 /* OrderedCollections in Frameworks */,
E2F1B9D52CD006C700410991 /* GRDB in Frameworks */,
E290323C2D9EA52000A0CD8F /* PulseUI in Frameworks */,
Expand All @@ -414,7 +438,7 @@
E24D3BFD2CF3ACDC003484CC /* FrameUp in Frameworks */,
E2549B232CBD6886005DFE14 /* AsyncAlgorithms in Frameworks */,
E2390F982EBCC7250013F0FC /* ObservableLRUCache in Frameworks */,
E213C6B92EA29F570085C60E /* SwiftSoup in Frameworks */,
E26ED9932FFC11680089B9C9 /* SwiftSoup in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -538,7 +562,6 @@
E290323B2D9EA52000A0CD8F /* PulseUI */,
E290323E2D9EA84700A0CD8F /* GRDBQuery */,
E26D503B2DA5A75F00621D1C /* CommonsAPI */,
E213C6B82EA29F570085C60E /* SwiftSoup */,
E2390F952EBCC64C0013F0FC /* ObservableLRUCache */,
E2390F972EBCC7250013F0FC /* ObservableLRUCache */,
E20677792EBE52FF00981A79 /* H3kit */,
Expand All @@ -547,6 +570,8 @@
E2D76FAA2F3E2CB8009CC4CB /* Nuke */,
E2D76FAC2F3E2CB8009CC4CB /* NukeUI */,
E2BA76312FCEDAA90027717A /* OAuthenticator */,
E26ED9922FFC11680089B9C9 /* SwiftSoup */,
E26ED9942FFC11680089B9C9 /* SwiftSoupProfile */,
);
productName = CommonsFinder;
productReference = E2839FE72CA2DD900053C312 /* CommonsFinder.app */;
Expand Down Expand Up @@ -616,12 +641,12 @@
E29032382D9EA52000A0CD8F /* XCRemoteSwiftPackageReference "Pulse" */,
E290323D2D9EA84700A0CD8F /* XCRemoteSwiftPackageReference "GRDBQuery" */,
E26D503A2DA5A75F00621D1C /* XCLocalSwiftPackageReference "CommonsAPI" */,
E213C6B72EA29F570085C60E /* XCRemoteSwiftPackageReference "SwiftSoup" */,
E20677782EBE52FF00981A79 /* XCRemoteSwiftPackageReference "h3kit-ios" */,
E2D8C6682ECF5B8200CEBB37 /* XCRemoteSwiftPackageReference "GEOSwiftMapKit" */,
E2C92DD12ED60D4000EAA248 /* XCRemoteSwiftPackageReference "ObservableLRUCache" */,
E2D76FA92F3E2CB8009CC4CB /* XCRemoteSwiftPackageReference "Nuke" */,
E2BA76302FCEDAA90027717A /* XCRemoteSwiftPackageReference "OAuthenticator" */,
E26ED9912FFC11680089B9C9 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = E2839FE82CA2DD900053C312 /* Products */;
Expand Down Expand Up @@ -1119,14 +1144,6 @@
kind = branch;
};
};
E213C6B72EA29F570085C60E /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/scinfu/SwiftSoup.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.11.1;
};
};
E24D3BFB2CF3ACDC003484CC /* XCRemoteSwiftPackageReference "FrameUp" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ryanlintott/FrameUp";
Expand Down Expand Up @@ -1159,6 +1176,14 @@
version = 1.4.1;
};
};
E26ED9912FFC11680089B9C9 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/scinfu/SwiftSoup";
requirement = {
kind = revision;
revision = 8d6ad267714cac3ae747cefdd21f7a6665006e1f;
};
};
E29032382D9EA52000A0CD8F /* XCRemoteSwiftPackageReference "Pulse" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kean/Pulse";
Expand Down Expand Up @@ -1239,11 +1264,6 @@
isa = XCSwiftPackageProductDependency;
productName = H3kit;
};
E213C6B82EA29F570085C60E /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = E213C6B72EA29F570085C60E /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
E2390F952EBCC64C0013F0FC /* ObservableLRUCache */ = {
isa = XCSwiftPackageProductDependency;
productName = ObservableLRUCache;
Expand Down Expand Up @@ -1284,6 +1304,16 @@
isa = XCSwiftPackageProductDependency;
productName = CommonsAPI;
};
E26ED9922FFC11680089B9C9 /* SwiftSoup */ = {
isa = XCSwiftPackageProductDependency;
package = E26ED9912FFC11680089B9C9 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
E26ED9942FFC11680089B9C9 /* SwiftSoupProfile */ = {
isa = XCSwiftPackageProductDependency;
package = E26ED9912FFC11680089B9C9 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoupProfile;
};
E28B17652CB03CBB00D6FEA0 /* GRDBQuery */ = {
isa = XCSwiftPackageProductDependency;
productName = GRDBQuery;
Expand Down
Loading