Flutter – Lists

With xmas round the corner, our sponsor has asked that we add a new digest to share recipes.

Santa’s helper will need to get up and have a pre-breakfast boiled egg, so lets just cut to to the chase and start with some actual data, some json:

{
	"title": "How to boil an egg, the Heston Blumenthal way",
	"titleImage": "https://i.guim.co.uk/img/static/sys-images/Guardian/Pix/pictures/2014/11/5/1415205733799/4bfbd71a-6cd0-4494-833f-eaaed20a15b3-1020x612.jpeg?width=620&quality=45&auto=format&fit=max&dpr=2&s=ca3a95d7e761d267eff1b79b58cc4849",
	"author": "Heston Blumenthal",
	"origin": "https://www.theguardian.com/lifeandstyle/2014/nov/11/how-to-boil-an-egg-the-heston-blumenthal-way",
	"ingredients": [
        {
        	"quantity": "1",
			"quantityType": "unit",
            "name": "egg",
			"type": "Dairy"
        }
	],
	"steps": [
        {
        	"sequenceNumber": "1",
            "instruction": "Take a small saucepan with a glass lid and carefully place a single egg (or two, or three) inside it. Burford brown eggs have a nice orange yolk. Fill the pan so the water only just covers the eggs – not even a millimetre more. If you had a centimetre of water covering the egg then you could still get the same result, but you’d have to play with the timing."
        },
        {
        	"sequenceNumber": "2",
            "instruction": "Put the pan on maximum heat with the lid on and bring to the boil."
        },
		{
        	"sequenceNumber": "3",
            "instruction": "As soon as the water starts to bubble, remove from the heat. As you take the pan off, set a timer for six minutes; keep the lid on. Make sure you time it exactly, and you’ll end up with the perfect egg."
        }
	]
}

Some acceptance criteria:

  • Displays a list of recipes
  • Each recipe has a title, author and a picture.

And a design for our list item:

Recipe list item design
Recipe list item design

Because it’s so close to Christmas we couldn’t get our API approved, so we created a simple api client class that mocks the data for the recipe list.

Now we have the data we can display the data items in a list using the ListView widget.

Ta Da

Recipe List
Recipe List

You maybe thinking why didn’t we build the digest list first ?

Because we only have one interest, recipes, we can add the digest list when it is needed.

The focus is on building content that matters and not framework or things we might need.

So on that note, for now, lets just change the tab description:

Recipe Tab Descriptionrecipies_tab.dart

BackBurner

Use of a API on a server, instead of mocked data.

Better application of styles, including inclusion of Apples design guideline for the TabBar, that state that it should be translucent when items are under it and opaque when at the end of the list.

XP

Use of dio a powerful Http client for Dart, which supports Interceptors, used to inject canned data.

The reason for choosing and using a package now is that it saves us having to create requests and response objects, we will hook it up to a real Api soon.

People

“The speed of the project is the speed at which ideas move between minds”

“or its inverse which is stronger, anything that slows the movement of ideas between minds slows the project down”

 Alistar Cockburn_

One more thing

“You can’t connect the dots looking forward; you can only connect them looking backward. So you have to trust that the dots will somehow connect in your future.”

Steve Jobs_

Restful Api’s

Here’s my way, my influences, but feel free to follow any highway…

Naming

Resources are objects, things, so use a noun to specify a resource e.g clients and then use HTTP methods to define actions like get, modify, delete.

If you use verbs like send, get then you are falling into the Remote Procedure Call trap. RPC style endpoints are notorious for specifying the function in the URL e.g. /getClientNameById

Resource names should be plural as this is the most widely adopted approach:

/clients not /client /getClients /user/client

Unless there can only ever be one (0-1, exists or not) e.g. users/1/avatar.

HTTP Methods

Often call HTTP verbs are POST, GET, PUT, PATCH, DELETE and OPTIONS.

Considering the resource (Entity) life cycle can help you determine which HTTP methods to allow, in particular where it is stored, db, file and if it can be archived.

Patch & Put

  • Favor Patch for updating resources, use put when resources are replaced not updated e.g. binary documents.
  • Do not use Put to create new items.

    Be aware that PUT does a complete overwrite of the data,
    it is a request to replace a resource, PATCH is a request to
    update part or all of it i.e. a new version.

The difference between the PUT and PATCH requests is reflected in the way the server processes the enclosed entity to modify the resource identified by the Request-URI.

In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.

PATCH is less likely to have side affects when a resource is updated frequently because you only update the things that change, whereas PUT will update fields that have not changed with the values retrieved and if another request happens after the retrieval you will reset them. You can/should guard against this at the repository level by checking a resource version and invaliding the update.

Do not use ‘Put’ to create new items, use ‘Post’.

If barn 11 does not exist then PUT /barn/11 should return a 404 and message saying I couldn’t modify it because it doesn’t exist.

Options Verb

The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.

A pre-flight options request is triggered by some CORS requests, see the section on CORS for more details.

Response Codes

Always return a response code for each request.

HTTP/1.1 200 OK
Date: Sun, 18 Oct 2012 10:36:20 GMT
Server: Apache/2.2.14 (Win32)
Content-Length: 230
Connection: Closed
Content-Type: text/html; charset=iso-8859-1
Response Codes

If I did GET or an update, 200 is okay.

However, if I do POST or a PUT and something is created, use 201. Tell me explicitly that the new object was created.

If I do PUT or a PATCH and nothing’s modified, return 304 Not Modified.

If I send the wrong data or use the wrong format request, return 400 Bad Request.

If I haven’t logged in or I sent an invalid auth token, return 401 Not Authorised.

If I try to do something I’m not allowed to do, 403 Forbidden.

404 if the object never existed, or it’s not there. Technically, you want to use the status code for Gone namely, 410 if it once existed, but 404 is traditionally Not Found. DO NOT use this when a GET returns no rows/result, it’s still a 200.

405 represents Method Not Allowed. This goes beyond Forbidden 403 and says, “Hey, you can’t do this for example, delete an object. If you try a different method, that would work.”

415 corresponds to Unsupported Media Type, for example if I request XML but you only support JSON.

Formatting Content

I follow the conventions in the { json;api }  specification.

// Articles with fields title, body and author.
GET /articles?include=author&fields[articles]=title,body,author

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON:API paints my bikeshed!",
      "body": "The shortest article. Ever."
    },
    "relationships": {
      "author": {
        "data": {"id": "42", "type": "people"}
      }
    }
  }]
}

The spec has matured overtime and specifies some of our important practices showing us how to format the negotiation between the client and server:

  • Relationships
  • Hypermedia
  • Pagination
  • Filtering
  • Sparse fields
  • Errors

Relationships (Related Resources)

Multiple related resources can be requested in a comma-separated list:

GET /articles/1?include=author,comments.author HTTP/1.1
Accept: application/vnd.api+json

In order to request resources related to other resources, a dot-separated path for each relationship name can be specified:

GET /articles/1?include=comments.author HTTP/1.1
Accept: application/vnd.api+json

To update a related resource include the resource as a relation in the PATCH e.g. request will update the author relationship of an article:

PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "articles",
    "id": "1",
    "relationships": {
      "author": {
        "data": { "type": "people", "id": "1" }
      }
    }
  }
}

Hypermedia

Hypermedia As The Engine Of Application State.

Richardson Maturity Model
Richardson Maturity Model

The term “hypermedia” was coined back in 1965 by Ted Nelson, and over the years has dominated the technology industry. Hypermedia, in its most basic sense is an extension of hypertext – something you may recognise from HTML.

Hypertext is essentially text that is written in a structured format and contains relationships to other objects via links.

Hypermedia is just an extension of the term hypertext, hypermedia includes images, video, audio, text, and links.

In a REST API, this means that your API is able to function similarly to a web page, providing the user with guidance on what type of content they can retrieve, or what actions they can perform, as well as the appropriate links to do so.

This in page guidance via links means that your clients do not need to remember much, they can just request a resource and check the response to see how to work with information provided, take appropriate actions, or access related information.

A good example of this is a client reading your site news only needs a single endpoint https://<site>.com/news.

The response would included all of the related articles and actions which you can change daily without coupling the client to news articles in any way.

Hypermedia can be express as links in a JSON API response:

{
  "data": [{
    "type": "clients",
    "id": "1",
    "attributes": {
      "name": "John"
    },
    "links": {
      "self": "http://example.com/clients/1"
	  "accounts": "http://example.com/clients/1/accounts"	
    },
    "relationships": {
      "accounts": {
        "links": {
            { 
               "name"  : "self",
               "method": "get",
               "href"  : "http://example.com/client/1/relationships/accounts"
            },
            { 
               "name"  : "related",
               "method": "get",
               "href"  : "http://example.com/client/1/accounts"
            },
            { 
               "name"  : "next",
               "method": "get",
               "href"  : "http://example.com/client/1/accounts"
            },
            { 
               "name"  : "prev",
               "method": "get",
               "href"  : "http://example.com/client/1/accounts"
            },
            { 
               "name"  : "close",
               "method": "patch",
               "href"  : "http://example.com/client/1/accounts"
            },
          "next" : "http://example.com/clients/1/accounts?page=3&page_size=2",
          "prev" : "http://example.com/clients/1/accounts?page=1&page_size=2",
          "close": "http://example.com/clients/1/accounts/close"
        },
        "data": [
          { "type": "accounts", "id": "5" },
          { "type": "accounts", "id": "12" },
          { "type": "accounts", "id": "13" }
        ]
      }
    }
  }],
  "included": [{
    "type": "acounts",
    "id": "12",
    "attributes": {
      "type": "Investment"
    }
    "links": {
        { 
           "name"  : "close",
           "method": "patch",
           "href"  : "http://example.com/accounts/12"
        },
        { 
           "name"  : "update",
           "method": "patch",
           "href"  : "http://example.com/accounts/12"
        },
    }
  }]
}

The action links description the content that can be retrieved and the actions that can be performed by user in a response to their request.

This is powerful as it gives the server flexibility to change without breaking the interface with the client.

Use the Accept and Content-Type Headers

We tend to think, “I’m going to build a JSON REST API and it’s going to be awesome.” It works great, until you get that million?dollar customer who needs XML. Then you have to go back and refactor the entire API for this customer. That’s why you should build your API from the start with the ability to add content types in the future.

Give yourself the ability to support multiple specifications without worrying about breaking backward compatibility.

Incoming request may have an entity attached to it. To determine it’s type, server uses the HTTP request header Content-Type. Common content types are:

  • application/json
  • application/xml
  • text/plain
  • text/html
  • image/gif
  • image/jpeg

Similarly, to determine what type of representation is desired at client side, HTTP header ACCEPT is used. It will have one of the values as mentioned for Content-Type above.

Sparse Fields

Sparse fields are key to creating Api’s that can be used by many clients.

If we do not support sparse fields we will force all clients to get the full set of data which will grow we add more functionality, similar to large objects graphs created in our monolith applications.

Use a fieldsTYPE parameter to return only specific fields in the response on a per-type basis.

GET /articles?include=author&fields[articles]=title,body 

Here we want articles objects to have fields title, body and author only.

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON:API paints my bikeshed!",
      "body": "The shortest article. Ever."
    },
    "relationships": {
      "author": {
        "data": {"id": "42", "type": "people"}
      }
    }
  }]
}

Timeouts

The client is in a better position to tell the server how long it wants to wait before timing out, so generally allow the client to override the default timeout period.

?Timeout=3000

Caching

ETag (entity tag) response header provides a mechanism to cache unchanged resources.

It’s value is an identifier which represents a specific version of the resource. Here’s an example ETag header:

ETag: "version1"

See ETags

Warning

Designing an API – that’s the most difficult part. That’s why you need to spend your time there and say, “Let’s get the design right, and everything else will follow.”

It only takes one tiny little thing, just one mistake in your API that goes to production, to screw things up.

Just like Facebook: they have this issue in production, but it’s in production now and they can’t change it.

Back Burner

  • Filtering, Sorting & Grouping
  • Descriptive Error Messages
  • Automate end-to-end functional testing
  • Cross Origin Resource Sharing.
  • Accelerate functional testing from your CI/CD pipelines
  • Tests to generate realistic load scenarios and security attacks
  • Remove dependencies during testing and development
  • Auto-gen & publish documentation.

Links

Flutter – Layout

This is a good place to start, you can really get going with making your ideas reality, once you can show content and navigate around.

Login, logging and other framework items, although vital can and should wait until later. It’s all too easy to start a project and never get past the login screen, so simple don’t start with them.

I chose to use tab navigation for DigestableMe.

The advantage of the Tab Bar is it is always visible, one click navigation that people with Apple and Android phones are used to, which will give your app a native feel, and good Ux experience.

When you have a larger screen a Side Bar or Menu will provide a better experience. These layouts will be covered in other posts and if you are not concentrating on phone/mobile first you may want to skip this post.

Let’s assume we have analytics showing we should target iPhone users, so we can start by creating a traditional bottom Tab Bar.

In a future post we will then modify the application for Android phones and other devices.

Google recommends up to 5 top level icons and Apple 3-5, so we are going to set the max number of top level items to 5.

DigestableMe will start with these three tabs:

  • Interests, a list of things to digest.
  • Friends, people to digest with.
  • History, recent first list of actions.

The new widget will be called TabbedLayout and will contain a Tab Bar and Content Area.

Tabbed Layout Skeleton Design
Tabbed Layout Skeleton Design

Its purpose is to:

  • Highlight the core functions
  • Simplify the user journey

We can now add some acceptance criteria to give direction and to know when we are done:

  1. It will contain a Tab Bar
  2. The Tab Bar can contain 2-5 items
  3. The Tab Bar will be anchored to the bottom of the widget
  4. The height of the Tab Bar will be the size of the largest item
  5. Each item will comprise of a title and an icon
  6. Cupertino icons will be used on Apple devices, Material on all others.
  7. There will be a Content Area above the above the Tab Bar
  8. The Content Area will expand to fill the available height.
  9. When a Tab is pressed it displays the appropriate Content
  10. Tab Items and Content are configurable I.e. inputs.
  11. The  TabbedLayout widget will expand to fill the available space.
  12. The widget can be themed.
  13. The widget can display as a skeleton when loading.

To start I created these new files:

  • /test/widget/layout/tabbed_layout_tab_bar_test.dart
  • /lib/widget/layout/tabbed_layout.dart

I then started writing tests and code to:

  • Implement the criteria above
  • Provide a contract for the code.
  • Document the code through readable tests.

Ta Da

Tabbed Layout screenshots.
Tabbed Layout screenshots.
Tab Bar Test result
Tab Bar Test result
Content Test result
Content Test result
Tab Item Test result
Tab Item Test result

During the writing of the tests these additional files where added.

  • /lib/widget/layout/domain/tab_item.dart
  • /test/widget/layout/fixture/tabbed_layout_fixture.dart
  • /test/widget/layout/tabbed_layout_tab_item_test.dart
  • /test/widget/layout/tabbed_layout_content_test.dart

Some of the acceptance criteria have been skipped because the functionality is covered and tested by the CupertinoTabScaffold widget:

  1. The Tab Bar will be anchored to the bottom of the widget
  2. The height of the Tab Bar will be the size of the largest item
  3. The Content Area will expand to fill the available height.
  4. When a Tab is pressed it displays the appropriate Content
  5. The Tabs widget will expand to fill the available space.

See the code for more details on the new widget and tests.

BackBurner

  • Using material design for other devices and browsers, to give a cross platform look and feel.
  • Branding, themes.

Including these acceptance criteria:

  1. Cupertino icons will be used on Apple devices, Material on all others.
  2. The widget can be themed.
  3. The widget can display as a skeleton when loading.

XP

VSCode

Use the VSCode extension

VSCode extenstion - Awesome Flutter Snippets
VSCode extenstion – Awesome Flutter Snippets

to create the stateful widget skeleton code using a snippet.

Using the stateful widget snippet.
Using the stateful widget snippet.

Use the VSCode extension to create the TabItem class.

Package, Dart data class generator
Package, Dart data class generator

NB: I reduced the codeine to the constructor and toString, we will add more if needed in later posts.

People

Kent Beck, Agile Manifesto founder, TDD, extreme programming.

Picture of Kent Beck

eXtreme programming practices
eXtreme programming practices

Sound & Vision

Love my way, Psychodelic Furs.

YAGNI, just sounded like Cagney…

Film: Public Enemy - James Cagney

Links

One more thing

We had a fundamental belief that doing it right the first time was going to be easier than having to go back and fix it. And I cannot say strongly enough that the repercussions of that attitude are staggering. I’ve seen them again and again throughout my business life.

Steve Jobs_

Confusion without Fusion

Error

The binding error:

  • located assemblies manifest definition does not match
The binding error.
The binding error.

Diagnosis

Excerpt from the fusion log
Excerpt from the fusion log

Fix

Example of adding a binding redirect to fix the issue.
Example of adding a binding redirect to fix the issue.

Toolin…

I started simple thinking I would improve, and never have.

These .bat scripts and FusLogVwSet.wsf from Travis Illig, are more than enough to get the job done.

Shows the batch files used to control logging.
Shows the batch files used to control logging.

Enable Fusion

FusionLogEnable.bat

e:
cd commands
IF EXIST d:\fusionlogs RMDIR /S /Q d:\fusionlogs
cscript fuslogvwset.wsf /enable
iisreset 
pause 0 

View
c:
cd "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\"
FUSLOGVW.exe

Disable Fusion

FusionLogDisable.bat

e:
cd commands
cscript fuslogvwset.wsf /disable 
iisreset 
pause 0

View Fusion Log

FusionLogView.bat

c:
cd "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\"
FUSLOGVW.exe

FusLogVwSet.wsf

https://www.paraesthesia.com/archive/2004/10/20/fusion-log-viewer-settings-changer.aspx/

\<?xml version="1.0" ?\>
\<?job error="true" debug="false" ?\>
\<!--
'============================================================================
' FUSION LOG VIEWER SETTINGS
' FusLogVwSet.wsf
' Travis Illig
' tillig@paraesthesia.com
' http://www.paraesthesia.com
'
' Overview:  Enables/disables custom settings for the fuslogvw.exe tool.
'
' Command syntax:  (Run "FusLogVwSet.wsf /?" for syntax and usage)
'
'============================================================================
--\>
\<package\>
\<job id="FusLogVwSet"\>
\<runtime\>
\<description\>
FusLogVwSet
---- 
This script "enables" and "disables" custom settings for the Fusion Log Viewer tool.

Enabling settings will:
* Create a log folder (default: D:\fusionlogs)
* Add HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogPath and set it to the log folder
* Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogFailures to 1
* Optionally set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\ForceLog to 1
* Optionally set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogResourceBinds to 1

Disabling settings will:
* Delete the log folder and its contents
* Delete HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogPath
* Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogFailures to 0
* Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\ForceLog to 0
* Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogResourceBinds to 0
	\</description\>
	\<named
	name="enable"
	helpstring="Enable custom fuslogvw.exe settings."
	type="simple"
	required="false"
	/\>
	\<named
	name="all"
	helpstring="When used with /enable, logs both failures and successes.  Only valid with /enable."
	type="simple"
	required="false"
	/\>
	\<named
	name="disable"
	helpstring="Disable custom fuslogvw.exe settings."
	type="simple"
	required="false"
	/\>
	\<named
	name="logpath"
	helpstring="Sets the log path (default is D:\fusionlogs).  Only valid with /enable."
	type="string"
	required="false"
	/\>
	\</runtime\>
	
	
	\<!-- Helper Objects --\>
	\<object id="fso" progid="Scripting.FileSystemObject" /\>
	\<object id="shell" progid="WScript.Shell" /\>
	
	\<!-- Main Script --\>
	\<script language="VBScript"\>
	\<!\[CDATA\[
	]()
	'============================================================================
	' INITIALIZATION
	Option Explicit
	
	'Declare variables/constants
	Const SCRIPTNAME = "Fusion Log Viewer Settings"
	Const VERSION = "1.0"
	
	Const DEFAULT\_FUSIONLOGPATH = "D:\fusionlogs"
	Const REG\_LOGPATH = "HKLM\SOFTWARE\Microsoft\Fusion\LogPath"
	Const REG\_LOGFAILURES = "HKLM\SOFTWARE\Microsoft\Fusion\LogFailures"
	Const REG\_FORCELOG = "HKLM\SOFTWARE\Microsoft\Fusion\ForceLog"
	Const REG\_RESOURCEBINDS = "HKLM\SOFTWARE\Microsoft\Fusion\LogResourceBinds"
	
	
	'============================================================================
	'PRIMARY CODE
	'============================================================================
	On Error Resume Next
	
	WScript.echo SCRIPTNAME & " v" & VERSION & vbCrLf
	
	'Parse arguments
	Dim argsSpecified
	Dim argsEnable, argsDisable, argsAll, argsLogPath
	
	argsEnable = WScript.Arguments.Named.Exists("enable")
	argsDisable = WScript.Arguments.Named.Exists("disable")
	argsAll = WScript.Arguments.Named.Exists("all")
	If(WScript.Arguments.Named.Exists("logpath"))Then
	argsLogPath = WScript.Arguments.Named.Item("logpath")
	End If
	
	'Validate arguments
	If(not argsEnable and not argsDisable)Then
	' Must specify either enable or disable
	WScript.Echo "\*\*\* You must specify enable or disable."
	WScript.Arguments.ShowUsage
	WScript.Quit
	End If
	
	If(argsEnable and argsDisable)Then
	' Can't enable and disable at the same time
	WScript.Echo "\*\*\* You must specify EITHER enable OR disable; not both."
	WScript.Arguments.ShowUsage
	WScript.Quit
	End If
	
	If(argsDisable and argsAll)Then
	'all is only valid with enable
	WScript.Echo "\*\*\* Argument 'all' is only valid with 'enable'."
	WScript.Arguments.ShowUsage
	WScript.Quit
	End If
	
	If(argsDisable and WScript.Arguments.Named.Exists("logpath"))Then
	'logpath is only valid with enable
	WScript.Echo "\*\*\* Argument 'logpath' is only valid with 'enable'."
	WScript.Arguments.ShowUsage
	WScript.Quit
	End If
	
	If(argsLogPath = "" and WScript.Arguments.Named.Exists("logpath"))Then
	'If logpath is specified, must put a value
	WScript.Echo "\*\*\* Argument 'logpath' must have a value if specified."
	WScript.Arguments.ShowUsage
	WScript.Quit
	End If
	
	
	' Output settings
	If(argsEnable)Then
	If(argsAll)Then
	LogMessage "Action: Enable Custom Logging - Failure and Success", 0
	Else
	LogMessage "Action: Enable Custom Logging - Failure Only", 0
	End If
	If(argsLogPath \<\> "")Then
	LogMessage "LogPath: " & argsLogPath, 0
	End If
	Else
	LogMessage "Action: Disable Custom Logging", 0
	End If
	
	
	' Update settings
	Dim logFolder, logFolderObj, regVal
	If(argsEnable)Then
	' Enable settings
	' Create a log folder (default: D:\fusionlogs)
	If(argsLogPath = "")Then
	logFolder = DEFAULT\_FUSIONLOGPATH
	Else
	logFolder = argsLogPath
	End If
	
	If(FolderExists(logFolder))Then
	' The folder already exists; since we're deleting it when we disable
	' settings, we don't want to use a pre-existing folder.
	LogMessage "Folder " & logFolder & " exists.  Custom log folder must not already exist.", 1
	WScript.Quit(0)
	End If
	
	Set logFolderObj = fso.CreateFolder(logFolder)
	If Err.Number \<\> 0 Then
	LogMessage "Unable to create log folder" & logFolder, 1
	WScript.Quit(-1)
	End If
	Err.Clear
	LogMessage "Created log folder " & logFolderObj.Path, 0
	
	
	' Add HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogPath and set it to the log folder path.
	SetRegKey REG\_LOGPATH, logFolderObj.Path, "REG\_SZ"
	regVal = GetRegKey(REG\_LOGPATH)
	If(regVal \<\> logFolderObj.Path)Then
	LogMessage "Unable to write registry key " & REG\_LOGPATH, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_LOGPATH, 0
	
	' Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogFailures to 1
	SetRegKey REG\_LOGFAILURES, 1, "REG\_DWORD"
	regVal = GetRegKey(REG\_LOGFAILURES)
	If(regVal \<\> 1)Then
	LogMessage "Unable to write registry key " & REG\_LOGFAILURES, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_LOGFAILURES, 0
	
	If(argsAll)Then
	' Optionally set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\ForceLog to 1
	SetRegKey REG\_FORCELOG, 1, "REG\_DWORD"
	regVal = GetRegKey(REG\_FORCELOG)
	If(regVal \<\> 1)Then
	LogMessage "Unable to write registry key " & REG\_FORCELOG, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_FORCELOG, 0
	
	' Optionally set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogResourceBinds to 1
	SetRegKey REG\_RESOURCEBINDS, 1, "REG\_DWORD"
	regVal = GetRegKey(REG\_RESOURCEBINDS)
	If(regVal \<\> 1)Then
	LogMessage "Unable to write registry key " & REG\_RESOURCEBINDS, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_RESOURCEBINDS, 0
	End If
	Else
	' Disable settings
	logFolder = GetRegKey(REG\_LOGPATH)
	If(logFolder = "")Then
	LogMessage "Unable to read registry key " & REG\_LOGPATH, 1
	WScript.Quit(-1)
	End If
	
	If(FolderExists(logFolder))Then
	' The folder exists; delete it and its contents
	fso.DeleteFolder logFolder, true
	If Err.Number \<\> 0 Then
	LogMessage "Unable to delete log folder" & logFolder, 1
	WScript.Quit(-1)
	End If
	Err.Clear
	LogMessage "Deleted log folder " & logFolder, 0
	End If
	
	' Delete HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogPath
	If(DeleteRegKey(REG\_LOGPATH))Then
	LogMessage "Deleted registry key " & REG\_LOGPATH, 0
	Else
	LogMessage "Unable to delete registry key " & REG\_LOGPATH, 1
	WScript.Quit(-1)
	End If
	
	' Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogFailures to 0
	SetRegKey REG\_LOGFAILURES, 0, "REG\_DWORD"
	regVal = GetRegKey(REG\_LOGFAILURES)
	If(regVal \<\> 0)Then
	LogMessage "Unable to write registry key " & REG\_LOGFAILURES, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_LOGFAILURES, 0
	
	' Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\ForceLog to 0
	SetRegKey REG\_FORCELOG, 0, "REG\_DWORD"
	regVal = GetRegKey(REG\_FORCELOG)
	If(regVal \<\> 0)Then
	LogMessage "Unable to write registry key " & REG\_FORCELOG, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_FORCELOG, 0
	
	' Set HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Fusion\LogResourceBinds to 0
	SetRegKey REG\_RESOURCEBINDS, 0, "REG\_DWORD"
	regVal = GetRegKey(REG\_RESOURCEBINDS)
	If(regVal \<\> 0)Then
	LogMessage "Unable to write registry key " & REG\_RESOURCEBINDS, 1
	WScript.Quit(-1)
	End If
	LogMessage "Wrote to " & REG\_RESOURCEBINDS, 0
	End If
	
	LogMessage "Log settings update COMPLETE.  You must reset IIS for changes to take effect in ASP.NET apps.", 0
	
	On Error Goto 0
	Wscript.Quit(0)
	
	
	'============================================================================
	' CreateNewObject
	'
	' Creates a new object, given a type, and performs requisite error checking.
	' Exits the program if the object can't be created.
	'
	Function CreateNewObject(objType)
	On Error Resume Next
	
	'Create a new object
	Dim obj
	Set obj = WScript.CreateObject(objType)
	If Err.Number \<\> 0 Then
	LogMessage "Unable to create " & objType, 1
	WScript.Quit(-1)
	End If
	Err.Clear
	
	Set CreateNewObject = obj
	
	On Error Goto 0
	End Function
	
	
	'============================================================================
	' FolderExists
	'
	' Returns a Boolean based on whether a folder exists or not
	'
	Function FolderExists(foldername)
	On Error Resume Next
	
	'Create a FileSystemObject object
	Dim fso
	Set fso = CreateNewObject("Scripting.FileSystemObject")
	
	'Check for the folder
	FolderExists = false
	FolderExists = fso.FolderExists(foldername)
	
	Set fso = Nothing
	
	On Error Goto 0
	End Function
	
	
	'============================================================================
	' DeleteRegKey
	'
	' Deletes a given registry key
	' Returns true if the delete was successful, false otherwise
	'
	Function DeleteRegKey(regkey\_name)
	On Error Resume Next
	
	'Create a shell object
	Dim wshell
	Set wshell = CreateNewObject("WScript.Shell")
	
	'Write the regkey
	wshell.RegDelete regkey\_name
	If Err.Number \<\> 0 Then
	'Something else went wrong
	LogMessage "Unable to delete key " & regkey\_name, 1
	DeleteRegKey = false
	Else
	DeleteRegKey = true
	End If
	Err.Clear
	
	Set wshell = Nothing
	
	On Error Goto 0
	End Function
	
	
	'============================================================================
	' SetRegKey
	'
	' Sets the value for a given registry key
	'
	Sub SetRegKey(regkey\_name, regkey\_value, regkey\_type)
	On Error Resume Next
	
	'Create a shell object
	Dim wshell
	Set wshell = CreateNewObject("WScript.Shell")
	
	'Write the regkey
	wshell.RegWrite regkey\_name, regkey\_value, regkey\_type
	If Err.Number \<\> 0 Then
	'Something else went wrong
	LogMessage "Unable to write key " & regkey\_name, 1
	End If
	Err.Clear
	
	Set wshell = Nothing
	
	On Error Goto 0
	End Sub
	
	
	'============================================================================
	' GetRegKey
	'
	' Retrieves the value for a given registry key
	'
	Function GetRegKey(regkey\_name)
	On Error Resume Next
	
	'Create a shell object
	Dim wshell
	Set wshell = CreateNewObject("WScript.Shell")
	
	'Read the regkey
	Dim val
	val = wshell.RegRead(regkey\_name)
	If Err.Number \<\> 0 Then
	'Either we don't have permission to read the key or the key doesn't exist.
	' If the key doesn't exist, it's error -2147024894
	If Err.Number = -2147024894 Then
	'The key doesn't exist
	val=""
	Else
	'Something else went wrong
	LogMessage "Unable to read key " & regkey\_name, 1
	val=""
	End If
	End If
	Err.Clear
	
	Set wshell = Nothing
	
	GetRegKey = val
	On Error Goto 0
	End Function
	
	
	'============================================================================
	' LogMessage
	'
	' Writes a message to the event log
	'
	' msgType:
	'   0 = Info
	'   1 = Error
	'   2 = Warning
	Sub LogMessage(msgBody, msgType)
	On Error Resume Next
	
	'Create a shell object
	Dim wshell
	Set wshell = WScript.CreateObject("WScript.Shell")
	If Err.Number \<\> 0 Then
	WScript.Quit(-1)
	End If
	Err.Clear
	
	'Figure out the error type
	Dim msgTypeFull
	If(msgType = 0) Then
	msgTypeFull = "INFO"
	ElseIf(msgType = 1) Then
	msgTypeFull = "ERROR"
	ElseIf(msgType = 2) Then
	msgTypeFull = "WARNING"
	End If
	
	msgBody = WScript.ScriptName & " -- " & msgTypeFull & ": " & msgBody
	wscript.echo msgbody
	
	'Log the message
	wshell.LogEvent msgType, msgBody
	
	'Cleanup
	Set wshell = Nothing
	
	On Error Goto 0
	End Sub
	
	]]\>
	</script\>
	\</job\>
</package\>