Here is the overall structure of the workflow, start to finish. Each piece is broken down and explained below.
using GenerallyPositive;
using GenerallyPositive
using GenerallyPositive
using static GenerallyPositive
GPAL
// Step 1: collect Google OAuth credentials
GPAL
if (!credConnected) return;
// Step 2: configure and upload
GPAL
GPAL.Credentials.WithService(CredentialServiceType.Google) starts the credential chain. WithServiceKey takes the Client ID and Client Secret joined by a colon, WithScope pins the credential to YouTube upload permissions, and WithRefreshToken supplies the long-lived refresh token from the OAuth 2.0 Playground. Once ToGPALObject() is called the credential object is ready to pass to GPAL.YouTube.
ICredentials credentials = GPAL
Client ID and Client Secret come from a project in the Google Cloud Console with the YouTube Data API v3 enabled. The refresh token is obtained from the OAuth 2.0 Playground once you authorize the YouTube upload scope.
GPAL.YouTube.WithCredentials sets the active credential, then WithTitle, WithDescription, and WithPrivacy configure the video metadata before upload. Upload((GPALFile)filePath) performs the upload and returns a YouTubeUploadResult that carries the video ID and processing state -- needed later if you want to schedule a premiere.
YouTubeUploadResult result = GPAL
After upload, YouTube needs time to process the video before a premiere can be scheduled. WaitForProcessing(60) sets how long to wait between status polls, 60 seconds here and never less than 10. WithOutputTo routes the polling log lines to a RichTextBox so the user can see progress. ScheduleAt starts a background thread that polls the video and sets its premiere time from the PremiereTime enum value as soon as YouTube marks it processed. ScheduleAt returns at once; call WaitForCompletion when the workflow has to wait for the scheduling to finish.
GPAL
YouTube's processing pipeline can take several minutes for longer videos. ScheduleAt keeps polling until every video is processed or has failed, so there is no timeout to raise. A video that fails, is rejected or is deleted is reported and not scheduled. In a console workflow, call WaitForCompletion so the process does not exit while the polling is still running.
Uploads can take minutes, so the wizard runs them on a background thread to keep the UI responsive. The thread uploads every file first, then starts a ScheduleAt for each video that needs a premiere. ScheduleAt returns at once and polls on its own thread, so each video gets a small waiter thread that calls WaitForCompletion and signals a CountdownEvent. The last waiter to finish reports that every premiere is scheduled, so the status only says done when it is.
var thread = new Thread(() =>
{
var toSchedule = new List<(YouTubeUploadResult result, PremiereTime premiere)>();
foreach (VideoConfig config in configs)
{
YouTubeUploadResult result = GPAL
if (result != null && config
toSchedule
}
if (0 == toSchedule
statusStrip
else
{
var countdown = new CountdownEvent(toSchedule
foreach (var (result, premiere) in toSchedule)
{
IGPALYouTube scheduler = GPAL
scheduler
var waiter = new Thread(() =>
{
scheduler
if (countdown
statusStrip
}) { IsBackground = true };
waiter
}
}
}) { IsBackground = true };
thread
Setting statusStrip.Text from the background thread is safe -- GPALStatusStrip marshals the update to the UI thread internally. For other controls accessed from a background thread, use Control.InvokeRequired and BeginInvoke.
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();