Skip to content

Commit 9c5e079

Browse files
author
Jareth Hein
committed
# Conflicts: # Intuit.QuickBase.Client/QTable.cs # Intuit.QuickBase.Core/Exceptions/APIRequestLimitExceededException.cs # Intuit.QuickBase.Core/HttpPostXml.cs # Intuit.QuickBase.Core/Payload/AddRecordPayload.cs # Intuit.QuickBase.Core/Payload/EditRecordPayload.cs # Intuit.QuickBase.Core/Payload/ImportFromCSVPayload.cs
2 parents 1b66c79 + f377727 commit 9c5e079

File tree

13 files changed

+252
-53
lines changed

13 files changed

+252
-53
lines changed

Intuit.QuickBase.Client/QField.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ internal string QBValue
7070
case FieldType.date:
7171
return ConvertDateTimeToQBMilliseconds((DateTime)_value);
7272
case FieldType.timeofday:
73+
return ConvertTimeSpanToQBTimeOfDay((TimeSpan)_value);
7374
case FieldType.duration:
74-
return ((TimeSpan)_value).ToString();
75+
return ConvertTimeSpanToQBDuration((TimeSpan)_value);
7576
case FieldType.checkbox:
7677
return (bool)_value == true ? "1" : "0";
7778
default:
@@ -90,8 +91,10 @@ internal string QBValue
9091
_value = String.IsNullOrEmpty(value) ? new DateTime?() : ConvertQBMillisecondsToDate(value);
9192
break;
9293
case FieldType.timeofday:
94+
_value = String.IsNullOrEmpty(value) ? new TimeSpan?() : ConvertQBTimeOfDayToTime(value);
95+
break;
9396
case FieldType.duration:
94-
_value = String.IsNullOrEmpty(value) ? new TimeSpan?() : ConvertQBMillisecondsToTime(value);
97+
_value = String.IsNullOrEmpty(value) ? new TimeSpan?() : ConvertQBDurationToTimeSpan(value);
9598
break;
9699
case FieldType.timestamp:
97100
_value = String.IsNullOrEmpty(value) ? new DateTime?() : ConvertQBMillisecondsToDateTime(value);
@@ -269,25 +272,39 @@ public override string ToString()
269272

270273
private static DateTime ConvertQBMillisecondsToDateTime(string milliseconds)
271274
{
272-
return qbTSOffset.AddMilliseconds(double.Parse(milliseconds)).ToLocalTime();
275+
DateTime dtu = new DateTime(long.Parse(milliseconds) * TimeSpan.TicksPerMillisecond + qbTSOffset.Ticks, DateTimeKind.Utc);
276+
return dtu.ToLocalTime();
273277
}
274278
private static DateTime ConvertQBMillisecondsToDate(string milliseconds)
275279
{
276-
return qbTSOffset.AddMilliseconds(double.Parse(milliseconds)).Date;
280+
DateTime dtu = new DateTime(long.Parse(milliseconds) * TimeSpan.TicksPerMillisecond + qbTSOffset.Ticks, DateTimeKind.Utc);
281+
return dtu.ToLocalTime().Date;
282+
}
283+
private static TimeSpan ConvertQBDurationToTimeSpan(string milliseconds)
284+
{
285+
return new TimeSpan(long.Parse(milliseconds) * TimeSpan.TicksPerMillisecond);
277286
}
278-
private static TimeSpan ConvertQBMillisecondsToTime(string milliseconds)
287+
288+
private static TimeSpan ConvertQBTimeOfDayToTime(string milliseconds)
279289
{
280-
return qbTSOffset.AddMilliseconds(double.Parse(milliseconds)).TimeOfDay;
290+
return new TimeSpan(long.Parse(milliseconds) * TimeSpan.TicksPerMillisecond);
281291
}
282292

283293
private static string ConvertDateTimeToQBMilliseconds(DateTime inDT)
284294
{
285-
return ((inDT.Ticks - qbTSOffset.Ticks) / TimeSpan.TicksPerMillisecond).ToString();
295+
DateTime dte = inDT.ToUniversalTime();
296+
return ((dte.Ticks - qbTSOffset.Ticks) / TimeSpan.TicksPerMillisecond).ToString();
297+
}
298+
299+
private static string ConvertTimeSpanToQBTimeOfDay(TimeSpan inTime)
300+
{
301+
DateTime dt = new DateTime() + inTime;
302+
return dt.ToString("h:mm tt");
286303
}
287304

288-
private static string ConvertTimeSpanToQBMilliseconds(TimeSpan inTime)
305+
private static string ConvertTimeSpanToQBDuration(TimeSpan inTime)
289306
{
290-
return (inTime.Ticks / TimeSpan.TicksPerMillisecond).ToString();
307+
return (inTime.TotalSeconds).ToString();
291308
}
292309
}
293310
}

Intuit.QuickBase.Client/QRecord.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ public void AcceptChanges()
175175
field.Update = false;
176176
}
177177
}
178-
var editRecord = new EditRecord.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, Table.TableId, RecordId, fieldsToPost).Build();
178+
var editBuilder = new EditRecord.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, Table.TableId, RecordId, fieldsToPost);
179+
editBuilder.SetTimeInUtc(true);
180+
var editRecord = editBuilder.Build();
179181
editRecord.Post();
180182
RecordState = RecordState.Unchanged;
181183
}
@@ -191,7 +193,9 @@ public void AcceptChanges()
191193
}
192194
fieldsToPost.Add(qField);
193195
}
194-
var addRecord = new AddRecord.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, Table.TableId, fieldsToPost).Build();
196+
var addBuilder = new AddRecord.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, Table.TableId, fieldsToPost);
197+
addBuilder.SetTimeInUtc(true);
198+
var addRecord = addBuilder.Build();
195199
RecordState = RecordState.Unchanged;
196200

197201
var xml = addRecord.Post();
@@ -232,6 +236,8 @@ private string CSVQuoter(string inStr)
232236

233237
public void ForceUpdateState(int recId)
234238
{
239+
RecordState = RecordState.Unchanged;
240+
IsOnServer = true;
235241
RecordId = recId;
236242
}
237243

Intuit.QuickBase.Client/QTable.cs

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Xml.Linq;
1414
using Intuit.QuickBase.Core;
1515
using Intuit.QuickBase.Core.Exceptions;
16+
using System.Text.RegularExpressions;
1617

1718
namespace Intuit.QuickBase.Client
1819
{
@@ -37,6 +38,7 @@ internal QTable(QColumnFactoryBase columnFactory, QRecordFactoryBase recordFacto
3738
TableName = tableName;
3839
RecordNames = pNoun;
3940
CommonConstruction(columnFactory, recordFactory, application, tableId);
41+
RefreshColumns(); //grab basic columns that QB automatically makes
4042
}
4143

4244
private void CommonConstruction(QColumnFactoryBase columnFactory, QRecordFactoryBase recordFactory, IQApplication application, string tableId)
@@ -72,6 +74,9 @@ private void CommonConstruction(QColumnFactoryBase columnFactory, QRecordFactory
7274

7375
public int KeyCIdx { get; private set; }
7476

77+
private static readonly string[] QuerySeparator = {"}OR{"};
78+
private static readonly Regex QueryCheckRegex = new Regex(@"[\)}]AND[\({]");
79+
7580
// Methods
7681
public void Clear()
7782
{
@@ -177,9 +182,39 @@ private void _doQuery(DoQuery qry)
177182
try
178183
{
179184
XElement xml = qry.Post();
180-
LoadColumns(xml); //Must be done each time, incase the schema changes due to another user, or from a previous query that has a differing subset of columns
185+
LoadColumns(xml); //In case the schema changes due to another user, or from a previous query that has a differing subset of columns TODO: remove this requirement
181186
LoadRecords(xml);
182187
}
188+
catch (TooManyCriteriaInQueryException)
189+
{
190+
//If and only if all elements of a query are OR operations, we can split the query in 99 element chunks
191+
string query = qry.Query;
192+
if (string.IsNullOrEmpty(query) || QueryCheckRegex.IsMatch(query))
193+
throw;
194+
string[] args = query.Split(QuerySeparator, StringSplitOptions.None);
195+
int argCnt = args.Length;
196+
if (argCnt < 100) //We've no idea how to split this, apparently...
197+
throw;
198+
if (args[0].StartsWith("{")) args[0] = args[0].Substring(1); //remove leading {
199+
if (args[argCnt = 1].EndsWith("}")) args[argCnt - 1] = args[argCnt - 1].Substring(0, args[argCnt - 1].Length - 1); // remove trailing }
200+
int sentArgs = 0;
201+
while (sentArgs < argCnt)
202+
{
203+
int useArgs = Math.Min(99, argCnt - sentArgs);
204+
string[] argsToSend = args.Skip(sentArgs).Take(useArgs).ToArray();
205+
string sendQuery = "{" + string.Join("}OR{", argsToSend) + "}";
206+
DoQuery dqry = new DoQuery.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, TableId)
207+
.SetQuery(sendQuery)
208+
.SetCList(qry.Collist)
209+
.SetOptions(qry.Options)
210+
.SetFmt(true)
211+
.Build();
212+
var xml = dqry.Post();
213+
if (sentArgs == 0) LoadColumns(xml);
214+
LoadRecords(xml);
215+
sentArgs += useArgs;
216+
}
217+
}
183218
catch (ViewTooLargeException)
184219
{
185220
//split into smaller queries automagically
@@ -220,29 +255,29 @@ private void _doQuery(DoQuery qry)
220255
var cntXml = dqryCnt.Post();
221256
maxCount = int.Parse(cntXml.Element("numMatches").Value);
222257
}
223-
int stride = maxCount/2;
258+
int stride = maxCount / 2;
224259
int fetched = 0;
225260
while (fetched < maxCount)
226261
{
227262
List<string> optLst = new List<string>();
228263
optLst.AddRange(optionsList);
229264
optLst.Add("skp-" + (fetched + baseSkip));
230265
optLst.Add("num-" + stride);
231-
string options = string.Join(".",optLst);
266+
string options = string.Join(".", optLst);
232267
DoQuery dqry;
233268
if (string.IsNullOrEmpty(query))
234-
dqry = new DoQuery.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, TableId)
235-
.SetCList(collist)
236-
.SetOptions(options)
237-
.SetFmt(true)
238-
.Build();
269+
dqry = new DoQuery.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, TableId)
270+
.SetCList(collist)
271+
.SetOptions(options)
272+
.SetFmt(true)
273+
.Build();
239274
else
240-
dqry = new DoQuery.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, TableId)
241-
.SetQuery(query)
242-
.SetCList(collist)
243-
.SetOptions(options)
244-
.SetFmt(true)
245-
.Build();
275+
dqry = new DoQuery.Builder(Application.Client.Ticket, Application.Token, Application.Client.AccountDomain, TableId)
276+
.SetQuery(query)
277+
.SetCList(collist)
278+
.SetOptions(options)
279+
.SetFmt(true)
280+
.Build();
246281
try
247282
{
248283
XElement xml = dqry.Post();
@@ -252,7 +287,12 @@ private void _doQuery(DoQuery qry)
252287
}
253288
catch (ViewTooLargeException)
254289
{
255-
stride = stride/2;
290+
stride = stride / 2;
291+
}
292+
catch (ApiRequestLimitExceededException ex)
293+
{
294+
TimeSpan waitTime = ex.WaitUntil - DateTime.Now;
295+
System.Threading.Thread.Sleep(waitTime);
256296
}
257297
}
258298
}
@@ -430,7 +470,7 @@ public void AcceptChanges()
430470
//optimize record uploads
431471
List<IQRecord> addList = Records.Where(record => record.RecordState == RecordState.New && record.UncleanState == false).ToList();
432472
List<IQRecord> modList = Records.Where(record => record.RecordState == RecordState.Modified && record.UncleanState == false).ToList();
433-
List<IQRecord> uncleanList = Records.Where(record => record.UncleanState == true).ToList();
473+
List<IQRecord> uncleanList = Records.Where(record => record.UncleanState).ToList();
434474
int acnt = addList.Count;
435475
int mcnt = modList.Count;
436476
bool hasFileColumn = Columns.Any(c => c.ColumnType == FieldType.file);
@@ -450,6 +490,7 @@ public void AcceptChanges()
450490
var csvBuilder = new ImportFromCSV.Builder(Application.Client.Ticket, Application.Token,
451491
Application.Client.AccountDomain, TableId, String.Join("\r\n", csvLines.ToArray()));
452492
csvBuilder.SetCList(clist);
493+
csvBuilder.SetTimeInUtc(true);
453494
var csvUpload = csvBuilder.Build();
454495

455496
var xml = csvUpload.Post();

Intuit.QuickBase.Core/AddRecord.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ public Builder SetDisprec(bool val)
6464
return this;
6565
}
6666

67+
internal bool TimeInUtc { get; private set; }
68+
69+
public Builder SetTimeInUtc(bool val)
70+
{
71+
TimeInUtc = val;
72+
return this;
73+
}
74+
6775
internal bool Fform { get; private set; }
6876

6977
public Builder SetFform(bool val)
@@ -82,6 +90,7 @@ private AddRecord(Builder builder)
8290
{
8391
_addRecordPayload = new AddRecordPayload.Builder(builder.Fields)
8492
.SetDisprec(builder.Disprec)
93+
.SetTimeInUtc(builder.TimeInUtc)
8594
.SetFform(builder.Fform)
8695
.Build();
8796
_addRecordPayload = new ApplicationTicket(_addRecordPayload, builder.Ticket);

Intuit.QuickBase.Core/EditRecord.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ public Builder SetDisprec(bool val)
8585
return this;
8686
}
8787

88+
internal bool TimeInUtc { get; private set; }
89+
90+
public Builder SetTimeInUtc(bool val)
91+
{
92+
TimeInUtc = val;
93+
return this;
94+
}
95+
8896
internal bool Fform { get; private set; }
8997

9098
public Builder SetFform(bool val)
@@ -104,6 +112,7 @@ private EditRecord(Builder builder)
104112
_editRecordPayload = new EditRecordPayload.Builder(builder.Rid, builder.Fields)
105113
.SetUpdateId(builder.UpdateId)
106114
.SetDisprec(builder.Disprec)
115+
.SetTimeInUtc(builder.TimeInUtc)
107116
.SetFform(builder.Fform)
108117
.Build();
109118
_editRecordPayload = new ApplicationTicket(_editRecordPayload, builder.Ticket);

Intuit.QuickBase.Core/Exceptions/APIRequestLimitExceededException.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ public class ApiRequestLimitExceededException : Exception
1515
{
1616
public ApiRequestLimitExceededException() { }
1717

18-
public ApiRequestLimitExceededException(string message)
18+
public ApiRequestLimitExceededException(string message, DateTime waitUntil)
1919
{
2020
Message = message;
21+
WaitUntil = waitUntil;
2122
}
2223

23-
public new string Message { get; set; }
24+
public new string Message { get; private set; }
25+
26+
public DateTime WaitUntil { get; private set; }
2427

2528
public override void GetObjectData(SerializationInfo info, StreamingContext context)
2629
{

Intuit.QuickBase.Core/Http.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ public static void CheckForException(XElement xmlNav)
278278

279279
if ("77".Equals(errorcode))
280280
{
281-
throw new ApiRequestLimitExceededException(errortext);
281+
System.DateTime waitUntil = System.DateTime.Now.AddSeconds(5); //I can't find any examples of where the 'wait time' is supposedly included in the error message, so just putting in a 5 sec wait time
282+
throw new ApiRequestLimitExceededException(errortext, waitUntil);
282283
}
283284

284285
if ("80".Equals(errorcode))

Intuit.QuickBase.Core/HttpPostXml.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal override void Post(IQObject qObject)
2323
XElement parent = new XElement("qdbapi"); ;
2424
qObject.BuildXmlPayload(ref parent);
2525
var bytes = Encoding.UTF8.GetBytes(parent.ToString());
26+
//File.AppendAllText(@"C:\Temp\QBDebugLog.txt", "**Sent->>" + qObject.Uri + " " + QUICKBASE_HEADER + qObject.Action + "\r\n" + qObject.XmlPayload + "\r\n");
2627
Stream requestStream = null;
2728
WebResponse webResponse = null;
2829
Stream responseStream = null;
@@ -32,7 +33,7 @@ internal override void Post(IQObject qObject)
3233
{
3334
var request = (HttpWebRequest)WebRequest.Create(qObject.Uri);
3435
request.Method = METHOD;
35-
request.ProtocolVersion = HttpVersion.Version10;
36+
request.ProtocolVersion = HttpVersion.Version11;
3637
request.ContentType = CONTENT_TYPE;
3738
request.ContentLength = bytes.Length;
3839
request.KeepAlive = false;
@@ -45,12 +46,13 @@ internal override void Post(IQObject qObject)
4546
webResponse = request.GetResponse();
4647
responseStream = webResponse.GetResponseStream();
4748
xml = XElement.Load(responseStream);
49+
//File.AppendAllText(@"C:\Temp\QBDebugLog.txt", "**Received-<<\r\n" + xml.CreateNavigator().InnerXml + "\r\n");
4850
}
4951
finally
5052
{
51-
if (requestStream != null) requestStream.Close();
52-
if (responseStream != null) responseStream.Close();
53-
if (webResponse != null) webResponse.Close();
53+
requestStream?.Close();
54+
responseStream?.Close();
55+
webResponse?.Close();
5456
}
5557

5658
Http.CheckForException(xml);

Intuit.QuickBase.Core/ImportFromCSV.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ public Builder SetSkipFirst(bool val)
6767
return this;
6868
}
6969

70+
internal bool TimeInUtc { get; private set; }
71+
72+
public Builder SetTimeInUtc(bool val)
73+
{
74+
TimeInUtc = val;
75+
return this;
76+
}
77+
7078
public ImportFromCSV Build()
7179
{
7280
return new ImportFromCSV(this);
@@ -78,6 +86,7 @@ private ImportFromCSV(Builder builder)
7886
_importFromCSVPayload = new ImportFromCSVPayload.Builder(builder.RecordsCsv)
7987
.SetCList(builder.CList)
8088
.SetSkipFirst(builder.SkipFirst)
89+
.SetTimeInUtc(builder.TimeInUtc)
8190
.Build();
8291
_importFromCSVPayload = new ApplicationTicket(_importFromCSVPayload, builder.Ticket);
8392
_importFromCSVPayload = new ApplicationToken(_importFromCSVPayload, builder.AppToken);

0 commit comments

Comments
 (0)